v6/site/posts/2022-01-17-lwjgl-arm64.md

9.5 KiB

title = "Run LWJGL 2 Natively on Apple Silicon"
tags = ["minecraft"]
date = "2022-01-17 10:28:42 -0400"
short_desc = "Yes, I wanted to go back and play Minecraft 1.7.10. Don't judge."
slug = "lwjgl-arm64"

Running Minecraft 1.13 and later natively on Apple Silicon Macs isn't terribly complicated. Since those versions use LWJGL 3 which includes arm64 macOS as a supported platform, you can just download the appropriate version, replace the LWJGL version info in MultiMC, and you're off to the races. The end of maintenance for LWJGL 2, however, long predates the ARM transition and so getting Minecraft versions prior to 1.13 up and running requries a bit more work.

First off: This article assumes you're using MultiMC and already have the ARM Zulu 8 JDK installed1.

There are guides on the internet that tell you to download some precompiled libraries and run Minecraft through a wrapper script, but doing that doesn't sit right with me. Didn't your mother ever tell you not to download executables from strangers on the internet2? So, I wanted to actually compile LWJGL 2 and its natives from source myself.

You can find the source code for my modified version of LWJGL 2 here (and you can compare against upstream to see I haven't made any malicious changes).

If you're interested, here's a brief overview of the changes that were necessary. If not, skip ahead.

First, there are a bunch of changes to the Ant build.xml. javah was replaced with javac -h, source/target versions less than 1.7 aren't supported when compiling with a Java 17 JDK. In the build.xml for the native library, the big changes are the macOS SDK being in a different place and that we're compiling against the Zulu JVM, not the Apple one.

In MacOSXSysImplementation.java, a since-removed class was being used to ask the OS to open URLs. That's been replaced with a JNI function (see org_lwjgl_MacOSXSysImplementation.m).

In MemoryUtilSun.java, a accessor implementation that was using a removed Sun-internal class was removed (it was only used as a fallback, so it wasn't replaced with anything).

Some applet code that was using a removed Java archive format was commented out (irrelevant because the applet build is disabled altogether).

Lastly in Java-land, there were a bunch of casts from java.nio.ByteBuffer to Buffer added in places where methods ByteBuffer inherits from Buffer (e.g., flip) are being called. This is because, in between Java 8 and 17, ByteBuffer itself got overriden implementations and so trying to use a LWJGL jar built against Java 17 on Java 8 would result in a NoSuchMethodError.

In the native code, there are a few more changes.

First, an JAWT_MacOSXDrawingSurfaceInfo struct is defined. It's used for the NSView-backed drawing mode that Minecraft uses on Mac. This was previously defined in the JDK headers, however it's no longer present. An old version of the Apple JVM headers say that it is being removed, however it is clearly still implemented internally (fortuntely for us).

The other significant change is a bunch of AppKit calls being wrapped in dispatch_sync blocks since they were previously being called from background threads, which AppKit Does Not Like. This solution is rather less than ideal. I suspect dispatch_sync is part of the significant performance delta between LWJGL3 versions and earlier ones, because it blocks the render thread until the work on the main thread completes. I tried using the -XstartOnFirstThread JVM argument so that the Minecraft client thread would be the same as the AppKit main thread, however that only caused more problems.

Finally, high DPI mode is disabled altogether. I spent some time trying to get it working, but whenever I'd launch the game it would only render at 50% scale. And frankly, I don't care enough about high DPI mode to spend even more time debugging it.

To get Minecraft up and running, there's actually a second library that we also need to compile for arm64: jinput. My fork's source code can be found here (compare against upstream).

The changes for this one are much simpler, thankfully. A bunch of stuff that we don't need is disabled in the various maven files.

Then, one (1) unused import for a no-longer-extant Java stdlib class was removed.

Lastly, the build.xml for the macOS platform specific module was changed similar to the LWJGL one to make it use the macOS SDK in the correct location and link agains the Zulu 8 JDK rather than the system JavaVM framework.

Building Everything

You'll need Ant and Maven installed if you don't have them already. You also need to have a current version of Xcode downloaded.

LWJGL

  1. Clone the repo: git clone https://github.com/shadowfacts/lwjgl-arm64.git
  2. Run ant generate-all to generate some required Java files from templates.
  3. Run ant jars to build the LWJGL jar
  4. Run ant compile_native to build the native libraries
  5. In the libs/macosx/ folder inside your LWJGL clone, select liblwjgl.dylib and openal.dylib in Finder and right-click and select Compress. Then rename the created Archive.zip to lwjgl-platform-natives-osx.jar

jinput

  1. Clone the repo: git clone https://github.com/shadowfacts/jinput-arm64.git
  2. Run mvn package to build everything

Setup MultiMC

In MultiMC, create an instance of whichever pre-LWJGL3 version you want and make sure it's configured to use the ARM Java 8 you installed. Then, in the Version section of the Edit Instance window, click the Open Libraries button in the sidebar. To the folder that opens, copy the lwjgl-platform-natives-osx.jar you created earlier. Then, copy osx-plugin-2.0.10-SNAPSHOT-natives-osx.jar from inside the plugins/OSX/target folder of the jinput directory as well.

Next, in the Version section of the Edit Instance window, select LWJGL 2 and click the Customize and then Edit buttons in the sidebar.

The file that opens needs to be modified to point to local versions of the libraries we compiled. Replace its contents with the JSON below.

Click me to expand for full LWJGL 2 version JSON.
{
    "formatVersion": 1,
    "libraries": [
        {
            "name": "net.java.jinput:jinput-platform:2.0.10-SNAPSHOT",
            "natives": {
                "linux": "natives-linux",
                "osx": "natives-osx",
                "windows": "natives-windows"
            },
            "MMC-hint": "local",
			"MMC-filename": "osx-plugin-2.0.10-SNAPSHOT-natives-osx.jar"
        },
        {
            "downloads": {
                "artifact": {
                    "sha1": "39c7796b469a600f72380316f6b1f11db6c2c7c4",
                    "size": 208338,
                    "url": "https://libraries.minecraft.net/net/java/jinput/jinput/2.0.5/jinput-2.0.5.jar"
                }
            },
            "name": "net.java.jinput:jinput:2.0.5"
        },
        {
            "downloads": {
                "artifact": {
                    "sha1": "e12fe1fda814bd348c1579329c86943d2cd3c6a6",
                    "size": 7508,
                    "url": "https://libraries.minecraft.net/net/java/jutils/jutils/1.0.0/jutils-1.0.0.jar"
                }
            },
            "name": "net.java.jutils:jutils:1.0.0"
        },
        {
            "name": "org.lwjgl.lwjgl:lwjgl-platform:2.9.4-nightly-20150209",
            "natives": {
                "linux": "natives-linux",
                "osx": "natives-osx",
                "windows": "natives-windows"
            },
            "MMC-hint": "local",
			"MMC-filename": "lwjgl-platform-natives-osx.jar"
        },
        {
            "name": "org.lwjgl.lwjgl:lwjgl:2.9.4-nightly-20150209",
            "MMC-hint": "local",
            "MMC-filename": "lwjgl.jar"
        },
        {
            "downloads": {
                "artifact": {
                    "sha1": "d51a7c040a721d13efdfbd34f8b257b2df882ad0",
                    "size": 173887,
                    "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl_util/2.9.4-nightly-20150209/lwjgl_util-2.9.4-nightly-20150209.jar"
                }
            },
            "name": "org.lwjgl.lwjgl:lwjgl_util:2.9.4-nightly-20150209"
        }
    ],
    "name": "LWJGL 2",
    "releaseTime": "2017-04-05T13:58:01+00:00",
    "type": "release",
    "uid": "org.lwjgl",
    "version": "2.9.4-nightly-20150209",
    "volatile": true
}

From there, you should be able to launch Minecraft natively on ARM:

Minecraft 1.7.10 running natively on ARM

  1. If you want to play modded, a Java 8 runtime is necessary even if you've already got a newer JRE installed since older versions of FML are not compatible with the Project Jigsaw changes introduced in Java 9, even though Minecraft itself is. ↩︎

  2. Yes, playing with mods is doing just that. However, I feel there's a big difference between downloading mods that are part of widely played modpacks and downloading native binaries from completely random people. ↩︎