Files
Unlucky-Launcher/crates/core/src/download/region_system.rs
T
AskaEth 40a296471d fix(core): 所有子进程统一走 platform::child::quiet + 角色守卫(M213)
GUI 父进程(PE Subsystem = 2)spawn 控制台子进程时,Windows 会为子进程新建一块
控制台——桌面上就是「黑框闪一下」。改前 crates/core/src 有 21 处 Command::new,
只有 tree_kill 那一处带了 CREATE_NO_WINDOW,于是探 N 个 JDK 就闪 N 次 java -version。

- 新增 crates/core/src/platform/child.rs:唯一构造器 quiet(program),Windows 上统一
  .creation_flags(CREATE_NO_WINDOW),其它平台原样返回。tree_kill 里那份就地合并进来,
  不再保留第二份常量。
- 20 处 spawn 全部改走它(region_system / java{discover,manual,probe,proc,termux} /
  addon exec / platform{linux_caps,macos_version,theme_probe,vk_probe,windows_version,
  wallpaper/io})。
- launch/process.rs 零改动:游戏进程要 CREATE_NEW_PROCESS_GROUP 撑 taskkill /T,
  带不带控制台窗口是产品决定。
- crates/core/tests/child_quiet.rs:按角色判「有没有绕开构造器」的守卫,外加构造器
  本身的 4 条行为用例。三条变异(裸 Command::new / 别名 Spawn::new / 删掉 creation_flags)
  都实测变红并已还原。
2026-11-09 15:07:56 +08:00

159 lines
5.6 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 地区判定的**系统级**信号补充:环境变量之外的那一半(D23「国内用户默认都走镜像」)。
//!
//! 为什么需要它:`Signals::from_env` 只读 `TZ` / `LANG` / `LC_*`。Windows 上这三个通常**全是空的**
//! ——中文版 Windows 既不设 `TZ` 也不设 `LANG`,于是「国内机器」被误判成国外,
//! D23 定下的国内默认序(Temurin→Oracle→MS)就拿不到了。本机实测就是这个现象。
//!
//! 补法(只在环境变量没给出答案时才做,且读不到就静默跳过):
//!
//! | 平台 | 时区 | 语言 |
//! | --- | --- | --- |
//! | Windows | `tzutil /g` → `China Standard Time` | `reg query` 的 `LocaleName` → `zh-CN` |
//! | 其它 | 不补(Unix 一般都有 `LANG`,`/etc/localtime` 解析成本不划算) | 同左 |
//!
//! 两个纪律:①**不引依赖**——`tzutil` / `reg` 都是系统自带,`discover.rs` 早就有 `reg query`
//! 的先例;②**不做网络探测**——探测(google 可达性)仍由调用方决定开不开。
use crate::platform::child::quiet;
use super::region::{Signals, GREATER_CHINA_TIMEZONES};
/// Windows 时区显示名里能判成「大中华区」的关键词(`tzutil /g` 的输出形态)。
const GREATER_CHINA_ZONE_HINTS: &[&str] = &[
"China Standard Time",
"Taipei Standard Time",
"Hong Kong Standard Time",
"Ulaanbaatar",
];
/// 系统级信号(环境变量之外;读不到就是空信号)。
#[must_use]
pub fn system_signals() -> Signals {
let mut signals = Signals::default();
if !cfg!(windows) {
return signals;
}
if let Some(zone) = windows_timezone() {
if zone_in_greater_china(&zone) {
// 折成 IANA 名,和 `Signals::from_env` 的取值空间保持一致。
signals.timezone = Some("Asia/Shanghai".to_string());
}
}
if let Some(locale) = windows_locale() {
signals.languages.push(locale);
}
signals
}
/// 环境变量信号 + 系统级信号(系统级只补空缺,不覆盖环境变量)。
#[must_use]
pub fn detect_signals() -> Signals {
let mut signals = Signals::from_env();
let system = system_signals();
if signals.timezone.is_none() {
signals.timezone = system.timezone;
}
for language in system.languages {
if !signals.languages.contains(&language) {
signals.languages.push(language);
}
}
signals
}
/// `tzutil /g` 的输出(失败为 `None`)。
#[must_use]
pub fn windows_timezone() -> Option<String> {
let output = quiet("tzutil").arg("/g").output().ok()?;
if !output.status.success() {
return None;
}
let text = String::from_utf8_lossy(&output.stdout).trim().to_string();
(!text.is_empty()).then_some(text)
}
/// 系统的 locale 名(`HKCU\Control Panel\International` 的 `LocaleName`)。
#[must_use]
pub fn windows_locale() -> Option<String> {
let output = quiet("reg")
.args([
"query",
"HKCU\\Control Panel\\International",
"/v",
"LocaleName",
])
.output()
.ok()?;
if !output.status.success() {
return None;
}
let stdout = String::from_utf8_lossy(&output.stdout).into_owned();
parse_reg_locale_name(&stdout)
}
/// 从 `reg query` 的输出里取 `LocaleName` 的值(` LocaleName REG_SZ zh-CN`)。
#[must_use]
pub fn parse_reg_locale_name(stdout: &str) -> Option<String> {
for line in stdout.lines() {
let line = line.trim();
if !line.starts_with("LocaleName") {
continue;
}
let value = line.rsplit("REG_SZ").next()?.trim();
if !value.is_empty() {
return Some(value.to_string());
}
}
None
}
/// Windows 时区显示名是否落在大中华区。
#[must_use]
pub fn zone_in_greater_china(zone: &str) -> bool {
let zone = zone.trim();
GREATER_CHINA_ZONE_HINTS
.iter()
.any(|hint| zone.eq_ignore_ascii_case(hint))
|| GREATER_CHINA_TIMEZONES
.iter()
.any(|known| known.eq_ignore_ascii_case(zone))
}
#[cfg(test)]
mod tests {
use super::{detect_signals, parse_reg_locale_name, zone_in_greater_china};
#[test]
fn windows_zone_names_map_to_the_greater_china_list() {
assert!(zone_in_greater_china("China Standard Time"));
assert!(zone_in_greater_china("Taipei Standard Time"));
assert!(zone_in_greater_china("Asia/Shanghai"), "IANA 名同样要认");
assert!(!zone_in_greater_china("Pacific Standard Time"));
assert!(!zone_in_greater_china(""));
}
#[test]
fn reg_output_yields_the_locale_name() {
// 真机 `reg query "HKCU\Control Panel\International" /v LocaleName` 的形态。
let stdout = "\r\nHKEY_CURRENT_USER\\Control Panel\\International\r\n \
LocaleName REG_SZ zh-CN\r\n\r\n";
assert_eq!(parse_reg_locale_name(stdout).as_deref(), Some("zh-CN"));
assert_eq!(
parse_reg_locale_name(" LocaleName REG_SZ \r\n"),
None
);
assert_eq!(parse_reg_locale_name("没有这一行"), None);
}
#[test]
fn detect_layers_system_signals_under_the_environment() {
// 环境变量优先:这里能断言的只有「不 panic 且返回一个 Signals」,
// 具体取值取决于跑测试的机器(CI 上没有 tzutil/reg 也要安静退化)。
let signals = detect_signals();
// 无论哪种情况,都不该凭空造出「大中华区」以外的时区值。
if let Some(zone) = &signals.timezone {
assert!(!zone.trim().is_empty());
}
}
}