package net.minecraftforge.lex.fffixer; import static org.objectweb.asm.Opcodes.*; import java.util.Iterator; import org.objectweb.asm.Label; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.InsnList; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.MethodNode; import org.objectweb.asm.tree.VarInsnNode; /** * Fixes decompiler differences between JVM versions caused by HashSet's sorting order changing between JVM implementations. * Simple solution is to hijack the Iterator to make it use a properly sorted one. * Thanks to fry for finding this issue with class names, which then led me to look for var names. * * * Code injected in aa: * implements net.minecraftfroge.lex.fffixer.Util.Indexed * public final int getIndex() * { * if (!isDeclaration) return -1; * return this.c; * } * * Code injected in bB: * var = net.minecraftfroge.lex.fffixer.Util.sortIndexed(var) * * Code Injected in d: * var = net.minecraftfroge.lex.fffixer.Util.sortComparable(var); * * Code Injected in de (IntPair): * implements Comparable<de> * public int compareTo(de o) * { * if (this.a != o.a) return this.a - o.a; * return this.b - o.b * } * public int compareTo(Object o) * { * return compareTo((de)o); * } * */ public class VariableNumberFixer implements IClassProcessor { private FFFixerImpl inst; public VariableNumberFixer(FFFixerImpl inst) { this.inst = inst; } @Override public void process(ClassNode node) { if (node.name.equals("aa")) fix_aa(node); //if (node.name.equals("aC")) fix_aC(node); if (node.name.equals("bB")) fix_bB(node); if (node.name.equals("d" )) fix_d (node); if (node.name.equals("de")) fixIntPair(node); } private void fix_aa(ClassNode node) { FFFixerImpl.log.info("Adding index getter to aa"); node.interfaces.add(Type.getInternalName(Util.Indexed.class)); MethodNode mn = new MethodNode(ACC_PUBLIC | ACC_FINAL, "getIndex", "()I", null, null); mn.visitCode(); Label isDeclaration = new Label(); mn.visitVarInsn(ALOAD, 0); mn.visitFieldInsn(GETFIELD, "aa", "e", "Z"); mn.visitJumpInsn(IFNE, isDeclaration); mn.visitInsn(ICONST_M1); mn.visitInsn(IRETURN); mn.visitLabel(isDeclaration); mn.visitVarInsn(ALOAD, 0); mn.visitFieldInsn(GETFIELD, "aa", "c", "I"); mn.visitInsn(IRETURN); mn.visitEnd(); node.methods.add(mn); inst.setWorkDone(); } /* Old Code, Kept in case we wanna inject it again private void fix_aC(ClassNode node) { FFFixerImpl.log.info("Adding index getter to aC"); node.interfaces.add(Type.getInternalName(Util.Indexed.class)); String idx = Type.getInternalName(Util.Indexed.class); MethodNode mn = new MethodNode(ACC_PUBLIC | ACC_FINAL, "getIndex", "()I", null, null); mn.visitCode(); mn.visitVarInsn(ALOAD, 0); mn.visitFieldInsn(GETFIELD, "aC", "d", "LaJ;"); mn.visitTypeInsn(INSTANCEOF, idx); Label l0 = new Label(); mn.visitJumpInsn(IFNE, l0); mn.visitInsn(ICONST_M1); mn.visitInsn(IRETURN); mn.visitLabel(l0); mn.visitVarInsn(ALOAD, 0); mn.visitFieldInsn(GETFIELD, "aC", "d", "LaJ;"); mn.visitTypeInsn(CHECKCAST, idx); mn.visitMethodInsn(INVOKEINTERFACE, idx, "getIndex", "()I", true); mn.visitInsn(ICONST_M1); mn.visitInsn(IMUL); mn.visitInsn(IRETURN); mn.visitEnd(); node.methods.add(mn); inst.setWorkDone(); } */ private void fix_bB(ClassNode node) { MethodNode mtd = FFFixerImpl.getMethod(node, "a", "(Ljava/util/List;I)Ljava/lang/String;"); Iterator<AbstractInsnNode> itr = mtd.instructions.iterator(); while(itr.hasNext()) { AbstractInsnNode insn = itr.next(); if (insn instanceof MethodInsnNode) { MethodInsnNode v = (MethodInsnNode)insn; // first iterator call if(v.getOpcode() == INVOKEINTERFACE && (v.owner + "/" + v.name + v.desc).equals("java/util/List/iterator()Ljava/util/Iterator;")) { FFFixerImpl.log.info("Injecting Var Order Fix in bB"); mtd.instructions.insert(insn, new MethodInsnNode( INVOKESTATIC, Type.getInternalName(Util.class), "sortIndexed", "(Ljava/util/Iterator;)Ljava/util/Iterator;", false)); inst.setWorkDone(); return; } } } } private void fix_d(ClassNode node) { MethodNode mtd = FFFixerImpl.getMethod(node, "b", "(Lcu;Lq;)V"); Iterator<AbstractInsnNode> itr = mtd.instructions.iterator(); while(itr.hasNext()) { AbstractInsnNode insn = itr.next(); if (insn instanceof MethodInsnNode) { MethodInsnNode v = (MethodInsnNode)insn; if (v.getOpcode() == INVOKEVIRTUAL && (v.owner + "/" + v.name + v.desc).equals("java/util/HashSet/iterator()Ljava/util/Iterator;")) { insn = itr.next(); //Pop off the next which is ASTORE 15 FFFixerImpl.log.info("Injecting Var Order Fix"); VarInsnNode var = (VarInsnNode)insn; InsnList toAdd = new InsnList(); toAdd.add(new VarInsnNode (ALOAD, var.var)); // var15 = fixInnerOrder(var15) toAdd.add(new MethodInsnNode(INVOKESTATIC, Type.getInternalName(Util.class), "sortComparable", "(Ljava/util/Iterator;)Ljava/util/Iterator;", false)); toAdd.add(new VarInsnNode (ASTORE, var.var)); mtd.instructions.insert(insn, toAdd); // Inject static call inst.setWorkDone(); return; } } } } /** * Make IntPair (de) extend Comparable, was doing this via string manipulation before because I was tired, * but converted to ASM method injection for speed by fry, thanks. * * @param node The IntPair (de) class node */ private void fixIntPair(ClassNode node) { FFFixerImpl.log.info("Making IntPair Comparable"); node.signature = "Ljava/lang/Object;Ljava/lang/Comparable<Lde;>;"; node.interfaces.add("java/lang/Comparable"); MethodNode mn = new MethodNode(ACC_PUBLIC, "compareTo", "(Lde;)I", null, null); mn.visitCode(); mn.visitVarInsn(ALOAD, 0); mn.visitFieldInsn(GETFIELD, "de", "a", "I"); mn.visitVarInsn(ALOAD, 1); mn.visitFieldInsn(GETFIELD, "de", "a", "I"); Label a_equals = new Label(); mn.visitJumpInsn(IF_ICMPEQ, a_equals); // if this.a == o.a goto a_euqals mn.visitVarInsn(ALOAD, 0); mn.visitFieldInsn(GETFIELD, "de", "a", "I"); mn.visitVarInsn(ALOAD, 1); mn.visitFieldInsn(GETFIELD, "de", "a", "I"); mn.visitInsn(ISUB); mn.visitInsn(IRETURN); // return this.a - o.a mn.visitLabel(a_equals); // a_equals mn.visitVarInsn(ALOAD, 0); mn.visitFieldInsn(GETFIELD, "de", "b", "I"); mn.visitVarInsn(ALOAD, 1); mn.visitFieldInsn(GETFIELD, "de", "b", "I"); mn.visitInsn(ISUB); mn.visitInsn(IRETURN); // return this.b - o.b mn.visitEnd(); node.methods.add(mn); mn = new MethodNode(ACC_PUBLIC | ACC_BRIDGE | ACC_SYNTHETIC, "compareTo", "(Ljava/lang/Object;)I", null, null); mn.visitCode(); mn.visitVarInsn(ALOAD, 0); mn.visitVarInsn(ALOAD, 1); mn.visitTypeInsn(CHECKCAST, "de"); mn.visitMethodInsn(INVOKEVIRTUAL, "de", "compareTo", "(Lde;)I", false); // Synthetic bounce of (Object) -> (IntPair) mn.visitInsn(IRETURN); mn.visitEnd(); node.methods.add(mn); inst.setWorkDone(); } }