Skip to content

Java 24/25 support: recompute stale StackMapTables + JEP 486 launch#193

Open
GribanovIvan wants to merge 4 commits into
CrucibleMC:stagingfrom
GribanovIvan:feature/java25-support
Open

Java 24/25 support: recompute stale StackMapTables + JEP 486 launch#193
GribanovIvan wants to merge 4 commits into
CrucibleMC:stagingfrom
GribanovIvan:feature/java25-support

Conversation

@GribanovIvan

Copy link
Copy Markdown
Contributor

Java 24/25 support

Lets Crucible launch and run a Mixin-heavy modpack on Java 24/25. Verified by building this branch (Java 8) and booting a large GTNH-style pack (~62 mods, incl. unimixins + lwjgl3ify + Galacticraft + Dynmap) to Done on Oracle GraalVM 25 with the bytecode verifier on (0 VerifyErrors).

Commit 1 — Recompute stale StackMapTables

A SpongePowered Mixin (via unimixins) rewrites a net.minecraft method with COMPUTE_MAXS only, leaving its StackMapTable stale. The split verifier then rejects it with VerifyError: Expecting a stackmap frame at branch target N — e.g. ChunkProviderServer.func_73153_a when Dynmap reflectively scans its fields in postInit — so the server can't start. Measured on every modern JDK tested (21–25); Java 8 is unaffected (its verifier fails over to the old type-inference verifier).

-Xverify:none also works but drops verification for every class. This is the surgical alternative: a transformer (AsmFrameSafetyTransformer, registered last on the LaunchClassLoader via CrucibleCoremodHook) force-recomputes frames for net.minecraft.* with a deobf-aware, never-throwing SafeAsmClassWriter, keeping the verifier on. RFB's rfb-asm-safety only helps transformers that already recompute; it doesn't force a recompute on a class whose stale frame a COMPUTE_MAXS-only pass baked in.

Toggles: -Dcrucible.frameSafety=false to disable; -Dcrucible.frameSafety.scope=all (or a prefix list) to widen beyond net.minecraft.

Commit 2 — Java 24+ launch plumbing

  • FMLTweaker: on Java 24+ System.setSecurityManager() always throws UnsupportedOperationException (JEP 486); FMLTweaker only caught SecurityException and crashed. Catch it, log, continue (the FML SM only trapped rogue System.exit()/SM-replacement).
  • java24args.txt: java9args.txt minus -Djava.security.manager=allow (makes a Java 24+ JVM refuse to start) and the obsolete --illegal-access=warn.
  • README: supported range 8–218–25.

🤖 Sorry for generating it with Claude Code, one day I shall learn java

GribanovIvan and others added 2 commits June 25, 2026 16:11
…JDKs

When a SpongePowered Mixin (e.g. via unimixins) rewrites a net.minecraft method,
it recomputes only the operand-stack/local sizes (COMPUTE_MAXS) and leaves the
method's StackMapTable stale. The split bytecode verifier rejects that with
"VerifyError: Expecting a stackmap frame at branch target N" the moment the class
is verified -- e.g. net.minecraft.world.gen.ChunkProviderServer.func_73153_a when
Dynmap reflectively scans its fields during postInit -- so the server cannot
start.

Measured on the lwjgl3ify server path with a large GTNH-style pack: this happens
on every modern JDK tested (21, 22, 23, 24, 25). Java 8 is unaffected because its
verifier fails over to the old type-inference verifier for these class files. A
bare server is unaffected; only packs whose Mixins touch net.minecraft hit it.

Disabling the verifier (-Xverify:none) also lets the pack start but drops bytecode
verification for every class. This is the surgical alternative: repair the
affected frames and keep the verifier on.

RFB's rfb-asm-safety plugin only makes getCommonSuperClass safe for transformers
that already recompute frames; it does not force a recompute on a class whose
stale frame was baked in by a COMPUTE_MAXS-only pass.

