SciCraft/tools/patches/Patcher.java
MiTHRAL be6fa51900
All checks were successful
Build & Release Pack / build (push) Successful in 1m50s
fix: patch Create Ore Excavation kubejs_create wrapper race + validate remote URLs
- COE 1.6.8 races kubejs-create for the ProcessingOutput rhino wrapper
  (guard flag set in @Mod ctor, KubeJS plugin callbacks run during its own
  construction) → nondeterministic 'Wrapper already exists!' boot crash.
  Ship ASM-patched jar (tools/patches/build_patched_coe.sh, Patcher.java).
- validate_pack.py: new 'remote' command — fetches every direct URL and
  byte-verifies non-CDN hashes; catches unpushed/ drifted patched jars.
- docs/server.md: stale-metadata failure row (-g is GUI-only), patched-jars
  table; docs/mods.md compat notes.
2026-08-24 14:17:35 -04:00

78 lines
3.2 KiB
Java

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.JumpInsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.MethodNode;
import java.nio.file.Files;
import java.nio.file.Paths;
/**
* Strips Create Ore Excavation's guarded ProcessingOutput type-wrapper registration
* from KubeJSExcavation.registerTypeWrappers.
*
* Upstream guard: `if (!CreateOreExcavation.kubeJSCreate) register(ProcessingOutput, ...)`
* — but kubeJSCreate is assigned in the @Mod constructor while KubeJS invokes plugin
* callbacks during KubeJS's own construction. FML constructs independent mods on
* parallel workers, so the flag may still be false when the callback runs → duplicate
* wrapper vs kubejs-create → "Wrapper already exists!" crash (nondeterministic).
*
* This pack always ships kubejs-create, so COE never needs its own wrapper: remove
* the whole guarded block (getstatic/ifne/…/invokevirtual) up to its branch-target
* label. Later frames stay valid because operand stack at that label is unchanged.
*/
public class Patcher {
public static void main(String[] args) throws Exception {
if (args.length != 2) {
System.err.println("usage: Patcher <KubeJSExcavation.class-in> <class-out>");
System.exit(2);
}
byte[] bytes = Files.readAllBytes(Paths.get(args[0]));
ClassNode cn = new ClassNode();
new ClassReader(bytes).accept(cn, 0);
boolean found = false;
for (MethodNode mn : cn.methods) {
if (!"registerTypeWrappers".equals(mn.name)) continue;
found = true;
AbstractInsnNode start = null;
for (AbstractInsnNode n : mn.instructions) {
if (n instanceof FieldInsnNode f
&& n.getOpcode() == Opcodes.GETSTATIC
&& "kubeJSCreate".equals(f.name)) {
start = n;
break;
}
}
if (start == null) throw new IllegalStateException("kubeJSCreate guard not found");
AbstractInsnNode jump = start.getNext();
if (!(jump instanceof JumpInsnNode) || jump.getOpcode() != Opcodes.IFNE) {
throw new IllegalStateException("expected IFNE after getstatic");
}
LabelNode target = ((JumpInsnNode) jump).label;
int removed = 0;
AbstractInsnNode n = start;
while (n != target) {
AbstractInsnNode next = n.getNext();
mn.instructions.remove(n);
n = next;
removed++;
if (n == null) throw new IllegalStateException("branch target not reached");
}
System.out.println("patched " + cn.name + "." + mn.name
+ ": removed " + removed + " instructions up to branch target");
}
if (!found) throw new IllegalStateException("registerTypeWrappers not found");
ClassWriter cw = new ClassWriter(0);
cn.accept(cw);
Files.write(Paths.get(args[1]), cw.toByteArray());
}
}