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

+386
View File
@@ -0,0 +1,386 @@
//! 镜像清单与 URL 前缀重写。
//!
//! 内置清单 `mirrors.json` 随源码嵌入(`include_str!`),发行后可由设置层追加自定义规则。
//! 重写语义承 analyse/03 §5.2 的「前缀替换而非全量重写」——保留子路径,因此 `/libraries`、
//! `/maven`、`/fabric-meta` 这类子路径天然兼容;`inject` 用于补上路径形状不同的场景
//! (资源对象:官方 `<xx>/<hash>`,BMCLAPI `/assets/<xx>/<hash>`)。
//!
//! gh-proxy 规则(用户定案):**只有国内模式 + GitHub host** 才套前缀,其它资源不受影响。
use serde::{Deserialize, Serialize};
pub use super::adoptium::AdoptiumMirror;
use super::jdk_sources::{MsOpenJdkTable, OracleMirror, ZuluTable};
use super::region::{RegionMode, RegionProfile};
use super::types::{Candidate, ResourceKind, SOURCE_OFFICIAL};
/// gh-proxy 候选的源标识。
pub const SOURCE_GH_PROXY: &str = "gh_proxy";
const fn default_true() -> bool {
true
}
/// 一条前缀重写规则。
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PrefixRule {
/// 规则 id(日志/诊断用)。
pub id: String,
/// 匹配的 URL 前缀(含 scheme)。
pub from: String,
/// 替换成的前缀。
pub to: String,
/// 插在 `to` 与剩余路径之间的内容(默认空)。
#[serde(default)]
pub inject: String,
/// 「仅加速不替代」:镜像优先模式下仍排在官方之后。
#[serde(default)]
pub fallback: bool,
/// 只对这些资源类型生效(空 = 全部)。
#[serde(default)]
pub kinds: Vec<ResourceKind>,
/// 是否启用(设置层可关)。
#[serde(default = "default_true")]
pub enabled: bool,
}
impl PrefixRule {
/// 构造一条最简规则。
#[must_use]
pub fn new(id: impl Into<String>, from: impl Into<String>, to: impl Into<String>) -> Self {
Self {
id: id.into(),
from: from.into(),
to: to.into(),
inject: String::new(),
fallback: false,
kinds: Vec::new(),
enabled: true,
}
}
/// 设一个「仅加速」标记。
#[must_use]
pub fn fallback(mut self) -> Self {
self.fallback = true;
self
}
/// 设置注入片段。
#[must_use]
pub fn with_inject(mut self, inject: impl Into<String>) -> Self {
self.inject = inject.into();
self
}
/// 是否命中该 URL(前缀匹配 + 资源类型过滤 + 启用位)。
#[must_use]
pub fn matches(&self, url: &str, kind: ResourceKind) -> bool {
self.enabled
&& url.starts_with(&self.from)
&& (self.kinds.is_empty() || self.kinds.contains(&kind))
}
/// 生成镜像候选。
#[must_use]
pub fn apply(&self, url: &str) -> Option<Candidate> {
if !url.starts_with(&self.from) {
return None;
}
let rest = &url[self.from.len()..];
let rewritten = format!("{}{}{}", self.to, self.inject, rest);
Some(Candidate::new(source_id_for(&rewritten), rewritten))
}
}
/// 一个 gh-proxy 类前缀。
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GhProxy {
/// 源标识。
pub id: String,
/// 展示名。
pub name: String,
/// 前缀(形如 `https://gh-proxy.com/`)。
pub prefix: String,
/// 是否启用。
#[serde(default = "default_true")]
pub enabled: bool,
}
/// 镜像表:内置规则 + 用户自定义规则。
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MirrorTable {
/// 清单版本(便于诊断页显示「镜像表是哪一版」)。
pub version: u32,
/// 清单自带的说明(假阳性成因、不可推导项等,给运维看的原地注释)。
#[serde(default)]
pub notes: Vec<String>,
/// 前缀重写规则(前面的优先)。
#[serde(default)]
pub rules: Vec<PrefixRule>,
/// Adoptium 院校镜像。
#[serde(default)]
pub adoptium: Vec<AdoptiumMirror>,
/// Oracle OpenJDK 镜像(v3 新增)。
#[serde(default)]
pub oracle_openjdk: Vec<OracleMirror>,
/// Microsoft OpenJDK 分发模板(v3 新增)。
#[serde(default)]
pub microsoft_openjdk: Option<MsOpenJdkTable>,
/// Zulu 元数据(v3 新增)。
#[serde(default)]
pub zulu: Option<ZuluTable>,
/// gh-proxy 类前缀。
#[serde(default)]
pub gh_proxies: Vec<GhProxy>,
}
impl Default for MirrorTable {
fn default() -> Self {
Self::builtin()
}
}
impl MirrorTable {
/// 内置清单(编译期嵌入)。
#[must_use]
pub fn builtin() -> Self {
Self::parse(include_str!("mirrors.json")).unwrap_or_else(|err| {
debug_note(&format!("内置 mirrors.json 解析失败(不应发生):{err}"));
Self {
version: 0,
notes: Vec::new(),
rules: Vec::new(),
adoptium: Vec::new(),
oracle_openjdk: Vec::new(),
microsoft_openjdk: None,
zulu: None,
gh_proxies: Vec::new(),
}
})
}
/// 从 JSON 解析。
pub fn parse(json: &str) -> Result<Self, serde_json::Error> {
serde_json::from_str(json)
}
/// 序列化成 JSON(诊断页/用户自定义文件回写用)。
pub fn to_json_pretty(&self) -> Result<String, serde_json::Error> {
serde_json::to_string_pretty(self)
}
/// 追加用户自定义规则(插到最前,优先于内置规则)。
pub fn add_custom_rule(&mut self, rule: PrefixRule) {
self.rules.insert(0, rule);
}
/// 覆盖 gh-proxy 前缀(设置项);传 `None` 表示不默认启用。
pub fn set_gh_proxy(&mut self, prefix: Option<&str>) {
match prefix {
Some(prefix) => {
if let Some(proxy) = self.gh_proxies.first_mut() {
proxy.prefix = prefix.to_string();
proxy.enabled = true;
} else {
self.gh_proxies.push(GhProxy {
id: "gh-proxy".to_string(),
name: "自定义 gh-proxy".to_string(),
prefix: prefix.to_string(),
enabled: true,
});
}
}
None => {
for proxy in &mut self.gh_proxies {
proxy.enabled = false;
}
}
}
}
/// 找到第一条命中的前缀规则。
#[must_use]
pub fn prefix_rule_for(&self, url: &str, kind: ResourceKind) -> Option<&PrefixRule> {
self.rules.iter().find(|rule| rule.matches(url, kind))
}
/// 清单里第一个启用的 gh-proxy 前缀(默认值来源;设置层用它初始化地区档案)。
#[must_use]
pub fn default_gh_proxy(&self) -> Option<&str> {
self.gh_proxies
.iter()
.find(|proxy| proxy.enabled && !proxy.prefix.is_empty())
.map(|proxy| proxy.prefix.as_str())
}
/// GitHub host 且允许时套 gh-proxy 前缀。
///
/// **生效前缀的唯一真相源是 `region.gh_proxy`**(设置层/地区判定写入,`None` = 用户关闭)。
/// 表格里的 `gh_proxies` 只是「已知可用前缀」清单,配合 `default_gh_proxy()` 给设置做默认值。
#[must_use]
pub fn gh_proxy_candidates(&self, url: &str, region: &RegionProfile) -> Vec<Candidate> {
if region.mode != RegionMode::MirrorFirst {
return Vec::new();
}
let Some(prefix) = region
.gh_proxy
.as_deref()
.map(str::trim)
.filter(|prefix| !prefix.is_empty())
else {
return Vec::new();
};
let Some(host) = host_of(url) else {
return Vec::new();
};
if !is_github_host(host) {
return Vec::new();
}
vec![Candidate::new(SOURCE_GH_PROXY, format!("{prefix}{url}"))]
}
/// 展开候选序列:镜像优先模式下镜像在前、官方兜底;直连模式下官方在前、镜像兜底。
#[must_use]
pub fn expand(&self, url: &str, kind: ResourceKind, region: &RegionProfile) -> Vec<Candidate> {
let official = Candidate::new(direct_source_id(url), url);
let mut mirrors: Vec<Candidate> = Vec::new();
let mut official_first = false;
for candidate in self.adoptium_candidates(url) {
push_unique(&mut mirrors, url, candidate);
}
if let Some(rule) = self.prefix_rule_for(url, kind) {
if let Some(candidate) = rule.apply(url) {
// 「仅加速不替代」的规则在镜像优先模式下也排官方之后。
official_first = rule.fallback;
push_unique(&mut mirrors, url, candidate);
}
}
for candidate in self.gh_proxy_candidates(url, region) {
push_unique(&mut mirrors, url, candidate);
}
if region.mode == RegionMode::MirrorFirst && !official_first {
mirrors.push(official);
mirrors
} else {
let mut out = Vec::with_capacity(mirrors.len() + 1);
out.push(official);
out.append(&mut mirrors);
out
}
}
}
fn push_unique(list: &mut Vec<Candidate>, official_url: &str, candidate: Candidate) {
if candidate.url == official_url || list.iter().any(|known| known.url == candidate.url) {
return;
}
list.push(candidate);
}
/// 直连候选的源标识。
///
/// 绝大多数资源是 Mojang/CDN 的「官方源」,标 `official`;但 JDK 的四家发行版里,
/// Oracle(国内镜像)/ MS(VS CDN)/ Zulu(Azul CDN)的**直连 URL 本身就是某个厂商的 CDN**,
/// 标成 `official` 会让用户和健康度记忆都看错(「来源 official」其实来自华为云)。
/// 所以:认得的厂商/镜像 host 用它的稳定 id,其余一律 `official`。
#[must_use]
pub fn direct_source_id(url: &str) -> String {
let id = source_id_for(url);
if MIRROR_SOURCE_IDS.contains(&id.as_str()) {
id
} else {
SOURCE_OFFICIAL.to_string()
}
}
/// 会被 `direct_source_id` 认出来的厂商/镜像源 id(都是「非官方 CDN/镜像」那一类)。
pub const MIRROR_SOURCE_IDS: &[&str] = &[
"bmclapi",
"tuna",
"ustc",
"sjtu",
"bfsu",
"nju",
"huawei",
"azul",
"microsoft",
SOURCE_GH_PROXY,
];
/// 从 URL 里抠出 host(无 `url` 依赖)。
#[must_use]
pub fn host_of(url: &str) -> Option<&str> {
let rest = url.split_once("://").map_or(url, |(_, rest)| rest);
let authority = rest.split(['/', '?', '#']).next()?;
let host_port = authority.rsplit('@').next()?;
let host = host_port.split(':').next()?;
if host.is_empty() {
None
} else {
Some(host)
}
}
/// 是否是 GitHub 系 host(gh-proxy 只对这些套前缀)。
#[must_use]
pub fn is_github_host(host: &str) -> bool {
matches!(
host,
"github.com"
| "raw.githubusercontent.com"
| "objects.githubusercontent.com"
| "codeload.github.com"
| "gist.github.com"
| "gist.githubusercontent.com"
) || host.ends_with(".githubusercontent.com")
}
/// URL 的源标识(健康度按此归并;不知名 host 直接用 host)。
#[must_use]
pub fn source_id_for(url: &str) -> String {
let Some(host) = host_of(url) else {
return SOURCE_OFFICIAL.to_string();
};
if host.ends_with("bangbang93.com") {
return "bmclapi".to_string();
}
if host.contains("tuna.tsinghua.edu.cn") {
return "tuna".to_string();
}
if host.contains("ustc.edu.cn") {
return "ustc".to_string();
}
if host.contains("sjtu.edu.cn") {
return "sjtu".to_string();
}
if host.contains("bfsu.edu.cn") {
return "bfsu".to_string();
}
if host.contains("nju.edu.cn") {
return "nju".to_string();
}
if host.contains("huaweicloud.com") {
return "huawei".to_string();
}
if host.contains("azul.com") {
return "azul".to_string();
}
if host.contains("visualstudio.microsoft.com") || host == "aka.ms" {
return "microsoft".to_string();
}
if host.contains("gh-proxy") {
return SOURCE_GH_PROXY.to_string();
}
host.to_string()
}
fn debug_note(message: &str) {
if cfg!(debug_assertions) {
eprintln!("[ull::download] {message}");
} else {
let _ = message;
}
}