Add AsmFrameSafetyTransformer, registered last on the LaunchClassLoader via
CrucibleCoremodHook, which recomputes frames (COMPUTE_FRAMES) for net.minecraft.*
classes at load time using a deobf-aware, never-throwing ClassWriter
(SafeAsmClassWriter). Implementation note: ClassWriter(reader, flags) copies
unchanged methods verbatim and skips COMPUTE_FRAMES; super(flags) without the
reader forces a real recompute.

Toggles: -Dcrucible.frameSafety=false disables it;
-Dcrucible.frameSafety.scope=all (or a comma-separated prefix list) widens beyond
net.minecraft.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three small changes that let Crucible launch on Java 24/25, complementing the
StackMapTable fix:

* FMLTweaker: on Java 24+ System.setSecurityManager() always throws
  UnsupportedOperationException (JEP 486). FMLTweaker only caught
  SecurityException, so it crashed at startup. Catch the new exception, log it,
  and continue -- the FML SecurityManager only trapped rogue System.exit() and SM
  replacement, neither essential.

* java24args.txt: a Java 24+ launch-args file. It is java9args.txt without
  -Djava.security.manager=allow (which makes a Java 24+ JVM refuse to start) and
  the obsolete --illegal-access=warn (ignored since Java 17).

* README: bump the advertised supported range from "Java 8-21" to "Java 8-25".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 25, 2026 16:14

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@gravit0

gravit0 commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

May be we can move "-Dcrucible.frameSafety=false" and "-Dcrucible.frameSafety.scope=all" to Crucible config?

Per review feedback (gravit0): expose the StackMapTable-recompute toggle and its
class scope as Crucible.yml options instead of system-properties-only.

- crucible.asm.frameSafety (bool, default true) — enable the recompute transformer.
- crucible.asm.frameSafetyScope (string, default "net.minecraft.") — prefix filter;
  "all" recomputes everything.

The system properties still work and OVERRIDE the config when set, so CI/launch
scripts can flip them without editing the yml. Debug-only props
(crucible.frameSafety.debug/.debugClass) stay system-property-only.

CrucibleCoremodHook reads the config the same way the rest of the bootstrap
package does (Lwjgl3ifyIntegration) — a direct CrucibleConfigs.configs reference;
crucible.jar is the launch -jar so the class resolves. The transformer reads its
scope the same way.

@gravit0 gravit0 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to know what problem PR solves (specifically, working with ASM). I remember there was a problematic asjcore mod with a similar issue to the one described above: https://pastebin.com/TTGVr223

Comment thread patches/cpw/mods/fml/common/launcher/FMLTweaker.java.patch

