Skip to main content

mc_launcher_core/compatibility/
mod.rs

1//! Platform compatibility adjustments for Minecraft metadata.
2//!
3//! Mojang's historical version metadata does not always contain libraries that
4//! work on every modern platform. This module patches selected known cases
5//! before download planning or launch-command construction.
6
7use std::collections::HashMap;
8
9use crate::{
10    core::{
11        maven::MavenCoordinate,
12        version::{Library, LibraryArtifact, LibraryDownloads, VersionJson},
13    },
14    platform::{Arch, Os, Platform},
15};
16
17/// Whether compatibility patches should be applied.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum CompatibilityPolicy {
20    /// Apply known compatibility patches for the target platform.
21    Auto,
22    /// Leave version metadata unchanged.
23    Disabled,
24}
25
26/// Compatibility patch that was applied to version metadata.
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum CompatibilityPatch {
29    /// Legacy LWJGL 2 metadata was patched for macOS arm64.
30    LegacyMacArm64Lwjgl2,
31    /// Older LWJGL 3 metadata was patched for macOS arm64 natives.
32    MacArm64Lwjgl3,
33}
34
35/// Describes how a launcher should host the game process for window creation.
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub enum WindowingStrategy {
38    /// Start Java from the launcher's current process model.
39    CurrentProcess,
40    /// Host Java from a real macOS app bundle or equivalent GUI app process.
41    MacOsAppBundle,
42}
43
44/// Window-hosting guidance discovered while applying compatibility rules.
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub struct WindowingHint {
47    /// Recommended process-hosting strategy.
48    pub strategy: WindowingStrategy,
49    /// Whether the caller should verify that a visible game window was created.
50    pub requires_visible_window_verification: bool,
51    /// Human-readable explanation for the recommendation.
52    pub reason: &'static str,
53}
54
55/// Java runtime recommendation discovered while applying compatibility rules.
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57pub struct JavaRuntimeHint {
58    /// Suggested Java major version.
59    pub major_version: i32,
60    /// Suggested runtime architecture.
61    pub arch: Arch,
62    /// Short distribution hint suitable for logs or UI.
63    pub distribution_hint: &'static str,
64    /// Human-readable explanation for the recommendation.
65    pub reason: &'static str,
66}
67
68/// Version metadata plus compatibility guidance.
69#[derive(Debug, Clone, PartialEq)]
70pub struct CompatibilityResult {
71    /// Possibly patched version metadata.
72    pub version: VersionJson,
73    /// Patches applied to produce [`CompatibilityResult::version`].
74    pub applied_patches: Vec<CompatibilityPatch>,
75    /// Optional Java runtime recommendation.
76    pub java_runtime: Option<JavaRuntimeHint>,
77    /// Window-hosting recommendation for the caller.
78    pub windowing: WindowingHint,
79}
80
81/// Applies known compatibility adjustments for the given platform and policy.
82///
83/// The returned version is cloned from the input when no patch is needed.
84pub fn apply_compatibility(
85    version: &VersionJson,
86    platform: Platform,
87    policy: CompatibilityPolicy,
88) -> CompatibilityResult {
89    if policy == CompatibilityPolicy::Disabled {
90        return CompatibilityResult {
91            version: version.clone(),
92            applied_patches: Vec::new(),
93            java_runtime: None,
94            windowing: current_process_windowing_hint(),
95        };
96    }
97
98    if needs_legacy_macos_lwjgl2_patch(version, platform) {
99        return CompatibilityResult {
100            version: apply_legacy_macos_lwjgl2_patch(version),
101            applied_patches: vec![CompatibilityPatch::LegacyMacArm64Lwjgl2],
102            java_runtime: Some(JavaRuntimeHint {
103                major_version: 8,
104                arch: Arch::Aarch64,
105                distribution_hint: "Azul Zulu Java 8 arm64",
106                reason: "Legacy LWJGL 2 Minecraft versions need an arm64 Java 8 runtime on Apple Silicon.",
107            }),
108            windowing: legacy_macos_lwjgl2_windowing_hint(),
109        };
110    }
111
112    if needs_macos_arm64_lwjgl3_patch(version, platform) {
113        return CompatibilityResult {
114            version: apply_macos_arm64_lwjgl3_patch(version),
115            applied_patches: vec![CompatibilityPatch::MacArm64Lwjgl3],
116            java_runtime: Some(JavaRuntimeHint {
117                major_version: version
118                    .java_version
119                    .as_ref()
120                    .map(|java| java.major_version)
121                    .unwrap_or(8),
122                arch: Arch::Aarch64,
123                distribution_hint: "arm64 Java runtime matching version.json javaVersion",
124                reason: "Older LWJGL 3 Minecraft versions need arm64 macOS native libraries on Apple Silicon.",
125            }),
126            windowing: current_process_windowing_hint(),
127        };
128    }
129
130    CompatibilityResult {
131        version: version.clone(),
132        applied_patches: Vec::new(),
133        java_runtime: None,
134        windowing: current_process_windowing_hint(),
135    }
136}
137
138fn current_process_windowing_hint() -> WindowingHint {
139    WindowingHint {
140        strategy: WindowingStrategy::CurrentProcess,
141        requires_visible_window_verification: false,
142        reason: "The version can be launched as a normal Java process by the launcher.",
143    }
144}
145
146fn legacy_macos_lwjgl2_windowing_hint() -> WindowingHint {
147    WindowingHint {
148        strategy: WindowingStrategy::MacOsAppBundle,
149        requires_visible_window_verification: true,
150        reason: "Legacy LWJGL 2 can create 0x0 invisible windows when spawned directly from a CLI process on Apple Silicon; launch from a macOS app bundle or other GUI host and verify the visible window.",
151    }
152}
153
154fn needs_legacy_macos_lwjgl2_patch(version: &VersionJson, platform: Platform) -> bool {
155    platform.os == Os::MacOs
156        && platform.arch == Arch::Aarch64
157        && version
158            .libraries
159            .iter()
160            .any(|library| library.name.starts_with("org.lwjgl.lwjgl:lwjgl:"))
161}
162
163fn needs_macos_arm64_lwjgl3_patch(version: &VersionJson, platform: Platform) -> bool {
164    platform.os == Os::MacOs
165        && platform.arch == Arch::Aarch64
166        && version
167            .libraries
168            .iter()
169            .any(|library| library.name.starts_with("org.lwjgl:"))
170        && !version.libraries.iter().any(|library| {
171            library.name.contains("3.3.1-mmachina.1")
172                || library.name.contains(":natives-macos-arm64")
173                || library.name.contains(":natives-osx-arm64")
174        })
175}
176
177fn apply_legacy_macos_lwjgl2_patch(version: &VersionJson) -> VersionJson {
178    let mut patched = version.clone();
179    patched
180        .libraries
181        .retain(|library| !is_legacy_lwjgl2_replaced_library(&library.name));
182    patched.libraries.extend(legacy_macos_lwjgl2_libraries());
183    patched
184}
185
186fn apply_macos_arm64_lwjgl3_patch(version: &VersionJson) -> VersionJson {
187    let mut patched = version.clone();
188    patched
189        .libraries
190        .retain(|library| !is_lwjgl3_replaced_library(&library.name));
191    patched.libraries.extend(macos_arm64_lwjgl3_libraries());
192    patched
193}
194
195fn is_legacy_lwjgl2_replaced_library(name: &str) -> bool {
196    [
197        "org.lwjgl.lwjgl:",
198        "net.java.jinput:",
199        "net.java.jutils:",
200        "ca.weblite:java-objc-bridge:",
201        "com.mojang:text2speech:",
202    ]
203    .iter()
204    .any(|prefix| name.starts_with(prefix))
205}
206
207fn is_lwjgl3_replaced_library(name: &str) -> bool {
208    name.starts_with("org.lwjgl:") || name.starts_with("ca.weblite:java-objc-bridge:")
209}
210
211fn legacy_macos_lwjgl2_libraries() -> Vec<Library> {
212    vec![
213        artifact_library(
214            "com.mojang:text2speech:1.11.3",
215            "https://libraries.minecraft.net/com/mojang/text2speech/1.11.3/text2speech-1.11.3.jar",
216            "f378f889797edd7df8d32272c06ca80a1b6b0f58",
217            13164,
218            None,
219        ),
220        artifact_library(
221            "ca.weblite:java-objc-bridge:1.1.0-mmachina.1",
222            "https://github.com/MinecraftMachina/Java-Objective-C-Bridge/releases/download/1.1.0-mmachina.1/java-objc-bridge-1.1.jar",
223            "369a83621e3c65496348491e533cb97fe5f2f37d",
224            91947,
225            None,
226        ),
227        native_library(
228            "net.java.jinput:jinput-platform:2.0.5",
229            "net/java/jinput/jinput-platform/2.0.5/jinput-platform-2.0.5-natives-osx.jar",
230            "https://libraries.minecraft.net/net/java/jinput/jinput-platform/2.0.5/jinput-platform-2.0.5-natives-osx.jar",
231            "53f9c919f34d2ca9de8c51fc4e1e8282029a9232",
232            12186,
233        ),
234        artifact_library(
235            "net.java.jinput:jinput:2.0.5",
236            "https://libraries.minecraft.net/net/java/jinput/jinput/2.0.5/jinput-2.0.5.jar",
237            "39c7796b469a600f72380316f6b1f11db6c2c7c4",
238            208338,
239            None,
240        ),
241        artifact_library(
242            "net.java.jutils:jutils:1.0.0",
243            "https://libraries.minecraft.net/net/java/jutils/jutils/1.0.0/jutils-1.0.0.jar",
244            "e12fe1fda814bd348c1579329c86943d2cd3c6a6",
245            7508,
246            None,
247        ),
248        lwjgl_platform_library(),
249        artifact_library(
250            "org.lwjgl.lwjgl:lwjgl:2.9.4-nightly-20150209",
251            "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl/2.9.4-nightly-20150209/lwjgl-2.9.4-nightly-20150209.jar",
252            "697517568c68e78ae0b4544145af031c81082dfe",
253            1047168,
254            None,
255        ),
256        artifact_library(
257            "org.lwjgl.lwjgl:lwjgl_util:2.9.4-nightly-20150209",
258            "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl_util/2.9.4-nightly-20150209/lwjgl_util-2.9.4-nightly-20150209.jar",
259            "d51a7c040a721d13efdfbd34f8b257b2df882ad0",
260            173887,
261            None,
262        ),
263    ]
264}
265
266fn macos_arm64_lwjgl3_libraries() -> Vec<Library> {
267    vec![
268        artifact_library(
269            "ca.weblite:java-objc-bridge:1.1.0-mmachina.1",
270            "https://github.com/MinecraftMachina/Java-Objective-C-Bridge/releases/download/1.1.0-mmachina.1/java-objc-bridge-1.1.jar",
271            "369a83621e3c65496348491e533cb97fe5f2f37d",
272            91947,
273            None,
274        ),
275        lwjgl3_macos_arm64_library(
276            "lwjgl-glfw",
277            "e9a101bca4fa30d26b21b526ff28e7c2d8927f1b",
278            130128,
279            "71d793d0a5a42e3dfe78eb882abc2523a2c6b496",
280            129076,
281        ),
282        lwjgl3_macos_arm64_library(
283            "lwjgl-jemalloc",
284            "4fb94224378d3588d52d2beb172f2eeafea2d546",
285            36976,
286            "b0be721188d2e7195798780b1c5fe7eafe8091c1",
287            103478,
288        ),
289        lwjgl3_macos_arm64_library(
290            "lwjgl-openal",
291            "d48e753d85916fc8a200ccddc709b36e3865cc4e",
292            88880,
293            "6b80fc0b982a0723b141e88859c42d6f71bd723f",
294            346131,
295        ),
296        lwjgl3_macos_arm64_library(
297            "lwjgl-opengl",
298            "962c2a8d2a8cdd3b89de3d78d766ab5e2133c2f4",
299            929233,
300            "bb575058e0372f515587b5d2d04ff7db185f3ffe",
301            41667,
302        ),
303        lwjgl3_macos_arm64_library(
304            "lwjgl-stb",
305            "703e4b533e2542560e9f94d6d8bd148be1c1d572",
306            113273,
307            "98f0ad956c754723ef354d50057cc30417ef376a",
308            178409,
309        ),
310        lwjgl3_macos_arm64_library(
311            "lwjgl-tinyfd",
312            "1203660b3131cbb8681b17ce6437412545be95e0",
313            6802,
314            "015b931a2daba8f0c317d84c9d14e8e98ae56e0c",
315            41384,
316        ),
317        lwjgl3_macos_arm64_library(
318            "lwjgl",
319            "8e664dd69ad7bbcf2053da23efc7848e39e498db",
320            719038,
321            "984df31fadaab86838877b112e5b4e4f68a00ccf",
322            42693,
323        ),
324    ]
325}
326
327fn artifact_library(
328    name: &str,
329    url: &str,
330    sha1: &str,
331    size: i64,
332    path_override: Option<&str>,
333) -> Library {
334    let path = path_override.map(ToOwned::to_owned).unwrap_or_else(|| {
335        MavenCoordinate::parse(name)
336            .expect("static coordinate")
337            .artifact_path()
338            .to_string_lossy()
339            .to_string()
340    });
341    Library {
342        name: name.to_string(),
343        url: None,
344        rules: Vec::new(),
345        downloads: Some(LibraryDownloads {
346            artifact: Some(LibraryArtifact {
347                path,
348                url: url.to_string(),
349                sha1: sha1.to_string(),
350                size,
351            }),
352            classifiers: HashMap::new(),
353        }),
354        natives: None,
355        extract: None,
356    }
357}
358
359fn lwjgl3_macos_arm64_library(
360    artifact: &str,
361    artifact_sha1: &str,
362    artifact_size: i64,
363    native_sha1: &str,
364    native_size: i64,
365) -> Library {
366    let name = format!("org.lwjgl:{artifact}:3.3.1-mmachina.1");
367    let artifact_path = MavenCoordinate::parse(&name)
368        .expect("static coordinate")
369        .artifact_path()
370        .to_string_lossy()
371        .to_string();
372    let native_path = format!(
373        "org/lwjgl/{artifact}/3.3.1-mmachina.1/{artifact}-3.3.1-mmachina.1-natives-macos.jar"
374    );
375    let release_base =
376        "https://github.com/MinecraftMachina/lwjgl3/releases/download/3.3.1-mmachina.1";
377
378    let mut classifiers = HashMap::new();
379    classifiers.insert(
380        "natives-macos".to_string(),
381        LibraryArtifact {
382            path: native_path,
383            url: format!("{release_base}/{artifact}-natives-macos-arm64.jar"),
384            sha1: native_sha1.to_string(),
385            size: native_size,
386        },
387    );
388    let mut natives = HashMap::new();
389    natives.insert("osx".to_string(), "natives-macos".to_string());
390    let mut extract = HashMap::new();
391    extract.insert("exclude".to_string(), vec!["META-INF/".to_string()]);
392
393    Library {
394        name,
395        url: None,
396        rules: Vec::new(),
397        downloads: Some(LibraryDownloads {
398            artifact: Some(LibraryArtifact {
399                path: artifact_path,
400                url: format!("{release_base}/{artifact}.jar"),
401                sha1: artifact_sha1.to_string(),
402                size: artifact_size,
403            }),
404            classifiers,
405        }),
406        natives: Some(natives),
407        extract: Some(extract),
408    }
409}
410
411fn native_library(name: &str, path: &str, url: &str, sha1: &str, size: i64) -> Library {
412    let mut classifiers = HashMap::new();
413    classifiers.insert(
414        "natives-osx".to_string(),
415        LibraryArtifact {
416            path: path.to_string(),
417            url: url.to_string(),
418            sha1: sha1.to_string(),
419            size,
420        },
421    );
422    let mut natives = HashMap::new();
423    natives.insert("osx".to_string(), "natives-osx".to_string());
424    let mut extract = HashMap::new();
425    extract.insert("exclude".to_string(), vec!["META-INF/".to_string()]);
426
427    Library {
428        name: name.to_string(),
429        url: None,
430        rules: Vec::new(),
431        downloads: Some(LibraryDownloads {
432            artifact: None,
433            classifiers,
434        }),
435        natives: Some(natives),
436        extract: Some(extract),
437    }
438}
439
440fn lwjgl_platform_library() -> Library {
441    let mut library = native_library(
442        "org.lwjgl.lwjgl:lwjgl-platform:2.9.4-nightly-20150209-mmachina.2",
443        "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar",
444        "https://github.com/MinecraftMachina/lwjgl/releases/download/2.9.4-20150209-mmachina.2/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar",
445        "eff546c0b319d6ffc7a835652124c18089c67f36",
446        488316,
447    );
448    if let Some(downloads) = &mut library.downloads {
449        downloads.artifact = Some(LibraryArtifact {
450            path: "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar".to_string(),
451            url: "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar".to_string(),
452            sha1: "b04f3ee8f5e43fa3b162981b50bb72fe1acabb33".to_string(),
453            size: 22,
454        });
455    }
456    library
457}