From 7a698d3d7e06684419c1c7a99cd59f41fee99524 Mon Sep 17 00:00:00 2001 From: syeyoung Date: Thu, 17 Nov 2022 19:19:49 +0900 Subject: - 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 --- .../kr/syeyoung/dungeonsguide/launcher/Main.java | 73 ++++++-- .../dungeonsguide/launcher/coremod/DGTweaker.java | 29 +++ .../launcher/coremod/EventBusTransformer.java | 49 +++++ .../DGAwareEventSubscriptionTransformer.java | 198 +++++++++++++++++++++ .../launcher/events/OwnerAwareASMEventHandler.java | 149 ++++++++++++++++ .../launcher/gui/screen/GuiReferenceLeak.java | 90 ---------- .../launcher/gui/screen/GuiUnloadingError.java | 90 ++++++++++ .../launcher/gui/tooltip/NotificationManager.java | 59 ++++++ .../launcher/loader/DGClassLoader.java | 5 +- .../dungeonsguide/launcher/loader/JarLoader.java | 2 +- .../dungeonsguide/launcher/loader/LocalLoader.java | 7 +- .../launcher/loader/RemoteLoader.java | 2 +- 12 files changed, 641 insertions(+), 112 deletions(-) create mode 100644 loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/coremod/DGTweaker.java create mode 100644 loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/coremod/EventBusTransformer.java create mode 100644 loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/events/DGAwareEventSubscriptionTransformer.java create mode 100644 loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/events/OwnerAwareASMEventHandler.java delete mode 100644 loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/screen/GuiReferenceLeak.java create mode 100644 loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/screen/GuiUnloadingError.java (limited to 'loader/src/main/java/kr/syeyoung') 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 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 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)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("") && 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 () + * { + * super(); + * } + */ + if (!hasDefaultCtr) + { + MethodNode method = new MethodNode(ACC_PUBLIC, "", voidDesc, null, null); + method.instructions.add(new VarInsnNode(ALOAD, 0)); + method.instructions.add(new MethodInsnNode(INVOKESPECIAL, tSuper.getInternalName(), "", 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(), "", 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> 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, "", "(Ljava/lang/Object;)V", null, null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()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/GuiReferenceLeak.java deleted file mode 100644 index 9c0a5980..00000000 --- a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/screen/GuiReferenceLeak.java +++ /dev/null @@ -1,90 +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 . - */ - -package kr.syeyoung.dungeonsguide.launcher.gui.screen; - -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.FontRenderer; -import net.minecraft.client.gui.Gui; -import net.minecraft.client.gui.GuiButton; -import net.minecraft.client.gui.ScaledResolution; -import net.minecraftforge.fml.common.FMLCommonHandler; -import org.lwjgl.opengl.GL11; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; - -public class GuiReferenceLeak extends SpecialGuiScreen { - private final String stacktrace; - public GuiReferenceLeak(Throwable cause) { - - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - PrintStream printStream = new PrintStream(byteArrayOutputStream); - cause.printStackTrace(printStream); - this.stacktrace = byteArrayOutputStream.toString(); - } - - @Override - public void initGui() { - ScaledResolution sr = new ScaledResolution(Minecraft.getMinecraft()); - this.buttonList.add(new GuiButton(0, sr.getScaledWidth()/2-100,sr.getScaledHeight()-70 ,"Close Minecraft")); - this.buttonList.add(new GuiButton(1, sr.getScaledWidth()/2-100,sr.getScaledHeight()-40 ,"Play With DG in Inconsistent State")); - } - - @Override - protected void actionPerformed(GuiButton button) throws IOException { - super.actionPerformed(button); - if (button.id == 0) { - FMLCommonHandler.instance().exitJava(-1,true); - } else if (button.id == 1) { - dismiss(); - } - } - - @Override - public void drawScreen(int mouseX, int mouseY, float partialTicks) { - super.drawBackground(1); - - ScaledResolution sr = new ScaledResolution(Minecraft.getMinecraft()); - FontRenderer fontRenderer = Minecraft.getMinecraft().fontRendererObj; - fontRenderer.drawString("DungeonsGuide has ran into error while reloading", (sr.getScaledWidth()-fontRenderer.getStringWidth("DungeonsGuide has ran into error while reloading"))/2,40,0xFFFF0000); - fontRenderer.drawString("Please contact DungeonsGuide support with this screen", (sr.getScaledWidth()-fontRenderer.getStringWidth("Please contact developer with this screen"))/2, (int) (40+fontRenderer.FONT_HEIGHT*1.5),0xFFFF0000); - fontRenderer.drawString("Playing in this state is VERY UNSUGGESTED. Undesired behaviors might occur.", (sr.getScaledWidth()-fontRenderer.getStringWidth("Playing in this state is VERY UNSUGGESTED. Undesired behaviors might occur."))/2, (int) (40+fontRenderer.FONT_HEIGHT*3),0xFFFF0000); - - int tenth = sr.getScaledWidth() / 10; - - Gui.drawRect(tenth, 90,sr.getScaledWidth()-tenth, sr.getScaledHeight()-80, 0xFF5B5B5B); - String[] split = stacktrace.split("\n"); - clip(sr, tenth, 90,sr.getScaledWidth()-2*tenth, sr.getScaledHeight()-150); - GL11.glEnable(GL11.GL_SCISSOR_TEST); - for (int i = 0; i < split.length; i++) { - fontRenderer.drawString(split[i].replace("\t", " "), tenth+2,i*fontRenderer.FONT_HEIGHT + 92, 0xFFFFFFFF); - } - GL11.glDisable(GL11.GL_SCISSOR_TEST); - - super.drawScreen(mouseX, mouseY, partialTicks); - } - - public static void clip(ScaledResolution resolution, int x, int y, int width, int height) { - if (width < 0 || height < 0) return; - - int scale = resolution.getScaleFactor(); - GL11.glScissor((x ) * scale, Minecraft.getMinecraft().displayHeight - (y + height) * scale, (width) * scale, height * scale); - } -} diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/screen/GuiUnloadingError.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/screen/GuiUnloadingError.java new file mode 100644 index 00000000..dd05dec4 --- /dev/null +++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/screen/GuiUnloadingError.java @@ -0,0 +1,90 @@ +/* + * 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 . + */ + +package kr.syeyoung.dungeonsguide.launcher.gui.screen; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.Gui; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.gui.ScaledResolution; +import net.minecraftforge.fml.common.FMLCommonHandler; +import org.lwjgl.opengl.GL11; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; + +public class GuiUnloadingError extends SpecialGuiScreen { + private final String stacktrace; + public GuiUnloadingError(Throwable cause) { + + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + PrintStream printStream = new PrintStream(byteArrayOutputStream); + cause.printStackTrace(printStream); + this.stacktrace = byteArrayOutputStream.toString(); + } + + @Override + public void initGui() { + ScaledResolution sr = new ScaledResolution(Minecraft.getMinecraft()); + this.buttonList.add(new GuiButton(0, sr.getScaledWidth()/2-100,sr.getScaledHeight()-70 ,"Close Minecraft")); + this.buttonList.add(new GuiButton(1, sr.getScaledWidth()/2-100,sr.getScaledHeight()-40 ,"Play With DG in Inconsistent State")); + } + + @Override + protected void actionPerformed(GuiButton button) throws IOException { + super.actionPerformed(button); + if (button.id == 0) { + FMLCommonHandler.instance().exitJava(-1,true); + } else if (button.id == 1) { + dismiss(); + } + } + + @Override + public void drawScreen(int mouseX, int mouseY, float partialTicks) { + super.drawBackground(1); + + ScaledResolution sr = new ScaledResolution(Minecraft.getMinecraft()); + FontRenderer fontRenderer = Minecraft.getMinecraft().fontRendererObj; + fontRenderer.drawString("DungeonsGuide has ran into error while reloading", (sr.getScaledWidth()-fontRenderer.getStringWidth("DungeonsGuide has ran into error while reloading"))/2,40,0xFFFF0000); + fontRenderer.drawString("Please contact DungeonsGuide support with this screen", (sr.getScaledWidth()-fontRenderer.getStringWidth("Please contact developer with this screen"))/2, (int) (40+fontRenderer.FONT_HEIGHT*1.5),0xFFFF0000); + fontRenderer.drawString("Playing in this state is VERY UNSUGGESTED. Undesired behaviors might occur.", (sr.getScaledWidth()-fontRenderer.getStringWidth("Playing in this state is VERY UNSUGGESTED. Undesired behaviors might occur."))/2, (int) (40+fontRenderer.FONT_HEIGHT*3),0xFFFF0000); + + int tenth = sr.getScaledWidth() / 10; + + Gui.drawRect(tenth, 90,sr.getScaledWidth()-tenth, sr.getScaledHeight()-80, 0xFF5B5B5B); + String[] split = stacktrace.split("\n"); + clip(sr, tenth, 90,sr.getScaledWidth()-2*tenth, sr.getScaledHeight()-150); + GL11.glEnable(GL11.GL_SCISSOR_TEST); + for (int i = 0; i < split.length; i++) { + fontRenderer.drawString(split[i].replace("\t", " "), tenth+2,i*fontRenderer.FONT_HEIGHT + 92, 0xFFFFFFFF); + } + GL11.glDisable(GL11.GL_SCISSOR_TEST); + + super.drawScreen(mouseX, mouseY, partialTicks); + } + + public static void clip(ScaledResolution resolution, int x, int y, int width, int height) { + if (width < 0 || height < 0) return; + + int scale = resolution.getScaleFactor(); + GL11.glScissor((x ) * scale, Minecraft.getMinecraft().displayHeight - (y + height) * scale, (width) * scale, height * scale); + } +} 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; } } -- cgit