@SuppressWarnings("unchecked")
private void ensureRunsLast() {
final Field f = TRANSFORMERS_FIELD;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is quite risky code with reflection. Even though we guarantee that the launchwrapper implementation is RFB and we can change this code if it changes, it's still a dirty hack.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, and I agree the reflective reorder is fragile, it pokes LaunchClassLoader.transformers directly and only works as long as that field stays a List we're allowed to mutate. The reason it's there at all: this transformer has to run after every other IClassTransformer, because anything that transforms a class after we recompute its frames can re-introduce the stale StackMapTable we just fixed. Registration order alone doesn't guarantee that, so ensureRunsLast() moves us to the tail of the list on each transform() call.

The obvious clean replacement seems to be an RFB RfbClassTransformer in the LCL_WITH_TRANSFORMS phase, that phase already appears to run after the legacy IClassTransformer chain (in LaunchClassLoader I see runTransformers(...) invoked first and runRfbTransformers(..., LCL_WITH_TRANSFORMS, ...) after it), so the ordering would be handled by RFB and the reflection would go away. I tried fairly hard to make that work and kept hitting frame-computation problems that I couldn't fully explain, so I'd appreciate your read on it. Here's what I tried, all on a full GTNH-style pack on GraalVM 25:

  1. RFB's own recompute via ClassNodeHandle.computeFrames()
    public void transformClass(ExtensibleClassLoader cl, Context ctx, Manifest m, String name, ClassNodeHandle h) {
    h.computeFrames();
    }
    NoClassDefFoundError: adb during server bootstrap. adb seems to be the obfuscated name of a net.minecraft class, so my guess is the recompute produced a frame that's wrong for the remapped class, but I'm not certain that's the actual cause.

  2. The in-repo SafeAsmClassWriter on the node ASM gives me
    ClassNode node = h.getNode();
    SafeAsmClassWriter w = new SafeAsmClassWriter(null, ClassWriter.COMPUTE_FRAMES);
    node.accept(w);
    ClassNode recomputed = new ClassNode();
    new ClassReader(w.toByteArray()).accept(recomputed, 0);
    h.setNode(recomputed);
    VerifyError: Bad type on operand stack. With debug logging the transformer does see deobfuscated names here (className=net.minecraft.world.World, node.name=net/minecraft/world/World) and the scope filter behaves, so the inputs look right to me, but the resulting frames don't verify for some net.minecraft classes.

  3. A writer that resolves the hierarchy via ExtensibleClassLoader.findClassMetadata instead (reasoning: findClassMetadata appears to go through untransformName, i.e. it seems to be the deobf-aware lookup for this phase)
    FastClassAccessor a = cl.findClassMetadata(internalName.replace('/', '.'));
    // walk a.binarySuperName() / a.binaryInterfaceNames() to compute the common supertype
    NoClassDefFoundError: ahb, which appears to be net.minecraft.world.World again.

  4. In-repo writer + h.setWriterFlags(0) after setNode, in case RFB's final computeBytes() was re-running its own writer over my result and undoing it (it looks like computeBytes() does new SafeAsmClassWriter(writerFlags) and re-emits) → still VerifyError: Bad type on operand stack, so that doesn't seem to be it either.

  5. h.getOriginalBytes() + the exact byte-level path the legacy transformer uses
    byte[] orig = h.getOriginalBytes();
    ClassReader r = new ClassReader(orig);
    SafeAsmClassWriter w = new SafeAsmClassWriter(r, ClassWriter.COMPUTE_FRAMES);
    r.accept(w, 0);
    ClassNode recomputed = new ClassNode();
    new ClassReader(w.toByteArray()).accept(recomputed, 0);
    h.setNode(recomputed);
    h.setWriterFlags(0);
    still VerifyError: Bad type on operand stack.

The part I can't reconcile: the same in-repo SafeAsmClassWriter recompute boots clean (server reaches Done, no VerifyError) when it runs as a legacy IClassTransformer reordered to last, but the same writer on the LCL_WITH_TRANSFORMS phase produces a bad frame for some net.minecraft classes. So it seems less like the writer itself and more like the bytes/type-resolution context differing between "legacy chain, last position" and "the RFB phase" — but I genuinely don't know the RFB contract well enough to say which difference matters.

So for now I've kept the legacy IClassTransformer with the reflective tail-reorder, since that path boots reliably which is itself very much in the spirit of 1.7.10: code shaped by the 2014 toolchain, twelve years on, still somehow load-bearing. Doing it the clean way feels like it could mean untangling how the launch chain resolves types on that phase, and at that point that is rewriting half the server to win back one reflective field access.
Real question, if you have a minute: for recomputing StackMapTables of FML-remapped classes from an LCL_WITH_TRANSFORMS transformer — which bytes are the intended input (getOriginalBytes vs getNode), and how is getCommonSuperClass meant to resolve the deobfuscated type hierarchy on that phase? If there's a supported recipe I'll switch to it and drop the reflection.

if (dbg) dump(transformedName + ".pre", basicClass);
try {
ClassReader reader = new ClassReader(basicClass);
SafeAsmClassWriter writer = new SafeAsmClassWriter(reader, ClassWriter.COMPUTE_FRAMES);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use com.gtnewhorizons.retrofuturabootstrap.asm.SafeAsmClassWriter. Why do we need to re-implement it here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, and I tried that first. RFB's SafeAsmClassWriter has the flags-only constructor I need (new SafeAsmClassWriter(ClassWriter.COMPUTE_FRAMES) with no reader, so it can't fall into the "copies unchanged methods byte-for-byte and skips the recompute" trap), so on paper it's a drop-in and one less copy of this to maintain. But swapping it in regresses the full pack on GraalVM 25:
java.lang.NoClassDefFoundError: ahb
ahb is the obfuscated name of net.minecraft.world.World, so the recompute produced a class the JVM then can't define under its obf alias, i.e. the recomputed frames are wrong for that class. With the in-repo writer the same boot reaches Done cleanly.

As far as I can tell the difference is deobfuscation. The whole job of getCommonSuperClass during COMPUTE_FRAMES is to find the common supertype of two types without loading them, and on 1.7.10 those types are obfuscated and not yet loadable. The in-repo writer's getCommonSuperClass resolves the hierarchy through FMLDeobfuscatingRemapper (obf↔srg) and reads class headers off the LaunchClassLoader, so it gets the right supertype for a transformed net.minecraft class; RFB's appears to resolve names directly, which on these classes lands on the wrong supertype and emits a frame that doesn't verify. It's the same deobf-awareness gap I hit trying to move the whole pass onto the RFB phase...

This is also the exact failure mode the PR exists to fix in the first place, an imprecise getCommonSuperClass widening a type and producing a bad frame, so using a writer that isn't deobf-aware would reintroduce the bug it's meant to repair.

So I've kept the in-repo writer, but I'd rather not if RFB's can cover it. Is RFB's SafeAsmClassWriter.getCommonSuperClass meant to be deobf-aware when used on FML-remapped net.minecraft classes and if so, is there something I should be passing it (a loader, a remapper) to make it resolve the obf hierarchy correctly..?

Comment thread src/main/java/io/github/crucible/bootstrap/CrucibleCoremodHook.java Outdated
Comment thread java24args.txt Outdated
- frameSafety read directly from CrucibleConfigs: dropped the -Dcrucible.frameSafety
  system-property override (config is the configuration mechanism). Debug-only
  -Dcrucible.frameSafety.debug/.debugClass stay system properties.
- One args file: removed -Djava.security.manager=allow from java9args.txt (the actual
  Java-24 blocker) and deleted java24args.txt. lwjgl3ify redirects setSecurityManager
  so the flag isn't needed.
- FMLTweaker: dropped the UnsupportedOperationException catch. RFB's
  DeprecatedRedirectTransformer maps System.setSecurityManager to its shim on Java 9+,
  so the call never throws UOE there; Java 8 runs it natively. Verified the full pack
  boots to Done on GraalVM 25 with the catch removed.

Not changed (kept in-repo SafeAsmClassWriter, and the legacy IClassTransformer
registration): RFB's SafeAsmClassWriter / an LCL_WITH_TRANSFORMS RfbClassTransformer
both miscompute frames for FML-remapped net.minecraft classes here (NoClassDefFound /
VerifyError); the deobf-aware in-repo writer run last boots clean. Asked upstream for
the intended deobf resolution on that phase.
@EverNife EverNife added the This Looks Like AI - Beter TrippleCheck it AI is a tool, not good or bad. We have to always triple-check generated code. label Jun 28, 2026
@GribanovIvan

Copy link
Copy Markdown
Contributor Author

Some context on what the StackMapTable recompute in this PR (crucible.asm.frameSafety) is actually for, since it tends to come up and since the BiomeGenJungle / ASJCore VerifyError that's been floating around is, as far as I can tell, exactly this.

The problem (ASM/verifier side). The JVM has two verifiers: the old type-inference one, which tolerates StackMapTables that are missing or imprecise, and the type-checking one, which requires every method's frames to be present and precise. Which one a class gets, and whether there's any fallback from the strict one to the lenient one, depends on the class-file version and the JVM, and it's shifted across releases, so I won't pin it to a specific Java version. The practical effect is that on the runtimes where the strict verifier kicks in (which is what we keep landing on with lwjgl3ify on current JDKs), a frame that an older/looser setup quietly ignored becomes a hard VerifyError.

What changed concretely is that the escape hatches went away. There used to be ways to keep a bad frame alive: -XX:FailOverToOldVerifier (on by default for class-file version 50) let a class that failed strict type-checking fall back to the lenient inference verifier, and -Xverify:none / -noverify skipped verification entirely. Both are gone or going, I checked across JDKs: -Xverify:none / -noverify were deprecated in JDK 13 (silent on 11/12, they warn "will likely be removed in a future release" from 13 onward and still run), and -XX:FailOverToOldVerifier was removed outright in JDK 15 - present on 14, an Unrecognized VM option from 15 on. So on anything 15+ there's no longer a knob to let an imprecise StackMapTable slide; the strict verifier is the only path and the frames have to be correct, which is exactly why this matters now.

The frames go bad in the first place because ASM transformers in the chain rewrite net.minecraft methods with COMPUTE_MAXS only (no COMPUTE_FRAMES) — that recomputes max stack/locals but leaves the original frames in place - or recompute frames with a ClassWriter whose getCommonSuperClass can't resolve the (obfuscated, not-yet-loaded) net.minecraft hierarchy and so widens a type to java/lang/Object.

Those two failure modes produce different-looking VerifyErrors but have the same root:

  • Missing frameVerifyError: Expecting a stackmap frame at branch target N. This is the one we actually hit: net.minecraft.world.gen.ChunkProviderServer.func_73153_a, triggered by Dynmap's reflective field-scan in postInit. The method had been rewritten with COMPUTE_MAXS only (in our pack a SpongePowered Mixin from unimixins, but it isn't specific to that mod. Any COMPUTE_MAXS-only transform of a net.minecraft method does it), so the new branch had no frame.

  • Imprecise frameVerifyError: Bad type on operand stack / Bad return type which is exactly attached log. BiomeGenJungle.func_150567_a returns WorldGenAbstractTree, and the method merges several new WorldGen…(…) branches (WorldGenMegaJungle, WorldGenTrees, WorldGenShrub, …, all subclasses of WorldGenAbstractTree) into one areturn. The frame at @87 says Object on the stack instead of WorldGenAbstractTree. That Object is the tell-tale sign of a COMPUTE_FRAMES pass whose getCommonSuperClass(WorldGenMegaJungle, WorldGenTrees) couldn't see that both extend WorldGenAbstractTree (the classes weren't loadable at transform time) and fell back to Object. areturn-ing Object from a method typed WorldGenAbstractTree is what the verifier rejects.

