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 | |
| 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>
23 files changed, 610 insertions, 220 deletions
diff --git a/build.gradle b/build.gradle deleted file mode 100644 index 7252969e..00000000 --- a/build.gradle +++ /dev/null @@ -1,147 +0,0 @@ - -/* - * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod - * Copyright (C) 2021 cyoung06 - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - */ - -plugins { - id "idea" - id "java" - id "com.github.johnrengelman.shadow" version "7.1.2" - id "dev.architectury.architectury-pack200" version "0.1.3" - id "gg.essential.loom" version "0.10.0.+" -} - -tasks.wrapper { - gradleVersion = "7.4" - // You can either download the binary-only version of Gradle (BIN) or - // the full version (with sources and documentation) of Gradle (ALL) - distributionType = Wrapper.DistributionType.ALL -} -version = "3.8.0" -group = "kr.syeyoung.dungeonsguide" -archivesBaseName = "dungeonsguide" - -java { - toolchain.languageVersion.set(JavaLanguageVersion.of(8)) -} - -loom { - launchConfigs { - client { - } - } - runs { - "client" { - property('devauth.enabled','true') - client() - } - } - forge { - pack200Provider.set(new dev.architectury.pack200.java.Pack200Adapter()) - } -} - - -sourceSets.main { - output.setResourcesDir(file("$buildDir/classes/java/main")) -} - - -repositories { - mavenCentral() - maven { url "https://jitpack.io" } - maven {url "https://pkgs.dev.azure.com/djtheredstoner/DevAuth/_packaging/public/maven/v1"} -} - -configurations { - implementation.extendsFrom shadowImpl -} - - -dependencies { - minecraft("com.mojang:minecraft:1.8.9") - mappings("de.oceanlabs.mcp:mcp_stable:22-1.8.9") - forge("net.minecraftforge:forge:1.8.9-11.15.1.2318-1.8.9") - - - implementation 'org.jetbrains:annotations-java5:23.0.0' - implementation 'org.java-websocket:Java-WebSocket:1.5.3' - implementation "org.json:json:20220924" - implementation 'com.twelvemonkeys.imageio:imageio-bmp:3.8.3' - - compileOnly 'org.projectlombok:lombok:1.18.24' - annotationProcessor 'org.projectlombok:lombok:1.18.24' - - compileOnly files("jars/Hychat-1.12.1-BETA.jar") - - testCompileOnly "org.projectlombok:lombok:1.18.24" - testAnnotationProcessor "org.projectlombok:lombok:1.18.24" - modRuntimeOnly("me.djtheredstoner:DevAuth-forge-legacy:1.1.0") -} - -tasks.withType(JavaCompile) { - options.encoding = "UTF-8" -} - -tasks.withType(Jar) { - archivesBaseName = "dungeonsguide" - manifest { - attributes["FMLCorePluginContainsFMLMod"] = "true" - attributes["ForceLoadAsMod"] = "true" - - // If you don't want mixins, remove these lines -// this["TweakClass"] = "org.spongepowered.asm.launch.MixinTweaker" -// this["MixinConfigs"] = "mixins.examplemod.json" - } -} - - -tasks.shadowJar { - - archiveFileName = jar.archiveFileName - - relocate "org.java_websocket", "kr.syeyoung.org.java_websocket" - - dependencies { - include(dependency("org.java-websocket:Java-WebSocket:1.5.3")) - include(dependency("org.slf4j:slf4j-api:1.7.25")) - include(dependency("org.json:json:20220924")) - include(dependency("com.twelvemonkeys..*:.*")) - } -} - -tasks.named("remapJar") { - archiveClassifier = "all" - from(tasks.shadowJar) - input = tasks.shadowJar.archiveFile -} - - -tasks.assemble.dependsOn tasks.remapJar - -processResources { - inputs.property 'version', project.version - - // replace stuff in mcmod.info, nothing else - from(sourceSets.main.resources.srcDirs) { - duplicatesStrategy = 'include' - include 'mcmod.info' - - // replace version and mcversion - expand 'version': project.version - } -}
\ No newline at end of file diff --git a/loader/build.gradle b/loader/build.gradle index 14bd8e7b..921d9d41 100644 --- a/loader/build.gradle +++ b/loader/build.gradle @@ -21,7 +21,7 @@ loom { // probably will have to my own mixin tweaker, due to dungeonsguide's weird dynamic loading stuff // property("mixin.debug", "true") // property("asmhelper.verbose", "true") -// arg("--tweakClass", "org.spongepowered.asm.launch.MixinTweaker") + arg("--tweakClass", "kr.syeyoung.dungeonsguide.launcher.coremod.DGTweaker") // arg("--mixin", "mixins.examplemod.json") } @@ -87,6 +87,7 @@ tasks.withType(Jar) { manifest { attributes["FMLCorePluginContainsFMLMod"] = "true" attributes["ForceLoadAsMod"] = "true" + attributes["TweakClass"] = "kr.syeyoung.dugneonsguide.launcher.coremod.DGTweaker" // If you don't want mixins, remove these lines // this["TweakClass"] = "org.spongepowered.asm.launch.MixinTweaker" diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/Main.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/Main.java index 782c3648..1678c58f 100755 --- a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/Main.java +++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/Main.java @@ -24,17 +24,16 @@ import kr.syeyoung.dungeonsguide.launcher.exceptions.DungeonsGuideLoadingExcepti import kr.syeyoung.dungeonsguide.launcher.exceptions.NoSuitableLoaderFoundException; import kr.syeyoung.dungeonsguide.launcher.exceptions.NoVersionFoundException; import kr.syeyoung.dungeonsguide.launcher.exceptions.ReferenceLeakedException; -import kr.syeyoung.dungeonsguide.launcher.exceptions.auth.PrivacyPolicyRequiredException; import kr.syeyoung.dungeonsguide.launcher.gui.screen.GuiDisplayer; import kr.syeyoung.dungeonsguide.launcher.gui.screen.GuiLoadingError; -import kr.syeyoung.dungeonsguide.launcher.gui.screen.GuiPrivacyPolicy; -import kr.syeyoung.dungeonsguide.launcher.gui.screen.GuiReferenceLeak; +import kr.syeyoung.dungeonsguide.launcher.gui.screen.GuiUnloadingError; import kr.syeyoung.dungeonsguide.launcher.gui.tooltip.Notification; import kr.syeyoung.dungeonsguide.launcher.gui.tooltip.NotificationManager; import kr.syeyoung.dungeonsguide.launcher.loader.IDGLoader; import kr.syeyoung.dungeonsguide.launcher.loader.JarLoader; import kr.syeyoung.dungeonsguide.launcher.loader.LocalLoader; import kr.syeyoung.dungeonsguide.launcher.loader.RemoteLoader; +import lombok.Getter; import net.minecraft.client.Minecraft; import net.minecraft.client.resources.IReloadableResourceManager; import net.minecraftforge.common.MinecraftForge; @@ -44,8 +43,9 @@ import net.minecraftforge.fml.common.Mod.EventHandler; import net.minecraftforge.fml.common.ProgressManager; import net.minecraftforge.fml.common.event.FMLInitializationEvent; import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; -import javax.swing.*; import java.io.*; import java.util.*; @@ -75,9 +75,11 @@ public class Main listeners.remove(dungeonsGuideReloadListener); } + + @Getter private IDGLoader currentLoader; - private UUID dgUnloaded = UUID.randomUUID(); + private static final UUID dgUnloaded = UUID.randomUUID(); @EventHandler public void initEvent(FMLInitializationEvent initializationEvent) throws ClassNotFoundException, InstantiationException, IllegalAccessException { @@ -121,7 +123,14 @@ public class Main GuiDisplayer.INSTANCE.displayGui(new GuiLoadingError(e)); } catch (DungeonsGuideLoadingException e) { e.printStackTrace(); + + try { + unload(); + } catch (Exception e2) { + GuiDisplayer.INSTANCE.displayGui(new GuiUnloadingError(e2)); + } GuiDisplayer.INSTANCE.displayGui(new GuiLoadingError(e)); + } } @@ -147,7 +156,7 @@ public class Main File f = new File(configDir, "loader.cfg"); Configuration configuration = new Configuration(f); IDGLoader idgLoader = obtainLoader(configuration); - load(idgLoader); + reload(idgLoader); } catch (NoSuitableLoaderFoundException e) { e.printStackTrace(); GuiDisplayer.INSTANCE.displayGui(new GuiLoadingError(e)); @@ -156,7 +165,14 @@ public class Main GuiDisplayer.INSTANCE.displayGui(new GuiLoadingError(e)); } catch (DungeonsGuideLoadingException e) { e.printStackTrace(); + try { + unload(); + } catch (Exception e2) { + GuiDisplayer.INSTANCE.displayGui(new GuiUnloadingError(e2)); + } GuiDisplayer.INSTANCE.displayGui(new GuiLoadingError(e)); + } catch (ReferenceLeakedException e) { + GuiDisplayer.INSTANCE.displayGui(new GuiUnloadingError(e)); } }) .unremovable(true) @@ -166,9 +182,11 @@ public class Main if (dgInterface != null) throw new IllegalStateException("DG is loaded"); dgInterface = newLoader.loadDungeonsGuide(); currentLoader = newLoader; - - dgInterface.init(configDir); - + try { + dgInterface.init(configDir); + } catch (Exception e) { + throw new DungeonsGuideLoadingException("Exception occured while calling init", e); + } for (DungeonsGuideReloadListener listener : listeners) { listener.onLoad(dgInterface); } @@ -183,21 +201,42 @@ public class Main NotificationManager.INSTANCE.removeNotification(dgUnloaded); } - public void reload(IDGLoader newLoader) { + + private volatile IDGLoader reqLoader = null; + public void reloadWithoutStacktraceReference(IDGLoader newLoader) { + reqLoader = newLoader; + } + @SubscribeEvent + public void onTick(TickEvent.ClientTickEvent tickEvent) { + if (reqLoader != null) { + IDGLoader loader = reqLoader; + reqLoader = null; + + try { + reload(loader); + } catch (DungeonsGuideLoadingException e) { + try { + unload(); + } catch (Exception e2) { + GuiDisplayer.INSTANCE.displayGui(new GuiUnloadingError(e2)); + } + GuiDisplayer.INSTANCE.displayGui(new GuiLoadingError(e)); + } catch (ReferenceLeakedException e) { + GuiDisplayer.INSTANCE.displayGui(new GuiUnloadingError(e)); + } + } + } + + public void reload(IDGLoader newLoader) throws DungeonsGuideLoadingException, ReferenceLeakedException { try { unload(); load(newLoader); - } catch (DungeonsGuideLoadingException | ReferenceLeakedException e) { + } catch (DungeonsGuideLoadingException | ReferenceLeakedException | UnsupportedOperationException e) { dgInterface = null; currentLoader = null; e.printStackTrace(); - - if (e instanceof DungeonsGuideLoadingException) { - GuiDisplayer.INSTANCE.displayGui(new GuiLoadingError(e)); - } else { - GuiDisplayer.INSTANCE.displayGui(new GuiReferenceLeak(e)); - } + throw e; } } diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/coremod/DGTweaker.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/coremod/DGTweaker.java new file mode 100644 index 00000000..cd12e47c --- /dev/null +++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/coremod/DGTweaker.java @@ -0,0 +1,29 @@ +package kr.syeyoung.dungeonsguide.launcher.coremod; + +import net.minecraft.launchwrapper.ITweaker; +import net.minecraft.launchwrapper.LaunchClassLoader; + +import java.io.File; +import java.util.List; + +public class DGTweaker implements ITweaker { + @Override + public void acceptOptions(List<String> args, File gameDir, File assetsDir, String profile) { + + } + + @Override + public void injectIntoClassLoader(LaunchClassLoader classLoader) { + classLoader.registerTransformer(EventBusTransformer.class.getName()); + } + + @Override + public String getLaunchTarget() { + return null; + } + + @Override + public String[] getLaunchArguments() { + return new String[0]; + } +} diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/coremod/EventBusTransformer.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/coremod/EventBusTransformer.java new file mode 100644 index 00000000..bcfa0cef --- /dev/null +++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/coremod/EventBusTransformer.java @@ -0,0 +1,49 @@ +package kr.syeyoung.dungeonsguide.launcher.coremod; + +import net.minecraft.launchwrapper.IClassTransformer; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.tree.*; + +import java.util.Iterator; + +public class EventBusTransformer implements IClassTransformer { + + @Override + public byte[] transform(String name, String transformedName, byte[] basicClass) { + if (name.equals("net.minecraftforge.fml.common.eventhandler.EventBus")) { + + String targetMethodName = "register"; + + ClassNode classNode = new ClassNode(); + ClassReader classReader = new ClassReader(basicClass); + classReader.accept(classNode, 0); + + Iterator<MethodNode> methods = classNode.methods.iterator(); + while(methods.hasNext()) + { + MethodNode m = methods.next(); + if ((m.name.equals(targetMethodName) && m.desc.equals("(Ljava/lang/Class;Ljava/lang/Object;Ljava/lang/reflect/Method;Lnet/minecraftforge/fml/common/ModContainer;)V"))) + { + AbstractInsnNode curr = m.instructions.getFirst(); + while ((curr = curr.getNext()) != m.instructions.getLast()) { + if (curr instanceof TypeInsnNode && ((TypeInsnNode) curr).desc.equals("net/minecraftforge/fml/common/eventhandler/ASMEventHandler")) { + ((TypeInsnNode) curr).desc = "kr/syeyoung/dungeonsguide/launcher/events/OwnerAwareASMEventHandler"; + } + if (curr instanceof MethodInsnNode && ((MethodInsnNode) curr).owner.equals("net/minecraftforge/fml/common/eventhandler/ASMEventHandler")) { + ((MethodInsnNode) curr).owner = "kr/syeyoung/dungeonsguide/launcher/events/OwnerAwareASMEventHandler"; + } + } + + break; + } + } + + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); + classNode.accept(writer); + return writer.toByteArray(); + } + + return basicClass; + } +} 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 |
