diff --git a/.gitignore b/.gitignore index d0485ae..9a33f88 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* +.idea/ +.iml diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..9182bc3 --- /dev/null +++ b/pom.xml @@ -0,0 +1,104 @@ + + + 4.0.0 + + eu.mikroskeem + bytecodehackery + 1.0-SNAPSHOT + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.5.1 + + 1.8 + 1.8 + + + + + org.apache.maven.plugins + maven-shade-plugin + 2.4.3 + + + package + + shade + + + + + + + *:* + + META-INF/LICENSE + META-INF/DEPENDENCIES + META-INF/NOTICE + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + META-INF/*.properties + META-INF/maven/** + META-INF/services/** + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + true + eu.mikroskeem.redpowder.leviathan.Main + + + + + + + + src/main/resources + true + + + clean compile package + + + + + org.ow2.asm + asm-all + 5.1 + + + \ No newline at end of file diff --git a/src/main/java/eu/mikroskeem/bytecodehackery/GeneratedClassLoader.java b/src/main/java/eu/mikroskeem/bytecodehackery/GeneratedClassLoader.java new file mode 100644 index 0000000..889d59d --- /dev/null +++ b/src/main/java/eu/mikroskeem/bytecodehackery/GeneratedClassLoader.java @@ -0,0 +1,63 @@ +package eu.mikroskeem.bytecodehackery; + +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +public class GeneratedClassLoader extends ClassLoader { + + /* + * Note: "Stolen: from Techcable's Event4j, + * https://github.com/Techcable/Event4J/blob/master/src/main/java/net/techcable/event4j/asm/ASMEventExecutorFactory.java + */ + + private static final ConcurrentMap loaders = new ConcurrentHashMap<>(); + public static GeneratedClassLoader getLoader(ClassLoader parent) { + return loaders.computeIfAbsent(Objects.requireNonNull(parent, "Null parent class-loader"), GeneratedClassLoader::new); + } + + public Class defineClass(String name, byte[] data) { + name = Objects.requireNonNull(name, "Null name").replace('/', '.'); + synchronized (getClassLoadingLock(name)) { + if (hasClass(name)) throw new IllegalStateException(name + " already defined"); + Class c = this.define(name, Objects.requireNonNull(data, "Null data")); + if (!c.getName().equals(name)) throw new IllegalArgumentException("class name " + c.getName() + " != requested name " + name); + return c; + } + } + + protected GeneratedClassLoader(ClassLoader parent) { + super(parent); + } + + private Class define(String name, byte[] data) { + synchronized (getClassLoadingLock(name)) { + if (hasClass(name)) throw new IllegalStateException("Already has class: " + name); + Class c; + try { + c = defineClass(name, data, 0, data.length); + } catch (ClassFormatError e) { + throw new IllegalArgumentException("Illegal class data", e); + } + resolveClass(c); + return c; + } + } + + @Override + public Object getClassLoadingLock(String name) { + return super.getClassLoadingLock(name); + } + + public boolean hasClass(String name) { + synchronized (getClassLoadingLock(name)) { + try { + Class.forName(name); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } + } + +} diff --git a/src/main/java/eu/mikroskeem/bytecodehackery/Main.java b/src/main/java/eu/mikroskeem/bytecodehackery/Main.java new file mode 100644 index 0000000..3e120af --- /dev/null +++ b/src/main/java/eu/mikroskeem/bytecodehackery/Main.java @@ -0,0 +1,77 @@ +package eu.mikroskeem.bytecodehackery; + +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Type; + +import static org.objectweb.asm.Opcodes.*; + +public class Main { + private Main() throws Exception { + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); + + /* Generate class */ + cw.visit(V1_8, ACC_PUBLIC, (getClass().getPackage().getName() +".kek.Topkek").replaceAll("\\.", "/"), + null, "java/lang/Object", new String[]{Type.getInternalName(TestInterface.class)}); + + /* Generate constructor */ + MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null); + mv.visitVarInsn(ALOAD, 0); + + /* Must-have for all classes */ + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false); + mv.visitInsn(RETURN); + mv.visitMaxs(1, 1); + mv.visitEnd(); + + /* Generate toString method */ + String toStringDesc = String.format("(%s)%s", "", Type.getDescriptor(String.class)); + mv = cw.visitMethod(ACC_PUBLIC, "toString", toStringDesc, null, null); + mv.visitLdcInsn("TOPKEK LMAUUU"); + mv.visitInsn(ARETURN); + mv.visitMaxs(1, 1); + mv.visitEnd(); + + /* Generate a() method */ + String aDesc = String.format("(%s)%s", "", Type.getDescriptor(String.class)); + mv = cw.visitMethod(ACC_PUBLIC, "a", aDesc, null, null); + mv.visitLdcInsn("some a() call"); + mv.visitInsn(ARETURN); + mv.visitMaxs(1, 1); + mv.visitEnd(); + + + mv = cw.visitMethod(ACC_PUBLIC, "b", "()Ljava/util/List;", "()Ljava/util/List;", null); + mv.visitCode(); + mv.visitTypeInsn(NEW, "java/util/ArrayList"); + mv.visitInsn(DUP); + mv.visitMethodInsn(INVOKESPECIAL, "java/util/ArrayList", "", "()V", false); + mv.visitVarInsn(ASTORE, 1); + mv.visitVarInsn(ALOAD, 1); + mv.visitLdcInsn("kek"); + mv.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "add", "(Ljava/lang/Object;)Z", true); + mv.visitVarInsn(ALOAD, 1); + mv.visitLdcInsn("ayy lmao"); + mv.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "add", "(Ljava/lang/Object;)Z", true); + mv.visitInsn(POP); + mv.visitVarInsn(ALOAD, 1); + mv.visitInsn(ARETURN); + mv.visitMaxs(2, 2); + mv.visitEnd(); + + /* Class end */ + cw.visitEnd(); + + /* Load that class */ + Class a = GeneratedClassLoader.getLoader(this.getClass().getClassLoader()) + .defineClass(getClass().getPackage().getName() +".kek.Topkek", cw.toByteArray()); + + TestInterface b = (TestInterface) a.newInstance(); + System.out.println(b.toString()); + System.out.println(b.a()); + System.out.println(b.b()); + } + public static void main(String... a) throws Exception { + new Main(); + } +} diff --git a/src/main/java/eu/mikroskeem/bytecodehackery/TestClass.java b/src/main/java/eu/mikroskeem/bytecodehackery/TestClass.java new file mode 100644 index 0000000..f98c45c --- /dev/null +++ b/src/main/java/eu/mikroskeem/bytecodehackery/TestClass.java @@ -0,0 +1,18 @@ +package eu.mikroskeem.bytecodehackery; + +import java.util.ArrayList; +import java.util.List; + +public class TestClass { + @Override + public String toString() { + return "TestClass lel"; + } + + public List get(){ + List r = new ArrayList<>(); + r.add("kek"); + r.add("kek2"); + return r; + } +} diff --git a/src/main/java/eu/mikroskeem/bytecodehackery/TestInterface.java b/src/main/java/eu/mikroskeem/bytecodehackery/TestInterface.java new file mode 100644 index 0000000..43ad943 --- /dev/null +++ b/src/main/java/eu/mikroskeem/bytecodehackery/TestInterface.java @@ -0,0 +1,8 @@ +package eu.mikroskeem.bytecodehackery; + +import java.util.List; + +public interface TestInterface { + String a(); + List b(); +}