```
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](https://www.lwjgl.org/customize) 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.

<!-- excerpt-end -->

First off: This article assumes you're using MultiMC and already have the ARM [Zulu 8 JDK](https://www.azul.com/downloads/?version=java-8-lts&os=macos&architecture=arm-64-bit&package=jdk) installed[^1].

[^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.

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 internet[^2]? So, I wanted to actually compile LWJGL 2 and its natives from source myself.

[^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.

You can find the source code for my modified version of LWJGL 2 [here](https://github.com/shadowfacts/lwjgl2-arm64) (and you can [compare](https://github.com/LWJGL/lwjgl/compare/master...shadowfacts:master) 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](#building-everything).

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](https://github.com/phracker/MacOSX-SDKs/blob/master/MacOSX10.7.sdk/System/Library/Frameworks/JavaVM.framework/Versions/A/Headers/jawt_md.h#L42) 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](https://social.shadowfacts.net/notice/AF0rlm8MrA8fSo1Qa8) 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](https://github.com/shadowfacts/jinput-arm64) ([compare against upstream](https://github.com/jinput/jinput/compare/master...shadowfacts:master)).

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](https://ant.apache.org/) and [Maven](https://maven.apache.org/) 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.

<details>
<summary>Click me to expand for full LWJGL 2 version JSON.</summary>

```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
}
```

</details>

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

<img src="/2022/lwjgl-arm64/minecraft.png" alt="Minecraft 1.7.10 running natively on ARM">

