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}