From 2e1ccbb89a3a754626e486d6041d03f4fe059f41 Mon Sep 17 00:00:00 2001 From: Lassebq Date: Sat, 12 Oct 2024 19:58:30 +0300 Subject: [PATCH] Error screen handler --- .../micromixin/tweak/MicroMixinTweak.java | 12 ++- .../micromixin/MicroMixinInjection.java | 11 ++- .../org/mcphackers/launchwrapper/Launch.java | 17 +++- .../launchwrapper/tweak/LegacyTweak.java | 13 ++- .../mcphackers/launchwrapper/tweak/Tweak.java | 7 ++ .../launchwrapper/tweak/VanillaTweak.java | 8 +- .../tweak/injection/MinecraftGetter.java | 2 + .../tweak/injection/legacy/BitDepthFix.java | 5 +- .../injection/legacy/ClassicCrashScreen.java | 96 +++++++++++++++++-- .../injection/legacy/OptionsLoadFix.java | 5 +- .../vanilla/VanillaTweakContext.java | 14 +++ 11 files changed, 167 insertions(+), 23 deletions(-) diff --git a/launchwrapper-micromixin/src/main/java/org/mcphackers/launchwrapper/micromixin/tweak/MicroMixinTweak.java b/launchwrapper-micromixin/src/main/java/org/mcphackers/launchwrapper/micromixin/tweak/MicroMixinTweak.java index fe30940..cece82c 100644 --- a/launchwrapper-micromixin/src/main/java/org/mcphackers/launchwrapper/micromixin/tweak/MicroMixinTweak.java +++ b/launchwrapper-micromixin/src/main/java/org/mcphackers/launchwrapper/micromixin/tweak/MicroMixinTweak.java @@ -29,8 +29,10 @@ public class MicroMixinTweak extends Tweak { private static final boolean ACCESS_FIXER = Boolean.parseBoolean(System.getProperty("launchwrapper.accessfixer", "false")); public static final boolean DEV = Boolean.parseBoolean(System.getProperty("launchwrapper.dev", "false")); - private List mods = new ArrayList(); - private Tweak baseTweak; + protected List mods = new ArrayList(); + protected Tweak baseTweak; + protected MicroMixinInjection microMixinInjection = new MicroMixinInjection(mods, this); + public MicroMixinTweak(LaunchConfig launch, Tweak tweak) { super(launch); baseTweak = tweak; @@ -91,7 +93,7 @@ public List getInjections() { } } injects.addAll(baseTweak.getInjections()); - injects.add(new MicroMixinInjection(mods, this)); + injects.add(microMixinInjection); return injects; } @@ -105,6 +107,10 @@ public List getLazyTweakers() { return list; } + public boolean handleError(LaunchClassLoader loader, Throwable t) { + return baseTweak.handleError(loader, microMixinInjection.exception == null ? t : microMixinInjection.exception); + } + @Override public LaunchTarget getLaunchTarget() { return baseTweak.getLaunchTarget(); diff --git a/launchwrapper-micromixin/src/main/java/org/mcphackers/launchwrapper/micromixin/tweak/injection/micromixin/MicroMixinInjection.java b/launchwrapper-micromixin/src/main/java/org/mcphackers/launchwrapper/micromixin/tweak/injection/micromixin/MicroMixinInjection.java index ece4d43..1052712 100644 --- a/launchwrapper-micromixin/src/main/java/org/mcphackers/launchwrapper/micromixin/tweak/injection/micromixin/MicroMixinInjection.java +++ b/launchwrapper-micromixin/src/main/java/org/mcphackers/launchwrapper/micromixin/tweak/injection/micromixin/MicroMixinInjection.java @@ -35,6 +35,8 @@ public class MicroMixinInjection implements Injection { private Collection mixinMods; private MicroMixinTweak mixinTweak; + + public Exception exception; public MicroMixinInjection(Collection mixinMods, MicroMixinTweak mixinTweak) { this.mixinMods = mixinMods; @@ -113,7 +115,7 @@ public boolean apply(ClassNodeSource source, LaunchConfig config) { // remapper2.remapNode(mixin, sharedSB); source.overrideClass(mixin); } catch (Exception e) { - e.printStackTrace(); + exception = e; return false; } } @@ -128,7 +130,12 @@ public boolean apply(ClassNodeSource source, LaunchConfig config) { } } - mixinTweak.transformer.addMixin(source, mixinConfig); + try { + mixinTweak.transformer.addMixin(source, mixinConfig); + } catch (IllegalStateException e) { + exception = e; + return false; + } } return true; diff --git a/src/main/java/org/mcphackers/launchwrapper/Launch.java b/src/main/java/org/mcphackers/launchwrapper/Launch.java index 891af6c..b0d670a 100644 --- a/src/main/java/org/mcphackers/launchwrapper/Launch.java +++ b/src/main/java/org/mcphackers/launchwrapper/Launch.java @@ -39,9 +39,18 @@ public void launch() { return; } mainTweak.prepare(loader); - if(!mainTweak.transform(loader)) { - LOGGER.logErr("Tweak could not be applied"); - return; + try { + if(!mainTweak.transform(loader)) { + LOGGER.logErr("Tweak could not be applied"); + if(!mainTweak.handleError(loader, null)) { // if handled successfully, continue + return; + } + } + } catch(Throwable t) { + LOGGER.logErr("Tweak could not be applied", t); + if(!mainTweak.handleError(loader, t)) { // if handled successfully, continue + return; + } } mainTweak.transformResources(loader); LaunchTarget target = mainTweak.getLaunchTarget(); @@ -80,7 +89,7 @@ public void logErr(String format, Object... args) { System.err.println("[LaunchWrapper] " + String.format(format, args)); } - public void logErr(String msg, Exception e) { + public void logErr(String msg, Throwable e) { System.err.println("[LaunchWrapper] " + msg); e.printStackTrace(System.err); } diff --git a/src/main/java/org/mcphackers/launchwrapper/tweak/LegacyTweak.java b/src/main/java/org/mcphackers/launchwrapper/tweak/LegacyTweak.java index a64ab33..7b52965 100644 --- a/src/main/java/org/mcphackers/launchwrapper/tweak/LegacyTweak.java +++ b/src/main/java/org/mcphackers/launchwrapper/tweak/LegacyTweak.java @@ -14,6 +14,7 @@ import org.mcphackers.launchwrapper.Launch; import org.mcphackers.launchwrapper.LaunchConfig; +import org.mcphackers.launchwrapper.loader.LaunchClassLoader; import org.mcphackers.launchwrapper.protocol.LegacyURLStreamHandler; import org.mcphackers.launchwrapper.protocol.URLStreamHandlerProxy; import org.mcphackers.launchwrapper.target.AppletLaunchTarget; @@ -58,6 +59,7 @@ public class LegacyTweak extends Tweak { "com/mojang/minecraft/MinecraftApplet" }; protected LegacyTweakContext context = new LegacyTweakContext(); + protected ClassicCrashScreen crashPatch = new ClassicCrashScreen(context); public LegacyTweak(LaunchConfig config) { super(config); @@ -66,7 +68,7 @@ public LegacyTweak(LaunchConfig config) { public List getInjections() { return Arrays.asList( context, - new ClassicCrashScreen(context), + crashPatch, new ClassicLoadingFix(context), new UnlicensedCopyText(context), new FixSplashScreen(context), @@ -79,7 +81,7 @@ public List getInjections() { new MouseFix(context), new ReplaceGameDir(context), new OptionsLoadFix(context), - new FixClassicSession(context), + // new FixClassicSession(context), new GameModeSwitch(context), new AddMain(context), new ForgeVersionCheck(), @@ -91,6 +93,13 @@ public List getLazyTweakers() { return Collections.singletonList(new Java5LazyTweaker()); } + public boolean handleError(LaunchClassLoader loader, Throwable t) { + if(config.isom.get()) { + return false; + } + return crashPatch.injectErrorAtInit(loader, t); + } + private void downloadServer() { if(config.serverURL.get() == null || config.gameDir.get() == null) { return; diff --git a/src/main/java/org/mcphackers/launchwrapper/tweak/Tweak.java b/src/main/java/org/mcphackers/launchwrapper/tweak/Tweak.java index cc16a91..0c3914f 100644 --- a/src/main/java/org/mcphackers/launchwrapper/tweak/Tweak.java +++ b/src/main/java/org/mcphackers/launchwrapper/tweak/Tweak.java @@ -41,6 +41,13 @@ public void transformResources(ResourceSource source) { // Any classpath changes required for transformers can be set here public void prepare(LaunchClassLoader loader) { } + + public boolean handleError(LaunchClassLoader loader, Throwable t) { + if(t != null) { + t.printStackTrace(); + } + return false; + } public final boolean transform(ClassNodeSource source) { if(!clean) { diff --git a/src/main/java/org/mcphackers/launchwrapper/tweak/VanillaTweak.java b/src/main/java/org/mcphackers/launchwrapper/tweak/VanillaTweak.java index 6c763c6..0639ab3 100644 --- a/src/main/java/org/mcphackers/launchwrapper/tweak/VanillaTweak.java +++ b/src/main/java/org/mcphackers/launchwrapper/tweak/VanillaTweak.java @@ -5,6 +5,7 @@ import java.util.List; import org.mcphackers.launchwrapper.LaunchConfig; +import org.mcphackers.launchwrapper.loader.LaunchClassLoader; import org.mcphackers.launchwrapper.protocol.LegacyURLStreamHandler; import org.mcphackers.launchwrapper.protocol.URLStreamHandlerProxy; import org.mcphackers.launchwrapper.target.LaunchTarget; @@ -20,6 +21,7 @@ public class VanillaTweak extends Tweak { public static final String MAIN_CLASS = "net/minecraft/client/main/Main"; protected VanillaTweakContext context = new VanillaTweakContext(); + protected ClassicCrashScreen crashPatch = new ClassicCrashScreen(context); public VanillaTweak(LaunchConfig launch) { super(launch); @@ -28,13 +30,17 @@ public VanillaTweak(LaunchConfig launch) { public List getInjections() { return Arrays.asList( context, + crashPatch, new OutOfFocusFullscreen(context), - new ClassicCrashScreen(context), new OneSixAssetsFix(context), new ChangeBrand() ); } + public boolean handleError(LaunchClassLoader loader, Throwable t) { + return crashPatch.injectErrorAtInit(loader, t); + } + public LaunchTarget getLaunchTarget() { URLStreamHandlerProxy.setURLStreamHandler("http", new LegacyURLStreamHandler(config)); URLStreamHandlerProxy.setURLStreamHandler("https", new LegacyURLStreamHandler(config)); diff --git a/src/main/java/org/mcphackers/launchwrapper/tweak/injection/MinecraftGetter.java b/src/main/java/org/mcphackers/launchwrapper/tweak/injection/MinecraftGetter.java index ba45098..3b36022 100644 --- a/src/main/java/org/mcphackers/launchwrapper/tweak/injection/MinecraftGetter.java +++ b/src/main/java/org/mcphackers/launchwrapper/tweak/injection/MinecraftGetter.java @@ -13,4 +13,6 @@ public interface MinecraftGetter { MethodNode getRun(); FieldNode getIsRunning(); + + MethodNode getInit(); } diff --git a/src/main/java/org/mcphackers/launchwrapper/tweak/injection/legacy/BitDepthFix.java b/src/main/java/org/mcphackers/launchwrapper/tweak/injection/legacy/BitDepthFix.java index 0eb5cd0..e5baad6 100644 --- a/src/main/java/org/mcphackers/launchwrapper/tweak/injection/legacy/BitDepthFix.java +++ b/src/main/java/org/mcphackers/launchwrapper/tweak/injection/legacy/BitDepthFix.java @@ -5,6 +5,7 @@ import org.mcphackers.launchwrapper.LaunchConfig; import org.mcphackers.launchwrapper.tweak.injection.InjectionWithContext; +import org.mcphackers.launchwrapper.tweak.injection.MinecraftGetter; import org.mcphackers.launchwrapper.util.ClassNodeSource; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.InsnList; @@ -17,9 +18,9 @@ /** * Fixes Z-fighting issues on certain graphics drivers by increasing bit depth to 24 */ -public class BitDepthFix extends InjectionWithContext { +public class BitDepthFix extends InjectionWithContext { - public BitDepthFix(LegacyTweakContext storage) { + public BitDepthFix(MinecraftGetter storage) { super(storage); } diff --git a/src/main/java/org/mcphackers/launchwrapper/tweak/injection/legacy/ClassicCrashScreen.java b/src/main/java/org/mcphackers/launchwrapper/tweak/injection/legacy/ClassicCrashScreen.java index 916ba19..0479d55 100644 --- a/src/main/java/org/mcphackers/launchwrapper/tweak/injection/legacy/ClassicCrashScreen.java +++ b/src/main/java/org/mcphackers/launchwrapper/tweak/injection/legacy/ClassicCrashScreen.java @@ -34,7 +34,9 @@ * Note that some crashes related to rendering may break Tesselator which will make it stuck in an exception loop */ public class ClassicCrashScreen extends InjectionWithContext { - + private ClassNode errScreen; + private ClassNode button; + public ClassicCrashScreen(MinecraftGetter context) { super(context); } @@ -49,7 +51,7 @@ public boolean required() { return false; } - public MethodNode getOpenScreen() { + private MethodNode getOpenScreen() { ClassNode minecraft = context.getMinecraft(); for(MethodNode m : minecraft.methods) { @@ -85,6 +87,72 @@ && compareInsn(insns2[3], INVOKEVIRTUAL, minecraft.name, null, null)) { return null; } + public boolean injectErrorAtInit(ClassNodeSource source, Throwable t) { + if(errScreen == null) { + // Error was either caused too early or we couldn't patch error screen + return false; + } + MethodNode openScreen = getOpenScreen(); + if(openScreen == null) { + return false; + } + MethodNode initScreen = null; + for(MethodNode m : errScreen.methods) { + if(!m.desc.equals("()V") || m.name.equals("")) { + continue; + } + initScreen = m; + break; + } + if(initScreen == null) { + return false; + } + ClassNode minecraft = context.getMinecraft(); + MethodNode init = context.getInit(); + MethodNode run = context.getRun(); + FieldInsnNode screen = null; + for(AbstractInsnNode insn = openScreen.instructions.getFirst(); insn != null; insn = nextInsn(insn)) { + if(compareInsn(insn, ALOAD, 1) + && compareInsn(nextInsn(insn), PUTFIELD, minecraft.name, null, "L" + errScreen.superName + ";")) { + screen = (FieldInsnNode)nextInsn(insn); + } + } + for(AbstractInsnNode insn = run.instructions.getFirst(); insn != null; insn = nextInsn(insn)) { + if(compareInsn(insn, INVOKEVIRTUAL, minecraft.name, init.name, init.desc) + || compareInsn(insn, INVOKESPECIAL, minecraft.name, init.name, init.desc)) { + InsnList handle = new InsnList(); + handle.add(new VarInsnNode(ALOAD, 0)); + handle.add(new TypeInsnNode(NEW, errScreen.name)); + handle.add(new InsnNode(DUP)); + handle.add(new LdcInsnNode("LaunchWrapper error")); + handle.add(new LdcInsnNode(t == null ? "Required tweaks failed to apply" : "[" + t.getClass().getName() + "]")); + handle.add(new MethodInsnNode(INVOKESPECIAL, errScreen.name, "", "(Ljava/lang/String;Ljava/lang/String;)V")); + handle.add(new MethodInsnNode(INVOKEVIRTUAL, minecraft.name, openScreen.name, openScreen.desc)); + run.instructions.insert(insn, handle); + LabelNode skip = new LabelNode(); + handle = new InsnList(); + handle.add(new VarInsnNode(ALOAD, 0)); + handle.add(new FieldInsnNode(GETFIELD, minecraft.name, screen.name, screen.desc)); + handle.add(new JumpInsnNode(IFNULL, skip)); + handle.add(new VarInsnNode(ALOAD, 0)); + handle.add(new FieldInsnNode(GETFIELD, minecraft.name, screen.name, screen.desc)); + handle.add(new TypeInsnNode(INSTANCEOF, errScreen.name)); + handle.add(new JumpInsnNode(IFEQ, skip)); + handle.add(new InsnNode(RETURN)); + handle.add(skip); + openScreen.instructions.insert(handle); + handle = new InsnList(); + handle.add(new InsnNode(RETURN)); + initScreen.instructions = handle; + return true; + } + if(insn.getType() == AbstractInsnNode.METHOD_INSN) { + return false; + } + } + return false; + } + @Override public boolean apply(ClassNodeSource source, LaunchConfig config) { // TODO catch MinecraftServer crashes in 1.3+ @@ -153,7 +221,6 @@ && compareInsn(testInsn2, INVOKEVIRTUAL, minecraft.name, null, "()V")) { MethodNode setWorld = getWorldSetter(putWorld); ClassNode titleScreen = null; - ClassNode errScreen = null; MethodNode openScreen = getOpenScreen(); if(openScreen == null) { return false; @@ -251,6 +318,8 @@ && compareInsn(insns[2], GOTO)) { insn = nextInsn(insn); } } + MethodInsnNode translateInvoke = null; + if(errScreen == null && titleScreen != null) { ClassNode worldSelectScreen = null; methods: @@ -315,6 +384,12 @@ && compareInsn(insns[3], INVOKEVIRTUAL, null, null, "()V")) { for(AbstractInsnNode insn2 = m2.instructions.getFirst(); insn2 != null; insn2 = nextInsn(insn2)) { if(compareInsn(insn2, LDC, "selectWorld.unable_to_load") || compareInsn(insn2, LDC, "Unable to load worlds")) { + AbstractInsnNode[] insns3 = fill(nextInsn(insn2), 3); + if(compareInsn(insns3[0], ICONST_0) + && compareInsn(insns3[1], ANEWARRAY) + && compareInsn(insns3[2], INVOKESTATIC, null, null, "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;")) { + translateInvoke = (MethodInsnNode)insns3[2]; + } AbstractInsnNode[] insns2 = fillBackwards(insn2, 3); if(compareInsn(insns2[0], NEW) && compareInsn(insns2[1], DUP)) { @@ -334,7 +409,7 @@ && compareInsn(insns2[1], DUP)) { if(errScreen == null) { return false; } - if(patchErrorScreen(source, errScreen, openScreen) && instanceOf != null) { + if(patchErrorScreen(source, errScreen, openScreen, translateInvoke) && instanceOf != null) { openScreen.instructions.set(instanceOf, new InsnNode(ICONST_0)); } if(setWorld == null && cleanup == null) { @@ -437,7 +512,7 @@ && compareInsn(insns[3], INVOKESPECIAL, minecraft.name, null, null)) { return setWorld; } - public boolean patchErrorScreen(ClassNodeSource source, ClassNode errScreen, MethodNode openScreen) { + public boolean patchErrorScreen(ClassNodeSource source, ClassNode errScreen, MethodNode openScreen, MethodInsnNode translate) { boolean needCancelButton = true; String[] fields = {"message", "description"}; MethodNode init = NodeHelper.getMethod(errScreen, "", "(Ljava/lang/String;Ljava/lang/String;)V"); @@ -543,7 +618,7 @@ && compareInsn(insns[3], CHECKCAST)) { buttonClicked = m; break; } - ClassNode button = source.getClass(buttonType); + button = source.getClass(buttonType); if(button != null) { if(NodeHelper.getMethod(button, "", "(IIILjava/lang/String;)V") == null) { break cancelButton; @@ -609,7 +684,14 @@ && compareInsn(insns[5], GETFIELD, null, null, "I")) { list.add(intInsn(100)); list.add(new InsnNode(ISUB)); list.add(intInsn(140)); - list.add(new LdcInsnNode("Cancel")); // TODO translate string? + if(translate != null) { + list.add(new LdcInsnNode("gui.cancel")); + list.add(new InsnNode(ICONST_0)); + list.add(new InsnNode(ANEWARRAY)); + list.add(new MethodInsnNode(INVOKESTATIC, translate.owner, translate.name, translate.desc)); + } else { + list.add(new LdcInsnNode("Cancel")); + } list.add(new MethodInsnNode(INVOKESPECIAL, buttonType, "", "(IIILjava/lang/String;)V")); list.add(new MethodInsnNode(INVOKEINTERFACE, "java/util/List", "add", "(Ljava/lang/Object;)Z")); list.add(new InsnNode(POP)); diff --git a/src/main/java/org/mcphackers/launchwrapper/tweak/injection/legacy/OptionsLoadFix.java b/src/main/java/org/mcphackers/launchwrapper/tweak/injection/legacy/OptionsLoadFix.java index 0499734..341852a 100644 --- a/src/main/java/org/mcphackers/launchwrapper/tweak/injection/legacy/OptionsLoadFix.java +++ b/src/main/java/org/mcphackers/launchwrapper/tweak/injection/legacy/OptionsLoadFix.java @@ -5,6 +5,7 @@ import org.mcphackers.launchwrapper.LaunchConfig; import org.mcphackers.launchwrapper.tweak.injection.InjectionWithContext; +import org.mcphackers.launchwrapper.tweak.injection.MinecraftGetter; import org.mcphackers.launchwrapper.util.ClassNodeSource; import org.mcphackers.rdi.util.NodeHelper; import org.objectweb.asm.tree.AbstractInsnNode; @@ -24,9 +25,9 @@ * Prevents bad options from crashing the game. * (This does not include ArrayIndexOutOfBoundsException errors when viewing these options in options menu) */ -public class OptionsLoadFix extends InjectionWithContext { +public class OptionsLoadFix extends InjectionWithContext { - public OptionsLoadFix(LegacyTweakContext storage) { + public OptionsLoadFix(MinecraftGetter storage) { super(storage); } diff --git a/src/main/java/org/mcphackers/launchwrapper/tweak/injection/vanilla/VanillaTweakContext.java b/src/main/java/org/mcphackers/launchwrapper/tweak/injection/vanilla/VanillaTweakContext.java index bfc00a9..90932e3 100644 --- a/src/main/java/org/mcphackers/launchwrapper/tweak/injection/vanilla/VanillaTweakContext.java +++ b/src/main/java/org/mcphackers/launchwrapper/tweak/injection/vanilla/VanillaTweakContext.java @@ -44,6 +44,20 @@ public FieldNode getIsRunning() { return running; } + public MethodNode getInit() { + for(AbstractInsnNode insn : run.instructions) { + if(insn.getType() == AbstractInsnNode.METHOD_INSN) { + MethodInsnNode invoke = (MethodInsnNode) insn; + if(invoke.owner.equals(minecraft.name)) { + return NodeHelper.getMethod(minecraft, invoke.name, invoke.desc); + } else { + return null; + } + } + } + return null; + } + @Override public String name() { return "VanillaTweak init";