1use std::sync::OnceLock;
4
5use crate::base::generated::steam_language::EOSType;
6use crate::error::SteamError;
7
8#[derive(Debug)]
10pub(crate) struct Utils;
11
12impl Utils {
13 pub(crate) fn encode_hex_string(input: &[u8]) -> String {
15 hex::encode(input)
16 }
17
18 pub(crate) fn decode_hex_string(hex: Option<&str>) -> Result<Option<Vec<u8>>, SteamError> {
20 let Some(hex) = hex else {
21 return Ok(None);
22 };
23
24 hex::decode(hex)
25 .map(Some)
26 .map_err(|error| SteamError::InvalidArgument(format!("Invalid hex string: {error}")))
27 }
28
29 pub(crate) fn get_os_type() -> EOSType {
31 static OS_TYPE: OnceLock<EOSType> = OnceLock::new();
32
33 *OS_TYPE.get_or_init(detect_os_type)
34 }
35}
36
37pub(crate) fn encode_hex_string(input: &[u8]) -> String {
39 Utils::encode_hex_string(input)
40}
41
42pub(crate) fn decode_hex_string(hex: Option<&str>) -> Result<Option<Vec<u8>>, SteamError> {
44 Utils::decode_hex_string(hex)
45}
46
47pub(crate) fn get_os_type() -> EOSType {
49 Utils::get_os_type()
50}
51
52fn detect_os_type() -> EOSType {
56 #[cfg(target_os = "windows")]
57 {
58 detect_windows_os_type()
59 }
60
61 #[cfg(target_os = "macos")]
62 {
63 detect_macos_os_type()
64 }
65
66 #[cfg(target_os = "linux")]
67 {
68 detect_linux_os_type()
69 }
70
71 #[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]
72 {
73 EOSType::Unknown
74 }
75}
76
77#[cfg(target_os = "windows")]
80fn detect_windows_os_type() -> EOSType {
81 let version = std::process::Command::new("cmd")
84 .args(["/c", "ver"])
85 .output()
86 .ok()
87 .and_then(|output| String::from_utf8(output.stdout).ok())
88 .and_then(|text| {
89 let start = text.find('[')? + 1;
90 let end = text[start..].find(']')? + start;
91 Some(text[start..end].trim().to_string())
92 })
93 .and_then(|text| parse_version(strip_version_prefix(&text)));
96
97 let Some((major, minor, build)) = version else {
98 return EOSType::Windows10;
101 };
102
103 match major {
104 4 => EOSType::WinNT,
105 5 => match minor {
106 0 => EOSType::Win2000,
107 1 => EOSType::WinXP,
108 2 => EOSType::Win2003,
111 _ => EOSType::WinUnknown,
112 },
113 6 => match minor {
114 0 => EOSType::WinVista, 1 => EOSType::Windows7, 2 => EOSType::Windows8, 3 => EOSType::Windows81, _ => EOSType::WinUnknown,
119 },
120 10 if build >= 22000 => EOSType::Win11,
121 10 => EOSType::Windows10, _ => EOSType::WinUnknown,
123 }
124}
125
126#[cfg(target_os = "linux")]
129fn detect_linux_os_type() -> EOSType {
130 let Some((major, minor, _)) = std::fs::read_to_string("/proc/sys/kernel/osrelease")
131 .ok()
132 .and_then(|text| parse_version(text.trim()))
133 else {
134 return EOSType::LinuxUnknown;
135 };
136
137 match major {
139 2 => match minor {
140 2 => EOSType::Linux22,
141 4 => EOSType::Linux24,
142 6 => EOSType::Linux26,
143 _ => EOSType::LinuxUnknown,
144 },
145 3 => match minor {
146 2 => EOSType::Linux32,
147 5 => EOSType::Linux35,
148 6 => EOSType::Linux36,
149 10 => EOSType::Linux310,
150 16 => EOSType::Linux316,
151 18 => EOSType::Linux318,
152 _ => EOSType::Linux3x,
153 },
154 4 => match minor {
155 1 => EOSType::Linux41,
156 4 => EOSType::Linux44,
157 9 => EOSType::Linux49,
158 14 => EOSType::Linux414,
159 19 => EOSType::Linux419,
160 _ => EOSType::Linux4x,
161 },
162 5 => match minor {
163 4 => EOSType::Linux54,
164 10 => EOSType::Linux510,
165 _ => EOSType::Linux5x,
166 },
167 6 => EOSType::Linux6x,
168 7 => EOSType::Linux7x,
169 _ => EOSType::LinuxUnknown,
170 }
171}
172
173#[cfg(target_os = "macos")]
176fn detect_macos_os_type() -> EOSType {
177 let Some((product_major, product_minor, _)) = std::process::Command::new("sw_vers")
178 .arg("-productVersion")
179 .output()
180 .ok()
181 .and_then(|output| String::from_utf8(output.stdout).ok())
182 .and_then(|text| parse_version(text.trim()))
183 else {
184 return EOSType::MacOSUnknown;
185 };
186
187 let darwin_major = if product_major == 10 {
191 product_minor + 4
192 } else {
193 product_major + 9
194 };
195
196 match darwin_major {
197 11 => EOSType::MacOS107, 12 => EOSType::MacOS108, 13 => EOSType::MacOS109, 14 => EOSType::MacOS1010, 15 => EOSType::MacOS1011, 16 => EOSType::MacOS1012, 17 => EOSType::Macos1013, 18 => EOSType::Macos1014, 19 => EOSType::Macos1015, 20 => EOSType::MacOS11, 21 => EOSType::MacOS12, 22 => EOSType::MacOS13, 23 => EOSType::MacOS14, 24 => EOSType::MacOS15, _ => EOSType::MacOSUnknown,
212 }
213}
214
215fn strip_version_prefix(text: &str) -> &str {
218 match text.find(|character: char| character.is_ascii_digit()) {
219 Some(start) => &text[start..],
220 None => text,
221 }
222}
223
224fn parse_version(text: &str) -> Option<(u64, u64, u64)> {
227 let mut parts = text.split('.');
228
229 let major = parts.next()?.trim().parse::<u64>().ok()?;
230 let minor = parts.next().unwrap_or("0").trim().parse::<u64>().ok()?;
231 let build = parts.next().unwrap_or("0").trim().parse::<u64>().ok()?;
232
233 Some((major, minor, build))
234}