Files
Unlucky-Launcher/crates/core/src/download/region_system.rs
T
AskaEth bc41dfcf7d
CI / fmt (rustfmt --check) (push) Canceled after 0s
CI / clippy (ubuntu-latest) (push) Canceled after 0s
CI / clippy (windows-latest) (push) Canceled after 0s
CI / test (ubuntu-latest) (push) Canceled after 0s
CI / test (windows-latest) (push) Canceled after 0s
CI / build release (ubuntu-latest) (push) Canceled after 0s
CI / build release (windows-latest) (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Unlucky Launcher v0.0.1-alpha
Rust 原生单文件 Minecraft 启动器:CLI 完整可用(install/launch/accounts/java/diagnose/agent),
MCP server 接入外部 Agent 工具,GUI 开发中。
2026-10-31 21:54:10 +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 std::process::Command;
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 = Command::new("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 = Command::new("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());
}
}
}