siglus_scene_vm/runtime/
game_title.rs1use std::path::Path;
8
9use siglus_assets::gameexe::{decode_gameexe_dat_bytes, GameexeConfig, GameexeDecodeOptions};
10
11const GAMEEXE_CANDIDATES: &[&str] = &[
12 "Gameexe.dat",
13 "Gameexe.ini",
14 "gameexe.dat",
15 "gameexe.ini",
16 "GameexeEN.dat",
17 "GameexeEN.ini",
18 "GameexeZH.dat",
19 "GameexeZH.ini",
20 "GameexeZHTW.dat",
21 "GameexeZHTW.ini",
22 "GameexeDE.dat",
23 "GameexeDE.ini",
24 "GameexeES.dat",
25 "GameexeES.ini",
26 "GameexeFR.dat",
27 "GameexeFR.ini",
28 "GameexeID.dat",
29 "GameexeID.ini",
30];
31
32pub fn resolve_game_title_from_project_dir(project_dir: impl AsRef<Path>) -> String {
40 let project_dir = project_dir.as_ref();
41 load_gameexe_config(project_dir)
42 .as_ref()
43 .and_then(game_title_from_config)
44 .unwrap_or_else(|| fallback_title_from_project_dir(project_dir))
45}
46
47pub fn resolve_game_title(
49 gameexe: Option<&GameexeConfig>,
50 project_dir: impl AsRef<Path>,
51) -> String {
52 let project_dir = project_dir.as_ref();
53 gameexe
54 .and_then(game_title_from_config)
55 .unwrap_or_else(|| fallback_title_from_project_dir(project_dir))
56}
57
58pub fn game_title_from_config(cfg: &GameexeConfig) -> Option<String> {
60 if let Some(v) = cfg.get_unquoted("GAMENAME") {
61 let s = normalize_game_title(v);
62 if !s.is_empty() {
63 return Some(s);
64 }
65 }
66 for entry in cfg.entries.iter().rev() {
67 if matches!(entry.key_parts.last().map(|s| s.as_str()), Some("GAMENAME")) {
68 let s = normalize_game_title(entry.scalar_unquoted());
69 if !s.is_empty() {
70 return Some(s);
71 }
72 }
73 }
74 None
75}
76
77fn load_gameexe_config(project_dir: &Path) -> Option<GameexeConfig> {
78 let gameexe_path = find_gameexe_path(project_dir)?;
79 let raw = std::fs::read(&gameexe_path).ok()?;
80 let text = if gameexe_path
81 .extension()
82 .and_then(|s| s.to_str())
83 .is_some_and(|ext| ext.eq_ignore_ascii_case("ini"))
84 {
85 String::from_utf8(raw).ok()?
86 } else {
87 let opt = GameexeDecodeOptions::from_project_dir(project_dir).ok()?;
88 decode_gameexe_dat_bytes(&raw, &opt).ok()?.0
89 };
90 Some(GameexeConfig::from_text(&text))
91}
92
93fn find_gameexe_path(project_dir: &Path) -> Option<std::path::PathBuf> {
94 for name in GAMEEXE_CANDIDATES {
95 let p = project_dir.join(name);
96 if p.is_file() {
97 return Some(p);
98 }
99 }
100 None
101}
102
103fn normalize_game_title(raw: &str) -> String {
104 raw.trim().trim_matches('"').trim().to_string()
105}
106
107fn fallback_title_from_project_dir(project_dir: &Path) -> String {
108 project_dir
109 .file_name()
110 .and_then(|s| s.to_str())
111 .filter(|s| !s.is_empty())
112 .unwrap_or("Siglus")
113 .to_string()
114}