package codechicken.lib.asm; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.InsnList; import org.objectweb.asm.tree.MethodNode; import java.io.File; import java.util.*; import static codechicken.lib.asm.ASMHelper.*; public class ModularASMTransformer { public static class ClassNodeTransformerList { List<ClassNodeTransformer> transformers = new LinkedList<ClassNodeTransformer>(); HashSet<ObfMapping> methodsToSort = new HashSet<ObfMapping>(); public void add(ClassNodeTransformer t) { transformers.add(t); t.addMethodsToSort(methodsToSort); } public byte[] transform(byte[] bytes) { ClassNode cnode = new ClassNode(); ClassReader reader = new ClassReader(bytes); ClassVisitor cv = cnode; if (!methodsToSort.isEmpty()) { cv = new LocalVariablesSorterVisitor(methodsToSort, cv); } reader.accept(cv, ClassReader.EXPAND_FRAMES); try { int writeFlags = 0; for (ClassNodeTransformer t : transformers) { t.transform(cnode); writeFlags |= t.writeFlags; } bytes = createBytes(cnode, writeFlags); if (config.getTag("dump_asm").getBooleanValue(true)) { dump(bytes, new File("asm/ccl_modular/" + cnode.name.replace('/', '#') + ".txt"), false, false); } return bytes; } catch (RuntimeException e) { dump(bytes, new File("asm/ccl_modular/" + cnode.name.replace('/', '#') + ".txt"), false, false); throw e; } } } public static abstract class ClassNodeTransformer { public int writeFlags; public ClassNodeTransformer(int writeFlags) { this.writeFlags = writeFlags; } public ClassNodeTransformer() { this(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); } public abstract String className(); public abstract void transform(ClassNode cnode); public void addMethodsToSort(Set<ObfMapping> set) { } } public static abstract class MethodTransformer extends ClassNodeTransformer { public final ObfMapping method; public MethodTransformer(ObfMapping method) { this.method = method.toClassloading(); } @Override public String className() { return method.javaClass(); } @Override public void transform(ClassNode cnode) { MethodNode mv = findMethod(method, cnode); if (mv == null) { throw new RuntimeException("Method not found: " + method); } try { transform(mv); } catch (Exception e) { throw new RuntimeException("Error transforming method: " + method, e); } } public abstract void transform(MethodNode mv); } public static class MethodWriter extends ClassNodeTransformer { public final int access; public final ObfMapping method; public final String[] exceptions; public InsnList list; public MethodWriter(int access, ObfMapping method) { this(access, method, null, (InsnList) null); } public MethodWriter(int access, ObfMapping method, InsnList list) { this(access, method, null, list); } public MethodWriter(int access, ObfMapping method, ASMBlock block) { this(access, method, null, block); } public MethodWriter(int access, ObfMapping method, String[] exceptions) { this(access, method, exceptions, (InsnList) null); } public MethodWriter(int access, ObfMapping method, String[] exceptions, InsnList list) { this.access = access; this.method = method.toClassloading(); this.exceptions = exceptions; this.list = list; } public MethodWriter(int access, ObfMapping method, String[] exceptions, ASMBlock block) { this(access, method, exceptions, block.rawListCopy()); } @Override public String className() { return method.javaClass(); } @Override public void transform(ClassNode cnode) { MethodNode mv = findMethod(method, cnode); if (mv == null) { mv = (MethodNode) method.visitMethod(cnode, access, exceptions); } else { mv.access = access; mv.instructions.clear(); if (mv.localVariables != null) { mv.localVariables.clear(); } if (mv.tryCatchBlocks != null) { mv.tryCatchBlocks.clear(); } } write(mv); } public void write(MethodNode mv) { logger.debug("Writing method " + method); list.accept(mv); } } public static class MethodInjector extends MethodTransformer { public ASMBlock needle; public ASMBlock injection; public boolean before; public MethodInjector(ObfMapping method, ASMBlock needle, ASMBlock injection, boolean before) { super(method); this.needle = needle; this.injection = injection; this.before = before; } public MethodInjector(ObfMapping method, ASMBlock injection, boolean before) { this(method, null, injection, before); } public MethodInjector(ObfMapping method, InsnList needle, InsnList injection, boolean before) { this(method, new ASMBlock(needle), new ASMBlock(injection), before); } public MethodInjector(ObfMapping method, InsnList injection, boolean before) { this(method, null, new ASMBlock(injection), before); } @Override public void addMethodsToSort(Set<ObfMapping> set) { set.add(method); } @Override public void transform(MethodNode mv) { if (needle == null) { logger.debug("Injecting " + (before ? "before" : "after") + " method " + method); if (before) { mv.instructions.insert(injection.rawListCopy()); } else { mv.instructions.add(injection.rawListCopy()); } } else { for (InsnListSection key : InsnComparator.findN(mv.instructions, needle.list)) { logger.debug("Injecting " + (before ? "before" : "after") + " method " + method + " @ " + key.start + " - " + key.end); ASMBlock injectBlock = injection.copy().mergeLabels(needle.applyLabels(key)); if (before) { key.insertBefore(injectBlock.list.list); } else { key.insert(injectBlock.list.list); } } } } } public static class MethodReplacer extends MethodTransformer { public ASMBlock needle; public ASMBlock replacement; public MethodReplacer(ObfMapping method, ASMBlock needle, ASMBlock replacement) { super(method); this.needle = needle; this.replacement = replacement; } public MethodReplacer(ObfMapping method, InsnList needle, InsnList replacement) { this(method, new ASMBlock(needle), new ASMBlock(replacement)); } @Override public void addMethodsToSort(Set<ObfMapping> set) { set.add(method); } @Override public void transform(MethodNode mv) { for (InsnListSection key : InsnComparator.findN(mv.instructions, needle.list)) { logger.debug("Replacing method " + method + " @ " + key.start + " - " + key.end); ASMBlock replaceBlock = replacement.copy().pullLabels(needle.applyLabels(key)); key.insert(replaceBlock.list.list); } } } public static class FieldWriter extends ClassNodeTransformer { public final ObfMapping field; public final int access; public final Object value; public FieldWriter(int access, ObfMapping field, Object value) { this.field = field.toClassloading(); this.access = access; this.value = value; } public FieldWriter(int access, ObfMapping field) { this(access, field, null); } @Override public String className() { return field.javaClass(); } @Override public void transform(ClassNode cnode) { field.visitField(cnode, access, value); } } public HashMap<String, ClassNodeTransformerList> transformers = new HashMap<String, ClassNodeTransformerList>(); public void add(ClassNodeTransformer t) { ClassNodeTransformerList list = transformers.get(t.className()); if (list == null) { transformers.put(t.className(), list = new ClassNodeTransformerList()); } list.add(t); } public byte[] transform(String name, byte[] bytes) { if (bytes == null) { return null; } ClassNodeTransformerList list = transformers.get(name); return list == null ? bytes : list.transform(bytes); } }