aboutsummaryrefslogtreecommitdiff
path: root/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/events
diff options
context:
space:
mode:
authorsyeyoung <cyoung06@naver.com>2022-11-17 19:19:49 +0900
committersyeyoung <cyoung06@naver.com>2022-11-17 19:19:49 +0900
commit7a698d3d7e06684419c1c7a99cd59f41fee99524 (patch)
treecc4596d4176e28741542e677d1243eae44c3b0e6 /loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/events
parentf033c34b2e2a7824df41ea750b66b21685462279 (diff)
downloadSkyblock-Dungeons-Guide-7a698d3d7e06684419c1c7a99cd59f41fee99524.tar.gz
Skyblock-Dungeons-Guide-7a698d3d7e06684419c1c7a99cd59f41fee99524.tar.bz2
Skyblock-Dungeons-Guide-7a698d3d7e06684419c1c7a99cd59f41fee99524.zip
- Transformer to inject custom ASMEventHandler to handle classloader inconsistency issue
- Transformer to transform Event classes, because FML for some reason injects some stuff - Move all event registration into DungeonsGuide.java Signed-off-by: syeyoung <cyoung06@naver.com>
Diffstat (limited to 'loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/events')
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/events/DGAwareEventSubscriptionTransformer.java198
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/events/OwnerAwareASMEventHandler.java149
2 files changed, 347 insertions, 0 deletions
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/events/DGAwareEventSubscriptionTransformer.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/events/DGAwareEventSubscriptionTransformer.java
new file mode 100644
index 00000000..cbe83e9b
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/events/DGAwareEventSubscriptionTransformer.java
@@ -0,0 +1,198 @@
+package kr.syeyoung.dungeonsguide.launcher.events;
+
+import net.minecraft.launchwrapper.IClassTransformer;
+import net.minecraftforge.fml.common.eventhandler.Event;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.tree.*;
+
+import java.util.List;
+
+import static org.objectweb.asm.ClassWriter.COMPUTE_FRAMES;
+import static org.objectweb.asm.Opcodes.*;
+import static org.objectweb.asm.Type.*;
+import static org.objectweb.asm.Type.VOID_TYPE;
+
+public class DGAwareEventSubscriptionTransformer implements IClassTransformer
+{
+ private ClassLoader classLoader;
+ public DGAwareEventSubscriptionTransformer(ClassLoader classLoader)
+ {
+ this.classLoader = classLoader;
+ }
+
+ @Override
+ public byte[] transform(String name, String transformedName, byte[] bytes)
+ {
+ if (bytes == null || name.equals("net.minecraftforge.fml.common.eventhandler.Event") || name.startsWith("net.minecraft.") || name.indexOf('.') == -1)
+ {
+ return bytes;
+ }
+ ClassReader cr = new ClassReader(bytes);
+ ClassNode classNode = new ClassNode();
+ cr.accept(classNode, 0);
+
+ try
+ {
+ if (buildEvents(classNode))
+ {
+ ClassWriter cw = new ClassWriter(COMPUTE_FRAMES);
+ classNode.accept(cw);
+ return cw.toByteArray();
+ }
+ return bytes;
+ }
+ catch (ClassNotFoundException ex)
+ {
+ // Discard silently- it's just noise
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+
+ return bytes;
+ }
+
+ private boolean buildEvents(ClassNode classNode) throws Exception
+ {
+ // Yes, this recursively loads classes until we get this base class. THIS IS NOT A ISSUE. Coremods should handle re-entry just fine.
+ // If they do not this a COREMOD issue NOT a Forge/LaunchWrapper issue.
+ Class<?> parent = classLoader.loadClass(classNode.superName.replace('/', '.'));
+ if (!Event.class.isAssignableFrom(parent))
+ {
+ return false;
+ }
+
+ //Class<?> listenerListClazz = Class.forName("net.minecraftforge.fml.common.eventhandler.ListenerList", false, getClass().getClassLoader());
+ Type tList = Type.getType("Lnet/minecraftforge/fml/common/eventhandler/ListenerList;");
+
+ boolean edited = false;
+ boolean hasSetup = false;
+ boolean hasGetListenerList = false;
+ boolean hasDefaultCtr = false;
+ boolean hasCancelable = false;
+ boolean hasResult = false;
+ String voidDesc = Type.getMethodDescriptor(VOID_TYPE);
+ String boolDesc = Type.getMethodDescriptor(BOOLEAN_TYPE);
+ String listDesc = tList.getDescriptor();
+ String listDescM = Type.getMethodDescriptor(tList);
+
+ for (MethodNode method : (List<MethodNode>)classNode.methods)
+ {
+ if (method.name.equals("setup") && method.desc.equals(voidDesc) && (method.access & ACC_PROTECTED) == ACC_PROTECTED) hasSetup = true;
+ if ((method.access & ACC_PUBLIC) == ACC_PUBLIC)
+ {
+ if (method.name.equals("getListenerList") && method.desc.equals(listDescM)) hasGetListenerList = true;
+ if (method.name.equals("isCancelable") && method.desc.equals(boolDesc)) hasCancelable = true;
+ if (method.name.equals("hasResult") && method.desc.equals(boolDesc)) hasResult = true;
+ }
+ if (method.name.equals("<init>") && method.desc.equals(voidDesc)) hasDefaultCtr = true;
+ }
+
+ if (classNode.visibleAnnotations != null)
+ {
+ for (AnnotationNode node : classNode.visibleAnnotations)
+ {
+ if (!hasResult && node.desc.equals("Lnet/minecraftforge/fml/common/eventhandler/Event$HasResult;"))
+ {
+ /* Add:
+ * public boolean hasResult()
+ * {
+ * return true;
+ * }
+ */
+ MethodNode method = new MethodNode(ACC_PUBLIC, "hasResult", boolDesc, null, null);
+ method.instructions.add(new InsnNode(ICONST_1));
+ method.instructions.add(new InsnNode(IRETURN));
+ classNode.methods.add(method);
+ edited = true;
+ }
+ else if (!hasCancelable && node.desc.equals("Lnet/minecraftforge/fml/common/eventhandler/Cancelable;"))
+ {
+ /* Add:
+ * public boolean isCancelable()
+ * {
+ * return true;
+ * }
+ */
+ MethodNode method = new MethodNode(ACC_PUBLIC, "isCancelable", boolDesc, null, null);
+ method.instructions.add(new InsnNode(ICONST_1));
+ method.instructions.add(new InsnNode(IRETURN));
+ classNode.methods.add(method);
+ edited = true;
+ }
+ }
+ }
+
+ if (hasSetup)
+ {
+ if (!hasGetListenerList)
+ throw new RuntimeException("Event class defines setup() but does not define getListenerList! " + classNode.name);
+ else
+ return edited;
+ }
+
+ Type tSuper = Type.getType(classNode.superName);
+
+ //Add private static ListenerList LISTENER_LIST
+ classNode.fields.add(new FieldNode(ACC_PRIVATE | ACC_STATIC, "LISTENER_LIST", listDesc, null, null));
+
+ /*Add:
+ * public <init>()
+ * {
+ * super();
+ * }
+ */
+ if (!hasDefaultCtr)
+ {
+ MethodNode method = new MethodNode(ACC_PUBLIC, "<init>", voidDesc, null, null);
+ method.instructions.add(new VarInsnNode(ALOAD, 0));
+ method.instructions.add(new MethodInsnNode(INVOKESPECIAL, tSuper.getInternalName(), "<init>", voidDesc, false));
+ method.instructions.add(new InsnNode(RETURN));
+ classNode.methods.add(method);
+ }
+
+ /*Add:
+ * protected void setup()
+ * {
+ * super.setup();
+ * if (LISTENER_LIST != NULL)
+ * {
+ * return;
+ * }
+ * LISTENER_LIST = new ListenerList(super.getListenerList());
+ * }
+ */
+ MethodNode method = new MethodNode(ACC_PROTECTED, "setup", voidDesc, null, null);
+ method.instructions.add(new VarInsnNode(ALOAD, 0));
+ method.instructions.add(new MethodInsnNode(INVOKESPECIAL, tSuper.getInternalName(), "setup", voidDesc, false));
+ method.instructions.add(new FieldInsnNode(GETSTATIC, classNode.name, "LISTENER_LIST", listDesc));
+ LabelNode initLisitener = new LabelNode();
+ method.instructions.add(new JumpInsnNode(IFNULL, initLisitener));
+ method.instructions.add(new InsnNode(RETURN));
+ method.instructions.add(initLisitener);
+ method.instructions.add(new FrameNode(F_SAME, 0, null, 0, null));
+ method.instructions.add(new TypeInsnNode(NEW, tList.getInternalName()));
+ method.instructions.add(new InsnNode(DUP));
+ method.instructions.add(new VarInsnNode(ALOAD, 0));
+ method.instructions.add(new MethodInsnNode(INVOKESPECIAL, tSuper.getInternalName(), "getListenerList", listDescM, false));
+ method.instructions.add(new MethodInsnNode(INVOKESPECIAL, tList.getInternalName(), "<init>", getMethodDescriptor(VOID_TYPE, tList), false));
+ method.instructions.add(new FieldInsnNode(PUTSTATIC, classNode.name, "LISTENER_LIST", listDesc));
+ method.instructions.add(new InsnNode(RETURN));
+ classNode.methods.add(method);
+
+ /*Add:
+ * public ListenerList getListenerList()
+ * {
+ * return this.LISTENER_LIST;
+ * }
+ */
+ method = new MethodNode(ACC_PUBLIC, "getListenerList", listDescM, null, null);
+ method.instructions.add(new FieldInsnNode(GETSTATIC, classNode.name, "LISTENER_LIST", listDesc));
+ method.instructions.add(new InsnNode(ARETURN));
+ classNode.methods.add(method);
+ return true;
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/events/OwnerAwareASMEventHandler.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/events/OwnerAwareASMEventHandler.java
new file mode 100644
index 00000000..3475e672
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/events/OwnerAwareASMEventHandler.java
@@ -0,0 +1,149 @@
+package kr.syeyoung.dungeonsguide.launcher.events;
+
+import static org.objectweb.asm.Opcodes.*;
+
+import java.lang.reflect.Method;
+import java.util.HashMap;
+
+import net.minecraftforge.fml.common.ModContainer;
+
+import net.minecraftforge.fml.common.eventhandler.Event;
+import net.minecraftforge.fml.common.eventhandler.EventPriority;
+import net.minecraftforge.fml.common.eventhandler.IEventListener;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+import org.apache.logging.log4j.ThreadContext;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Type;
+
+import com.google.common.collect.Maps;
+
+
+public class OwnerAwareASMEventHandler implements IEventListener
+{
+ private static int IDs = 0;
+ private static final String HANDLER_DESC = Type.getInternalName(IEventListener.class);
+ private static final String HANDLER_FUNC_DESC = Type.getMethodDescriptor(IEventListener.class.getDeclaredMethods()[0]);
+ private static final HashMap<Method, Class<?>> cache = Maps.newHashMap();
+ private static final boolean GETCONTEXT = Boolean.parseBoolean(System.getProperty("fml.LogContext", "false"));
+
+ private final IEventListener handler;
+ private final SubscribeEvent subInfo;
+ private ModContainer owner;
+ private String readable;
+
+ public OwnerAwareASMEventHandler(Object target, Method method, ModContainer owner) throws Exception
+ {
+ this.owner = owner;
+ handler = (IEventListener)createWrapper(method).getConstructor(Object.class).newInstance(target);
+ subInfo = method.getAnnotation(SubscribeEvent.class);
+ readable = "ASM: " + target + " " + method.getName() + Type.getMethodDescriptor(method);
+ }
+
+ @Override
+ public void invoke(Event event)
+ {
+ if (GETCONTEXT)
+ ThreadContext.put("mod", owner == null ? "" : owner.getName());
+ if (handler != null)
+ {
+ if (!event.isCancelable() || !event.isCanceled() || subInfo.receiveCanceled())
+ {
+ handler.invoke(event);
+ }
+ }
+ if (GETCONTEXT)
+ ThreadContext.remove("mod");
+ }
+
+ public EventPriority getPriority()
+ {
+ return subInfo.priority();
+ }
+
+ public Class<?> createWrapper(Method callback)
+ {
+ if (cache.containsKey(callback))
+ {
+ return cache.get(callback);
+ }
+
+ ClassWriter cw = new ClassWriter(0);
+ MethodVisitor mv;
+
+ String name = getUniqueName(callback);
+ String desc = name.replace('.', '/');
+ String instType = Type.getInternalName(callback.getDeclaringClass());
+ String eventType = Type.getInternalName(callback.getParameterTypes()[0]);
+
+ /*
+ System.out.println("Name: " + name);
+ System.out.println("Desc: " + desc);
+ System.out.println("InstType: " + instType);
+ System.out.println("Callback: " + callback.getName() + Type.getMethodDescriptor(callback));
+ System.out.println("Event: " + eventType);
+ */
+
+ cw.visit(V1_6, ACC_PUBLIC | ACC_SUPER, desc, null, "java/lang/Object", new String[]{ HANDLER_DESC });
+
+ cw.visitSource(".dynamic", null);
+ {
+ cw.visitField(ACC_PUBLIC, "instance", "Ljava/lang/Object;", null, null).visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "(Ljava/lang/Object;)V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitFieldInsn(PUTFIELD, desc, "instance", "Ljava/lang/Object;");
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(2, 2);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "invoke", HANDLER_FUNC_DESC, null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitFieldInsn(GETFIELD, desc, "instance", "Ljava/lang/Object;");
+ mv.visitTypeInsn(CHECKCAST, instType);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitTypeInsn(CHECKCAST, eventType);
+ mv.visitMethodInsn(INVOKEVIRTUAL, instType, callback.getName(), Type.getMethodDescriptor(callback), false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(2, 2);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+ Class<?> ret = new ASMClassLoader(callback.getDeclaringClass().getClassLoader()).define(name, cw.toByteArray());
+ cache.put(callback, ret);
+ return ret;
+ }
+
+ private String getUniqueName(Method callback)
+ {
+ return String.format("%s_%d_%s_%s_%s", getClass().getName(), IDs++,
+ callback.getDeclaringClass().getSimpleName(),
+ callback.getName(),
+ callback.getParameterTypes()[0].getSimpleName());
+ }
+
+ private static class ASMClassLoader extends ClassLoader
+ {
+ private ASMClassLoader(ClassLoader classLoader)
+ {
+ super(classLoader);
+ }
+
+ public Class<?> define(String name, byte[] data)
+ {
+ return defineClass(name, data, 0, data.length);
+ }
+ }
+
+ public String toString()
+ {
+ return readable;
+ }
+}