diff --git a/build.gradle.kts b/build.gradle.kts index 151a025..3843b55 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,7 +5,7 @@ plugins { group = "com.falsepattern" //bump this after ANY change to the deploader! -val deploaderVersion = 2 +val deploaderVersion = 3 minecraft_fp { java { diff --git a/src/deploader/java/com/falsepattern/deploader/DependencyLoaderImpl.java b/src/deploader/java/com/falsepattern/deploader/DependencyLoaderImpl.java index ccc3cf6..221d8b4 100644 --- a/src/deploader/java/com/falsepattern/deploader/DependencyLoaderImpl.java +++ b/src/deploader/java/com/falsepattern/deploader/DependencyLoaderImpl.java @@ -25,6 +25,7 @@ import com.falsepattern.deploader.version.RawVersion; import com.falsepattern.deploader.version.SemanticVersion; import com.falsepattern.deploader.version.Version; import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; import com.google.gson.JsonParser; import io.netty.util.internal.ConcurrentSet; import lombok.NonNull; @@ -41,6 +42,7 @@ import org.jetbrains.annotations.Nullable; import cpw.mods.fml.common.MetadataCollection; import cpw.mods.fml.common.ModMetadata; +import cpw.mods.fml.fpdeploader.SystemExitBypassHelper; import javax.swing.JFrame; import javax.swing.JLabel; @@ -81,6 +83,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; +import java.util.jar.JarFile; import java.util.jar.JarInputStream; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -123,7 +126,7 @@ public class DependencyLoaderImpl { metadataCollectionModListAccessor.setAccessible(true); } - private static AtomicBoolean modDownloaded = new AtomicBoolean(false); + private static AtomicBoolean needReboot = new AtomicBoolean(false); private static void ensureExists(Path directory) { if (!Files.exists(directory)) { @@ -464,17 +467,17 @@ public class DependencyLoaderImpl { baseTasks.addAll(tasks); tasks = executeArtifactLoading(baseTasks, false); } - if (modDownloaded.get()) { + if (needReboot.get()) { + val msg = "One or more minecraft mods which have been loaded by the FP DepLoader require a game restart to fully install."; if (!SystemUtils.IS_OS_MAC) { try { - JOptionPane.showMessageDialog(null, "A minecraft mod has been downloaded by the FalsePatternLib dependency downloader.\nYou must restart the game to apply these changes.", "Mod Dependency Download notice", JOptionPane.WARNING_MESSAGE); + JOptionPane.showMessageDialog(null, msg + "\nThe game will exit when you close this prompt.", "Mod Dependency Download notice", JOptionPane.WARNING_MESSAGE); } catch (Exception ignored) {} } - val msg = "A minecraft mod has been downloaded by the FalsePatternLib dependency downloader, and requires a game restart to get installed properly."; for (int i = 0; i < 16; i++) { LOG.warn(msg); } - System.exit(0); + SystemExitBypassHelper.exit(); throw new ModDependencyDownloaded(msg); } } @@ -1113,7 +1116,7 @@ public class DependencyLoaderImpl { Files.delete(tmpFile); } else { if (isMod) { - modDownloaded.set(true); + detectModReloadRequirement(tmpFile); } try { Files.move(tmpFile, file, StandardCopyOption.ATOMIC_MOVE); @@ -1154,6 +1157,60 @@ public class DependencyLoaderImpl { } } + private void detectModReloadRequirement(Path file) { + if (needReboot.get()) + return; + try (val jar = new JarFile(file.toFile())) { + if (LowLevelCallMultiplexer.rfbDetected) { + //Detect RFB plugins + val entries = jar.entries(); + while (entries.hasMoreElements()) { + val entry = entries.nextElement(); + if (entry.getName().contains("META-INF/rfb-plugin")) { + needReboot.set(true); + return; + } + } + return; + } + //Detect coremods + val manifest = jar.getManifest().getMainAttributes(); + val isCoreMod = manifest.getValue("TweakClass") != null || manifest.getValue("FMLCorePlugin") != null; + if (isCoreMod) { + needReboot.set(true); + return; + } + //Detect mixins + val entries = jar.entries(); + while (entries.hasMoreElements()) { + val entry = entries.nextElement(); + val name = entry.getName(); + if (name.contains("META-INF/rfb-plugin")) { + needReboot.set(true); + return; + } + if (!name.endsWith(".json")) { + continue; + } + val data = jar.getInputStream(entry); + val gson = new GsonBuilder().create(); + val element = gson.fromJson(new InputStreamReader(data), JsonElement.class); + if (!element.isJsonObject()) { + continue; + } + val obj = element.getAsJsonObject(); + if (obj.has("refmap") || obj.has("compatibilityLevel") || obj.has("target")) { + //Most likely contains mixins, better safe than sorry + needReboot.set(true); + return; + } + } + } catch (IOException e) { + //bail out safely + needReboot.set(true); + } + } + private ChecksumStatus validateChecksum(String url) throws IOException { for (val checksumType : CHECKSUM_TYPES) { val checksumURL = url + "." + checksumType; diff --git a/src/deploader/java/com/falsepattern/deploader/LowLevelCallMultiplexer.java b/src/deploader/java/com/falsepattern/deploader/LowLevelCallMultiplexer.java index 9fc78b1..a4accae 100644 --- a/src/deploader/java/com/falsepattern/deploader/LowLevelCallMultiplexer.java +++ b/src/deploader/java/com/falsepattern/deploader/LowLevelCallMultiplexer.java @@ -37,7 +37,7 @@ import java.util.List; @UtilityClass public class LowLevelCallMultiplexer { - private static boolean rfbDetected = false; + public static boolean rfbDetected = false; public static void rfbDetected() { rfbDetected = true; } diff --git a/src/deploader/java/cpw/mods/fml/fpdeploader/SystemExitBypassHelper.java b/src/deploader/java/cpw/mods/fml/fpdeploader/SystemExitBypassHelper.java new file mode 100644 index 0000000..04bc848 --- /dev/null +++ b/src/deploader/java/cpw/mods/fml/fpdeploader/SystemExitBypassHelper.java @@ -0,0 +1,29 @@ +/* + * This file is part of FalsePatternLib. + * + * Copyright (C) 2022-2025 FalsePattern + * All Rights Reserved + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * FalsePatternLib is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, only version 3 of the License. + * + * FalsePatternLib 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with FalsePatternLib. If not, see . + */ + +package cpw.mods.fml.fpdeploader; + +public class SystemExitBypassHelper { + public static void exit() { + System.exit(0); + } +} diff --git a/src/deploaderStub/java/com/falsepattern/deploader/DeploaderStub.java b/src/deploaderStub/java/com/falsepattern/deploader/DeploaderStub.java index e664f4c..0646556 100644 --- a/src/deploaderStub/java/com/falsepattern/deploader/DeploaderStub.java +++ b/src/deploaderStub/java/com/falsepattern/deploader/DeploaderStub.java @@ -56,6 +56,8 @@ public final class DeploaderStub { if (rfb) { rfbDetected = true; RFBUtil.preinit(); + } else { + LaunchWrapperUtil.preinit(); } Object deploader = Launch.blackboard.get(BLACKBOARD_MARKER); if (deploader != null) { @@ -203,7 +205,7 @@ public final class DeploaderStub { try { Method exc = loader.getClass() .getDeclaredMethod("addClassLoaderExclusion", String.class); - exc.invoke(loader, "com_falsepattern_deploader_".replace("_", ".")); + exc.invoke(loader, "com_falsepattern_deploader_".replace('_', '.')); } catch (Exception ignored) {} } @@ -221,6 +223,9 @@ public final class DeploaderStub { } private static class LaunchWrapperUtil { + static void preinit() { + Launch.classLoader.addTransformerExclusion("com_falsepattern_deploader_".replace('_', '.')); + } static Path gameDir() { return Launch.minecraftHome == null ? Paths.get(".") : Launch.minecraftHome.toPath(); }