OneLauncher-Cracked/packages/oneclient_java/src/vendors/microsoft.rs
Maciek ba74bedf92
feat: added microsoft jdk and new shared mods folder layout (#812)
* feat: added microsoft sdk provider

* feat: changed the default provider to microsoft everywhere

* feat: added checking for microsoft jdks and prompting user to install them (ui for discussion)

* feat: added toast popup when user tries to remove runtime that is pinned to some cluster

* feat: added 'migrating' user to the microsoft jdks

* feat: changed reading mods from cluster folders on launch to having cluster folders symlinks in the shared .minecraft folder where mods stay per given cluster; changed resource packs and shader packs from working per cluster to global, every resource pack and shader pack is now being shared between clusters; added mods disabling whenever a mod is manually removed from the mods folder

* fix/feat: fixed hardlinks counting as a duplicate of files it was pointing to (windows); added a progress bar to the disk storage view in settings

* chore: connected microsoft jdk branch and package folder management one

---------

Co-authored-by: LynithDev <61880709+LynithDev@users.noreply.github.com>
2026-09-13 00:37:40 +02:00

286 lines
8.9 KiB
Rust

use futures_util::StreamExt;
use reqwest::{Method, Request};
use url::Url;
use oneclient_net::RequestClient;
use polyio::Checksum;
use crate::data::{JavaPackage, PackageArchive};
use crate::error::JavaResult;
use crate::platform::{HostArch, HostOs, HostTarget};
use crate::vendors::{JavaRuntimeProvider, JavaVendor};
pub struct MicrosoftRuntimeProvider;
/// Microsoft publishes no metadata API at all only static `aka.ms` vanity
/// links so the majors it still builds have to be listed here
const MICROSOFT_MAJORS: &[u32] = &[25, 21, 17, 11];
/// Alpine is the one platform Microsoft never filled out x64 only and nothing
/// newer than 17
const MICROSOFT_ALPINE_MAJORS: &[u32] = &[17, 11];
/// Four small redirects when no major is pinned nowhere near enough to be
/// worth throttling harder
const MAJOR_LOOKUP_CONCURRENCY: usize = 4;
#[async_trait::async_trait]
impl JavaRuntimeProvider for MicrosoftRuntimeProvider {
fn vendor(&self) -> JavaVendor {
JavaVendor::Microsoft
}
#[tracing::instrument(level = "debug", skip(self, net))]
async fn list_packages(
&self,
major: Option<u32>,
net: &RequestClient,
) -> JavaResult<Vec<JavaPackage>> {
let Some(arch) = MICROSOFT_ARCH else {
tracing::debug!(
"Microsoft publishes no build for this architecture; listing nothing"
);
return Ok(Vec::new());
};
let wanted: Vec<u32> = MICROSOFT_MAJORS
.iter()
.copied()
.filter(|candidate| major.is_none_or(|filter| filter == *candidate))
.filter(|candidate| publishes_host_build(*candidate, arch))
.collect();
let mut packages: Vec<JavaPackage> = futures_util::stream::iter(
wanted
.into_iter()
.map(|major| async move { resolve_package(major, arch, net).await }),
)
.buffer_unordered(MAJOR_LOOKUP_CONCURRENCY)
.collect::<Vec<_>>()
.await
.into_iter()
.collect::<JavaResult<Vec<Option<JavaPackage>>>>()?
.into_iter()
.flatten()
.collect();
packages.sort_by_key(|p| std::cmp::Reverse(p.java_version.first().copied().unwrap_or(0)));
tracing::debug!(count = packages.len(), "listed Microsoft packages");
Ok(packages)
}
}
/// The checksum file names the build it belongs to so one request resolves the
/// exact version, the filename and the hash the major-only link would download
#[tracing::instrument(level = "debug", skip(net))]
async fn resolve_package(
major: u32,
arch: &str,
net: &RequestClient,
) -> JavaResult<Option<JavaPackage>> {
let url = checksum_url(major, arch)?;
let response = net.send(Request::new(Method::GET, url)).await?;
let status = response.status();
let body = response
.text()
.await
.map_err(oneclient_net::RequestError::from)?;
if !status.is_success() {
tracing::debug!(
major,
status = status.as_u16(),
"Microsoft checksum file is unavailable; skipping the major"
);
return Ok(None);
}
let Some(build) = parse_checksum_file(&body, major) else {
tracing::debug!(
major,
"Microsoft published no build under this link; skipping the major"
);
return Ok(None);
};
Ok(Some(
JavaPackage {
archive: MICROSOFT_EXT.1,
download_url: download_url(&build.filename),
java_version: build.java_version,
name: build.filename,
vendor: JavaVendor::Microsoft,
checksum: None,
size: None,
}
.with_checksum(Some(build.checksum)),
))
}
struct MicrosoftBuild {
filename: String,
java_version: Vec<u32>,
checksum: Checksum,
}
/// `aka.ms` answers a link it does not know with a 200 and a search page
/// rather than a 404 so only the body can say whether a build exists
fn parse_checksum_file(body: &str, major: u32) -> Option<MicrosoftBuild> {
let line = body.lines().next()?.trim();
let (hex, filename) = line.split_once(char::is_whitespace)?;
let filename = filename.trim();
if hex.len() != 64 || !hex.bytes().all(|b| b.is_ascii_hexdigit()) {
return None;
}
let java_version = version_from_filename(filename)?;
// A link that quietly moved to another major would otherwise install the
// wrong runtime under the requested one's name
if java_version.first() != Some(&major) {
tracing::warn!(
major,
filename,
"Microsoft's major link resolved to a different major; skipping it"
);
return None;
}
Some(MicrosoftBuild {
filename: filename.to_string(),
java_version,
checksum: Checksum::sha256(hex),
})
}
/// Splitting the whole filename the way the other vendors split their version
/// strings would fold the `64` of `x64` into the version
fn version_from_filename(filename: &str) -> Option<Vec<u32>> {
let (version, _) = filename
.strip_prefix("microsoft-jdk-")?
.split_once('-')?;
let parts: Vec<u32> = version
.split('.')
.map(str::parse)
.collect::<Result<_, _>>()
.ok()?;
(!parts.is_empty()).then_some(parts)
}
/// Microsoft never published a JRE so every link is a kit by construction
fn checksum_url(major: u32, arch: &str) -> JavaResult<Url> {
Ok(Url::parse(&format!(
"https://aka.ms/download-jdk/microsoft-jdk-{major}-{MICROSOFT_OS}-{arch}.{}.sha256sum.txt",
MICROSOFT_EXT.0
))?)
}
/// Prefer the versioned link the checksum names over the major-only one it was
/// read through so the download cannot drift onto a newer build mid-release
fn download_url(filename: &str) -> String {
format!("https://aka.ms/download-jdk/{filename}")
}
fn publishes_host_build(major: u32, arch: &str) -> bool {
if HostTarget::CURRENT.is_musl() {
return arch == "x64" && MICROSOFT_ALPINE_MAJORS.contains(&major);
}
true
}
/// Microsoft doesn't supply 32-bit
const MICROSOFT_ARCH: Option<&str> = match HostTarget::CURRENT.arch {
HostArch::X86_64 => Some("x64"),
HostArch::Aarch64 => Some("aarch64"),
HostArch::X86 | HostArch::Arm => None,
};
/// Microsoft files musl builds under `alpine` where the other vendors say
/// `linux-musl`
const MICROSOFT_OS: &str = match HostTarget::CURRENT.os {
HostOs::Windows => "windows",
HostOs::MacOs => "macos",
HostOs::Linux { musl: true } => "alpine",
HostOs::Linux { musl: false } => "linux",
};
const MICROSOFT_EXT: (&str, PackageArchive) = (
HostTarget::CURRENT.archive_ext(),
HostTarget::CURRENT.archive(),
);
#[cfg(test)]
mod tests {
use super::*;
const LINE: &str =
"bf27a5d6298c736af8daf5b8c883098e83291446e5766118d8a5ea6a2617195d microsoft-jdk-21.0.12-windows-x64.zip";
#[test]
fn only_kits_are_ever_requested() {
let url = checksum_url(21, "x64").expect("a valid url");
assert!(url.as_str().contains("microsoft-jdk-21-"));
assert!(!url.as_str().contains("jre"));
}
#[test]
fn a_checksum_file_carries_the_build_it_belongs_to() {
let build = parse_checksum_file(LINE, 21).expect("a build");
assert_eq!(build.filename, "microsoft-jdk-21.0.12-windows-x64.zip");
assert_eq!(build.java_version, vec![21, 0, 12]);
assert_eq!(build.checksum.algorithm, polyio::ChecksumAlgorithm::Sha256);
assert!(build.checksum.is_well_formed());
}
#[test]
fn the_search_page_a_dead_link_redirects_to_is_not_a_build() {
assert!(parse_checksum_file("<!doctype html><html lang=\"en\">", 21).is_none());
assert!(parse_checksum_file("", 21).is_none());
}
#[test]
fn a_link_resolving_to_another_major_is_refused() {
assert!(parse_checksum_file(LINE, 17).is_none());
}
#[test]
fn the_architecture_never_leaks_into_the_version() {
assert_eq!(
version_from_filename("microsoft-jdk-21.0.12-windows-x64.zip"),
Some(vec![21, 0, 12])
);
assert_eq!(
version_from_filename("microsoft-jdk-25.0.4-linux-aarch64.tar.gz"),
Some(vec![25, 0, 4])
);
}
#[test]
fn debug_symbol_builds_are_never_mistaken_for_runtimes() {
assert_eq!(
version_from_filename("microsoft-jdk-debugsymbols-21.0.12-windows-x64.zip"),
None
);
}
#[test]
fn alpine_only_gets_the_majors_microsoft_actually_built_for_it() {
for major in MICROSOFT_MAJORS {
let published = publishes_host_build(*major, "x64");
if HostTarget::CURRENT.is_musl() {
assert_eq!(published, MICROSOFT_ALPINE_MAJORS.contains(major));
} else {
assert!(published);
}
}
if HostTarget::CURRENT.is_musl() {
assert!(!publishes_host_build(17, "aarch64"));
}
}
}