use std::sync::OnceLock;
use crate::base::generated::steam_language::EOSType;
use crate::error::SteamError;
#[derive(Debug)]
pub(crate) struct Utils;
impl Utils {
pub(crate) fn encode_hex_string(input: &[u8]) -> String {
hex::encode(input)
}
pub(crate) fn decode_hex_string(hex: Option<&str>) -> Result<Option<Vec<u8>>, SteamError> {
let Some(hex) = hex else {
return Ok(None);
};
hex::decode(hex)
.map(Some)
.map_err(|error| SteamError::InvalidArgument(format!("Invalid hex string: {error}")))
}
pub(crate) fn get_os_type() -> EOSType {
static OS_TYPE: OnceLock<EOSType> = OnceLock::new();
*OS_TYPE.get_or_init(detect_os_type)
}
}
pub(crate) fn encode_hex_string(input: &[u8]) -> String {
Utils::encode_hex_string(input)
}
pub(crate) fn decode_hex_string(hex: Option<&str>) -> Result<Option<Vec<u8>>, SteamError> {
Utils::decode_hex_string(hex)
}
pub(crate) fn get_os_type() -> EOSType {
Utils::get_os_type()
}
fn detect_os_type() -> EOSType {
#[cfg(target_os = "windows")]
{
detect_windows_os_type()
}
#[cfg(target_os = "macos")]
{
detect_macos_os_type()
}
#[cfg(target_os = "linux")]
{
detect_linux_os_type()
}
#[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]
{
EOSType::Unknown
}
}
#[cfg(target_os = "windows")]
fn detect_windows_os_type() -> EOSType {
let version = std::process::Command::new("cmd")
.args(["/c", "ver"])
.output()
.ok()
.and_then(|output| String::from_utf8(output.stdout).ok())
.and_then(|text| {
let start = text.find('[')? + 1;
let end = text[start..].find(']')? + start;
Some(text[start..end].trim().to_string())
})
.and_then(|text| parse_version(strip_version_prefix(&text)));
let Some((major, minor, build)) = version else {
return EOSType::Windows10;
};
match major {
4 => EOSType::WinNT,
5 => match minor {
0 => EOSType::Win2000,
1 => EOSType::WinXP,
2 => EOSType::Win2003,
_ => EOSType::WinUnknown,
},
6 => match minor {
0 => EOSType::WinVista, 1 => EOSType::Windows7, 2 => EOSType::Windows8, 3 => EOSType::Windows81, _ => EOSType::WinUnknown,
},
10 if build >= 22000 => EOSType::Win11,
10 => EOSType::Windows10, _ => EOSType::WinUnknown,
}
}
#[cfg(target_os = "linux")]
fn detect_linux_os_type() -> EOSType {
let Some((major, minor, _)) = std::fs::read_to_string("/proc/sys/kernel/osrelease")
.ok()
.and_then(|text| parse_version(text.trim()))
else {
return EOSType::LinuxUnknown;
};
match major {
2 => match minor {
2 => EOSType::Linux22,
4 => EOSType::Linux24,
6 => EOSType::Linux26,
_ => EOSType::LinuxUnknown,
},
3 => match minor {
2 => EOSType::Linux32,
5 => EOSType::Linux35,
6 => EOSType::Linux36,
10 => EOSType::Linux310,
16 => EOSType::Linux316,
18 => EOSType::Linux318,
_ => EOSType::Linux3x,
},
4 => match minor {
1 => EOSType::Linux41,
4 => EOSType::Linux44,
9 => EOSType::Linux49,
14 => EOSType::Linux414,
19 => EOSType::Linux419,
_ => EOSType::Linux4x,
},
5 => match minor {
4 => EOSType::Linux54,
10 => EOSType::Linux510,
_ => EOSType::Linux5x,
},
6 => EOSType::Linux6x,
7 => EOSType::Linux7x,
_ => EOSType::LinuxUnknown,
}
}
#[cfg(target_os = "macos")]
fn detect_macos_os_type() -> EOSType {
let Some((product_major, product_minor, _)) = std::process::Command::new("sw_vers")
.arg("-productVersion")
.output()
.ok()
.and_then(|output| String::from_utf8(output.stdout).ok())
.and_then(|text| parse_version(text.trim()))
else {
return EOSType::MacOSUnknown;
};
let darwin_major = if product_major == 10 {
product_minor + 4
} else {
product_major + 9
};
match darwin_major {
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,
}
}
fn strip_version_prefix(text: &str) -> &str {
match text.find(|character: char| character.is_ascii_digit()) {
Some(start) => &text[start..],
None => text,
}
}
fn parse_version(text: &str) -> Option<(u64, u64, u64)> {
let mut parts = text.split('.');
let major = parts.next()?.trim().parse::<u64>().ok()?;
let minor = parts.next().unwrap_or("0").trim().parse::<u64>().ok()?;
let build = parts.next().unwrap_or("0").trim().parse::<u64>().ok()?;
Some((major, minor, build))
}