diff options
author | syeyoung <cyoung06@naver.com> | 2022-11-17 19:19:49 +0900 |
---|---|---|
committer | syeyoung <cyoung06@naver.com> | 2022-11-17 19:19:49 +0900 |
commit | 7a698d3d7e06684419c1c7a99cd59f41fee99524 (patch) | |
tree | cc4596d4176e28741542e677d1243eae44c3b0e6 /loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/events | |
parent | f033c34b2e2a7824df41ea750b66b21685462279 (diff) | |
download | Skyblock-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')
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; + } +} |