package coloredlightscore.src.asm.transformer.core;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.*;
import java.io.IOException;
public abstract class HelperMethodTransformer extends MethodTransformer {
protected String className;
protected String classNameDeobfuscated;
/**
* When set, this class will check for the existance of the proper target function.
* This is useful during development to help narrow down programming errors that would
* otherwise crash the game. Set to false on release.
*/
public boolean checkForHelperFunction = true;
public HelperMethodTransformer(String className) {
this.className = className;
this.classNameDeobfuscated = NameMapper.getInstance().getClassName(className).replace('/', '.');
checkForHelperFunction = !NameMapper.getInstance().isObfuscated();
}
protected abstract Class<?> getHelperClass();
@Override
protected boolean transforms(String className) {
return className.equals(this.classNameDeobfuscated) || className.equals(this.className);
}
/**
* Replaces the entire target method with a single call to the helper method. This is the more
* invasive method of transformation, and should only be used when addReturnMethod is not an
* option!
* <p/>
* Method Descriptor Details: For TargetMethod(args...), HelperMethod must be HelperMethod(targetClass, args...), and MUST MATCH IN RETURN TYPE.
* targetClass is the instance of the targetClass that targetMethod is running under.
*
* @param clazz Reference of the class to transform
* @param targetMethod Reference of the method to transform
* @param helperMethod The name of the method in the helper class to invoke. See description for Descriptor details.
* @return
* @author heaton84
*/
protected final boolean redefineMethod(ClassNode clazz, MethodNode targetMethod, String helperMethod) {
Type targetMethodReturnType = Type.getReturnType(targetMethod.desc);
InsnList staticInvoke = generateHelperInvoke(clazz, targetMethod, helperMethod, false);
int returnOpcode = targetMethodReturnType.getOpcode(Opcodes.IRETURN);
staticInvoke.add(new InsnNode(returnOpcode));
// Turns out this bit is important, otherwise we could step on the existing LVT
targetMethod.localVariables.clear();
targetMethod.instructions.clear();
targetMethod.instructions.add(staticInvoke);
return true;
}
/**
* Replaces the return statement of the target method with an invokation to the helper method.
* This is the preferred method of transformation, but is very simplistic.
* <p/>
* Method Descriptor Details: For TargetMethod(args...), HelperMethod must be HelperMethod(retVar, targetClass, args...), and MUST MATCH IN RETURN TYPE.
* retVar will be the value that *would* have been returned by the target method.
* targetClass is the instance of the targetClass that targetMethod is running under.
*
* @param clazz
* @param targetMethod
* @param helperMethod The name of the method in the helper class to invoke. Refer to method documentation for proper Descriptor!
* @return
* @author heaton84
*/
protected final boolean addReturnMethod(ClassNode clazz, MethodNode targetMethod, String helperMethod) {
// NOTE new helper sig: helper(ret, ref, args) for target(args)
InsnList staticInvoke = generateHelperInvoke(clazz, targetMethod, helperMethod, true);
// NOTE: This logic assumes that there is only ONE return instruction in the method!
targetMethod.instructions.insertBefore(ASMUtils.findLastReturn(targetMethod), staticInvoke);
return true;
}
protected InsnList generateHelperInvoke(ClassNode targetClass, MethodNode targetMethod, String helperMethod, boolean includeReturnVarAsParam) {
InsnList staticInvoke = new InsnList();
Type targetClassType = Type.getType(targetClass.name);
Type helperClassType = Type.getType(getHelperClass());
Type[] args = Type.getArgumentTypes(targetMethod.desc);
int argIndex;
String helperMethodDescriptor;
if (helperMethod.indexOf(' ') > -1)
helperMethod = helperMethod.substring(0, helperMethod.indexOf(' '));
// Prepare helperMethodDescriptor
// Refer to add...Method documentation for proper method Descriptors of helper methods (or just read the comments below)
if (includeReturnVarAsParam) {
// Descriptor should be:
// Param 0 <Type targetMethodReturnType> - What target method was about to return
// Param 1 <Type targetClass> - The instance of the target class
// Param 2..n - All parameters that were passed to targetMethod
helperMethodDescriptor = String.format("(L%s;L%s;%s",
Type.getReturnType(targetMethod.desc).getInternalName(),
targetClassType.getInternalName(),
targetMethod.desc.substring(1)); // Omit leading '(' as we just redefined it
} else {
// Descriptor should be:
// Param 0 <Type targetClass> - The instance of the target class
// Param 1..n - All parameters that were passed to targetMethod
helperMethodDescriptor = String.format("(L%s;%s",
targetClassType.getInternalName(),
targetMethod.desc.substring(1)); // Omit leading '(' as we just redefined it
}
if (checkForHelperFunction) {
// Debugging trap to assert that the helper function exists with
// the correct method descriptor
try {
ASMUtils.assertClassContainsHelperMethod(getHelperClass().getName(), helperMethod, helperMethodDescriptor);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// Push reference to target class onto stack : aload_0 [this]
staticInvoke.add(new VarInsnNode(Opcodes.ALOAD, 0));
// Push all calling arguments from target method onto stack
for (argIndex = 0; argIndex < args.length; argIndex++) {
int LoadOpCode = args[argIndex].getOpcode(Opcodes.ILOAD);
staticInvoke.add(new VarInsnNode(LoadOpCode, argIndex + 1));
}
// Now prepare a call to a static method (did I mention the helper method should be static?)
MethodInsnNode invokestatic = new MethodInsnNode(Opcodes.INVOKESTATIC,
helperClassType.getInternalName(), // target class of invoke
helperMethod, // target method of invoke
helperMethodDescriptor); // target Descriptor of invoke
staticInvoke.add(invokestatic);
return staticInvoke;
}
}