What the PR does about it. It runs a final recompute pass (COMPUTE_FRAMES) over net.minecraft.* classes, using a ClassWriter whose getCommonSuperClass resolves the hierarchy through FMLDeobfuscatingRemapper instead of Class.forName. That fixes both modes: it adds the missing frames, and relevant to log before, it resolves the WorldGen… branches to WorldGenAbstractTree rather than Object, so the areturn verifies. (The one subtlety we had to get right: new ClassWriter(reader, flags) copies unchanged methods byte-for-byte and silently skips the recompute, so the writer is constructed without the reader to force a real COMPUTE_FRAMES.)

I reproduced your log. Minimal pack: lwjgl3ify + unimixins + ASJCore on this PR's core, GraalVM 25:

  • crucible.asm.frameSafety = false → your exact crash:

    [KASMLib]: Trying to make 1 changes in aik(net.minecraft.world.biome.BiomeGenJungle)
    java.lang.VerifyError: Bad return type
      Location: net/minecraft/world/biome/BiomeGenJungle.func_150567_a(...)Lnet/minecraft/world/gen/feature/WorldGenAbstractTree; @87: areturn
      Reason:   Type 'java/lang/Object' is not assignable to 'net/minecraft/world/gen/feature/WorldGenAbstractTree'
    

    So the offending transform is ASJCore's KASMLib hook engine recomputing BiomeGenJungle with a writer that widens the merged WorldGen… return branches to Object.

  • crucible.asm.frameSafety = trueDone (1.1s), no VerifyError. ASJCore still patches BiomeGenJungle (the log shows the same KASMLib hook); our pass just runs after it and recomputes the frame back to WorldGenAbstractTree, so it verifies.

