Skip to main content

minecraft_java_rs_core/game/
lwjgl_native.rs

1use serde::Deserialize;
2
3use crate::error::LaunchError;
4use crate::models::minecraft::{Library, MinecraftVersionJson};
5
6// ── Embedded LWJGL ARM library manifests ─────────────────────────────────────
7//
8// These JSON files list the ARM-compiled LWJGL/JInput libraries that replace
9// the official x86 ones.  They follow the same `{ "libraries": [...] }` shape
10// as a Minecraft version JSON subset.
11//
12// IMPORTANT: The stub files bundled here contain empty library lists.  A real
13// deployment must replace them with the actual ARM LWJGL libraries.  See
14// `assets/LWJGL/` in the repository root.
15
16macro_rules! lwjgl_bytes {
17    ($arch:literal, $ver:literal) => {
18        include_bytes!(concat!(
19            "../../assets/LWJGL/",
20            $arch,
21            "/",
22            $ver,
23            ".json"
24        ))
25        .as_ref()
26    };
27}
28
29/// Return the embedded JSON bytes for `(arch, version)`, or `None` if the
30/// combination is not bundled.
31fn arm_lwjgl_data(arch: &str, version: &str) -> Option<&'static [u8]> {
32    // Mojang 2.9.x releases are all patched to 2.9.4 (matches JS behaviour).
33    let version = if version.contains("2.9") { "2.9.4" } else { version };
34
35    match (arch, version) {
36        ("aarch64", "2.9.4") => Some(lwjgl_bytes!("aarch64", "2.9.4")),
37        ("aarch64", "3.1.2") => Some(lwjgl_bytes!("aarch64", "3.1.2")),
38        ("aarch64", "3.2.2") => Some(lwjgl_bytes!("aarch64", "3.2.2")),
39        ("aarch64", "3.3.1") => Some(lwjgl_bytes!("aarch64", "3.3.1")),
40        ("aarch64", "3.3.2") => Some(lwjgl_bytes!("aarch64", "3.3.2")),
41        ("aarch", "2.9.4") => Some(lwjgl_bytes!("aarch", "2.9.4")),
42        ("aarch", "3.3.1") => Some(lwjgl_bytes!("aarch", "3.3.1")),
43        _ => None,
44    }
45}
46
47// ── Public API ────────────────────────────────────────────────────────────────
48
49/// Patch `version`'s library list for Linux ARM compatibility.
50///
51/// - Removes official LWJGL and JInput libraries (x86-only binaries).
52/// - Injects ARM-compiled replacements from the bundled JSON manifests.
53///
54/// On non-ARM platforms this is a no-op; the check uses
55/// `std::env::consts::ARCH` so a cross-compiled binary still detects its
56/// actual execution environment at runtime.
57pub fn process_json(version: &mut MinecraftVersionJson) -> Result<(), LaunchError> {
58    let mapped_arch = match std::env::consts::ARCH {
59        "aarch64" => "aarch64",
60        "arm" => "aarch",
61        _ => return Ok(()), // not ARM — nothing to do
62    };
63
64    // Detect LWJGL and JInput versions from the existing library list.
65    let version_jinput = find_version(&version.libraries, &["net.java.jinput:jinput-platform:", "net.java.jinput:jinput:"]);
66    let version_lwjgl = find_version(&version.libraries, &["org.lwjgl:lwjgl:", "org.lwjgl.lwjgl:lwjgl:"]);
67
68    // Remove official JInput libraries (replaced by ARM equivalents).
69    if version_jinput.is_some() {
70        version.libraries.retain(|lib| !lib.name.contains("jinput"));
71    }
72
73    // Remove official LWJGL libraries and inject ARM ones.
74    if let Some(lwjgl_ver) = version_lwjgl {
75        version.libraries.retain(|lib| !lib.name.contains("lwjgl"));
76
77        match arm_lwjgl_data(mapped_arch, &lwjgl_ver) {
78            Some(bytes) => {
79                let set: LwjglLibrarySet = serde_json::from_slice(bytes)?;
80                version.libraries.extend(set.libraries);
81            }
82            None => {
83                // Bundled data missing for this LWJGL version — skip the patch
84                // rather than crashing.  Callers can detect ARM support gaps by
85                // checking whether `version.libraries` still contains x86 LWJGL.
86            }
87        }
88    }
89
90    Ok(())
91}
92
93// ── Helpers ───────────────────────────────────────────────────────────────────
94
95/// Extract the version component (last `:` segment) from the first library
96/// whose `name` starts with any of `prefixes`.
97fn find_version(libs: &[Library], prefixes: &[&str]) -> Option<String> {
98    libs.iter()
99        .find(|lib| prefixes.iter().any(|p| lib.name.starts_with(p)))
100        .and_then(|lib| lib.name.split(':').last())
101        .map(|v| v.to_string())
102}
103
104/// Minimal subset of a Minecraft version JSON — just the `libraries` field.
105#[derive(Deserialize)]
106struct LwjglLibrarySet {
107    libraries: Vec<Library>,
108}
109
110// ── Tests ─────────────────────────────────────────────────────────────────────
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115
116    fn make_lib(name: &str) -> Library {
117        Library {
118            name: name.to_string(),
119            rules: None,
120            natives: None,
121            downloads: None,
122            url: None,
123            loader: None,
124        }
125    }
126
127    fn libs(names: &[&str]) -> Vec<Library> {
128        names.iter().map(|n| make_lib(n)).collect()
129    }
130
131    #[test]
132    fn find_version_returns_last_colon_segment() {
133        let l = libs(&["org.lwjgl:lwjgl:3.3.1", "org.lwjgl:lwjgl-opengl:3.3.1"]);
134        assert_eq!(find_version(&l, &["org.lwjgl:lwjgl:"]), Some("3.3.1".into()));
135    }
136
137    #[test]
138    fn find_version_returns_none_when_absent() {
139        let l = libs(&["com.example:something:1.0"]);
140        assert_eq!(find_version(&l, &["org.lwjgl:lwjgl:"]), None);
141    }
142
143    #[test]
144    fn find_version_matches_multiple_prefixes() {
145        let l = libs(&["org.lwjgl.lwjgl:lwjgl:2.9.4"]);
146        let v = find_version(&l, &["org.lwjgl:lwjgl:", "org.lwjgl.lwjgl:lwjgl:"]);
147        assert_eq!(v, Some("2.9.4".into()));
148    }
149
150    #[test]
151    fn arm_lwjgl_data_normalises_29x() {
152        // Any 2.9.x version should resolve to the 2.9.4 bundle.
153        assert!(arm_lwjgl_data("aarch64", "2.9.0").is_some());
154        assert!(arm_lwjgl_data("aarch64", "2.9.1").is_some());
155        assert!(arm_lwjgl_data("aarch64", "2.9.4").is_some());
156    }
157
158    #[test]
159    fn arm_lwjgl_data_returns_none_for_unknown() {
160        assert!(arm_lwjgl_data("aarch64", "4.0.0").is_none());
161        assert!(arm_lwjgl_data("x86_64", "3.3.1").is_none());
162    }
163
164    #[test]
165    fn process_json_noop_on_current_arch() {
166        // On x86_64 (the typical CI/dev machine) this should be a no-op.
167        if matches!(std::env::consts::ARCH, "aarch64" | "arm") {
168            return; // ARM machine — test would modify libraries, skip.
169        }
170
171        let mut version = MinecraftVersionJson {
172            id: "1.20.4".into(),
173            version_type: "release".into(),
174            assets: None,
175            asset_index: None,
176            downloads: None,
177            libraries: libs(&["org.lwjgl:lwjgl:3.3.1", "org.lwjgl:lwjgl-opengl:3.3.1"]),
178            arguments: None,
179            minecraft_arguments: None,
180            java_version: None,
181            main_class: None,
182            has_natives: false,
183        };
184
185        let original_count = version.libraries.len();
186        process_json(&mut version).unwrap();
187        // No change on non-ARM.
188        assert_eq!(version.libraries.len(), original_count);
189    }
190}