aboutsummaryrefslogtreecommitdiff
path: root/loader
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
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')
-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
12 files changed, 555 insertions, 25 deletions
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, "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;
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/screen/GuiReferenceLeak.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/screen/GuiUnloadingError.java
index 9c0a5980..dd05dec4 100644
--- a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/screen/GuiReferenceLeak.java
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/screen/GuiUnloadingError.java
@@ -30,9 +30,9 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
-public class GuiReferenceLeak extends SpecialGuiScreen {
+public class GuiUnloadingError extends SpecialGuiScreen {
private final String stacktrace;
- public GuiReferenceLeak(Throwable cause) {
+ public GuiUnloadingError(Throwable cause) {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
PrintStream printStream = new PrintStream(byteArrayOutputStream);
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/tooltip/NotificationManager.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/tooltip/NotificationManager.java
index f3146e7f..661ad93c 100644
--- a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/tooltip/NotificationManager.java
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/tooltip/NotificationManager.java
@@ -6,6 +6,7 @@ import net.minecraft.client.gui.Gui;
import net.minecraft.client.gui.ScaledResolution;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraftforge.client.event.GuiScreenEvent;
+import net.minecraftforge.client.event.RenderGameOverlayEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import org.lwjgl.input.Mouse;
@@ -28,6 +29,64 @@ public class NotificationManager {
tooltipList.remove(uid);
}
+
+ @SubscribeEvent
+ public void onRender(RenderGameOverlayEvent.Post postRender) {
+ ScaledResolution sr = new ScaledResolution(Minecraft.getMinecraft());
+ FontRenderer fr = Minecraft.getMinecraft().fontRendererObj;
+ int widthX = fr.getStringWidth("X");
+
+ GlStateManager.pushMatrix();
+ GlStateManager.translate(sr.getScaledWidth() - 5, sr.getScaledHeight() -5, 0);
+
+ int currY = sr.getScaledHeight() - 5;
+
+ for (Notification tooltip : tooltipList.values()) {
+ int width, height;
+ String[] description = tooltip.getDescription().split("\n");
+ width =
+ Math.max(
+ fr.getStringWidth(tooltip.getTitle()),
+ Arrays.stream(description).map(fr::getStringWidth).max(Integer::compareTo).orElse(300)
+ ) + 10;
+ height = description.length * fr.FONT_HEIGHT + 15 + fr.FONT_HEIGHT;
+
+ GlStateManager.translate(0, -height, 0);
+ currY -= height;
+
+ GlStateManager.pushMatrix();
+ GlStateManager.translate(-width, 0, 0);
+ Gui.drawRect(0, 0,width,height, 0xFF23272a);
+ Gui.drawRect(1, 1, width-1, height-1, 0XFF2c2f33);
+
+ if (!tooltip.isUnremovable()) {
+ fr.drawString("X", width - widthX - 2, 2, 0xFFFF0000);
+ }
+
+ GlStateManager.translate(5,5,0);
+ fr.drawString(tooltip.getTitle(), 0,0, tooltip.getTitleColor());
+ GlStateManager.translate(0, fr.FONT_HEIGHT + 5, 0);
+ int y = 0;
+ for (String line : description) {
+ fr.drawString(line, 0, y, 0xFFAAAAAA);
+ y += fr.FONT_HEIGHT;
+ }
+ GlStateManager.popMatrix();
+
+ tooltip.setBoundRect(new Rectangle(
+ sr.getScaledWidth() - width - 5,
+ currY,
+ width,
+ height
+ ));
+
+ currY -= 5;
+ GlStateManager.translate(0, -5, 0);
+ }
+
+ GlStateManager.popMatrix();
+
+ }
@SubscribeEvent
public void onGuiPostRender(GuiScreenEvent.DrawScreenEvent.Post rendered) {
ScaledResolution sr = new ScaledResolution(Minecraft.getMinecraft());
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/DGClassLoader.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/DGClassLoader.java
index 1fbc39b0..a2f1a784 100644
--- a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/DGClassLoader.java
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/DGClassLoader.java
@@ -1,12 +1,14 @@
package kr.syeyoung.dungeonsguide.launcher.loader;
-import sun.misc.Resource;
+import kr.syeyoung.dungeonsguide.launcher.events.DGAwareEventSubscriptionTransformer;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
public abstract class DGClassLoader extends ClassLoader implements ByteStreamURLHandler.InputStreamGenerator{
+
+ DGAwareEventSubscriptionTransformer eventSubscriptionTransformer = new DGAwareEventSubscriptionTransformer(this);
public DGClassLoader(ClassLoader parent) {
super(parent);
}
@@ -53,6 +55,7 @@ public abstract class DGClassLoader extends ClassLoader implements ByteStreamURL
throw new ClassNotFoundException(name, e);
}
if (res != null) {
+ res = eventSubscriptionTransformer.transform(name, name, res);
return defineClass(name, res, 0, res.length);
} else {
throw new ClassNotFoundException(name);
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/JarLoader.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/JarLoader.java
index dbb86dee..39fcbf58 100644
--- a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/JarLoader.java
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/JarLoader.java
@@ -66,7 +66,7 @@ public class JarLoader implements IDGLoader {
@Override
public InputStream convert(String name) { // / separated
if (this.loadedResources.containsKey(name.substring(1)))
- return new ByteArrayInputStream(this.loadedResources.get(name.substring(1)));
+ return new ByteArrayInputStream(this.loadedResources.get(name));
return null;
}
}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/LocalLoader.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/LocalLoader.java
index a47b2aaf..ec3e59fb 100644
--- a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/LocalLoader.java
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/LocalLoader.java
@@ -23,6 +23,7 @@ import kr.syeyoung.dungeonsguide.launcher.exceptions.DungeonsGuideLoadingExcepti
import kr.syeyoung.dungeonsguide.launcher.exceptions.ReferenceLeakedException;
import org.apache.commons.io.IOUtils;
+import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -47,14 +48,16 @@ public class LocalLoader implements IDGLoader {
}
@Override
public byte[] getClassBytes(String name) throws IOException { // . separated.
- InputStream in = convert("/"+name.replace(".", "/")+".class");
+ if (name.startsWith("kr.syeyoung.dungeonsguide.launcher")) return null;
+ InputStream in = convert(name.replace(".", "/")+".class");
+ if (!(in instanceof BufferedInputStream)) return null;
if (in == null) return null;
return IOUtils.toByteArray(in);
}
@Override
public InputStream convert(String name) { // / separated
- return LocalLoader.class.getResourceAsStream(name);
+ return LocalLoader.class.getResourceAsStream("/"+name);
}
}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/RemoteLoader.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/RemoteLoader.java
index 446958a0..ceafb6f7 100644
--- a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/RemoteLoader.java
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/RemoteLoader.java
@@ -73,7 +73,7 @@ public class RemoteLoader implements IDGLoader {
@Override
public InputStream convert(String name) { // / separated
if (this.loadedResources.containsKey(name.substring(1)))
- return new ByteArrayInputStream(this.loadedResources.get(name.substring(1)));
+ return new ByteArrayInputStream(this.loadedResources.get(name));
return null;
}
}