scl_core/
utils.rs

1//! 一些启动/安装游戏时会用到的实用模块
2
3use std::fmt::Display;
4
5use inner_future::io::{AsyncRead, AsyncReadExt};
6use sha1_smol::*;
7
8use crate::prelude::*;
9
10/// 根据当前构建目标判定的当前操作系统类型
11///
12/// 用于版本元数据的参数和依赖库的条件判断
13#[cfg(target_os = "windows")]
14pub const TARGET_OS: &str = "windows";
15/// 根据当前构建目标判定的当前操作系统类型
16///
17/// 用于版本元数据的参数和依赖库的条件判断
18#[cfg(target_os = "macos")]
19pub const TARGET_OS: &str = "osx";
20/// 根据当前构建目标判定的当前操作系统类型
21///
22/// 用于版本元数据的参数和依赖库的条件判断
23#[cfg(target_os = "linux")]
24pub const TARGET_OS: &str = "linux";
25
26/// 根据当前构建目标判定的当前操作系统架构类型
27///
28/// 用于版本元数据的参数和依赖库的条件判断
29#[cfg(target_arch = "x86")]
30pub const TARGET_ARCH: &str = "x86";
31/// 根据当前构建目标判定的当前操作系统架构类型
32///
33/// 用于版本元数据的参数和依赖库的条件判断
34#[cfg(target_arch = "x86_64")]
35pub const TARGET_ARCH: &str = "x86_64";
36/// 根据当前构建目标判定的当前操作系统架构类型
37///
38/// 用于版本元数据的参数和依赖库的条件判断
39#[cfg(target_arch = "arm")]
40pub const NATIVE_ARCH: &str = "arm";
41/// 根据当前构建目标判定的当前操作系统架构类型
42///
43/// 用于版本元数据的参数和依赖库的条件判断
44#[cfg(target_arch = "aarch64")]
45pub const TARGET_ARCH: &str = "aarch64";
46
47/// 根据当前构建目标判定的当前操作系统的操作位数
48///
49/// 用于版本元数据的参数和依赖库的条件判断
50#[cfg(target_arch = "x86")]
51pub const NATIVE_ARCH: &str = "32";
52/// 根据当前构建目标判定的当前操作系统的操作位数
53///
54/// 用于版本元数据的参数和依赖库的条件判断
55#[cfg(target_arch = "x86_64")]
56pub const NATIVE_ARCH: &str = "64";
57/// 根据当前构建目标判定的当前操作系统的操作位数
58///
59/// 用于版本元数据的参数和依赖库的条件判断
60#[cfg(target_arch = "arm")]
61pub const NATIVE_ARCH: &str = "64";
62/// 根据当前构建目标判定的当前操作系统的操作位数
63///
64/// 用于版本元数据的参数和依赖库的条件判断
65#[cfg(target_arch = "aarch64")]
66pub const NATIVE_ARCH: &str = "64";
67
68/// 根据当前构建目标判定的类路径的分隔符
69///
70/// 用于启动参数的 `classpath` 部分的拼接
71#[cfg(target_os = "windows")]
72pub const CLASSPATH_SEPARATOR: &str = ";";
73/// 根据当前构建目标判定的类路径的分隔符
74///
75/// 用于启动参数的 `classpath` 部分的拼接
76#[cfg(not(target_os = "windows"))]
77pub const CLASSPATH_SEPARATOR: &str = ":";
78
79/// 一个内存页面位移值,仅 MacOS 用,用于自动内存计算
80#[cfg(target_os = "macos")]
81pub static PAGESHIFT: once_cell::sync::Lazy<libc::c_int> = once_cell::sync::Lazy::new(|| {
82    let mut pagesize = unsafe { getpagesize() };
83    let mut pageshift = 0;
84    while pagesize > 1 {
85        pageshift += 1;
86        pagesize >>= 1;
87    }
88    pageshift - 10 // LOG1024
89});
90
91#[cfg(target_os = "macos")]
92#[link(name = "c")]
93extern "C" {
94    fn getpagesize() -> libc::c_int;
95}
96
97/// 异步计算一个数据的 SHA1 摘要值
98///
99/// 返回一个十六进制的小写摘要字符串
100pub async fn get_data_sha1(data: &mut (impl AsyncRead + Unpin)) -> DynResult<String> {
101    let mut buf = [0u8; 16];
102    let mut sha = Sha1::default();
103    loop {
104        let size = data.read(&mut buf).await?;
105        if size > 0 {
106            sha.update(&buf[..size]);
107        } else {
108            break;
109        }
110    }
111    Ok(sha.hexdigest())
112}
113
114/// 返回一个相对路径的绝对路径格式
115///
116/// 因为标准库的 [`std::fs::canonicalize`] 不支持不存在的路径的解析,所以做了这个
117///
118/// 用于启动参数的路径绝对化
119pub fn get_full_path(p: impl AsRef<std::path::Path>) -> String {
120    use path_absolutize::*;
121    let p = p.as_ref();
122    match p.absolutize() {
123        Ok(p) => {
124            #[cfg(windows)]
125            if let Some(p) = p.to_string_lossy().strip_prefix("\\\\?\\") {
126                p.to_string()
127            } else {
128                p.to_string_lossy().to_string()
129            }
130            #[cfg(not(windows))]
131            p.to_string_lossy().to_string()
132        }
133        Err(e) => {
134            println!(
135                "Warning: Can't convert path {} to full path: {}",
136                p.to_string_lossy(),
137                e
138            );
139            p.to_string_lossy().to_string()
140        }
141    }
142}
143
144/// 系统架构枚举
145///
146/// 目前根据 SCL 自身会支持的平台增加此处的枚举值
147///
148/// 用于启动参数的条件判断组合
149#[derive(Clone, Copy)]
150pub enum Arch {
151    /// 一个 `x86` 平台
152    X86,
153    /// 一个 `x86_64`/`amd64` 平台
154    X64,
155    /// 一个 `arm64`/`aarch64` 平台
156    ARM64,
157}
158
159impl Display for Arch {
160    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161        f.write_str(self.as_ref())
162    }
163}
164
165impl AsRef<str> for Arch {
166    fn as_ref(&self) -> &str {
167        match self {
168            Arch::X86 => "x86",
169            Arch::X64 => "x86_64",
170            Arch::ARM64 => "aarch64",
171        }
172    }
173}
174
175/// 一个延迟获取的当前系统的架构
176///
177/// 这个会获取到系统自身的架构,而非软件自身的编译目标架构
178pub static NATIVE_ARCH_LAZY: once_cell::sync::Lazy<Arch> =
179    once_cell::sync::Lazy::new(get_system_arch);
180
181fn get_system_arch() -> Arch {
182    #[cfg(windows)]
183    unsafe {
184        use windows::Win32::System::SystemInformation::*;
185        let mut info: SYSTEM_INFO = Default::default();
186        GetNativeSystemInfo(&mut info);
187        match info.Anonymous.Anonymous.wProcessorArchitecture.0 {
188            0 => Arch::X86,
189            12 => Arch::ARM64,
190            9 => Arch::X64,
191            _ => unreachable!(),
192        }
193    }
194    #[cfg(all(target_os = "linux", target_arch = "x86"))]
195    return Arch::X86;
196    #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
197    return Arch::X64;
198    #[cfg(all(target_os = "linux", target_arch = "aarch64"))]
199    return Arch::ARM64;
200    #[cfg(all(target_os = "macos", target_arch = "x86_64"))]
201    return Arch::X64;
202    #[cfg(all(target_os = "macos", target_arch = "aarch64"))]
203    return Arch::ARM64;
204}
205
206/// 内存状态对象,单位为 MB
207pub struct MemoryStatus {
208    /// 机器的内存总量,单位为 MB
209    pub max: u64,
210    /// 机器的可用内存总量,单位为 MB
211    pub free: u64,
212}
213
214/// 获取一个可执行程序所对应的运行架构,仅 MacOS 可用。
215///
216/// 这用于判定 MacOS 上 Java 运行时的架构类型
217///
218/// 对于 MacOS 的可执行文件格式描述,请参阅 "OS X ABI Mach-O File Format Reference"
219#[cfg(not(target_os = "macos"))]
220pub async fn get_exec_arch(_file_path: impl AsRef<std::path::Path>) -> DynResult<Arch> {
221    unimplemented!("此函数仅 MacOS 可用")
222}
223
224/// 获取一个可执行程序所对应的运行架构,仅 MacOS 可用。
225///
226/// 这用于判定 MacOS 上 Java 运行时的架构类型
227///
228/// 对于 MacOS 的可执行文件格式描述,请参阅 "OS X ABI Mach-O File Format Reference"
229#[cfg(target_os = "macos")]
230pub async fn get_exec_arch(file_path: impl AsRef<std::path::Path>) -> DynResult<Arch> {
231    let mut file = inner_future::fs::OpenOptions::new()
232        .read(true)
233        .open(file_path.as_ref())
234        .await?;
235
236    let mut buf = [0u8; 8];
237
238    file.read_exact(&mut buf).await?;
239
240    // Mach-O Magic Number
241    // CF FA ED FE
242    if !(buf[0] == 0xCF && buf[1] == 0xFA && buf[2] == 0xED && buf[3] == 0xFE) {
243        anyhow::bail!("文件不是一个合法的 Mach-O 可执行文件");
244    }
245
246    // CPU Arch Type
247    match (buf[4], buf[7]) {
248        (7, 0) => Ok(Arch::X86),    // X86 I386
249        (7, 1) => Ok(Arch::X64),    // X86_64
250        (12, 1) => Ok(Arch::ARM64), // ARM64
251        (_, _) => anyhow::bail!("不支持判定此架构"),
252    }
253}
254
255/// 获取当前内存使用状态,单位为 MB
256pub fn get_mem_status() -> MemoryStatus {
257    #[cfg(target_os = "windows")]
258    unsafe {
259        use windows::Win32::System::SystemInformation::MEMORYSTATUSEX;
260        let mut ms = MEMORYSTATUSEX {
261            dwLength: std::mem::size_of::<MEMORYSTATUSEX>() as _,
262            ..Default::default()
263        };
264        windows::Win32::System::SystemInformation::GlobalMemoryStatusEx(&mut ms).unwrap();
265        MemoryStatus {
266            max: ms.ullTotalPhys / 1024 / 1024,
267            free: ms.ullAvailPhys / 1024 / 1024,
268        }
269    }
270    #[cfg(target_os = "linux")]
271    {
272        let stat = std::fs::read_to_string("/proc/meminfo").unwrap();
273        let mut max = None;
274        let mut free = None;
275        for line in stat.lines() {
276            // 原单位是 kB
277            if line.starts_with("MemTotal:") {
278                max = line[10..line.len() - 3]
279                    .trim()
280                    .parse::<u64>()
281                    .map(|x| Some(x / 1024))
282                    .unwrap_or_default();
283            } else if line.starts_with("MemFree:") {
284                free = line[9..line.len() - 3]
285                    .trim()
286                    .parse::<u64>()
287                    .map(|x| Some(x / 1024))
288                    .unwrap_or_default();
289            }
290            if max.is_some() && free.is_some() {
291                break;
292            }
293        }
294        MemoryStatus {
295            max: max.unwrap_or(2048),
296            free: free.unwrap_or(2048),
297        }
298    }
299    #[cfg(target_os = "macos")]
300    {
301        unsafe {
302            let total = libc::sysconf(libc::_SC_PHYS_PAGES);
303            if total == -1 {
304                return MemoryStatus {
305                    max: 2048,
306                    free: 2048,
307                };
308            }
309
310            let host_port = libc::mach_host_self();
311            let mut stat = std::mem::MaybeUninit::<libc::vm_statistics64>::zeroed();
312            let mut stat_count = libc::HOST_VM_INFO64_COUNT;
313
314            if libc::host_statistics64(
315                host_port,
316                libc::HOST_VM_INFO64,
317                stat.as_mut_ptr() as *mut i32,
318                &mut stat_count,
319            ) != libc::KERN_SUCCESS
320            {
321                return MemoryStatus {
322                    max: 2048,
323                    free: 2048,
324                };
325            }
326
327            let stat = stat.assume_init();
328
329            MemoryStatus {
330                max: ((total as u64) << *PAGESHIFT) / 1024,
331                free: (((stat.inactive_count + stat.free_count) as u64) << *PAGESHIFT) / 1024,
332            }
333        }
334    }
335}