So this isn't only the ChunkProviderServer/Mixin case. It's the same root failure your log hit, and the PR clears it.

On whether the server should be doing this at all: I think it has to. A mod may be the one writing the bad frame, but "doesn't boot on a modern JVM" is the server's problem to solve regardless of who technically caused it. Nobody is going to go patch the ASM of every coremod in their pack. Recomputing net.minecraft frames last is the one spot that can guarantee the verifier is satisfied no matter what the chain did upstream, so I'd keep it global rather than chasing individual transformers.

The other half of the PR and the harder launch blocker. Separate from the frame stuff: JDK 24 (JEP 486) permanently disabled the Security Manager, and that's a stricter cutoff than the verifier change. I checked 23 vs 24: on 23, -Djava.security.manager=allow still starts and System.setSecurityManager(...) throws UnsupportedOperationException: The Security Manager is deprecated and will be removed; on 24, -Djava.security.manager=allow makes the JVM refuse to even initialize. Error: A command line option has attempted to allow or enable the Security Manager. Enabling a Security Manager is not supported. — and setSecurityManager throws UnsupportedOperationException: Setting a Security Manager is not supported. This is more fatal than a VerifyError because it kills the JVM before a single class loads, so it has to be handled at the launch-args / FMLTweaker level rather than at class-transform time. That's what the SecurityManager + args changes in this PR are for: drop the -Djava.security.manager=allow JVM arg (the thing that hard-stops 24+), and let System.setSecurityManager go through lwjgl3ify's redirect instead of installing an FML one.

