diff --git a/.gitignore b/.gitignore index c5474bf..6c21717 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ #Folders .gradle/* .settings/* +.idea/* bin/* build/* eclipse/* diff --git a/src/main/java/com/charles445/rltweaker/asm/ASMConfig.java b/src/main/java/com/charles445/rltweaker/asm/ASMConfig.java index 44629a4..444f9c6 100644 --- a/src/main/java/com/charles445/rltweaker/asm/ASMConfig.java +++ b/src/main/java/com/charles445/rltweaker/asm/ASMConfig.java @@ -1,143 +1,61 @@ package com.charles445.rltweaker.asm; -import java.io.File; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; +import net.minecraftforge.common.config.Configuration; +import net.minecraftforge.common.config.Property; -import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.nio.file.Path; +import java.nio.file.Paths; //FoamFix loads these three classes... Seems to work fine? -import net.minecraftforge.common.config.ConfigCategory; -import net.minecraftforge.common.config.Configuration; -import net.minecraftforge.common.config.Property; public class ASMConfig { - private static boolean setup = false; - - @Nullable - private static Configuration config = null; - - @Nonnull - public static Map configMap = new ConcurrentHashMap<>(); - - //Example map strings - - //general.server.roguelike dungeons.ENABLED - //general.server.battle towers.Change Tower Explosion Owner - - public static boolean getBoolean(String id, boolean _default) - { - if(hasConfig()) - { - Property prop = getProperty(id); - if(prop == null) - { - //Warn - System.out.println("WARNING: unknown config request: "+id); - return _default; - } - - return prop.getBoolean(_default); - - } - - return _default; - } + private final Configuration config; - @Nullable - public static Property getProperty(String s) - { - return configMap.get(s); - } - - public static boolean hasConfig() - { - return configMap.size() > 0; - } - - public static void setup() - { - //Run once - if(setup) - return; - - setup = true; - + public ASMConfig(String modName) { System.out.println("ConfigSetup is running."); //Look for the RLTweaker config - Path path = getConfig("rltweaker"); + Path path = getConfig(modName); //TODO the boolean here is "Case Sensitive Categories" //Which one is appropriate? config = new Configuration(path.toFile(), true); - - processConfiguration(); } - private static void processConfiguration() + //Example map strings + + //general.server.roguelike dungeons.ENABLED + //general.server.battle towers.Change Tower Explosion Owner + + public boolean getBoolean(String id, boolean _default) { - if(config != null) + Property prop = getProperty(id); + if(prop == null) { - //System.out.println("Processing configuration"); - - /* - System.out.println("CATEGORIES"); - for(String s : config.getCategoryNames()) - { - System.out.println(s); - } - */ - - //System.out.println("Populating configMap"); - - for(String s : config.getCategoryNames()) - { - loadCategory(config.getCategory(s), s); - } - - /* - System.out.println("Dumping configuration keys"); - - for(String s : configMap.keySet()) - { - System.out.println(s); - } - */ - - //Categories are all loaded - } - else - { - System.out.println("Config does not exist, patcher will assume defaults"); + //Warn + // TODO: shhhh, let's not be so harsh and just assume default + System.out.println("WARNING: unknown config request: "+id); + return _default; } + + return prop.getBoolean(_default); } - private static void loadCategory(ConfigCategory category, String header) + @Nullable + public Property getProperty(String s) { - for(Map.Entry entry : category.getValues().entrySet()) - { - String key = header + "." + entry.getKey(); - if(configMap.containsKey(key)) - { - System.out.println("WARNING: Duplicate key for: "+key); - } - - configMap.put(key, entry.getValue()); + int i = s.lastIndexOf(Configuration.CATEGORY_SPLITTER); + if (i < 0) { + System.out.println("WARNING: malformed config key: " + s); } - //No need to run through child categories, "getCategoryNames" already does this - //Optionally could do it manually by skipping anything with a dot in it but I really do not care at all + return this.config.getCategory(s.substring(0, i)).get(s.substring(i + 1)); } - private static Path getConfig(String modName) { - //TODO is File.separator appropriate here? - return Paths.get("config"+File.separator+modName+".cfg").toAbsolutePath(); + return Paths.get("config").resolve(modName + ".cfg").toAbsolutePath(); } } diff --git a/src/main/java/com/charles445/rltweaker/asm/CoreLoader.java b/src/main/java/com/charles445/rltweaker/asm/CoreLoader.java index 1ff5b41..e4b07fc 100644 --- a/src/main/java/com/charles445/rltweaker/asm/CoreLoader.java +++ b/src/main/java/com/charles445/rltweaker/asm/CoreLoader.java @@ -19,7 +19,6 @@ public class CoreLoader implements IFMLLoadingPlugin @Override public String[] getASMTransformerClass() { - ASMConfig.setup(); return new String[] { "com.charles445.rltweaker.asm.RLTweakerASM" }; } diff --git a/src/main/java/com/charles445/rltweaker/asm/Patch.java b/src/main/java/com/charles445/rltweaker/asm/Patch.java new file mode 100644 index 0000000..03ad64d --- /dev/null +++ b/src/main/java/com/charles445/rltweaker/asm/Patch.java @@ -0,0 +1,17 @@ +package com.charles445.rltweaker.asm; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface Patch { + + /** The path of the target class that this patch will be applied to. */ + String target(); + + /** A short description of what this patch does (optional). */ + String desc() default ""; +} diff --git a/src/main/java/com/charles445/rltweaker/asm/PatchResult.java b/src/main/java/com/charles445/rltweaker/asm/PatchResult.java new file mode 100644 index 0000000..df7b6d1 --- /dev/null +++ b/src/main/java/com/charles445/rltweaker/asm/PatchResult.java @@ -0,0 +1,69 @@ +package com.charles445.rltweaker.asm; + +import org.objectweb.asm.ClassWriter; + +public class PatchResult { + + public static PatchResult NO_MUTATION = new PatchResult(-1); + public static PatchResult NO_FLAGS = new PatchResult(0); + public static PatchResult MAXS = new PatchResult(ClassWriter.COMPUTE_MAXS); + public static PatchResult MAXS_FRAMES = new PatchResult(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); + public static PatchResult FRAMES = new PatchResult(ClassWriter.COMPUTE_FRAMES); + + /** + * {@link org.objectweb.asm.ClassWriter} flags + * no-mutation if negative + */ + private final int flags; + + public PatchResult(int flags) { + this.flags = flags; + } + + /** + * @return the {@link org.objectweb.asm.ClassWriter} flags that should be used + */ + public int getFlags() { + return Math.max(this.flags, 0); + } + + /** + * @return True if the {@link org.objectweb.asm.tree.ClassNode} has been mutated + */ + public boolean isMutated() { + return this.flags >= 0; + } + + public PatchResult add(PatchResult other) { + // if there is no mutation use -1, else bitwise-or the flags + return new PatchResult(this.isMutated() || other.isMutated() ? + this.getFlags() | other.getFlags() : -1); + } + + @Override + public boolean equals(Object other) { + return other instanceof PatchResult && + Math.max(-1, ((PatchResult) other).flags) == Math.max(-1, this.flags); + } + + @Override + public int hashCode() { + return Integer.hashCode(Math.max(-1, this.flags)); + } + + @Override + public String toString() { + switch (this.getFlags()) { + case 0: + return "None"; + case 1: + return "MAXS"; + case 2: + return "FRAMES"; + case 3: + return "MAXS | FRAMES"; + default: + return "(unknown " + this.getFlags() + ")"; + } + } +} diff --git a/src/main/java/com/charles445/rltweaker/asm/Patcher.java b/src/main/java/com/charles445/rltweaker/asm/Patcher.java new file mode 100644 index 0000000..0733d4d --- /dev/null +++ b/src/main/java/com/charles445/rltweaker/asm/Patcher.java @@ -0,0 +1,14 @@ +package com.charles445.rltweaker.asm; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface Patcher { + + /** The name of this set of patches (defaults to the class name). */ + String name() default ""; +} diff --git a/src/main/java/com/charles445/rltweaker/asm/RLTweakerASM.java b/src/main/java/com/charles445/rltweaker/asm/RLTweakerASM.java index 25a50c0..98aefb3 100644 --- a/src/main/java/com/charles445/rltweaker/asm/RLTweakerASM.java +++ b/src/main/java/com/charles445/rltweaker/asm/RLTweakerASM.java @@ -1,83 +1,53 @@ package com.charles445.rltweaker.asm; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.objectweb.asm.tree.ClassNode; - import com.charles445.rltweaker.asm.helper.ASMHelper; -import com.charles445.rltweaker.asm.patch.IPatch; import com.charles445.rltweaker.asm.patch.PatchBetterCombatMountFix; import com.charles445.rltweaker.asm.patch.PatchConcurrentParticles; -import com.charles445.rltweaker.asm.patch.PatchForgeNetwork; import com.charles445.rltweaker.asm.patch.PatchLessCollisions; import com.charles445.rltweaker.asm.patch.PatchRealBench; - import net.minecraft.launchwrapper.IClassTransformer; +import org.objectweb.asm.tree.ClassNode; + +import javax.annotation.Nullable; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.*; +import java.util.function.BiFunction; public class RLTweakerASM implements IClassTransformer { - private boolean run = true; + private Map> transformMap = new HashMap<>(); - protected static Map> transformMap = new HashMap<>(); + // TODO: Use MODID constant (It worked for ISeeDragons, but might not be safe if RLTweaker# loads the wrong classes. + private static final ASMConfig config = new ASMConfig("rltweaker"); public RLTweakerASM() { - super(); - - this.run = ASMConfig.getBoolean("general.patches.ENABLED", true); - - if(this.run) - { - System.out.println("Patcher is enabled"); - } - else - { - System.out.println("Patcher has been disabled"); - return; - } - this.createPatches(); } @Override public byte[] transform(String name, String transformedName, byte[] basicClass) { - if(!this.run) - return basicClass; - //Check for patches if(transformMap.containsKey(transformedName)) { System.out.println("Patch exists for "+transformedName); - int flags = 0; - int oldFlags = 0; - - boolean ranAnyPatch = false; + PatchResult result = PatchResult.NO_MUTATION; ClassNode clazzNode = ASMHelper.readClassFromBytes(basicClass); - for(IPatch patch : transformMap.get(transformedName)) + for(PatchMeta manager : transformMap.get(transformedName)) { - oldFlags = flags; - flags = flags | patch.getFlags(); try { - patch.patch(clazzNode); - if(patch.isCancelled()) - { - flags = oldFlags; - } - else - { - ranAnyPatch = true; - } + System.out.println("Applying patch [" + manager.name + "] " + manager.desc); + result = result.add(manager.patch.apply(this, clazzNode)); } catch(Exception e) { - System.out.println("Failed at patch "+patch.getPatchManager().getName()); + // TODO: If there was an error, clazzNode may have been mutated into an undefined state. + System.out.println("Failed at patch "+manager.name); System.out.println("Failed to write "+transformedName); e.printStackTrace(); return basicClass; @@ -85,75 +55,110 @@ public byte[] transform(String name, String transformedName, byte[] basicClass) } //TODO verbose - if(ranAnyPatch) + if(result.isMutated()) { - System.out.println("Writing class "+transformedName+" with flags "+flagsAsString(flags)); - return ASMHelper.writeClassToBytes(clazzNode, flags); + System.out.println("Writing class "+transformedName+" with flags "+result); + return ASMHelper.writeClassToBytes(clazzNode, result.getFlags()); } else { System.out.println("All patches for class "+transformedName+" were cancelled, skipping..."); return basicClass; } - } - return basicClass; } - - public static String flagsAsString(int flags) + + public void addPatcher(Class clazz) { - switch(flags) - { - case 0: return "None"; - case 1: return "MAXS"; - case 2: return "FRAMES"; - case 3: return "MAXS | FRAMES"; - default: return "(unknown "+flags+")"; + @Nullable + Patcher patcher = clazz.getAnnotation(Patcher.class); + if (patcher == null) { + throw new IllegalArgumentException(clazz.getName() + " does not have an @Patcher annotation"); + } + + for (Method m : clazz.getDeclaredMethods()) { + @Nullable + Patch patch = m.getAnnotation(Patch.class); + if (patch != null) { + if (!Modifier.isPublic(m.getModifiers()) || !Modifier.isStatic(m.getModifiers()) || + !Arrays.equals(m.getParameterTypes(), new Class[]{RLTweakerASM.class, ClassNode.class}) || + !m.getReturnType().equals(PatchResult.class)) { + throw new IllegalArgumentException(clazz.getName() + "#" + m.getName() + " is not declared correctly to be a @Patch"); + } + + addPatch(patch.target(), + patcher.name().equals("") ? clazz.getSimpleName() : patcher.name(), + patch.desc(), + (tweaker, clazzNode) -> { + try { + return (PatchResult) m.invoke(null, tweaker, clazzNode); + } catch (ReflectiveOperationException e) { + // We sanitized the method already + throw new RuntimeException("This shouldn't have happened (blame xcube)", e); + } + }); + } } } - public static void addPatch(IPatch patch) - { - String target = patch.getTargetClazz(); - - if(!transformMap.containsKey(target)) - transformMap.put(target, new ArrayList()); - - List patches = transformMap.get(target); - patches.add(patch); + public void addPatch(String target, String name, String desc, BiFunction patch) { + this.transformMap.computeIfAbsent(target, t -> new ArrayList<>()) + .add(new PatchMeta(name, desc, patch)); + } + + public void addPatch(String target, BiFunction patch) { + this.addPatch(target, "", "", patch); } private void createPatches() { //Create all the patches + if(!config.getBoolean("general.patches.ENABLED", true)) + { + System.out.println("Patcher has been disabled"); + return; + } + System.out.println("Patcher is enabled"); //particleThreading - if(ASMConfig.getBoolean("general.patches.particleThreading", true)) + if(config.getBoolean("general.patches.particleThreading", true)) { - new PatchConcurrentParticles(); + addPatcher(PatchConcurrentParticles.class); } //lessCollisions - if(ASMConfig.getBoolean("general.patches.lessCollisions", true)) + if(config.getBoolean("general.patches.lessCollisions", true)) { - new PatchLessCollisions(); + addPatcher(PatchLessCollisions.class); } //betterCombatMountFix - if(ASMConfig.getBoolean("general.patches.betterCombatMountFix", true)) + if(config.getBoolean("general.patches.betterCombatMountFix", true)) { - new PatchBetterCombatMountFix(); + addPatcher(PatchBetterCombatMountFix.class); } //realBenchDupeBugFix - if(ASMConfig.getBoolean("general.patches.realBenchDupeBugFix", true)) + if(config.getBoolean("general.patches.realBenchDupeBugFix", true)) { - new PatchRealBench(); + addPatcher(PatchRealBench.class); } - //new PatchForgeNetwork(); + //addPatcher(PatchForgeNetwork.class); + } + + private static final class PatchMeta { + + final String name; + final String desc; + final BiFunction patch; + + private PatchMeta(String name, String desc, BiFunction patch) { + this.name = name; + this.desc = desc; + this.patch = patch; + } } - } diff --git a/src/main/java/com/charles445/rltweaker/asm/patch/Patch.java b/src/main/java/com/charles445/rltweaker/asm/helper/PatchHelper.java similarity index 56% rename from src/main/java/com/charles445/rltweaker/asm/patch/Patch.java rename to src/main/java/com/charles445/rltweaker/asm/helper/PatchHelper.java index ab8aaf2..3c58fbb 100644 --- a/src/main/java/com/charles445/rltweaker/asm/patch/Patch.java +++ b/src/main/java/com/charles445/rltweaker/asm/helper/PatchHelper.java @@ -1,59 +1,19 @@ -package com.charles445.rltweaker.asm.patch; - -import javax.annotation.Nullable; +package com.charles445.rltweaker.asm.helper; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.InsnList; import org.objectweb.asm.tree.MethodNode; -import com.charles445.rltweaker.asm.helper.ASMHelper; +import javax.annotation.Nullable; -public abstract class Patch implements IPatch +public abstract class PatchHelper { - protected String target; - protected int flags; - protected IPatchManager manager; - protected boolean cancelled; - - public Patch(IPatchManager manager, String target, int flags) - { - this.manager = manager; - this.target = target; - this.flags = flags; - this.cancelled = false; - } - - @Override - public String getTargetClazz() - { - return target; - } - - @Override - public int getFlags() - { - return flags; - } - - @Override - public IPatchManager getPatchManager() - { - return manager; - } - - @Override - public boolean isCancelled() - { - return cancelled; - } - - @Override - public abstract void patch(ClassNode clazzNode); + private PatchHelper() {} //Utility - protected void announce(String s) + public static void announce(String s) { System.out.println("RLTweakerASM: "+s); } @@ -63,11 +23,11 @@ protected void announce(String s) /** * Finds the first method with the matching choice of names * @param classNode - * @param methodName + * @param methodNames * @return */ @Nullable - protected MethodNode findMethod(ClassNode classNode, String... methodNames) + public static MethodNode findMethod(ClassNode classNode, String... methodNames) { for(MethodNode m : classNode.methods) { @@ -82,7 +42,7 @@ protected MethodNode findMethod(ClassNode classNode, String... methodNames) } @Nullable - protected MethodNode findMethodWithDesc(ClassNode classNode, String desc, String... methodNames) + public static MethodNode findMethodWithDesc(ClassNode classNode, String desc, String... methodNames) { for(MethodNode m : classNode.methods) { @@ -101,27 +61,27 @@ protected MethodNode findMethodWithDesc(ClassNode classNode, String desc, String //FIRST @Nullable - protected AbstractInsnNode first(MethodNode methodNode) + public static AbstractInsnNode first(MethodNode methodNode) { return ASMHelper.findFirstInstruction(methodNode); } //LAST @Nullable - protected AbstractInsnNode last(MethodNode methodNode) + public static AbstractInsnNode last(MethodNode methodNode) { return ASMHelper.getOrFindInstruction(methodNode.instructions.getLast(), true); } //NEXT @Nullable - protected AbstractInsnNode next(AbstractInsnNode node) + public static AbstractInsnNode next(AbstractInsnNode node) { return node.getNext(); } @Nullable - protected AbstractInsnNode next(AbstractInsnNode node, int count) + public static AbstractInsnNode next(AbstractInsnNode node, int count) { AbstractInsnNode anchor = node; for(int i=0;i"); + + AbstractInsnNode anchor = TransformUtil.findNextFieldWithOpcodeAndName(first(m_init), Opcodes.PUTFIELD, "queue", "field_187241_h"); + + if(anchor == null) + throw new RuntimeException("Couldn't find queue or field_187241_h"); + + MethodInsnNode hookCaller = TransformUtil.findPreviousCallWithOpcodeAndName(anchor, Opcodes.INVOKESTATIC, "newArrayDeque"); + + if(hookCaller == null) + throw new RuntimeException("Couldn't find newArrayDeque"); + + hookCaller.owner = "com/charles445/rltweaker/hook/HookMinecraft"; + hookCaller.name = "newConcurrentLinkedDeque"; + hookCaller.desc = "()Ljava/util/concurrent/ConcurrentLinkedDeque;"; - add(new Patch(this, "net.minecraft.client.particle.ParticleManager", ClassWriter.COMPUTE_MAXS) - { - @Override - public void patch(ClassNode c_ParticleManager) - { - MethodNode m_init = findMethod(c_ParticleManager, ""); - - AbstractInsnNode anchor = TransformUtil.findNextFieldWithOpcodeAndName(first(m_init), Opcodes.PUTFIELD, "queue", "field_187241_h"); - - if(anchor == null) - throw new RuntimeException("Couldn't find queue or field_187241_h"); - - MethodInsnNode hookCaller = TransformUtil.findPreviousCallWithOpcodeAndName(anchor, Opcodes.INVOKESTATIC, "newArrayDeque"); - - if(hookCaller == null) - throw new RuntimeException("Couldn't find newArrayDeque"); - - hookCaller.owner = "com/charles445/rltweaker/hook/HookMinecraft"; - hookCaller.name = "newConcurrentLinkedDeque"; - hookCaller.desc = "()Ljava/util/concurrent/ConcurrentLinkedDeque;"; - } - }); + return PatchResult.MAXS; } } diff --git a/src/main/java/com/charles445/rltweaker/asm/patch/PatchForgeNetwork.java b/src/main/java/com/charles445/rltweaker/asm/patch/PatchForgeNetwork.java index 60799c7..db15ca8 100644 --- a/src/main/java/com/charles445/rltweaker/asm/patch/PatchForgeNetwork.java +++ b/src/main/java/com/charles445/rltweaker/asm/patch/PatchForgeNetwork.java @@ -1,78 +1,71 @@ package com.charles445.rltweaker.asm.patch; -import org.objectweb.asm.ClassWriter; +import com.charles445.rltweaker.asm.Patch; +import com.charles445.rltweaker.asm.PatchResult; +import com.charles445.rltweaker.asm.Patcher; +import com.charles445.rltweaker.asm.RLTweakerASM; import org.objectweb.asm.Opcodes; -import org.objectweb.asm.tree.AbstractInsnNode; -import org.objectweb.asm.tree.ClassNode; -import org.objectweb.asm.tree.InsnList; -import org.objectweb.asm.tree.LocalVariableNode; -import org.objectweb.asm.tree.MethodInsnNode; -import org.objectweb.asm.tree.MethodNode; -import org.objectweb.asm.tree.VarInsnNode; +import org.objectweb.asm.tree.*; import com.charles445.rltweaker.asm.util.TransformUtil; -public class PatchForgeNetwork extends PatchManager +import static com.charles445.rltweaker.asm.helper.PatchHelper.*; + +@Patcher(name = "Patch Forge Network") +public class PatchForgeNetwork { - public PatchForgeNetwork() - { - super("Patch Forge Network"); + @Patch(target = "net.minecraftforge.fml.common.network.simpleimpl.SimpleChannelHandlerWrapper") + public static PatchResult PatchForgeNetwork(RLTweakerASM tweaker, ClassNode clazzNode) { + MethodNode m_channelRead0 = findMethod(clazzNode, "channelRead0"); + + if(m_channelRead0 == null) + throw new RuntimeException("Couldn't findMethod channelRead0"); + + LocalVariableNode lvn_context = TransformUtil.findLocalVariableWithName(m_channelRead0, "context"); - add(new Patch(this, "net.minecraftforge.fml.common.network.simpleimpl.SimpleChannelHandlerWrapper", ClassWriter.COMPUTE_MAXS) + if(lvn_context == null) + throw new RuntimeException("Couldn't findLocalVariableWithName context"); + + AbstractInsnNode anchor = first(m_channelRead0); + + while(anchor != null) { - @Override - public void patch(ClassNode clazzNode) + if(anchor.getType() == AbstractInsnNode.VAR_INSN) { - MethodNode m_channelRead0 = findMethod(clazzNode, "channelRead0"); - - if(m_channelRead0 == null) - throw new RuntimeException("Couldn't findMethod channelRead0"); - - LocalVariableNode lvn_context = TransformUtil.findLocalVariableWithName(m_channelRead0, "context"); - - if(lvn_context == null) - throw new RuntimeException("Couldn't findLocalVariableWithName context"); - - AbstractInsnNode anchor = first(m_channelRead0); - - while(anchor != null) + VarInsnNode vAnchor = (VarInsnNode)anchor; + if(vAnchor.getOpcode() == Opcodes.ASTORE && vAnchor.var == lvn_context.index) { - if(anchor.getType() == AbstractInsnNode.VAR_INSN) - { - VarInsnNode vAnchor = (VarInsnNode)anchor; - if(vAnchor.getOpcode() == Opcodes.ASTORE && vAnchor.var == lvn_context.index) - { - break; - } - } - - anchor = anchor.getNext(); + break; } - - if(anchor == null) - throw new RuntimeException("Couldn't find 'context' astore"); - - //Anchor has our astore - //We also need msg - LocalVariableNode lvn_msg = TransformUtil.findLocalVariableWithName(m_channelRead0, "msg"); - - if(lvn_msg == null) - throw new RuntimeException("Couldn't findLocalVariableWithName msg"); - - //Create hook... - InsnList insert = new InsnList(); - insert.add(new VarInsnNode(Opcodes.ALOAD, lvn_msg.index)); - insert.add(new VarInsnNode(Opcodes.ALOAD, lvn_context.index)); - //stack has msg, context - insert.add(new MethodInsnNode( - Opcodes.INVOKESTATIC, - "com/charles445/rltweaker/hook/HookForge", - "receiveMessage", - "(Lnet/minecraftforge/fml/common/network/simpleimpl/IMessage;Lnet/minecraftforge/fml/common/network/simpleimpl/MessageContext;)V", - false)); - - m_channelRead0.instructions.insert(anchor, insert); } - }); + + anchor = anchor.getNext(); + } + + if(anchor == null) + throw new RuntimeException("Couldn't find 'context' astore"); + + //Anchor has our astore + //We also need msg + LocalVariableNode lvn_msg = TransformUtil.findLocalVariableWithName(m_channelRead0, "msg"); + + if(lvn_msg == null) + throw new RuntimeException("Couldn't findLocalVariableWithName msg"); + + //Create hook... + InsnList insert = new InsnList(); + insert.add(new VarInsnNode(Opcodes.ALOAD, lvn_msg.index)); + insert.add(new VarInsnNode(Opcodes.ALOAD, lvn_context.index)); + //stack has msg, context + insert.add(new MethodInsnNode( + Opcodes.INVOKESTATIC, + "com/charles445/rltweaker/hook/HookForge", + "receiveMessage", + "(Lnet/minecraftforge/fml/common/network/simpleimpl/IMessage;Lnet/minecraftforge/fml/common/network/simpleimpl/MessageContext;)V", + false)); + + m_channelRead0.instructions.insert(anchor, insert); + + return PatchResult.MAXS; } } diff --git a/src/main/java/com/charles445/rltweaker/asm/patch/PatchLessCollisions.java b/src/main/java/com/charles445/rltweaker/asm/patch/PatchLessCollisions.java index 0ea313d..7791f7e 100644 --- a/src/main/java/com/charles445/rltweaker/asm/patch/PatchLessCollisions.java +++ b/src/main/java/com/charles445/rltweaker/asm/patch/PatchLessCollisions.java @@ -1,19 +1,77 @@ package com.charles445.rltweaker.asm.patch; -import org.objectweb.asm.ClassWriter; +import com.charles445.rltweaker.asm.Patch; +import com.charles445.rltweaker.asm.PatchResult; +import com.charles445.rltweaker.asm.Patcher; +import com.charles445.rltweaker.asm.RLTweakerASM; +import com.charles445.rltweaker.asm.util.ClassDisplayer; +import com.charles445.rltweaker.asm.util.TransformUtil; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.MethodNode; -import com.charles445.rltweaker.asm.util.ClassDisplayer; -import com.charles445.rltweaker.asm.util.TransformUtil; +import static com.charles445.rltweaker.asm.helper.PatchHelper.*; -public class PatchLessCollisions extends PatchManager +@Patcher(name = "Less Collisions") +public class PatchLessCollisions { - public PatchLessCollisions() - { - super("Less Collisions"); + + //Possible issues: + // + //Explosion owner + //Projectiles that use ProjectileHelper forwardsRaycast + //Sponge compatible, 7.3.0 + @Patch(target = "net.minecraft.world.World") + public static PatchResult patchWorldyAABBThingy(RLTweakerASM tweaker, ClassNode c_World) { + if(true) // func_72839_b getEntitiesWithinAABBExcludingEntity + { + MethodNode m_getEntWithAABBExclEntity = findMethodWithDesc(c_World, "(Lnet/minecraft/entity/Entity;Lnet/minecraft/util/math/AxisAlignedBB;)Ljava/util/List;", "func_72839_b", "getEntitiesWithinAABBExcludingEntity"); + + if(m_getEntWithAABBExclEntity == null) + throw new RuntimeException("Couldn't find getEntitiesWithinAABBExcludingEntity or func_72839_b with matching desc"); + + MethodInsnNode getAABBCall = TransformUtil.findNextCallWithOpcodeAndName(first(m_getEntWithAABBExclEntity), Opcodes.INVOKEVIRTUAL, "func_175674_a","getEntitiesInAABBexcluding"); + if(getAABBCall == null) + { + System.out.println("Unexpected error, please show the below wall of text to the RLTweaker developer, thanks! Couldn't find getEntitiesInAABBexcluding or func_175674_a"); + ClassDisplayer.instance.printMethod(m_getEntWithAABBExclEntity); + throw new RuntimeException("Couldn't find getEntitiesInAABBexcluding or func_175674_a"); + } + //Stack here should have everything needed for the static call, which is convenient. + getAABBCall.setOpcode(Opcodes.INVOKESTATIC); + getAABBCall.owner = "com/charles445/rltweaker/hook/HookWorld"; + getAABBCall.name = "getEntitiesInAABBexcluding"; + getAABBCall.desc = "(Lnet/minecraft/world/World;Lnet/minecraft/entity/Entity;Lnet/minecraft/util/math/AxisAlignedBB;Lcom/google/common/base/Predicate;)Ljava/util/List;"; + } + return PatchResult.MAXS; + } + + @Patch(target = "net.minecraft.entity.EntityLivingBase") + public static PatchResult patchEntityLiving(RLTweakerASM tweaker, ClassNode c_EntityLivingBase) { + if(true) // func_85033_bc collideWithNearbyEntities + { + MethodNode m_collideWithNearbyEntities = findMethodWithDesc(c_EntityLivingBase, "()V", "func_85033_bc", "collideWithNearbyEntities"); + + if(m_collideWithNearbyEntities == null) + throw new RuntimeException("Couldn't find collideWithNearbyEntities or func_85033_bc with matching desc"); + + + MethodInsnNode getAABBCall = TransformUtil.findNextCallWithOpcodeAndName(first(m_collideWithNearbyEntities), Opcodes.INVOKEVIRTUAL, "func_175674_a", "getEntitiesInAABBexcluding"); + if(getAABBCall == null) + { + System.out.println("Unexpected error, please show the below wall of text to the RLTweaker developer, thanks! Couldn't find getEntitiesInAABBexcluding or func_175674_a"); + ClassDisplayer.instance.printMethod(m_collideWithNearbyEntities); + throw new RuntimeException("Couldn't find getEntitiesInAABBexcluding or func_175674_a"); + } + //Stack here should have everything needed for the static call, which is convenient. + getAABBCall.setOpcode(Opcodes.INVOKESTATIC); + getAABBCall.owner = "com/charles445/rltweaker/hook/HookWorld"; + getAABBCall.name = "getEntitiesInAABBexcluding"; + getAABBCall.desc = "(Lnet/minecraft/world/World;Lnet/minecraft/entity/Entity;Lnet/minecraft/util/math/AxisAlignedBB;Lcom/google/common/base/Predicate;)Ljava/util/List;"; + } + return PatchResult.MAXS; + } //Sponge overwrites this entirely, so it doesn't work /* @@ -65,73 +123,6 @@ public void patch(ClassNode c_World) }); */ - //Sponge compatible, 7.3.0 - add(new Patch(this, "net.minecraft.world.World", ClassWriter.COMPUTE_MAXS) - { - //Possible issues: - // - //Explosion owner - //Projectiles that use ProjectileHelper forwardsRaycast - - @Override - public void patch(ClassNode c_World) - { - if(true) // func_72839_b getEntitiesWithinAABBExcludingEntity - { - MethodNode m_getEntWithAABBExclEntity = findMethodWithDesc(c_World, "(Lnet/minecraft/entity/Entity;Lnet/minecraft/util/math/AxisAlignedBB;)Ljava/util/List;", "func_72839_b", "getEntitiesWithinAABBExcludingEntity"); - - if(m_getEntWithAABBExclEntity == null) - throw new RuntimeException("Couldn't find getEntitiesWithinAABBExcludingEntity or func_72839_b with matching desc"); - - MethodInsnNode getAABBCall = TransformUtil.findNextCallWithOpcodeAndName(first(m_getEntWithAABBExclEntity), Opcodes.INVOKEVIRTUAL, "func_175674_a","getEntitiesInAABBexcluding"); - if(getAABBCall == null) - { - System.out.println("Unexpected error, please show the below wall of text to the RLTweaker developer, thanks! Couldn't find getEntitiesInAABBexcluding or func_175674_a"); - ClassDisplayer.instance.printMethod(m_getEntWithAABBExclEntity); - throw new RuntimeException("Couldn't find getEntitiesInAABBexcluding or func_175674_a"); - } - //Stack here should have everything needed for the static call, which is convenient. - getAABBCall.setOpcode(Opcodes.INVOKESTATIC); - getAABBCall.owner = "com/charles445/rltweaker/hook/HookWorld"; - getAABBCall.name = "getEntitiesInAABBexcluding"; - getAABBCall.desc = "(Lnet/minecraft/world/World;Lnet/minecraft/entity/Entity;Lnet/minecraft/util/math/AxisAlignedBB;Lcom/google/common/base/Predicate;)Ljava/util/List;"; - } - } - }); - - //Sponge compatible, 7.3.0 - add(new Patch(this, "net.minecraft.entity.EntityLivingBase", ClassWriter.COMPUTE_MAXS) - { - @Override - public void patch(ClassNode c_EntityLivingBase) - { - if(true) // func_85033_bc collideWithNearbyEntities - { - MethodNode m_collideWithNearbyEntities = findMethodWithDesc(c_EntityLivingBase, "()V", "func_85033_bc", "collideWithNearbyEntities"); - - if(m_collideWithNearbyEntities == null) - throw new RuntimeException("Couldn't find collideWithNearbyEntities or func_85033_bc with matching desc"); - - - MethodInsnNode getAABBCall = TransformUtil.findNextCallWithOpcodeAndName(first(m_collideWithNearbyEntities), Opcodes.INVOKEVIRTUAL, "func_175674_a", "getEntitiesInAABBexcluding"); - if(getAABBCall == null) - { - System.out.println("Unexpected error, please show the below wall of text to the RLTweaker developer, thanks! Couldn't find getEntitiesInAABBexcluding or func_175674_a"); - ClassDisplayer.instance.printMethod(m_collideWithNearbyEntities); - throw new RuntimeException("Couldn't find getEntitiesInAABBexcluding or func_175674_a"); - } - //Stack here should have everything needed for the static call, which is convenient. - getAABBCall.setOpcode(Opcodes.INVOKESTATIC); - getAABBCall.owner = "com/charles445/rltweaker/hook/HookWorld"; - getAABBCall.name = "getEntitiesInAABBexcluding"; - getAABBCall.desc = "(Lnet/minecraft/world/World;Lnet/minecraft/entity/Entity;Lnet/minecraft/util/math/AxisAlignedBB;Lcom/google/common/base/Predicate;)Ljava/util/List;"; - } - } - }); - } - - - //Another implementation, this one works pretty well but isn't collisions specific... /* MethodNode m_getEntitiesInAABBexcluding = findMethod(c_World, "func_175674_a","getEntitiesInAABBexcluding"); diff --git a/src/main/java/com/charles445/rltweaker/asm/patch/PatchManager.java b/src/main/java/com/charles445/rltweaker/asm/patch/PatchManager.java deleted file mode 100644 index 87bf06b..0000000 --- a/src/main/java/com/charles445/rltweaker/asm/patch/PatchManager.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.charles445.rltweaker.asm.patch; - -import com.charles445.rltweaker.asm.RLTweakerASM; - -public abstract class PatchManager implements IPatchManager -{ - private String name; - - public PatchManager() - { - this.name = "PatchManager"; - } - - public PatchManager(String name) - { - System.out.println("RLTweakerASM PatchManager: "+name); - this.name = name; - } - - @Override - public String getName() - { - return name; - } - - public void add(Patch patch) - { - RLTweakerASM.addPatch(patch); - } -} diff --git a/src/main/java/com/charles445/rltweaker/asm/patch/PatchRealBench.java b/src/main/java/com/charles445/rltweaker/asm/patch/PatchRealBench.java index e1d7671..7c1c39c 100644 --- a/src/main/java/com/charles445/rltweaker/asm/patch/PatchRealBench.java +++ b/src/main/java/com/charles445/rltweaker/asm/patch/PatchRealBench.java @@ -1,131 +1,117 @@ package com.charles445.rltweaker.asm.patch; -import org.objectweb.asm.ClassWriter; +import com.charles445.rltweaker.asm.Patch; +import com.charles445.rltweaker.asm.PatchResult; +import com.charles445.rltweaker.asm.Patcher; +import com.charles445.rltweaker.asm.RLTweakerASM; 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.FieldNode; -import org.objectweb.asm.tree.InsnList; -import org.objectweb.asm.tree.MethodInsnNode; -import org.objectweb.asm.tree.MethodNode; -import org.objectweb.asm.tree.TypeInsnNode; -import org.objectweb.asm.tree.VarInsnNode; +import org.objectweb.asm.tree.*; import com.charles445.rltweaker.asm.util.TransformUtil; -public class PatchRealBench extends PatchManager +import static com.charles445.rltweaker.asm.helper.PatchHelper.*; + +@Patcher(name = "Real Bench") +public class PatchRealBench { - public PatchRealBench() - { - super("Real Bench"); + @Patch(target = "pw.prok.realbench.WorkbenchTile") + public static PatchResult patchWorkbenchTile(RLTweakerASM tweaker, ClassNode c_WorkbenchTile) { + FieldNode f_mResult = new FieldNode(Opcodes.ASM5, Opcodes.ACC_PROTECTED, "mResult", "Lnet/minecraft/util/NonNullList;", "Lnet/minecraft/item/ItemStack;", null); + + MethodNode m_init = findMethodWithDesc(c_WorkbenchTile, "()V", ""); + MethodNode m_writeSlots = findMethod(c_WorkbenchTile, "writeSlots"); + MethodNode m_readSlots = findMethod(c_WorkbenchTile, "readSlots"); + + //Add mResult field + c_WorkbenchTile.fields.add(f_mResult); - add(new Patch(this, "pw.prok.realbench.WorkbenchTile", ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES) + if(true) //Add mResult instantiation { - @Override - public void patch(ClassNode c_WorkbenchTile) + InsnList insert = new InsnList(); + insert.add(new VarInsnNode(Opcodes.ALOAD, 0)); //this + insert.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "com/charles445/rltweaker/hook/HookRealBench", "getResultSlotInit", "()Lnet/minecraft/util/NonNullList;", false)); + insert.add(new FieldInsnNode(Opcodes.PUTFIELD, "pw/prok/realbench/WorkbenchTile", "mResult", "Lnet/minecraft/util/NonNullList;")); + TransformUtil.insertBeforeFirst(m_init, insert); + } + + if(true) //writeSlots + { + InsnList insert = new InsnList(); + insert.add(new VarInsnNode(Opcodes.ALOAD, 1)); //nbt + insert.add(new VarInsnNode(Opcodes.ALOAD, 0)); //nbt, this + insert.add(new FieldInsnNode(Opcodes.GETFIELD, "pw/prok/realbench/WorkbenchTile", "mResult", "Lnet/minecraft/util/NonNullList;")); //nbt, mResult + insert.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "com/charles445/rltweaker/hook/HookRealBench", "writeSlots", "(Lnet/minecraft/nbt/NBTTagCompound;Lnet/minecraft/util/NonNullList;)V", false)); + TransformUtil.insertBeforeFirst(m_writeSlots, insert); + } + + if(true) //readSlots + { + InsnList insert = new InsnList(); + insert.add(new VarInsnNode(Opcodes.ALOAD, 1)); //nbt + insert.add(new VarInsnNode(Opcodes.ALOAD, 0)); //nbt, this + insert.add(new FieldInsnNode(Opcodes.GETFIELD, "pw/prok/realbench/WorkbenchTile", "mResult", "Lnet/minecraft/util/NonNullList;")); //nbt, mResult + insert.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "com/charles445/rltweaker/hook/HookRealBench", "readSlots", "(Lnet/minecraft/nbt/NBTTagCompound;Lnet/minecraft/util/NonNullList;)V", false)); + TransformUtil.insertBeforeFirst(m_readSlots, insert); + } + return PatchResult.MAXS_FRAMES; + } + + @Patch(target = "net.minecraft.inventory.ContainerWorkbench") + public static PatchResult patchWorkbench(RLTweakerASM tweaker, ClassNode c_ContainerWorkbench) { + MethodNode m_init = findMethodWithDesc(c_ContainerWorkbench, "(Lnet/minecraft/entity/player/InventoryPlayer;Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;)V", ""); + + //First we need to figure out if RealBench is even installed + boolean isRealBenchInstalled = false; + AbstractInsnNode anchor = first(m_init); + while(anchor != null) + { + if(anchor.getType() == AbstractInsnNode.METHOD_INSN) { - FieldNode f_mResult = new FieldNode(Opcodes.ASM5, Opcodes.ACC_PROTECTED, "mResult", "Lnet/minecraft/util/NonNullList;", "Lnet/minecraft/item/ItemStack;", null); - - MethodNode m_init = findMethodWithDesc(c_WorkbenchTile, "()V", ""); - MethodNode m_writeSlots = findMethod(c_WorkbenchTile, "writeSlots"); - MethodNode m_readSlots = findMethod(c_WorkbenchTile, "readSlots"); - - //Add mResult field - c_WorkbenchTile.fields.add(f_mResult); - - if(true) //Add mResult instantiation + if(((MethodInsnNode)anchor).owner.equals("pw/prok/realbench/asm/ASMHooks")) { - InsnList insert = new InsnList(); - insert.add(new VarInsnNode(Opcodes.ALOAD, 0)); //this - insert.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "com/charles445/rltweaker/hook/HookRealBench", "getResultSlotInit", "()Lnet/minecraft/util/NonNullList;", false)); - insert.add(new FieldInsnNode(Opcodes.PUTFIELD, "pw/prok/realbench/WorkbenchTile", "mResult", "Lnet/minecraft/util/NonNullList;")); - TransformUtil.insertBeforeFirst(m_init, insert); - } - - if(true) //writeSlots - { - InsnList insert = new InsnList(); - insert.add(new VarInsnNode(Opcodes.ALOAD, 1)); //nbt - insert.add(new VarInsnNode(Opcodes.ALOAD, 0)); //nbt, this - insert.add(new FieldInsnNode(Opcodes.GETFIELD, "pw/prok/realbench/WorkbenchTile", "mResult", "Lnet/minecraft/util/NonNullList;")); //nbt, mResult - insert.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "com/charles445/rltweaker/hook/HookRealBench", "writeSlots", "(Lnet/minecraft/nbt/NBTTagCompound;Lnet/minecraft/util/NonNullList;)V", false)); - TransformUtil.insertBeforeFirst(m_writeSlots, insert); - } - - if(true) //readSlots - { - InsnList insert = new InsnList(); - insert.add(new VarInsnNode(Opcodes.ALOAD, 1)); //nbt - insert.add(new VarInsnNode(Opcodes.ALOAD, 0)); //nbt, this - insert.add(new FieldInsnNode(Opcodes.GETFIELD, "pw/prok/realbench/WorkbenchTile", "mResult", "Lnet/minecraft/util/NonNullList;")); //nbt, mResult - insert.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "com/charles445/rltweaker/hook/HookRealBench", "readSlots", "(Lnet/minecraft/nbt/NBTTagCompound;Lnet/minecraft/util/NonNullList;)V", false)); - TransformUtil.insertBeforeFirst(m_readSlots, insert); + isRealBenchInstalled = true; } } - }); + anchor = anchor.getNext(); + } + + if(!isRealBenchInstalled) + { + //Cancel Patch + return PatchResult.NO_MUTATION; + } + //RealBench is installed, replace InventoryCraftResult constructor with our own - add(new Patch(this, "net.minecraft.inventory.ContainerWorkbench", ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES) + anchor = first(m_init); + while(anchor != null) { - @Override - public void patch(ClassNode c_ContainerWorkbench) + if(anchor.getOpcode() == Opcodes.NEW) { - MethodNode m_init = findMethodWithDesc(c_ContainerWorkbench, "(Lnet/minecraft/entity/player/InventoryPlayer;Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;)V", ""); - - //First we need to figure out if RealBench is even installed - boolean isRealBenchInstalled = false; - AbstractInsnNode anchor = first(m_init); - while(anchor != null) + TypeInsnNode newAnchor = (TypeInsnNode)anchor; + if(newAnchor.desc.equals("net/minecraft/inventory/InventoryCraftResult")) { - if(anchor.getType() == AbstractInsnNode.METHOD_INSN) - { - if(((MethodInsnNode)anchor).owner.equals("pw/prok/realbench/asm/ASMHooks")) - { - isRealBenchInstalled = true; - } - } - anchor = anchor.getNext(); + newAnchor.desc = "com/charles445/rltweaker/hook/HookRealBench$Result"; } - - if(!isRealBenchInstalled) - { - //Cancel Patch - this.cancelled = true; - return; - } - //RealBench is installed, replace InventoryCraftResult constructor with our own - - anchor = first(m_init); - while(anchor != null) + } + else if(anchor.getOpcode() == Opcodes.INVOKESPECIAL) + { + MethodInsnNode initAnchor = (MethodInsnNode)anchor; + if(initAnchor.name.equals("") && initAnchor.owner.equals("net/minecraft/inventory/InventoryCraftResult")) { - if(anchor.getOpcode() == Opcodes.NEW) - { - TypeInsnNode newAnchor = (TypeInsnNode)anchor; - if(newAnchor.desc.equals("net/minecraft/inventory/InventoryCraftResult")) - { - newAnchor.desc = "com/charles445/rltweaker/hook/HookRealBench$Result"; - } - } - else if(anchor.getOpcode() == Opcodes.INVOKESPECIAL) - { - MethodInsnNode initAnchor = (MethodInsnNode)anchor; - if(initAnchor.name.equals("") && initAnchor.owner.equals("net/minecraft/inventory/InventoryCraftResult")) - { - initAnchor.owner = "com/charles445/rltweaker/hook/HookRealBench$Result"; - initAnchor.desc = "(Lnet/minecraft/inventory/Container;)V"; - //Also push the container to the stack as it needs it - InsnList qq = new InsnList(); - qq.add(new VarInsnNode(Opcodes.ALOAD, 0)); - m_init.instructions.insertBefore(initAnchor, qq); - } - } - - anchor = anchor.getNext(); + initAnchor.owner = "com/charles445/rltweaker/hook/HookRealBench$Result"; + initAnchor.desc = "(Lnet/minecraft/inventory/Container;)V"; + //Also push the container to the stack as it needs it + InsnList qq = new InsnList(); + qq.add(new VarInsnNode(Opcodes.ALOAD, 0)); + m_init.instructions.insertBefore(initAnchor, qq); } - - //announce("CLASS DISPLAYER: ContainerWorkbench"); - //(new ClassDisplayer()).printMethod(m_init); } - }); + + anchor = anchor.getNext(); + } + + //announce("CLASS DISPLAYER: ContainerWorkbench"); + //(new ClassDisplayer()).printMethod(m_init); + return PatchResult.MAXS_FRAMES; } }