diff --git a/asm-api/build.gradle b/asm-api/build.gradle new file mode 100644 index 0000000000..78b4c99d0f --- /dev/null +++ b/asm-api/build.gradle @@ -0,0 +1,13 @@ +dependencies { + /* OW2 ASM v5.0.3 */ + compileOnly('org.ow2.asm:asm-debug-all:5.0.3') + + /* Google Guava v17.0 */ + compileOnly('com.google.guava:guava:17.0') + + /* Apache Commons Lang v3 */ + compileOnly('org.apache.commons:commons-lang3:3.3.2') + + /* Apache Log4J v2.0-beta9 */ + compileOnly('org.apache.logging.log4j:log4j-api:2.0-beta9') +} diff --git a/asm-api/src/main/java/codes/biscuit/skyblockaddons/asm/api/Hook.java b/asm-api/src/main/java/codes/biscuit/skyblockaddons/asm/api/Hook.java new file mode 100644 index 0000000000..27e0b1649b --- /dev/null +++ b/asm-api/src/main/java/codes/biscuit/skyblockaddons/asm/api/Hook.java @@ -0,0 +1,9 @@ +package codes.biscuit.skyblockaddons.asm.api; + +/** + * Listener to triggered actions by triggers that were injected using {@link Transformer} + * + * @author iHDeveloper + */ +public interface Hook { +} diff --git a/asm-api/src/main/java/codes/biscuit/skyblockaddons/asm/api/Transformer.java b/asm-api/src/main/java/codes/biscuit/skyblockaddons/asm/api/Transformer.java new file mode 100644 index 0000000000..1cd61887fb --- /dev/null +++ b/asm-api/src/main/java/codes/biscuit/skyblockaddons/asm/api/Transformer.java @@ -0,0 +1,48 @@ +package codes.biscuit.skyblockaddons.asm.api; + +import codes.biscuit.skyblockaddons.asm.api.helper.TransformClassHelper; +import org.objectweb.asm.tree.ClassNode; + +/** + * Transforming the targeted classes in order to change how the class behave. + * Or, injecting hooks triggers. + * + * @author iHDeveloper + */ +public abstract class Transformer { + + /** + * Puts a single class helper into an array + * + * @param classHelper The class helper to put into an array + * @return An array containing single class helper + */ + protected TransformClassHelper[] single(TransformClassHelper classHelper) { + return new TransformClassHelper[] { classHelper }; + } + + /** + * Puts multiple class helpers into an array + * @param classHelpers The class helpers to put into an array + * @return An array containing multiple class helpers + */ + protected TransformClassHelper[] multiple(TransformClassHelper... classHelpers) { + return classHelpers; + } + + /** + * Get the targeted class helpers + * + * @return An array of class helpers + */ + public abstract TransformClassHelper[] targets(); + + /** + * Transform a class node with its helper + * + * @param engine The engine which is performing the transformation process + * @param targetClassHelper The class helper of the target to transform + * @param node The class node of the target + */ + public abstract void transform(TransformerEngine engine, TransformClassHelper targetClassHelper, ClassNode node); +} diff --git a/asm-api/src/main/java/codes/biscuit/skyblockaddons/asm/api/TransformerEngine.java b/asm-api/src/main/java/codes/biscuit/skyblockaddons/asm/api/TransformerEngine.java new file mode 100644 index 0000000000..ba1323b972 --- /dev/null +++ b/asm-api/src/main/java/codes/biscuit/skyblockaddons/asm/api/TransformerEngine.java @@ -0,0 +1,97 @@ +package codes.biscuit.skyblockaddons.asm.api; + +import codes.biscuit.skyblockaddons.asm.api.helper.TransformClassHelper; +import codes.biscuit.skyblockaddons.asm.api.helper.normal.NormalTransformClassHelper; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Multimap; +import org.apache.commons.lang3.mutable.MutableInt; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.tree.ClassNode; + +/** + * Manage the transformers and transform any class node. + * + * @author iHDeveloper + */ +public abstract class TransformerEngine { + + private Logger logger = LogManager.getLogger("SBA: ASM Transformers"); + private Multimap transformers = ArrayListMultimap.create(); + private MutableInt writeFlags = new MutableInt(); + + /** + * Create an engine with transformers built-in + * + * @param transformers The transformers to register into the engine + */ + public TransformerEngine(Transformer[] transformers) { + for (Transformer transformer : transformers) { + register(transformer); + } + } + + /** + * Check if the class name is a target for transformation + * + * @param name The class name + */ + public boolean exists(String name) { + return transformers.containsKey(name); + } + + /** + * Transform the class node through the transformers + * + * @param node The class to transform + */ + public void transform(String name, ClassNode node) { + TransformClassHelper classHelper = new NormalTransformClassHelper(name); + + writeFlags.setValue(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); + + for (Transformer transformer : transformers.get(name)) { + transformer.transform(this, classHelper, node); + } + } + + /** + * Get the engine logger + * + * @return The logger of the engine + */ + public Logger getLogger() { + return logger; + } + + /** + * Set the write flags of the current class for the writer + * + * @param flags The flags for the class writer see {@link ClassWriter} + */ + public void setWriteFlags(int flags) { + writeFlags.setValue(flags); + } + + /** + * Get the current write flags + * + * @return The write flags as integer + */ + public int getWriteFlags() { + return writeFlags.getValue(); + } + + /** + * Register a transformer with its targets + * + * @param transformer The transformer to register + */ + protected void register(Transformer transformer) { + for (TransformClassHelper targetClassHelper : transformer.targets()) { + transformers.put(targetClassHelper.getTransformerName(), transformer); + } + } + +} diff --git a/asm-api/src/main/java/codes/biscuit/skyblockaddons/asm/api/helper/HookResult.java b/asm-api/src/main/java/codes/biscuit/skyblockaddons/asm/api/helper/HookResult.java new file mode 100644 index 0000000000..191facc512 --- /dev/null +++ b/asm-api/src/main/java/codes/biscuit/skyblockaddons/asm/api/helper/HookResult.java @@ -0,0 +1,50 @@ +package codes.biscuit.skyblockaddons.asm.api.helper; + +/** + * A helper for getting result from hook to include in return or another function. + * + * @param The type of the result we are getting from the hook + * + * @author iHDeveloper + */ +public class HookResult { + + private boolean empty = false; + private V value = null; + + /** + * Check if the result is empty or not. Or, have the result been set before or not. + * + * @return Is the result empty or not + */ + public boolean is() { + return empty; + } + + /** + * Set the result to be nothing but it's not empty! + */ + public void set() { + set(null); + } + + /** + * Set the result to certain value. And, it's not empty anymore. + * + * @param value The value to include in the result + */ + public void set(V value) { + this.value = value; + this.empty = true; + } + + /** + * Get the value from the result + * + * @return The value that's has been set by the hook + */ + public V get() { + return value; + } + +} diff --git a/asm-api/src/main/java/codes/biscuit/skyblockaddons/asm/api/helper/TransformClassHelper.java b/asm-api/src/main/java/codes/biscuit/skyblockaddons/asm/api/helper/TransformClassHelper.java new file mode 100644 index 0000000000..a3b8f5ee44 --- /dev/null +++ b/asm-api/src/main/java/codes/biscuit/skyblockaddons/asm/api/helper/TransformClassHelper.java @@ -0,0 +1,35 @@ +package codes.biscuit.skyblockaddons.asm.api.helper; + +import lombok.Getter; + +/** + * An identifier for a class to transform + * + * @author iHDeveloper + */ +public abstract class TransformClassHelper { + + /** + * @return The name used for the owner of a field or method, or a field type. + */ + public final String getNameAsOwner() { + return "L" + getName() + ";"; + } + + /** + * @return The name used to identify this class + */ + public String getTransformerName() { + // The regex matches single slash or multiple and replace them with one dot + return getName().replaceAll("(\\/)+", "."); + } + + public boolean equals(TransformClassHelper classHelper) { + return getTransformerName().equals(classHelper.getTransformerName()); + } + + /** + * @return The raw name of the class + */ + public abstract String getName(); +} diff --git a/asm-api/src/main/java/codes/biscuit/skyblockaddons/asm/api/helper/TransformFieldHelper.java b/asm-api/src/main/java/codes/biscuit/skyblockaddons/asm/api/helper/TransformFieldHelper.java new file mode 100644 index 0000000000..fb9aee53f1 --- /dev/null +++ b/asm-api/src/main/java/codes/biscuit/skyblockaddons/asm/api/helper/TransformFieldHelper.java @@ -0,0 +1,77 @@ +package codes.biscuit.skyblockaddons.asm.api.helper; + +import lombok.Getter; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.FieldInsnNode; + +/** + * An identifier for a field to transform + * + * @author iHDeveloper + */ +@Getter +public class TransformFieldHelper { + + private final TransformClassHelper owner; + private final String name; + private String type; + + /** + * Create a field helper to help the transformation process + * + * @param owner The owner of the field + * @param name The name of the field + * @param type A non-class field type {@link TransformFieldTypes} + */ + public TransformFieldHelper(TransformClassHelper owner, String name, char type) { + this(owner, name, ""); + this.type = "" + type; + } + + /** + * Create a field helper to help the transformation process + * + * @param owner The owner of the field + * @param name The name of the field + * @param type A class helper if the field type is class + */ + public TransformFieldHelper(TransformClassHelper owner, String name, TransformClassHelper type) { + this(owner, name, type.getNameAsOwner()); + } + + /** + * Create a field helper to help the transformation process + * + * @param owner The owner of the field + * @param name The name of the field + * @param type The raw name of the class + */ + public TransformFieldHelper(TransformClassHelper owner, String name, String type) { + this.owner = owner; + this.name = name; + this.type = "L" + type + ";"; + } + + /** + * Generate the get field instruction of the field + * + * @return An instruction node of the get field instruction + */ + public FieldInsnNode createGetInstruction() { + return new FieldInsnNode(Opcodes.GETFIELD, owner.getNameAsOwner(), name, type); + } + + /** + * Generate the put instruction of the field + * + * @return An instruction node of the put field instruction + */ + public FieldInsnNode createPutInstruction() { + return new FieldInsnNode(Opcodes.PUTFIELD, owner.getNameAsOwner(), name, type); + } + + public boolean equals(FieldInsnNode insnNode) { + return name.equals(insnNode.name) && type.equals(insnNode.desc); + } + +} diff --git a/asm-api/src/main/java/codes/biscuit/skyblockaddons/asm/api/helper/TransformFieldTypes.java b/asm-api/src/main/java/codes/biscuit/skyblockaddons/asm/api/helper/TransformFieldTypes.java new file mode 100644 index 0000000000..f9cb4125f9 --- /dev/null +++ b/asm-api/src/main/java/codes/biscuit/skyblockaddons/asm/api/helper/TransformFieldTypes.java @@ -0,0 +1,12 @@ +package codes.biscuit.skyblockaddons.asm.api.helper; + +public final class TransformFieldTypes { + public static final char BYTE = 'B'; + public static final char CHAR = 'C'; + public static final char DOUBLE = 'D'; + public static final char FLOAT = 'F'; + public static final char INT = 'I'; + public static final char LONG = 'j'; + public static final char SHORT = 'S'; + public static final char BOOLEAN = 'Z'; +} diff --git a/asm-api/src/main/java/codes/biscuit/skyblockaddons/asm/api/helper/TransformMethodHelper.java b/asm-api/src/main/java/codes/biscuit/skyblockaddons/asm/api/helper/TransformMethodHelper.java new file mode 100644 index 0000000000..cf5324e077 --- /dev/null +++ b/asm-api/src/main/java/codes/biscuit/skyblockaddons/asm/api/helper/TransformMethodHelper.java @@ -0,0 +1,125 @@ +package codes.biscuit.skyblockaddons.asm.api.helper; + +import lombok.Getter; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; + +/** + * An identifier for a method to transform + * + * @author iHDeveloper + */ +@Getter +public class TransformMethodHelper { + + /** + * Represents the constructor of the class + */ + public static final TransformMethodHelper INIT = new TransformMethodHelper("", ""); + + private final String name; + private String type; + private final String[] parameters; + private final boolean ioException; + + /** + * Create a method with name and void as return type + * + * @param name The name of the method + */ + public TransformMethodHelper(String name) { + this(name, null); + } + + /** + * Create a method with name and non-class return type + * + * @param name The name of the method + * @param type A non-class field type {@link TransformFieldTypes} + */ + public TransformMethodHelper(String name, char type) { + this(name, ""); + this.type = "" + type; + } + + /** + * Create a method with name and class as return type + * + * @param name The name of the method + * @param type A class as return type + */ + public TransformMethodHelper(String name, String type) { + this(name, type, new String[0]); + } + + /** + * Create a method with name, class as return type and parameters + * + * @param name The name of the method + * @param type A class as return type + * @param parameters The parameters of the method + */ + public TransformMethodHelper(String name, String type, String[] parameters) { + this(name, type, parameters, false); + } + + /** + * Create a method with name, class as return type and parameters + * + * @param name The name of the method + * @param type A class as return type + * @param parameters The parameters of the method + * @param ioException Does the method throw an IO exception + */ + public TransformMethodHelper(String name, String type, String[] parameters, boolean ioException) { + this.name = name; + this.type = type == null ? null : "L" + type + ";"; + this.parameters = parameters; + this.ioException = ioException; + } + + /** + * Generate a assembly method node of the method + * + * @return The generated node of the method + */ + public final MethodNode createMethodNode() { + return new MethodNode(Opcodes.ACC_PUBLIC, name, getDescriptor(), null, getExceptions()); + } + + public boolean equals(MethodInsnNode insnNode) { + return name.equals(insnNode.name) && getDescriptor().equals(insnNode.desc); + } + + public boolean equals(MethodNode node) { + return name.equals(node.name) && (getDescriptor().equals(node.desc) || this == INIT); + } + + /** + * Build a descriptor from the method parameters and result + * + * @return Method Descriptor + */ + public String getDescriptor() { + StringBuilder builder = new StringBuilder(); + builder.append("("); + for (String param : parameters) { + builder.append(param); + } + builder.append(")"); + if (type == null) builder.append("V"); + else builder.append(type); + return builder.toString(); + } + + /** + * Exceptions thrown by the method + * + * @return A list of exceptions + */ + public String[] getExceptions() { + if (ioException) return new String[0]; + return new String[] { "java/io/IOException" }; + } +} diff --git a/asm-api/src/main/java/codes/biscuit/skyblockaddons/asm/api/helper/normal/NormalTransformClassHelper.java b/asm-api/src/main/java/codes/biscuit/skyblockaddons/asm/api/helper/normal/NormalTransformClassHelper.java new file mode 100644 index 0000000000..853e056f62 --- /dev/null +++ b/asm-api/src/main/java/codes/biscuit/skyblockaddons/asm/api/helper/normal/NormalTransformClassHelper.java @@ -0,0 +1,28 @@ +package codes.biscuit.skyblockaddons.asm.api.helper.normal; + +import codes.biscuit.skyblockaddons.asm.api.helper.TransformClassHelper; + +/** + * A normal helper for transform class + * + * @author iHDeveloper + */ +public class NormalTransformClassHelper extends TransformClassHelper { + + private String name; + + /** + * Create a helper for transform class with name + * + * @param name The name of the class + */ + public NormalTransformClassHelper(String name) { + this.name = name; + } + + @Override + public String getName() { + return name; + } + +} diff --git a/build.gradle b/build.gradle index 4899eea92e..9efa5f7dff 100644 --- a/build.gradle +++ b/build.gradle @@ -10,20 +10,33 @@ group = 'codes.biscuit' // The below line is for version checkers <= 1.4.2 //version = "1.5.2" -// Java plugin settings -sourceCompatibility = JavaVersion.VERSION_1_8 -targetCompatibility = JavaVersion.VERSION_1_8 -compileJava.options.encoding = 'UTF-8' - -repositories { - mavenCentral() - maven { - name 'JitPack' - url 'https://jitpack.io' +allprojects { + apply plugin: 'java' + apply plugin: 'io.franzbecker.gradle-lombok' + + // Java plugin settings + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + compileJava.options.encoding = 'UTF-8' + + repositories { + mavenCentral() + maven { + name 'JitPack' + url 'https://jitpack.io' + } + } +} + +sourceSets { + main { + output.resourcesDir = java.outputDir } } dependencies { + // Assembly Transformer API + implementation(project('asm-api')) // Discord RPC for Java https://github.com/jagrosh/DiscordIPC implementation('com.github.jagrosh:DiscordIPC:e29d6d8') { exclude module: 'log4j' @@ -31,12 +44,6 @@ dependencies { testImplementation('org.junit.jupiter:junit-jupiter:5.6.2') } -sourceSets { - main { - output.resourcesDir = java.outputDir - } -} - minecraft { version = "${project.minecraftVersion}-${project.forgeVersion}" runDir = "run" diff --git a/settings.gradle b/settings.gradle index c3736c11b4..881036cb1a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -13,4 +13,7 @@ pluginManagement { } } } -} \ No newline at end of file +} + +// Assembly Transformer API +include 'asm-api'