@gravit0

gravit0 commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

After a little research into the ASJCore issue, I discovered the following:

  • In vanilla Forge + lwjgl3ify, ASJCore also builds the stack incorrectly.
  • However, in vanilla Forge + lwjgl3ify, the class version matches Java 6, so the error doesn't occur.
  • Since we compile Forge and Crucible ourselves on Java 8, we get the error.

@GribanovIvan

Copy link
Copy Markdown
Contributor Author

Yeah but in my small modpack MrTJPCore, CodeChickenCore, OpenModsLib and some other mods seem also seem to have similar launch problem. I would not focus on ASJcore or any other mod, that is general issue

@mts2200

mts2200 commented Jul 4, 2026

Copy link
Copy Markdown

Why did you decide that these changes are necessary at all? I'm developing my own fork of Crucible and to support Java 24+ all I had to do was remove the use of FMLSecurityManager. VerifyError crashes are associated with bad transformers in mods, there are no such things in Crucible. Try using the GTNH fixes, they did a great job of removing/fixing all the dirty transformers in the mods.

@GribanovIvan

Copy link
Copy Markdown
Contributor Author

You're right about the root cause. I tested it and hit VerifyErrors only in a vanilla class (ChunkProviderServer.populate) being ASM-transformed by mod coremods (MrTJPCore, CodeChickenCore, OpenModsLib) that don't recompute StackMapTables. There are no such classes in Crucible itself, exactly as you said.

Removing the dirty transformers in the mod jars is the correct fix and the right default cleanest bytecode without per-load cost. But when you hit a mod GTNH hasn't patched, you're cooked. I think having a fallback is good to have anyway.

I agree that SecurityManager removal is the only strictly-necessary change, but why stop here? With the load-time StackMap recompute, you can run an existing pack on Java 25 without swapping every mod for a GTNH-fixed jar (even if they are better in many other ways). Maybe keep it behind a frameSafety toggle if mainstream decides that it is unnecessary by default?

@gravit0

gravit0 commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

I think it's worth trying to implement a PR in Hodgepodge https://github.com/GTNewHorizons/Hodgepodge with a fix for at least ASJCore. I don't know what other mods are causing problems (Dynmap doesn't cause any issues for me).

@GribanovIvan

Copy link
Copy Markdown
Contributor Author

I'm not sure about that. Clean Forge+lwgl3ify works fine and does not require any changes, game client seems fine too. I think GTNH team won't accept PR only because it fixes something in Crucible, as they actively dislike this project and suggest using clean forge. However, I won't interfere if someone pulls this code into hodgepodge.

@gravit0

gravit0 commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

The fact that this works in Forge + lwjgl3ify is pure luck, and is essentially based on the fact that this class is compiled with Java 6

@GribanovIvan

Copy link
Copy Markdown
Contributor Author

in that case this PR also makes crucible lucky

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

This Looks Like AI - Beter TrippleCheck it AI is a tool, not good or bad. We have to always triple-check generated code.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants