Skip to content

fix(lwjgl): preserve org.lwjgl.system.Configuration static initializer - #427

Closed
ChipWolf wants to merge 1 commit into
headlesshq:mainfrom
ChipWolf:fix/lwjgl-configuration-clinit-26.2
Closed

ChipWolf wants to merge 1 commit into
headlesshq:mainfrom
ChipWolf:fix/lwjgl-configuration-clinit-26.2

Conversation

@ChipWolf

Copy link
Copy Markdown
Member

Problem

Launching Minecraft 26.2 headlessly with the --lwjgl agent crashes on startup (exit 253), for all three modloaders (Fabric, NeoForge, LexForge):

Description: Loading native libraries
java.lang.NullPointerException: Cannot invoke "org.lwjgl.system.Configuration.get()"
  because "org.lwjgl.system.Configuration.SHARED_LIBRARY_EXTRACT_PATH" is null
	at com.mojang.blaze3d.platform.NativeLibrariesBootstrap.configureLWJGLLibraryPath(NativeLibrariesBootstrap.java:168)
	at com.mojang.blaze3d.platform.NativeLibrariesBootstrap.loadLibraries(NativeLibrariesBootstrap.java:64)
	at net.minecraft.client.main.Main.main(Main.java:131)

Root cause

LwjglTransformer transforms every org/lwjgl class by replacing all method bodies with redirection calls, including the static initializer (<clinit>). For org.lwjgl.system.Configuration this means its public static final Configuration<?> option fields are never assigned and stay null.

Minecraft 26.2 introduced com.mojang.blaze3d.platform.NativeLibrariesBootstrap, which reads Configuration.SHARED_LIBRARY_EXTRACT_PATH during native-library loading. Because the field is null, the client NPEs before the window opens. Earlier versions (26.1.x and below) never touched Configuration at bootstrap, so the gutted initializer was harmless — this only surfaces on 26.2+.

Fix

Leave org.lwjgl.system.Configuration untransformed. It is a pure configuration registry with no native calls, so it can safely run as-is. It then behaves exactly as it does in a real (non-headless) launch — where these fields are always populated — while every actual native/GL class stays stubbed.

Testing

  • Added LwjglInstrumentationTest.testConfigurationStaticInitializerPreserved(), which loads a stub org.lwjgl.system.Configuration through the transformer and asserts its static field is non-null. Verified it fails without the guard (expected: not <null>) and passes with it.
  • Full :headlessmc-lwjgl:test suite passes.

Context

Surfaced while adding Minecraft 26.2 support to headlesshq/mc-runtime-test (headlesshq/mc-runtime-test#137). The default xvfb path already works on 26.2; only the --lwjgl headless path hit this. Once released, mc-runtime-test can bump its hmc-version to pick this up.

🤖 Generated with Claude Code

The lwjgl agent transforms every `org/lwjgl` class by gutting all method
bodies, including the static initializer. For `org.lwjgl.system.Configuration`
that leaves its `public static final Configuration<?>` option fields null.

Minecraft 26.2 added `com.mojang.blaze3d.platform.NativeLibrariesBootstrap`,
which reads `Configuration.SHARED_LIBRARY_EXTRACT_PATH` while loading native
libraries. With the field null the client crashes on startup before the window
opens (exit 253) under `--lwjgl`:

    Description: Loading native libraries
    java.lang.NullPointerException: Cannot invoke "org.lwjgl.system.Configuration.get()"
      because "org.lwjgl.system.Configuration.SHARED_LIBRARY_EXTRACT_PATH" is null
        at com.mojang.blaze3d.platform.NativeLibrariesBootstrap.configureLWJGLLibraryPath

Configuration is a pure settings registry with no native calls, so leave it
untouched. It then behaves exactly as it does in a real (non-headless) launch,
where these fields are always populated, while every actual native/GL class
stays stubbed. Fixes headless launches of Minecraft 26.2 (all modloaders).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ChipWolf

Copy link
Copy Markdown
Member Author

Closing in favour of #426, which I'd missed when I opened this — thanks @okafke. #426 reaches the same conclusion for the native-libraries crash (ignore org/lwjgl/system/Configuration) and goes further: it makes the ignore-list configurable, adds Platform, and handles the 26.2 GPUDevice/DeviceLimits redirections that this minimal patch would have crashed on next. #426 is the complete fix.

If it's useful, the regression test I added here (LwjglInstrumentationTest loading a stub org.lwjgl.system.Configuration and asserting its static field survives transformation) is easy to lift over, but no worries either way.

@ChipWolf ChipWolf closed this Jul 13, 2026
@ChipWolf
ChipWolf deleted the fix/lwjgl-configuration-clinit-26.2 branch July 13, 2026 13:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant