package openmods.movement; import openmods.Log; import openmods.api.IResultListener; import openmods.asm.MappedType; import openmods.asm.MethodMatcher; import openmods.asm.StopTransforming; import org.apache.commons.lang3.ArrayUtils; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.commons.Method; public class MovementPatcher extends ClassVisitor { private static final String MANAGER_CLASS = Type.getInternalName(PlayerMovementManager.class); private final Method callbackMethod; private final MethodMatcher injectedMethodMatcher; private final MethodMatcher calledMethodMatcher; private final IResultListener listener; public MovementPatcher(String obfClassName, ClassVisitor cv, IResultListener listener) { super(Opcodes.ASM5, cv); this.listener = listener; MappedType movementInput = MappedType.of("net/minecraft/util/MovementInput"); MappedType entityPlayer = MappedType.of("net/minecraft/entity/player/EntityPlayer"); calledMethodMatcher = new MethodMatcher(movementInput, "()V", "updatePlayerMoveState", "func_78898_a"); injectedMethodMatcher = new MethodMatcher(obfClassName, "()V", "onLivingUpdate", "func_70636_d"); callbackMethod = new Method("updateMovementState", Type.VOID_TYPE, ArrayUtils.toArray(movementInput.type(), entityPlayer.type())); } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { MethodVisitor parent = super.visitMethod(access, name, desc, signature, exceptions); return injectedMethodMatcher.match(name, desc)? new CallInjector(parent) : parent; } private class CallInjector extends MethodVisitor { public CallInjector(MethodVisitor mv) { super(Opcodes.ASM5, mv); } @Override public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean intf) { boolean patch = opcode == Opcodes.INVOKEVIRTUAL && calledMethodMatcher.match(name, desc); if (patch) { if (PlayerMovementManager.callbackInjected) { Log.warn("Method code mismatch, aborting"); PlayerMovementManager.callbackInjected = false; listener.onFailure(); throw new StopTransforming(); } visitInsn(Opcodes.DUP); // duplicate movement handler } super.visitMethodInsn(opcode, owner, name, desc, intf); if (patch) { // movement handler still on stack visitVarInsn(Opcodes.ALOAD, 0); // load this visitMethodInsn(Opcodes.INVOKESTATIC, MANAGER_CLASS, callbackMethod.getName(), callbackMethod.getDescriptor(), false); Log.debug("Callback inserted. Using new movement handler."); listener.onSuccess(); PlayerMovementManager.callbackInjected = true; } } } }