Skip to main content

game_manager_lib/utils/
executable_heuristics.rs

1//! Heurísticas compartilhadas para localizar o executável principal de um jogo quando a
2//! plataforma não fornece um manifest confiável (EA, IndieGala).
3
4use std::fs;
5use std::path::{Path, PathBuf};
6
7/// Substrings (case-insensitive) que indicam um executável auxiliar, não o jogo em si.
8/// Usa `contains` em vez de igualdade exata porque esses nomes variam por engine/versão
9/// (ex: `UnityCrashHandler32.exe` vs `UnityCrashHandler64.exe`, `UE4PrereqSetup_x64.exe`
10/// vs `UE5PrereqSetup_x64.exe`) — comparar por substring cobre as variações sem precisar
11/// enumerar cada versão específica.
12const IGNORED_EXECUTABLE_SUBSTRINGS: &[&str] = &[
13    "unins",         // unins000.exe, uninstall.exe, Uninstaller.exe
14    "crashhandler",  // UnityCrashHandler32.exe, UnityCrashHandler64.exe
15    "crashreporter", // CrashReporter.exe
16    "crashpad",      // chrome_crashpad_handler.exe (launchers baseados em Electron)
17    "vc_redist",     // vc_redist.x64.exe, vc_redist.x86.exe
18    "vcredist",
19    "directx_setup",
20    "dxsetup",
21    "prereqsetup", // UE4PrereqSetup_x64.exe, UE5PrereqSetup_x64.exe
22    "redist",      // instaladores de redistribuível em geral
23];
24
25/// Retorna `true` se o nome do arquivo (case-insensitive) contém alguma substring
26/// conhecida de executável auxiliar (uninstaller, crash handler, redistribuível, etc).
27fn is_ignored_executable(path: &Path) -> bool {
28    let Some(file_name) = path.file_name().and_then(|n| n.to_str()) else {
29        return true; // nome ilegível — descarta por segurança
30    };
31
32    let lower = file_name.to_lowercase();
33    IGNORED_EXECUTABLE_SUBSTRINGS
34        .iter()
35        .any(|ignored| lower.contains(ignored))
36}
37
38/// Tenta localizar o executável principal de um jogo sem manifest confiável: pega o
39/// maior `.exe` na *raiz* da pasta de instalação, ignorando utilitários auxiliares
40/// conhecidos (uninstaller, crash handler, redistribuíveis).
41///
42/// **Limitação deliberada — não escaneia subpastas.** Jogos com o executável aninhado
43/// (ex: relançamentos antigos rodando via DOSBox/emulador) retornam `None`.
44pub fn guess_main_executable(install_path: &Path) -> Option<PathBuf> {
45    fs::read_dir(install_path)
46        .ok()?
47        .flatten()
48        .map(|e| e.path())
49        .filter(|p| {
50            p.is_file()
51                && p.extension()
52                    .and_then(|e| e.to_str())
53                    .map(|e| e.eq_ignore_ascii_case("exe"))
54                    .unwrap_or(false)
55                && !is_ignored_executable(p)
56        })
57        .max_by_key(|p| fs::metadata(p).map(|m| m.len()).unwrap_or(0))
58}