From 147280d12a903114b99c307960a3937fe622c670 Mon Sep 17 00:00:00 2001 From: AskaEth Date: Sat, 7 Nov 2026 08:39:25 +0800 Subject: [PATCH] =?UTF-8?q?feat(termux):=20MC=20=E5=90=AF=E5=8A=A8?= =?UTF-8?q?=E6=8E=A5=E7=BA=BF=E2=80=94=E2=80=94Linux=20arm64=20natives=20+?= =?UTF-8?q?=20softpipe=20=E5=AD=90=E8=BF=9B=E7=A8=8B=E7=8E=AF=E5=A2=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Library::native_classifier:Linux/aarch64 上把 LWJGL3 的 natives-linux 改写成 natives-linux-arm64(Mojang 清单只有 x86_64) - install::libraries:arm64 分类器在清单里没有下载信息时,按 Maven 相对路径 兜底到阿里云镜像(实测 Mojang libraries 404、Aliyun 200); 单测覆盖 arm64 兜底与 x86_64 不变 - launch::defaults:Termux glibc 构建启动 MC 时默认带 MESA_LOADER_DRIVER_OVERRIDE=swrast / LIBGL_ALWAYS_SOFTWARE=1(用户可覆盖) - 文档:JRE patchelf 一次性步骤与接线现状;README 同步 - 验证:patched Temurin java 直接执行;LWJGL arm64+GLFW 探针 gl-ok --- README.termux.md | 8 +- crates/core/src/game/install/libraries.rs | 111 ++++++++++++++++++---- crates/core/src/game/library.rs | 20 +++- crates/core/src/launch/defaults.rs | 47 +++++++++ packaging/docs/termux-glibc-build.md | 19 +++- 5 files changed, 179 insertions(+), 26 deletions(-) diff --git a/README.termux.md b/README.termux.md index 218a5eb..128e624 100644 --- a/README.termux.md +++ b/README.termux.md @@ -15,7 +15,7 @@ | Java 发现 | ✅ | `JAVA_HOME`/PATH + `$PREFIX/lib/jvm` 扫描 | | Java 自动安装 | ✅ | `ull java install 17` 由内置镜像装完 openjdk-17(约 20 s) | | GPU(wgpu) | ✅ | 自编 Mesa 26.2.6 + KGSL;wgpu 选中 Adreno 840(Vulkan/turnip) | -| Minecraft 运行 | 🟡 | 栈已验证:glibc JRE + LWJGL arm64 + GLX/softpipe(GL 3.3);启动器接线待做 | +| Minecraft 运行 | 🟡 | 栈已验证;启动器已接线(natives arm64 / softpipe env);待真实 MC 验证 | ## 快速开始 @@ -39,9 +39,9 @@ DISPLAY=:0 ./unlucky-launcher-iced --software-renderer glibc Mesa 太旧(24.2.6 不认识 Adreno 840)。驱动缺失时自动降级 CPU,不会崩。 - **MC**:Android 原生 natives 走不通(LWJGL 没有官方 Android/bionic 产物,且 GLFW 的 窗口模型与 termux-x11 的 X11 不兼容)。有效路线是 glibc JVM——Temurin 21 JRE + AWT、 - LWJGL arm64 + GLFW + GLX/softpipe 已手动跑通(GL 3.3)。启动器接线还差两步: - Linux/aarch64 上把 natives 选成 `natives-linux-arm64`,并给 MC 子进程带 glibc JVM 与 - `MESA_LOADER_DRIVER_OVERRIDE=swrast`。 + LWJGL arm64 + GLFW + GLX/softpipe 已手动跑通(GL 3.3)。启动器已接线: + Linux/aarch64 自动选 `natives-linux-arm64`(阿里云 Maven 兜底),MC 子进程默认带 + softpipe 环境。还差把实例 Java 指到 glibc JRE 后跑一次真实 MC。 - Termux 上三条测试失败是环境差异(`/proc/*/children`、长路径脱敏、mtime flaky), PC 的 Linux CI 不受影响。 diff --git a/crates/core/src/game/install/libraries.rs b/crates/core/src/game/install/libraries.rs index 6638c38..8c867a3 100644 --- a/crates/core/src/game/install/libraries.rs +++ b/crates/core/src/game/install/libraries.rs @@ -79,31 +79,42 @@ pub fn plan( let Some(classifier) = library.native_classifier(environment) else { continue; }; - let Some(artifact) = library.native_artifact(&classifier) else { - warnings.push(format!( - "{} 的 natives 分类器 {classifier} 没有下载信息,已跳过", - library.name - )); - continue; - }; let Ok(native_relative) = library.native_relative_path(&classifier) else { warnings.push(format!("{} 的 natives 路径推导失败,已跳过", library.name)); continue; }; - if !artifact.has_url() { - warnings.push(format!( - "{} 的 natives 构件 {classifier} 没有下载地址,已跳过", - library.name - )); - continue; - } + let dest = layout.libraries_dir().join(&native_relative); + // Linux arm64:Mojang 清单没有 arm64 分类器,兜底到 Maven 镜像(见常量注释)。 + let arm64_fallback = classifier == LINUX_ARM64_CLASSIFIER; + let (url, sha1, size) = match library.native_artifact(&classifier) { + Some(artifact) if artifact.has_url() => ( + artifact.url.clone().unwrap_or_default(), + artifact.sha1.clone(), + artifact.size, + ), + _ if arm64_fallback => (arm64_native_url(&native_relative), None, None), + Some(_) => { + warnings.push(format!( + "{} 的 natives 构件 {classifier} 没有下载地址,已跳过", + library.name + )); + continue; + } + None => { + warnings.push(format!( + "{} 的 natives 分类器 {classifier} 没有下载信息,已跳过", + library.name + )); + continue; + } + }; files.push(PlanFile { role: FileRole::Native, name: format!("{}:{classifier}", library.name), - url: artifact.url.clone().unwrap_or_default(), - sha1: artifact.sha1.clone(), - size: artifact.size, - dest: layout.libraries_dir().join(native_relative), + url, + sha1, + size, + dest, resource: ResourceKind::Library, }); } @@ -141,6 +152,22 @@ fn maven_url(library: &Library, relative: &Path) -> String { } } +/// Linux arm64 的 natives 兜底仓库:Mojang 的 `libraries.minecraft.net` 对 +/// `natives-linux-arm64` 一律 404(实测 3.3.2 的 lwjgl/glfw/opengl/stb/tinyfd/openal), +/// 同一坐标在 Maven Central / 阿里云镜像有。 +pub const LINUX_ARM64_NATIVES_BASE: &str = "https://maven.aliyun.com/repository/public/"; + +/// Linux arm64 的 natives 分类器(见 `Library::native_classifier`)。 +pub const LINUX_ARM64_CLASSIFIER: &str = "natives-linux-arm64"; + +/// arm64 natives 的兜底 URL(按相对路径拼镜像基址)。 +fn arm64_native_url(relative: &Path) -> String { + format!( + "{LINUX_ARM64_NATIVES_BASE}{}", + relative.to_string_lossy().replace('\\', "/") + ) +} + /// 供上层判错用:库展开不产生硬失败,缺地址一律降级成告警。 #[cfg(test)] mod tests { @@ -255,4 +282,52 @@ mod tests { .dest .ends_with("versions/inst/libraries/net/minecraftforge/forge/1.20.1-47.2.0.jar")); } + + #[test] + fn linux_arm64_lwjgl3_natives_fall_back_to_maven_mirror() { + // Mojang 的清单只有 `natives-linux`(x86_64)且没有 arm64 分类器下载信息; + // Linux arm64 要改写成 `natives-linux-arm64` 并兜底到 Maven 镜像。 + let library = library_with_artifact( + r#"{"name":"org.lwjgl:lwjgl:3.3.2","natives":{"linux":"natives-linux"}}"#, + ); + let env = Environment { + os: "linux".to_string(), + os_version: String::new(), + arch: "arm64".to_string(), + ..environment() + }; + let layout = GameLayout::new("/g/.minecraft"); + let (files, warnings) = plan(&layout, "1.20.4", &[library], &env); + assert!(warnings.is_empty(), "不该有告警:{warnings:?}"); + assert_eq!(files.len(), 1); + assert_eq!(files[0].name, "org.lwjgl:lwjgl:3.3.2:natives-linux-arm64"); + assert_eq!( + files[0].url, + "https://maven.aliyun.com/repository/public/org/lwjgl/lwjgl/3.3.2/lwjgl-3.3.2-natives-linux-arm64.jar" + ); + assert!( + files[0].sha1.is_none(), + "镜像兜底没有 sha1,只能靠大小/存在性" + ); + } + + #[test] + fn linux_x86_64_keeps_the_manifest_classifier() { + let library = library_with_artifact( + r#"{"name":"org.lwjgl:lwjgl:3.3.2","natives":{"linux":"natives-linux"}}"#, + ); + let env = Environment { + os: "linux".to_string(), + os_version: String::new(), + arch: "x86_64".to_string(), + ..environment() + }; + let layout = GameLayout::new("/g/.minecraft"); + let (files, warnings) = plan(&layout, "1.20.4", &[library], &env); + assert!(files.is_empty(), "x86_64 没有清单下载信息,不该凭空拼 URL"); + assert!( + warnings.iter().any(|w| w.contains("natives-linux")), + "{warnings:?}" + ); + } } diff --git a/crates/core/src/game/library.rs b/crates/core/src/game/library.rs index 230c4b9..bb2c06c 100644 --- a/crates/core/src/game/library.rs +++ b/crates/core/src/game/library.rs @@ -172,6 +172,11 @@ impl Library { } /// natives 分类器名:按 OS 查 `natives` 表并展开 `${arch}`(64 位为 `64`)。 + /// + /// **Linux arm64 特例**:Mojang 的 LWJGL3 条目只写 `natives-linux`(x86_64), + /// 但同一个 Maven 坐标有 `natives-linux-arm64`。原样用 `natives-linux` 会下到 x86_64 + /// 的 `.so`,在 aarch64 JVM 里加载失败;这里改成 arm64 分类器(下载地址的兜底见 + /// `install::libraries`)。 pub fn native_classifier(&self, env: &Environment) -> Option { let raw = self.natives.as_ref()?.get(&env.os)?; let arch = if matches!(env.arch.as_str(), "x86_64" | "arm64") { @@ -179,7 +184,20 @@ impl Library { } else { "" }; - Some(raw.replace("${arch}", arch)) + let classifier = raw.replace("${arch}", arch); + if env.os == "linux" + && env.arch == "arm64" + && classifier == "natives-linux" + && self.is_lwjgl3() + { + return Some("natives-linux-arm64".to_owned()); + } + Some(classifier) + } + + /// 是否是 LWJGL 3(`org.lwjgl:`;LWJGL 2 的 group 是 `org.lwjgl.lwjgl`)。 + fn is_lwjgl3(&self) -> bool { + self.name.starts_with("org.lwjgl:") } /// natives 构件下载信息(按分类器查 `downloads.classifiers`)。 diff --git a/crates/core/src/launch/defaults.rs b/crates/core/src/launch/defaults.rs index ffcc157..b60f4cd 100644 --- a/crates/core/src/launch/defaults.rs +++ b/crates/core/src/launch/defaults.rs @@ -174,6 +174,13 @@ pub fn environment(req: &LaunchRequest, os: OsFamily) -> Vec<(String, String)> { set_env(&mut env, "MESA_LOADER_DRIVER_OVERRIDE", "llvmpipe"); set_env(&mut env, "GALLIUM_DRIVER", "llvmpipe"); } + // Termux + glibc 构建:termux-x11 下 glibc Mesa 的 zink 卡 DRI3,softpipe 是当前 + // 可用路径。放在软件渲染块之后、用户自定义之前(用户显式设置仍可覆盖)。 + if cfg!(target_os = "linux") + && crate::platform::termux::is_termux(&crate::platform::paths::ProcessEnv) + { + apply_termux_gl_defaults(&mut env); + } if os == OsFamily::MacOs { if let Some(icd) = &req.renderer.vulkan_icd { set_env(&mut env, "VK_ICD_FILENAMES", &icd.display().to_string()); @@ -201,3 +208,43 @@ fn set_env(env: &mut Vec<(String, String)>, key: &str, value: &str) { env.push((key.to_string(), value.to_string())); } } + +/// Termux(glibc 构建)上的默认 GL 环境:softpipe。 +/// +/// glibc Mesa 26 的 zink 在 termux-x11 下报 `DRI3 not available`,softpipe 是当前 +/// 可用的软件路径;硬件路径等 DRI3 问题解决后再切。 +fn apply_termux_gl_defaults(env: &mut Vec<(String, String)>) { + set_env(env, "MESA_LOADER_DRIVER_OVERRIDE", "swrast"); + set_env(env, "LIBGL_ALWAYS_SOFTWARE", "1"); +} + +#[cfg(test)] +mod tests { + use super::apply_termux_gl_defaults; + + #[test] + fn termux_gl_defaults_force_softpipe() { + let mut env = Vec::new(); + apply_termux_gl_defaults(&mut env); + assert!(env.contains(&( + "MESA_LOADER_DRIVER_OVERRIDE".to_string(), + "swrast".to_string() + ))); + assert!(env.contains(&("LIBGL_ALWAYS_SOFTWARE".to_string(), "1".to_string()))); + } + + #[test] + fn termux_gl_defaults_overwrite_earlier_llvmpipe_choice() { + let mut env = vec![( + "MESA_LOADER_DRIVER_OVERRIDE".to_string(), + "llvmpipe".to_string(), + )]; + apply_termux_gl_defaults(&mut env); + assert_eq!( + env.iter() + .find(|(key, _)| key == "MESA_LOADER_DRIVER_OVERRIDE") + .map(|(_, value)| value.as_str()), + Some("swrast") + ); + } +} diff --git a/packaging/docs/termux-glibc-build.md b/packaging/docs/termux-glibc-build.md index 6a1c571..43c8225 100644 --- a/packaging/docs/termux-glibc-build.md +++ b/packaging/docs/termux-glibc-build.md @@ -190,9 +190,22 @@ $PREFIX/glibc/lib/ld-linux-aarch64.so.1 \ - 端到端探针:LWJGL 3.3.3 arm64 natives + GLFW + GLX → `GL_VERSION=3.3 (Compatibility Profile) Mesa 26.2.6`、`GL_RENDERER=softpipe`、`gl-ok`。 -> 启动器侧还没接线:MC 的 library 清单在 Linux/aarch64 上要选 -> `natives-linux-arm64` 而不是 `natives-linux`(x64),并在启动 MC 的子进程里带上 -> `MESA_LOADER_DRIVER_OVERRIDE=swrast` 与 glibc JVM 的 `JAVA_HOME`。这两步是后续工作。 +> **启动器已接线**(本分支): +> - Linux/aarch64 上 LWJGL3 的 natives 自动改写成 `natives-linux-arm64` +> (Mojang 的 `libraries.minecraft.net` 对 arm64 一律 404,下载兜底到阿里云 Maven 镜像); +> - Termux glibc 构建启动 MC 时,子进程默认带 `MESA_LOADER_DRIVER_OVERRIDE=swrast` +> 与 `LIBGL_ALWAYS_SOFTWARE=1`(用户自定义环境变量仍可覆盖)。 +> +> **还差一步**:把实例的 Java 指到 glibc JRE。Temurin 解包后 `bin/java` 默认的 +> interpreter 在 Android 上不存在,先 patchelf 一次即可直接执行: +> +> ```sh +> patchelf --set-interpreter "$PREFIX/glibc/lib/ld-linux-aarch64.so.1" \ +> --set-rpath "$PREFIX/glibc/lib:$JRE/lib:$JRE/lib/server" "$JRE/bin/java" +> "$JRE/bin/java" -version +> ``` +> +> 之后在实例设置里把 Java 指向 `$JRE/bin/java`(或 `--java`)即可。 ## 5. GPU(已打通:自编 Mesa 26.2.6 + KGSL)