aboutsummaryrefslogtreecommitdiff
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
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>
-rw-r--r--build.gradle147
-rw-r--r--loader/build.gradle3
-rwxr-xr-xloader/src/main/java/kr/syeyoung/dungeonsguide/launcher/Main.java73
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/coremod/DGTweaker.java29
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/coremod/EventBusTransformer.java49
-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
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/screen/GuiUnloadingError.java (renamed from loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/screen/GuiReferenceLeak.java)4
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/tooltip/NotificationManager.java59
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/DGClassLoader.java5
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/JarLoader.java2
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/LocalLoader.java7
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/RemoteLoader.java2
-rwxr-xr-xmod/src/main/java/kr/syeyoung/dungeonsguide/mod/DungeonsGuide.java53
-rw-r--r--mod/src/main/java/kr/syeyoung/dungeonsguide/mod/chat/ChatTransmitter.java5
-rw-r--r--mod/src/main/java/kr/syeyoung/dungeonsguide/mod/commands/CommandDungeonsGuide.java24
-rw-r--r--mod/src/main/java/kr/syeyoung/dungeonsguide/mod/dungeon/DungeonFacade.java4
-rw-r--r--mod/src/main/java/kr/syeyoung/dungeonsguide/mod/features/impl/advanced/FeatureTestPepole.java1
-rw-r--r--mod/src/main/java/kr/syeyoung/dungeonsguide/mod/features/impl/etc/FeatureEpicCountdown.java8
-rw-r--r--mod/src/main/java/kr/syeyoung/dungeonsguide/mod/stomp/StompClient.java2
-rw-r--r--mod/src/main/java/kr/syeyoung/dungeonsguide/mod/stomp/StompManager.java2
-rw-r--r--mod/src/main/java/kr/syeyoung/dungeonsguide/mod/utils/BlockCache.java3
-rw-r--r--mod/src/main/java/kr/syeyoung/dungeonsguide/mod/utils/TitleRender.java1
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