compatdb_core/runtime/
process.rs1use crate::{EnvEntry, part::steam_app_id};
5
6pub const STEAM_APP_VAR: &str = "SteamAppId";
9
10#[derive(Clone, Copy, PartialEq, Eq, Debug)]
16pub enum Arch {
17 X86_64,
20 I386,
22 Aarch64,
24}
25
26impl Arch {
27 #[must_use]
32 pub const fn pe_dir(self) -> &'static str {
33 match self {
34 Self::I386 => "i386-windows",
35 Self::X86_64 | Self::Aarch64 => "aarch64-windows",
36 }
37 }
38
39 #[must_use]
51 pub const fn of_process(wow64: bool, machine: Option<u16>) -> Self {
52 match (wow64, machine) {
53 (true, _) => Self::I386,
54 (false, Some(IMAGE_FILE_MACHINE_ARM64)) => Self::Aarch64,
55 _ => Self::X86_64,
56 }
57 }
58}
59
60pub const IMAGE_FILE_MACHINE_ARM64: u16 = 0xaa64;
62
63#[must_use]
66pub fn basename_of(image_path: &[u16]) -> String {
67 let start = image_path
68 .iter()
69 .rposition(|&u| u == u16::from(b'\\') || u == u16::from(b'/'))
70 .map_or(0, |i| i.saturating_add(1));
71 String::from_utf16_lossy(image_path.get(start..).unwrap_or(&[]))
72}
73
74#[must_use]
80pub fn append_cmdline(cmdline: &[u16], appends: &[impl AsRef<str>]) -> Option<Vec<u16>> {
81 append_cmdline_each(cmdline, appends).0
82}
83
84#[must_use]
87pub fn append_cmdline_each(
88 cmdline: &[u16],
89 appends: &[impl AsRef<str>],
90) -> (Option<Vec<u16>>, Vec<bool>) {
91 let mut cur = cmdline.to_vec();
92 let mut appended = Vec::with_capacity(appends.len());
93 for text in appends {
94 let text = text.as_ref();
95 if text.is_empty() || contains_cs(&cur, text) {
96 appended.push(false);
97 continue;
98 }
99 cur.push(u16::from(b' '));
100 cur.extend(text.encode_utf16());
101 appended.push(true);
102 }
103 (appended.contains(&true).then_some(cur), appended)
104}
105
106#[must_use]
110pub fn env_value(block: &[u16], name: &str) -> Option<String> {
111 let seg = split_env(block)
112 .into_iter()
113 .find(|seg| env_name_matches(seg, name))?;
114 let value = seg
115 .iter()
116 .position(|&u| u == u16::from(b'='))
117 .and_then(|i| seg.get(i.saturating_add(1)..))
118 .unwrap_or(&[]);
119 Some(String::from_utf16_lossy(value))
120}
121
122#[must_use]
129pub fn steam_app(block: &[u16]) -> Option<u32> {
130 steam_app_id(&env_value(block, STEAM_APP_VAR)?)
131}
132
133#[must_use]
139pub fn merge_env(block: &[u16], entries: &[EnvEntry]) -> Option<Vec<u16>> {
140 let mut segs = split_env(block);
141 let mut changed = false;
142 for entry in entries {
143 let name = entry.name().as_str();
144 let found = segs.iter().position(|seg| env_name_matches(seg, name));
145 let Some(value) = entry.value() else {
146 if let Some(i) = found {
147 segs.remove(i);
148 changed = true;
149 }
150 continue;
151 };
152 let mut new_seg: Vec<u16> = name.encode_utf16().collect();
153 new_seg.push(u16::from(b'='));
154 new_seg.extend(value.encode_utf16());
155 match found {
156 Some(i) if segs.get(i) == Some(&new_seg) => {}
157 Some(i) => {
158 if let Some(slot) = segs.get_mut(i) {
159 *slot = new_seg;
160 changed = true;
161 }
162 }
163 None => {
164 segs.push(new_seg);
165 changed = true;
166 }
167 }
168 }
169 if !changed {
170 return None;
171 }
172 let mut out = Vec::new();
173 for seg in &segs {
174 out.extend_from_slice(seg);
175 out.push(0);
176 }
177 out.push(0);
178 Some(out)
179}
180
181pub const fn to_lower_u16(u: u16) -> u16 {
183 if u >= b'A' as u16 && u <= b'Z' as u16 {
184 u | 0x20
185 } else {
186 u
187 }
188}
189
190fn contains_cs(haystack: &[u16], needle: &str) -> bool {
192 let n: Vec<u16> = needle.encode_utf16().collect();
193 if n.is_empty() {
194 return true;
195 }
196 if haystack.len() < n.len() {
197 return false;
198 }
199 haystack.windows(n.len()).any(|w| w == n.as_slice())
200}
201
202fn split_env(block: &[u16]) -> Vec<Vec<u16>> {
205 let mut segs = Vec::new();
206 let mut cur = Vec::new();
207 for &u in block {
208 if u == 0 {
209 if cur.is_empty() {
210 break;
211 }
212 segs.push(std::mem::take(&mut cur));
213 } else {
214 cur.push(u);
215 }
216 }
217 segs
218}
219
220fn env_name_matches(seg: &[u16], name: &str) -> bool {
223 if seg.first() == Some(&u16::from(b'=')) {
224 return false;
225 }
226 let seg_name = seg.split(|&u| u == u16::from(b'=')).next().unwrap_or(seg);
227 let want: Vec<u16> = name.encode_utf16().map(to_lower_u16).collect();
228 seg_name.len() == want.len()
229 && seg_name
230 .iter()
231 .map(|&u| to_lower_u16(u))
232 .eq(want.iter().copied())
233}
234
235#[cfg(test)]
236mod tests;