1use std::path::{Path, PathBuf};
2
3use crate::{GameInfoProvider, PackFile};
4
5#[derive(Debug, Clone, Copy)]
8pub struct DummyVpk;
9
10impl PackFile for DummyVpk {
11 fn open<P: AsRef<Path>>(_path: P) -> Option<Self> {
12 None
14 }
15
16 fn has_entry(&self, _path: &str) -> bool {
17 false
18 }
19
20 fn read_entry(&self, _path: &str) -> Option<Vec<u8>> {
21 None
22 }
23}
24
25pub struct SimpleGameInfo;
27
28impl GameInfoProvider for SimpleGameInfo {
29 fn get_search_paths<P: AsRef<Path>>(path: P) -> Option<Vec<(String, String)>> {
30 let content = std::fs::read_to_string(&path).ok()?;
31 let path_ref = path.as_ref();
32 let mut paths = Vec::new();
33 let mut in_search_paths = false;
34
35
36 for line in content.lines() {
39 let line = line.trim().to_lowercase();
40
41 if line.contains("\"searchpaths\"") || line.contains("searchpaths") {
42 in_search_paths = true;
43 continue;
44 }
45
46 if in_search_paths {
47 if line == "}" {
48 break;
49 }
50
51 if line == "{" || line.is_empty() || line.starts_with("//") {
52 continue;
53 }
54
55 let parts: Vec<&str> = line
56 .trim()
57 .splitn(2, char::is_whitespace)
58 .map(|s| s.trim_start().trim_matches('"'))
59 .collect();
60
61 if parts.len() >= 2 {
62 let resolved = crate::utils::resolve_macro_path(parts[1], path_ref);
63 paths.push((parts[0].to_string(), resolved.to_string_lossy().to_string()));
64 }
65 }
66 }
67
68 if paths.is_empty() {
69 None
70 } else {
71 Some(paths)
72 }
73 }
74}
75
76pub struct SimpleWithMount;
79
80impl GameInfoProvider for SimpleWithMount {
81 fn get_search_paths<P: AsRef<Path>>(path: P) -> Option<Vec<(String, String)>> {
82 let path_ref = path.as_ref();
83 let main_game_dir = path_ref.parent()?;
84
85 let mut res_paths = SimpleGameInfo::get_search_paths(path_ref)?;
86
87 let mount_cfg_path = main_game_dir.join("cfg").join("mount.cfg");
88
89 if let Ok(mount_cfg_content) = std::fs::read_to_string(&mount_cfg_path) {
90 for line in mount_cfg_content.lines() {
92 let line = line.trim();
93 if line.starts_with("//") || line.is_empty() || line == "{" || line == "}" || line.contains("mountcfg") {
94 continue;
95 }
96
97 let parts: Vec<&str> = line
98 .splitn(2, |c: char| c.is_whitespace())
99 .map(|s| s.trim().trim_matches('"'))
100 .filter(|s| !s.is_empty())
101 .collect();
102
103 if parts.len() >= 2 {
104 let mount_path = parts[1];
105 let mount_game_dir = if Path::new(mount_path).is_absolute() {
106 PathBuf::from(mount_path)
107 } else {
108 main_game_dir.join(mount_path)
109 };
110
111 let mount_info_path = mount_game_dir.join("gameinfo.txt");
112 if mount_info_path.exists() {
113 if let Some(mounted_paths) = SimpleGameInfo::get_search_paths(&mount_info_path) {
114 res_paths.extend(mounted_paths);
115 }
116 }
117 }
118 }
119 }
120
121 if res_paths.is_empty() {
122 None
123 } else {
124 Some(res_paths)
125 }
126 }
127}
128
129
130pub struct P2GameInfo;
134
135impl GameInfoProvider for P2GameInfo {
136 fn get_search_paths<P: AsRef<Path>>(path: P) -> Option<Vec<(String, String)>> {
137
138 let path_ref = path.as_ref();
140 let game_path = path_ref.ancestors().nth(2).unwrap_or_else(|| Path::new(""));
141
142 let mut paths: Vec<_> = (1..)
143 .map(|idx| format!("portal2_dlc{}/", idx))
144 .take_while(|dlc_name| game_path.join(dlc_name).exists())
145 .map(|dlc_name| ("game".to_string(), dlc_name))
146 .collect();
147
148 paths.reverse();
149 paths.extend(SimpleGameInfo::get_search_paths(&path)?);
150
151 Some(paths)
152 }
153}
154
155