diff --git a/src/main/java/org/ultramine/server/UltraminePlugin.java b/src/main/java/org/ultramine/server/UltraminePlugin.java index 9e2092a..b532c4d 100644 --- a/src/main/java/org/ultramine/server/UltraminePlugin.java +++ b/src/main/java/org/ultramine/server/UltraminePlugin.java @@ -8,7 +8,7 @@ import net.minecraft.launchwrapper.LaunchClassLoader; -@SortingIndex(Integer.MAX_VALUE) //Java8ComputeFramesTransformer must be always the last +@SortingIndex(Integer.MAX_VALUE) //UMTransformerCollection must be always the last public class UltraminePlugin implements IFMLLoadingPlugin { public static File location; @@ -18,10 +18,7 @@ public String[] getASMTransformerClass() { return new String[]{ - "org.ultramine.server.asm.transformers.TrigMathTransformer", - "org.ultramine.server.asm.transformers.PrintStackTraceTransformer", - - "org.ultramine.server.asm.transformers.Java8ComputeFramesTransformer", //must be always the last + "org.ultramine.server.asm.transformers.UMTransformerCollection", //must be always the last }; } diff --git a/src/main/java/org/ultramine/server/asm/ComputeFramesClassWriter.java b/src/main/java/org/ultramine/server/asm/ComputeFramesClassWriter.java index 9b53171..fd82f9f 100644 --- a/src/main/java/org/ultramine/server/asm/ComputeFramesClassWriter.java +++ b/src/main/java/org/ultramine/server/asm/ComputeFramesClassWriter.java @@ -17,6 +17,7 @@ public class ComputeFramesClassWriter extends ClassWriter { private static final LaunchClassLoader CLASSLOADER = (LaunchClassLoader)ComputeFramesClassWriter.class.getClassLoader(); + private static final String JAVA_LANG_OBJECT = "java/lang/Object"; public ComputeFramesClassWriter() { @@ -28,21 +29,21 @@ { if(type1.equals(type2)) return type1; - if(type1.equals("java/lang/Object") || type2.equals("java/lang/Object")) - return "java/lang/Object"; + if(type1.equals(JAVA_LANG_OBJECT) || type2.equals(JAVA_LANG_OBJECT)) + return JAVA_LANG_OBJECT; ClassReader node1 = getClassNode(type1); ClassReader node2 = getClassNode(type2); if((node1 != null && (node1.getAccess() & Opcodes.ACC_INTERFACE) != 0) || (node2 != null && (node2.getAccess() & Opcodes.ACC_INTERFACE) != 0)) - return "java/lang/Object"; + return JAVA_LANG_OBJECT; List sup1 = getSuperTypesStack(type1, node1); if(sup1 == null) - return "java/lang/Object"; + return JAVA_LANG_OBJECT; List sup2 = getSuperTypesStack(type2, node2); if(sup2 == null) - return "java/lang/Object"; + return JAVA_LANG_OBJECT; if(sup2.contains(type1)) return type1; @@ -50,7 +51,7 @@ return type2; if(sup1.isEmpty() || sup2.isEmpty()) - return "java/lang/Object"; + return JAVA_LANG_OBJECT; for(int i = 0; i < sup1.size(); i++) { @@ -58,7 +59,7 @@ if(sup2.contains(s1)) return s1; } - return "java/lang/Object"; + return JAVA_LANG_OBJECT; } private static ClassReader getClassNode(String name) @@ -105,7 +106,7 @@ private static List getSuperTypesStack(ClassReader node) { String superName = FMLDeobfuscatingRemapper.INSTANCE.map(node.getSuperName()); - if(superName.equals("java/lang/Object")) + if(superName.equals(JAVA_LANG_OBJECT)) return Collections.emptyList(); List list = new ArrayList(4); list.add(superName); @@ -129,7 +130,7 @@ if(node != null) { String superName = FMLDeobfuscatingRemapper.INSTANCE.map(node.getSuperName()); - if(!superName.equals("java/lang/Object")) + if(!superName.equals(JAVA_LANG_OBJECT)) { list.add(superName); getSuperTypesStack(list, superName); diff --git a/src/main/java/org/ultramine/server/asm/UMTBatchTransformer.java b/src/main/java/org/ultramine/server/asm/UMTBatchTransformer.java new file mode 100644 index 0000000..24cb3ca --- /dev/null +++ b/src/main/java/org/ultramine/server/asm/UMTBatchTransformer.java @@ -0,0 +1,76 @@ +package org.ultramine.server.asm; + +import net.minecraft.launchwrapper.IClassTransformer; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.ClassNode; + +import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class UMTBatchTransformer implements IClassTransformer +{ + private List globalTransformers = new ArrayList<>(); + private Map> specialTransformers = new HashMap<>(); + + protected void registerGlobalTransformer(IUMClassTransformer transformer) + { + globalTransformers.add(transformer); + } + + protected void registerSpecialTransformer(IUMClassTransformer transformer, String className) + { + specialTransformers.computeIfAbsent(className, k -> new ArrayList<>(1)).add(transformer); + } + + protected void registerSpecialTransformer(IUMClassTransformer transformer, String... classNames) + { + for(String name : classNames) + registerSpecialTransformer(transformer, name); + } + + @Override + public byte[] transform(String name, String transformedName, byte[] basicClass) + { + if(basicClass == null) + return null; + ClassNode classNode = new ClassNode(); + ClassReader classReader = new ClassReader(basicClass); + classReader.accept(classNode, 0); + + int flags = 0; + for(IUMClassTransformer transformer : specialTransformers.getOrDefault(transformedName, Collections.emptyList())) + flags |= transformer.transform(name, transformedName, classReader, classNode).ordinal(); + for(IUMClassTransformer transformer : globalTransformers) + flags |= transformer.transform(name, transformedName, classReader, classNode).ordinal(); + + // Computing frames even if we did not changed class to fix other mod changes of 1.7 & 1.8 classes + boolean shouldComputeFrames = (classNode.version & 0xFFFF) > Opcodes.V1_6; + if(flags == 0 && !shouldComputeFrames) + return basicClass; + + ClassWriter writer = shouldComputeFrames ? new ComputeFramesClassWriter() : new ClassWriter(flags == 1 ? 0 : 1); + classNode.accept(writer); + return writer.toByteArray(); + } + + public enum TransformResult + { + /** ClassNode not modified by this transformer*/ + NOT_MODIFIED, + /** ClassNode modified, but stack max and stack map not changed */ + MODIFIED, + /** ClassNode modified and stack changed, stack max (and frames, of class version > 1.6) will be recounted */ + MODIFIED_STACK + } + + public interface IUMClassTransformer + { + @Nonnull TransformResult transform(String name, String transformedName, ClassReader classReader, ClassNode classNode); + } +} diff --git a/src/main/java/org/ultramine/server/asm/transformers/Java8ComputeFramesTransformer.java b/src/main/java/org/ultramine/server/asm/transformers/Java8ComputeFramesTransformer.java deleted file mode 100644 index 7646e19..0000000 --- a/src/main/java/org/ultramine/server/asm/transformers/Java8ComputeFramesTransformer.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.ultramine.server.asm.transformers; - -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.Opcodes; -import org.ultramine.server.asm.ComputeFramesClassWriter; - -import net.minecraft.launchwrapper.IClassTransformer; - -public class Java8ComputeFramesTransformer implements IClassTransformer -{ - @Override - public byte[] transform(String name, String transformedName, byte[] basicClass) - { - if(basicClass == null) - return null; - ClassReader classReader = new ClassReader(basicClass); - if(classReader.readInt(classReader.getItem(1) - 7) <= Opcodes.V1_6) - return basicClass; - - ClassWriter writer = new ComputeFramesClassWriter(); - classReader.accept(writer, 0); - return writer.toByteArray(); - } -} diff --git a/src/main/java/org/ultramine/server/asm/transformers/PrintStackTraceTransformer.java b/src/main/java/org/ultramine/server/asm/transformers/PrintStackTraceTransformer.java index 3972a03..ebf4d04 100644 --- a/src/main/java/org/ultramine/server/asm/transformers/PrintStackTraceTransformer.java +++ b/src/main/java/org/ultramine/server/asm/transformers/PrintStackTraceTransformer.java @@ -7,16 +7,19 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; 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.MethodInsnNode; import org.objectweb.asm.tree.MethodNode; +import org.ultramine.server.asm.UMTBatchTransformer.IUMClassTransformer; +import org.ultramine.server.asm.UMTBatchTransformer.TransformResult; -import net.minecraft.launchwrapper.IClassTransformer; - -public class PrintStackTraceTransformer implements IClassTransformer +/** + * This transformer redirects method invocations:
+ * from {@link Throwable#printStackTrace()} to {@link org.ultramine.server.internal.UMHooks#printStackTrace(Throwable)}; + */ +public class PrintStackTraceTransformer implements IUMClassTransformer { private static final Logger log = LogManager.getLogger(); @@ -35,34 +38,29 @@ } @Override - public byte[] transform(String name, String transformedName, byte[] basicClass) + public TransformResult transform(String name, String transformedName, ClassReader classReader, ClassNode classNode) { - if(basicClass == null) - return null; - ClassNode classNode = new ClassNode(); - ClassReader classReader = new ClassReader(basicClass); - classReader.accept(classNode, 0); - - for(MethodNode m: classNode.methods) + boolean modified = false; + for(MethodNode m : classNode.methods) { for(ListIterator it = m.instructions.iterator(); it.hasNext(); ) { AbstractInsnNode insnNode = it.next(); if(insnNode.getType() == AbstractInsnNode.METHOD_INSN) { - MethodInsnNode fi = (MethodInsnNode)insnNode; - if(THROWABLE_TYPES.contains(fi.owner) && PST_NAME.equals(fi.name) && PST_DESC.equals(fi.desc) && fi.getOpcode() == Opcodes.INVOKEVIRTUAL) + MethodInsnNode mi = (MethodInsnNode)insnNode; + if(THROWABLE_TYPES.contains(mi.owner) && PST_NAME.equals(mi.name) && PST_DESC.equals(mi.desc) && mi.getOpcode() == Opcodes.INVOKEVIRTUAL) { - log.trace("Method {}.{}{}: Replacing INVOKEVIRTUAL {}.printStackTrace with INVOKESTATIC UMHooks.printStackTrace", name, m.name, m.desc, fi.owner); + log.trace("Method {}.{}{}: Replacing INVOKEVIRTUAL {}.printStackTrace with INVOKESTATIC UMHooks.printStackTrace", name, m.name, m.desc, mi.owner); it.remove(); MethodInsnNode replace = new MethodInsnNode(Opcodes.INVOKESTATIC, UMHOOKS_TYPE, PST_NAME, UM_PST_DESC, false); it.add(replace); + modified = true; } } } } - ClassWriter writer = new ClassWriter(0); - classNode.accept(writer); - return writer.toByteArray(); + + return modified ? TransformResult.MODIFIED : TransformResult.NOT_MODIFIED; } } diff --git a/src/main/java/org/ultramine/server/asm/transformers/TrigMathTransformer.java b/src/main/java/org/ultramine/server/asm/transformers/TrigMathTransformer.java index 6f13bc4..d2a1f28 100644 --- a/src/main/java/org/ultramine/server/asm/transformers/TrigMathTransformer.java +++ b/src/main/java/org/ultramine/server/asm/transformers/TrigMathTransformer.java @@ -5,16 +5,21 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; 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.MethodInsnNode; import org.objectweb.asm.tree.MethodNode; -import net.minecraft.launchwrapper.IClassTransformer; +import org.ultramine.server.asm.UMTBatchTransformer.IUMClassTransformer; +import org.ultramine.server.asm.UMTBatchTransformer.TransformResult; -public class TrigMathTransformer implements IClassTransformer +/** + * This transformer redirects method invocations:
+ * from {@link Math#atan(double)} to {@link org.ultramine.server.util.TrigMath#atan(double)};
+ * from {@link Math#atan2(double, double)} to {@link org.ultramine.server.util.TrigMath#atan2(double, double)} + */ +public class TrigMathTransformer implements IUMClassTransformer { private static final Logger log = LogManager.getLogger(); @@ -26,42 +31,38 @@ private static final String ATAN_DESC = "(D)D"; @Override - public byte[] transform(String name, String transformedName, byte[] basicClass) + public TransformResult transform(String name, String transformedName, ClassReader classReader, ClassNode classNode) { - if (basicClass == null) - return null; - ClassNode classNode = new ClassNode(); - ClassReader classReader = new ClassReader(basicClass); - classReader.accept(classNode, 0); - - for (MethodNode m: classNode.methods) + boolean modified = false; + for (MethodNode m : classNode.methods) { for (ListIterator it = m.instructions.iterator(); it.hasNext(); ) { AbstractInsnNode insnNode = it.next(); if (insnNode.getType() == AbstractInsnNode.METHOD_INSN) { - MethodInsnNode fi = (MethodInsnNode)insnNode; - if (MATH_TYPE.equals(fi.owner) && ATAN2_NAME.equals(fi.name) && ATAN2_DESC.equals(fi.desc) && fi.getOpcode() == Opcodes.INVOKESTATIC) + MethodInsnNode mi = (MethodInsnNode)insnNode; + if (MATH_TYPE.equals(mi.owner) && ATAN2_NAME.equals(mi.name) && ATAN2_DESC.equals(mi.desc) && mi.getOpcode() == Opcodes.INVOKESTATIC) { log.trace("Method {}.{}{}: Replacing INVOKESTATIC Math.atan2 with INVOKESTATIC TrigMath.atan2", name, m.name, m.desc); it.remove(); MethodInsnNode replace = new MethodInsnNode(Opcodes.INVOKESTATIC, TRIGMATH_TYPE, ATAN2_NAME, ATAN2_DESC, false); it.add(replace); + modified = true; } - if (MATH_TYPE.equals(fi.owner) && ATAN_NAME.equals(fi.name) && ATAN_DESC.equals(fi.desc) && fi.getOpcode() == Opcodes.INVOKESTATIC) + if (MATH_TYPE.equals(mi.owner) && ATAN_NAME.equals(mi.name) && ATAN_DESC.equals(mi.desc) && mi.getOpcode() == Opcodes.INVOKESTATIC) { log.trace("Method {}.{}{}: Replacing INVOKESTATIC Math.atan with INVOKESTATIC TrigMath.atan", name, m.name, m.desc); it.remove(); MethodInsnNode replace = new MethodInsnNode(Opcodes.INVOKESTATIC, TRIGMATH_TYPE, ATAN_NAME, ATAN_DESC, false); it.add(replace); + modified = true; } } } } - ClassWriter writer = new ClassWriter(0); - classNode.accept(writer); - return writer.toByteArray(); + + return modified ? TransformResult.MODIFIED : TransformResult.NOT_MODIFIED; } } diff --git a/src/main/java/org/ultramine/server/asm/transformers/UMTransformerCollection.java b/src/main/java/org/ultramine/server/asm/transformers/UMTransformerCollection.java new file mode 100644 index 0000000..1e1b64b --- /dev/null +++ b/src/main/java/org/ultramine/server/asm/transformers/UMTransformerCollection.java @@ -0,0 +1,12 @@ +package org.ultramine.server.asm.transformers; + +import org.ultramine.server.asm.UMTBatchTransformer; + +public class UMTransformerCollection extends UMTBatchTransformer +{ + public UMTransformerCollection() + { + registerGlobalTransformer(new PrintStackTraceTransformer()); + registerGlobalTransformer(new TrigMathTransformer()); + } +}