feat: Support deploading non-coremods without restarting the game

This commit is contained in:
FalsePattern committed 2025-11-14 03:51:25 +01:00
1 parent c3055d2557
commit a075dd69b4
5 files changed
+100 -9

No files matched your search

+1 -1
View File
@@ -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 {
@@ -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;
@@ -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;
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
package cpw.mods.fml.fpdeploader;
public class SystemExitBypassHelper {
public static void exit() {
System.exit(0);
}
}
@@ -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();
}