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

+261
View File
@@ -0,0 +1,261 @@
//! Microsoft OpenJDK:`aka.ms` 模板下载 + 成对 `.sha256sum.txt` 校验。
//!
//! 分发形态与 Temurin / Oracle 都不同(research/06 §3.2):
//!
//! ```text
//! 下载 https://aka.ms/download-jdk/microsoft-jdk-{version}-{os}-{arch}.zip
//! → 301 → https://download.visualstudio.microsoft.com/download/pr/{guid}/{hash}/{同名}
//! 校验 https://aka.ms/download-jdk/<同一文件名>.sha256sum.txt # "<hash> <filename>"
//! ```
//!
//! 三条硬事实(都影响实现):
//!
//! 1. **版本是四段/五段**(`21.0.13.0.2` = JDK 21.0.13+2;11 线是 `11.0.32.1`),
//! 不能拿 Temurin 的三段号直接拼——所以这里有自己的 [`parse_version`] 与内置快照表;
//! 2. **没有 8**(微软已停发,官方页面让去下 Temurin 8),`supports()` 也这么判;
//! 3. **GitHub 侧没有任何资产**(`microsoft/openjdk` 的 release `assets` 全空),
//! 所以 gh-proxy 对这条链毫无用处——`domestic_mirrors` 实测为空数组。
use super::error::{JavaError, JavaErrorKind};
use super::net;
use super::release::{parse_sha256_text, JdkArchive, JdkRelease};
use super::target::AdoptiumTarget;
use super::vendor::JdkVendor;
use crate::download::MsOpenJdkTable;
/// 内置版本快照(`learn.microsoft.com/java/openjdk/download` 2026-10-30 的表,research/06 §1.3)。
///
/// 官方只有人读 HTML 表、没有 JSON API,逐版本探测又要 `aka.ms` 301(0.3–0.8 s/次),
/// 所以按 research/06 §3.3 的建议 A 内置快照;`size` 与实测字节数一致,用于长度对拍。
pub const SNAPSHOT: &[(u32, &str, u64)] = &[
(25, "25.0.5.0.2", 220_869_659),
(21, "21.0.13.0.2", 201_450_315),
(17, "17.0.21.0.2", 187_236_595),
(11, "11.0.32.1", 191_250_397),
];
/// 四段/五段版本号(`21.0.13.0.2` / `11.0.32.1`)。
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MsVersion {
/// 大版本(`21`)。
pub major: u32,
/// 各数字段(长度 3..=5)。
pub segments: Vec<u32>,
}
impl MsVersion {
/// 原样的版本串。
#[must_use]
pub fn text(&self) -> String {
self.segments
.iter()
.map(u32::to_string)
.collect::<Vec<_>>()
.join(".")
}
}
/// 解析 MS 的版本串;段数必须 3..=5(MS 的语义:`major.update.patch[.build[.rev]]`)。
#[must_use]
pub fn parse_version(text: &str) -> Option<MsVersion> {
let mut segments = Vec::new();
for segment in text.trim().split('.') {
segments.push(segment.parse::<u32>().ok()?);
}
if segments.len() < 3 || segments.len() > 5 {
return None;
}
Some(MsVersion {
major: segments[0],
segments,
})
}
/// MS 的 os 段(`windows` / `macos` / `linux`)。
#[must_use]
pub fn ms_os(target: AdoptiumTarget) -> &'static str {
match target.os {
"mac" => "macos",
other => other,
}
}
/// MS 的 arch 段(`x64` / `aarch64`)。
#[must_use]
pub fn ms_arch(target: AdoptiumTarget) -> &'static str {
target.arch
}
/// 归档文件名(模板:`microsoft-jdk-{version}-{os}-{arch}.zip`,全平台都是 zip)。
#[must_use]
pub fn archive_name(version: &str, target: AdoptiumTarget) -> String {
format!(
"microsoft-jdk-{version}-{}-{}.zip",
ms_os(target),
ms_arch(target)
)
}
/// 把模板里的 `{version}` / `{os}` / `{arch}` 换掉。
#[must_use]
pub fn expand_template(template: &str, version: &str, target: AdoptiumTarget) -> String {
template
.replace("{version}", version)
.replace("{os}", ms_os(target))
.replace("{arch}", ms_arch(target))
}
/// 快照里该大版本的版本串(没有 → `None`)。
#[must_use]
pub fn snapshot_version(major: u32) -> Option<&'static str> {
SNAPSHOT
.iter()
.find(|(known, _, _)| *known == major)
.map(|(_, version, _)| *version)
}
/// 快照里该大版本的字节数。
#[must_use]
pub fn snapshot_size(major: u32) -> Option<u64> {
SNAPSHOT
.iter()
.find(|(known, _, _)| *known == major)
.map(|(_, _, size)| *size)
}
/// 查一次「某大版本的最新 MS OpenJDK」。
///
/// 两跳:内置快照挑版本 → 取 `.sha256sum.txt` 拿哈希。校验文件取不到就整家失败
/// (宁可降级到 Zulu,也不下一个没法验证的 200 MB 归档)。
///
/// # Errors
///
/// 表里没配模板、该大版本微软不发(含 8)、校验文件取不到或格式不对时返回错误。
pub async fn fetch_latest(
client: &reqwest::Client,
table: &MsOpenJdkTable,
major: u32,
target: AdoptiumTarget,
) -> Result<JdkRelease, JavaError> {
let version = snapshot_version(major).ok_or_else(|| {
JavaError::new(
JavaErrorKind::Metadata,
format!(
"Microsoft OpenJDK 没有 {major} 的 GA(只发 {})",
majors_text()
),
)
.with_hint("8 用 temurin;其它大版本用 oracle / zulu")
})?;
let filename = archive_name(version, target);
let sha_url = expand_template(&table.sha256_template, version, target);
let sha_text = net::get_text(client, &sha_url, "Microsoft OpenJDK 校验文件").await?;
let sha256 = parse_sha256_text(&sha_text).ok_or_else(|| {
JavaError::new(
JavaErrorKind::Integrity,
format!("{filename} 的 .sha256sum.txt 里没有 64 位哈希"),
)
})?;
Ok(JdkRelease {
vendor: JdkVendor::Microsoft,
major,
version: version.to_string(),
semver: None,
release_name: version.to_string(),
archive: JdkArchive {
name: filename.clone(),
url: expand_template(&table.template, version, target),
sha256,
size: snapshot_size(major).unwrap_or(0),
},
})
}
fn majors_text() -> String {
SNAPSHOT
.iter()
.map(|(major, _, _)| major.to_string())
.collect::<Vec<_>>()
.join(" / ")
}
#[cfg(test)]
mod tests {
use super::{
archive_name, expand_template, ms_os, parse_version, snapshot_size, snapshot_version,
SNAPSHOT,
};
use crate::game::java::target::AdoptiumTarget;
const WINDOWS: AdoptiumTarget = AdoptiumTarget::new("windows", "x64");
#[test]
fn snapshot_covers_the_four_majors_microsoft_ships() {
assert_eq!(snapshot_version(21), Some("21.0.13.0.2"));
assert_eq!(snapshot_version(11), Some("11.0.32.1"));
assert_eq!(snapshot_version(8), None, "微软已停发 8");
assert_eq!(snapshot_size(17), Some(187_236_595));
assert_eq!(
SNAPSHOT
.iter()
.map(|(major, _, _)| *major)
.collect::<Vec<_>>(),
vec![25, 21, 17, 11]
);
}
#[test]
fn four_and_five_segment_versions_have_their_own_semantics() {
// 21.0.13.0.2 = JDK 21.0.13+2(五段);11 线只有四段。
let ms = parse_version("21.0.13.0.2").expect("五段应解析");
assert_eq!(ms.major, 21);
assert_eq!(ms.segments.len(), 5);
assert_eq!(ms.text(), "21.0.13.0.2");
let eleven = parse_version("11.0.32.1").expect("四段应解析");
assert_eq!(eleven.major, 11);
assert_eq!(eleven.segments.len(), 4);
// Temurin 的三段号形态在这里同样成立,但语义由快照表定义,不共用 Temurin 的解析。
assert_eq!(
parse_version("17.0.21").map(|version| version.major),
Some(17)
);
assert_eq!(parse_version("21"), None);
assert_eq!(parse_version("21.0"), None);
assert_eq!(parse_version("21.0.13.0.2.1"), None);
assert_eq!(parse_version("21.0.x.2"), None);
}
#[test]
fn archive_names_and_templates_expand_per_platform() {
assert_eq!(
archive_name("21.0.13.0.2", WINDOWS),
"microsoft-jdk-21.0.13.0.2-windows-x64.zip"
);
let template = "https://aka.ms/download-jdk/microsoft-jdk-{version}-{os}-{arch}.zip";
assert_eq!(
expand_template(template, "17.0.21.0.2", WINDOWS),
"https://aka.ms/download-jdk/microsoft-jdk-17.0.21.0.2-windows-x64.zip"
);
assert_eq!(
expand_template(
"https://aka.ms/download-jdk/microsoft-jdk-{version}-{os}-{arch}.zip.sha256sum.txt",
"21.0.13.0.2",
AdoptiumTarget::new("linux", "aarch64")
),
"https://aka.ms/download-jdk/microsoft-jdk-21.0.13.0.2-linux-aarch64.zip.sha256sum.txt"
);
assert_eq!(ms_os(AdoptiumTarget::new("mac", "x64")), "macos");
}
#[test]
fn builtin_table_uses_the_official_templates() {
let table = crate::download::MirrorTable::builtin();
let microsoft = table.microsoft_openjdk().expect("内置表应带 MS 分发");
assert!(microsoft
.template
.starts_with("https://aka.ms/download-jdk/"));
assert!(microsoft.sha256_template.ends_with(".sha256sum.txt"));
assert!(microsoft.domestic_mirrors.is_empty(), "MS 无国内镜像");
assert_eq!(microsoft.majors, vec![25, 21, 17, 11]);
}
}