Squake/java/squeek/quakemovement/ASMPlugin.java
2017-07-14 20:24:38 -07:00

235 lines
8.5 KiB
Java

package squeek.quakemovement;
import net.minecraft.launchwrapper.IClassTransformer;
import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.*;
import java.util.Map;
@IFMLLoadingPlugin.MCVersion("1.12")
public class ASMPlugin implements IFMLLoadingPlugin, IClassTransformer
{
public static boolean isObfuscated = false;
private static String CLASS_ENTITY_PLAYER = "net.minecraft.entity.player.EntityPlayer";
private static String CLASS_ENTITY = "net.minecraft.entity.Entity";
private static String CLASS_QUAKE_CLIENT_PLAYER = "squeek.quakemovement.QuakeClientPlayer";
private static String CLASS_QUAKE_SERVER_PLAYER = "squeek.quakemovement.QuakeServerPlayer";
@Override
public byte[] transform(String name, String transformedName, byte[] bytes)
{
if (transformedName.equals(CLASS_ENTITY_PLAYER))
{
ClassNode classNode = readClassFromBytes(bytes);
MethodNode method;
method = findMethodNodeOfClass(classNode, isObfuscated ? "a" : "travel", "(FFF)V");
if (method == null)
throw new RuntimeException("could not find EntityPlayer.travel");
InsnList loadParameters = new InsnList();
loadParameters.add(new VarInsnNode(Opcodes.ALOAD, 0));
loadParameters.add(new VarInsnNode(Opcodes.FLOAD, 1));
loadParameters.add(new VarInsnNode(Opcodes.FLOAD, 2));
loadParameters.add(new VarInsnNode(Opcodes.FLOAD, 3));
injectStandardHook(method, findFirstInstruction(method), CLASS_QUAKE_CLIENT_PLAYER, "moveEntityWithHeading", toMethodDescriptor("Z", CLASS_ENTITY_PLAYER, "F", "F", "F"), loadParameters);
method = findMethodNodeOfClass(classNode, isObfuscated ? "n" : "onLivingUpdate", "()V");
if (method == null)
throw new RuntimeException("could not find EntityPlayer.onLivingUpdate");
loadParameters.clear();
loadParameters.add(new VarInsnNode(Opcodes.ALOAD, 0));
injectSimpleHook(method, findFirstInstruction(method), CLASS_QUAKE_CLIENT_PLAYER, "beforeOnLivingUpdate", toMethodDescriptor("V", CLASS_ENTITY_PLAYER), loadParameters);
method = findMethodNodeOfClass(classNode, isObfuscated ? "cm" : "jump", "()V");
if (method == null)
throw new RuntimeException("could not find EntityPlayer.jump");
loadParameters.clear();
loadParameters.add(new VarInsnNode(Opcodes.ALOAD, 0));
injectSimpleHook(method, findLastInstructionWithOpcode(method, Opcodes.RETURN), CLASS_QUAKE_CLIENT_PLAYER, "afterJump", toMethodDescriptor("V", CLASS_ENTITY_PLAYER), loadParameters);
method = findMethodNodeOfClass(classNode, isObfuscated ? "e" : "fall", "(FF)V");
if (method == null)
throw new RuntimeException("could not find EntityPlayer.fall");
loadParameters.clear();
loadParameters.add(new VarInsnNode(Opcodes.ALOAD, 0));
loadParameters.add(new VarInsnNode(Opcodes.FLOAD, 1));
loadParameters.add(new VarInsnNode(Opcodes.FLOAD, 1));
injectSimpleHook(method, findFirstInstruction(method), CLASS_QUAKE_SERVER_PLAYER, "beforeFall", toMethodDescriptor("V", CLASS_ENTITY_PLAYER, "F", "F"), loadParameters);
loadParameters.clear();
loadParameters.add(new VarInsnNode(Opcodes.ALOAD, 0));
loadParameters.add(new VarInsnNode(Opcodes.FLOAD, 1));
loadParameters.add(new VarInsnNode(Opcodes.FLOAD, 1));
injectSimpleHook(method, findLastInstructionWithOpcode(method, Opcodes.RETURN), CLASS_QUAKE_SERVER_PLAYER, "afterFall", toMethodDescriptor("V", CLASS_ENTITY_PLAYER, "F", "F"), loadParameters);
return writeClassToBytes(classNode);
}
else if (transformedName.equals(CLASS_ENTITY))
{
ClassNode classNode = readClassFromBytes(bytes);
MethodNode method;
method = findMethodNodeOfClass(classNode, isObfuscated ? "b" : "moveRelative", "(FFFF)V");
if (method == null)
throw new RuntimeException("could not find Entity.moveRelative");
InsnList loadParameters = new InsnList();
loadParameters.add(new VarInsnNode(Opcodes.ALOAD, 0));
loadParameters.add(new VarInsnNode(Opcodes.FLOAD, 1));
loadParameters.add(new VarInsnNode(Opcodes.FLOAD, 2));
loadParameters.add(new VarInsnNode(Opcodes.FLOAD, 3));
loadParameters.add(new VarInsnNode(Opcodes.FLOAD, 4));
injectStandardHook(method, findFirstInstruction(method), CLASS_QUAKE_CLIENT_PLAYER, "moveRelativeBase", toMethodDescriptor("Z", CLASS_ENTITY, "F", "F", "F", "F"), loadParameters);
return writeClassToBytes(classNode);
}
return bytes;
}
private void injectStandardHook(MethodNode method, AbstractInsnNode node, String hookClassName, String hookMethodName, String hookMethodDescriptor, InsnList loadParameters)
{
InsnList toInject = new InsnList();
LabelNode ifNotCanceled = new LabelNode();
toInject.add(loadParameters);
toInject.add(new MethodInsnNode(Opcodes.INVOKESTATIC, toInternalClassName(hookClassName), hookMethodName, hookMethodDescriptor, false));
toInject.add(new JumpInsnNode(Opcodes.IFEQ, ifNotCanceled));
toInject.add(new InsnNode(Opcodes.RETURN));
toInject.add(ifNotCanceled);
method.instructions.insertBefore(node, toInject);
}
private void injectSimpleHook(MethodNode method, AbstractInsnNode node, String hookClassName, String hookMethodName, String hookMethodDescriptor, InsnList loadParameters)
{
InsnList toInject = new InsnList();
toInject.add(loadParameters);
toInject.add(new MethodInsnNode(Opcodes.INVOKESTATIC, toInternalClassName(hookClassName), hookMethodName, hookMethodDescriptor, false));
method.instructions.insertBefore(node, toInject);
}
@Override
public String[] getASMTransformerClass()
{
return new String[]{this.getClass().getName()};
}
@Override
public String getModContainerClass()
{
return null;
}
@Override
public String getSetupClass()
{
return null;
}
@Override
public void injectData(Map<String, Object> data)
{
isObfuscated = (Boolean) data.get("runtimeDeobfuscationEnabled");
}
@Override
public String getAccessTransformerClass()
{
return null;
}
private ClassNode readClassFromBytes(byte[] bytes)
{
ClassNode classNode = new ClassNode();
ClassReader classReader = new ClassReader(bytes);
classReader.accept(classNode, 0);
return classNode;
}
private byte[] writeClassToBytes(ClassNode classNode)
{
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
classNode.accept(writer);
return writer.toByteArray();
}
private MethodNode findMethodNodeOfClass(ClassNode classNode, String methodName, String methodDesc)
{
for (MethodNode method : classNode.methods)
{
if (method.name.equals(methodName) && method.desc.equals(methodDesc))
{
return method;
}
}
return null;
}
private static String toInternalClassName(String className)
{
return className.replace('.', '/');
}
private static boolean isDescriptor(String descriptor)
{
return descriptor.length() == 1 || (descriptor.startsWith("L") && descriptor.endsWith(";"));
}
private static String toDescriptor(String className)
{
return isDescriptor(className) ? className : "L" + toInternalClassName(className) + ";";
}
private static String toMethodDescriptor(String returnType, String... paramTypes)
{
StringBuilder paramDescriptors = new StringBuilder();
for (String paramType : paramTypes)
paramDescriptors.append(toDescriptor(paramType));
return "(" + paramDescriptors.toString() + ")" + toDescriptor(returnType);
}
private static boolean isLabelOrLineNumber(AbstractInsnNode insn)
{
return insn.getType() == AbstractInsnNode.LABEL || insn.getType() == AbstractInsnNode.LINE;
}
private static AbstractInsnNode getOrFindInstruction(AbstractInsnNode firstInsnToCheck, boolean reverseDirection)
{
for (AbstractInsnNode instruction = firstInsnToCheck; instruction != null; instruction = reverseDirection ? instruction.getPrevious() : instruction.getNext())
{
if (!isLabelOrLineNumber(instruction))
return instruction;
}
return null;
}
private static AbstractInsnNode findFirstInstruction(MethodNode method)
{
return getOrFindInstruction(method.instructions.getFirst(), false);
}
public static AbstractInsnNode getOrFindInstructionWithOpcode(AbstractInsnNode firstInsnToCheck, int opcode, boolean reverseDirection)
{
for (AbstractInsnNode instruction = firstInsnToCheck; instruction != null; instruction = reverseDirection ? instruction.getPrevious() : instruction.getNext())
{
if (instruction.getOpcode() == opcode)
return instruction;
}
return null;
}
public static AbstractInsnNode findLastInstructionWithOpcode(MethodNode method, int opcode)
{
return getOrFindInstructionWithOpcode(method.instructions.getLast(), opcode, true);
}
}