package de.sanandrew.core.manpack.transformer;
import de.sanandrew.core.manpack.init.ManPackLoadingPlugin;
import de.sanandrew.core.manpack.util.javatuples.Triplet;
import org.apache.logging.log4j.Level;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.tree.*;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.List;
public final class ASMHelper
{
/** A boolean which is true when we are in a development environment **/
public static boolean isMCP = false;
/**
* Creates a byte-array representation of the supplied ClassNode.
*
* @param cnode the ClassNode to be converted into a byte array
* @param cwFlags the flags to be supplied to the ClassWriter. You almost always want both COMPUTE_FRAMES and COMPUTE_MAXS
* @return a byte array representation of the ClassNode to be written back to the ClassLoader
*/
public static byte[] createBytes(ClassNode cnode, int cwFlags) {
ClassWriter cw = new ClassWriter(cwFlags);
cnode.accept(cw);
byte[] bArr = cw.toByteArray();
ManPackLoadingPlugin.MOD_LOG.log(Level.INFO, String.format("Class %s successfully transformed!", cnode.name));
return bArr;
}
/**
* Creates a ClassNode from a byte array to be used for ASM modifications.
*
* @param bytes A byte array representing the class to be modified by ASM
* @return a new ClassNode instance
*/
public static ClassNode createClassNode(byte[] bytes) {
ClassNode cnode = new ClassNode();
ClassReader reader = new ClassReader(bytes);
reader.accept(cnode, ClassReader.EXPAND_FRAMES);
return cnode;
}
/**
* Searches for the instruction set (needle) inside an another instruction set (haystack) and returns the first instruction node from the found needle.
*
* @param haystack The instruction set to be searched in
* @param needle The instruction set to search for
* @return The first instruction node from the haystack on the found position
* @throws de.sanandrew.core.manpack.transformer.ASMHelper.InvalidNeedleException when the needle was not found or was found multiple times
*/
public static AbstractInsnNode findFirstNodeFromNeedle(InsnList haystack, InsnList needle) {
List<AbstractInsnNode> ret = InstructionComparator.insnListFindStart(haystack, needle);
if( ret.size() != 1 ) {
throw new InvalidNeedleException(ret.size());
}
return ret.get(0);
}
/**
* Searches for the instruction set (needle) inside an another instruction set (haystack) and returns the last instruction node from the found needle.
*
* @param haystack The instruction set to be searched in
* @param needle The instruction set to search for
* @return The last instruction node from the haystack on the found position
* @throws de.sanandrew.core.manpack.transformer.ASMHelper.InvalidNeedleException when the needle was not found or was found multiple times
*/
public static AbstractInsnNode findLastNodeFromNeedle(InsnList haystack, InsnList needle) {
List<AbstractInsnNode> ret = InstructionComparator.insnListFindEnd(haystack, needle);
if( ret.size() != 1 ) {
throw new InvalidNeedleException(ret.size());
}
return ret.get(0);
}
/**
* Scans the ClassNode for a method name
*
* @param cn the ClassNode to be searched in
* @param method The method name to search for
* @return true, if the name was found, or else false
*/
public static boolean hasClassMethod(ClassNode cn, String method) {
Triplet<String, String, String[]> methodDesc = ASMNames.getSrgNameMd(method);
for( MethodNode methodNd : cn.methods ) {
if( methodNd.name.equals(methodDesc.getValue1()) && methodNd.desc.equals(methodDesc.getValue2()[0]) ) {
return true;
}
}
return false;
}
/**
* Scans the ClassNode for a method name and descriptor
*
* @param cnode the ClassNode to be searched in
* @param name The method name to search for
* @param desc The method descriptor to search for
* @return true, if the name was found, or else false
* @throws de.sanandrew.core.manpack.transformer.ASMHelper.MethodNotFoundException when the method name and descriptor couldn't be found
*/
private static MethodNode findMethodNode(ClassNode cnode, String name, String desc) {
for( MethodNode mnode : cnode.methods ) {
if( name.equals(mnode.name) && desc.equals(mnode.desc) ) {
return mnode;
}
}
throw new MethodNotFoundException(name, desc);
}
/**
* Gets the appropriate field/method name for either it is called in a development or productive environment
*
* @param mcp The name to be used in a development environment
* @param srg The name to be used in a productive environment
* @return The right name appropriate to the environment
*/
public static String getRemappedMF(String mcp, String srg) {
if( ASMHelper.isMCP ) {
return mcp;
}
return srg;
}
/**
* removes an entire instruction set in the haystack.
*
* @param haystack The instruction set to be searched in
* @param needle The instruction set to search for and to be removed
*/
public static void removeNeedleFromHaystack(InsnList haystack, InsnList needle) {
int firstInd = haystack.indexOf(findFirstNodeFromNeedle(haystack, needle));
int lastInd = haystack.indexOf(findLastNodeFromNeedle(haystack, needle));
List<AbstractInsnNode> realNeedle = new ArrayList<>();
for( int i = firstInd; i <= lastInd; i++ ) {
realNeedle.add(haystack.get(i));
}
for( AbstractInsnNode node : realNeedle ) {
haystack.remove(node);
}
}
/**
* Writes the class bytes into a file. Helpful for debugging ASM transformations.<br>
* Note: this will write the bytes as compiled .class file! Use JD-GUI to look into the class.
*
* @param classBytes The class bytes to be written as a class file
* @param file The filename (inclusive path) the bytes will be saved in
*/
public static void writeClassToFile(byte[] classBytes, String file) {
try( FileOutputStream out = new FileOutputStream(file) ) {
out.write(classBytes);
} catch( Throwable e ) {
e.printStackTrace();
}
}
public static MethodNode getMethodNode(int access, String method) {
Triplet<String, String, String[]> methodDesc = ASMNames.getSrgNameMd(method);
String sig = methodDesc.getValue2().length > 1 ? methodDesc.getValue2()[1] : null;
String throwing[] = methodDesc.getValue2().length > 2 ? methodDesc.getValue2()[2].split(";") : null;
return new MethodNode(access, methodDesc.getValue1(), methodDesc.getValue2()[0], sig, throwing);
}
public static MethodInsnNode getMethodInsnNode(int opcode, String method, boolean intf) {
Triplet<String, String, String[]> methodDesc = ASMNames.getSrgNameMd(method);
return new MethodInsnNode(opcode, methodDesc.getValue0(), methodDesc.getValue1(), methodDesc.getValue2()[0], intf);
}
public static void visitMethodInsn(MethodNode node, int opcode, String method, boolean intf) {
MethodInsnNode mdiNode = getMethodInsnNode(opcode, method, intf);
node.visitMethodInsn(opcode, mdiNode.owner, mdiNode.name, mdiNode.desc, intf);
}
public static MethodNode findMethod(ClassNode clazz, String method) {
Triplet<String, String, String[]> methodDesc = ASMNames.getSrgNameMd(method);
return findMethodNode(clazz, methodDesc.getValue1(), methodDesc.getValue2()[0]);
}
public static FieldInsnNode getFieldInsnNode(int opcode, String field) {
Triplet<String, String, String> fieldDesc = ASMNames.getSrgNameFd(field);
return new FieldInsnNode(opcode, fieldDesc.getValue0(), fieldDesc.getValue1(), fieldDesc.getValue2());
}
public static void visitFieldInsn(MethodNode node, int opcode, String field) {
FieldInsnNode fdiNode = getFieldInsnNode(opcode, field);
node.visitFieldInsn(opcode, fdiNode.owner, fdiNode.name, fdiNode.desc);
}
public static class InvalidNeedleException
extends RuntimeException
{
private static final long serialVersionUID = -913530798954926801L;
public InvalidNeedleException(int count) {
super(count > 1 ? "Multiple Needles found in Haystack!" : count < 1 ? "Needle not found in Haystack!" : "Wait, Needle was found!? o.O");
}
}
public static class MethodNotFoundException
extends RuntimeException
{
private static final long serialVersionUID = 7439846361566319105L;
public MethodNotFoundException(String methodName, String methodDesc) {
super(String.format("Could not find any method matching the name < %s > and description < %s >", methodName, methodDesc));
}
}
}