Unlucky Launcher v0.0.1-alpha
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

Rust 原生单文件 Minecraft 启动器:CLI 完整可用(install/launch/accounts/java/diagnose/agent),
MCP server 接入外部 Agent 工具,GUI 开发中。
This commit is contained in:
AskaEth committed 2026-10-31 21:54:10 +08:00
commit bc41dfcf7d
517 files changed
+92844

No files matched your search

+158
View File
@@ -0,0 +1,158 @@
//! 地区判定的**系统级**信号补充:环境变量之外的那一半(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());
}
}
}