Skip to content

Commit a075dd6

Browse files
committed
feat: Support deploading non-coremods without restarting the game
1 parent c3055d2 commit a075dd6

File tree

5 files changed

+100
-9
lines changed

5 files changed

+100
-9
lines changed

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ plugins {
55
group = "com.falsepattern"
66

77
//bump this after ANY change to the deploader!
8-
val deploaderVersion = 2
8+
val deploaderVersion = 3
99

1010
minecraft_fp {
1111
java {

src/deploader/java/com/falsepattern/deploader/DependencyLoaderImpl.java

Lines changed: 63 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import com.falsepattern.deploader.version.SemanticVersion;
2626
import com.falsepattern.deploader.version.Version;
2727
import com.google.gson.GsonBuilder;
28+
import com.google.gson.JsonElement;
2829
import com.google.gson.JsonParser;
2930
import io.netty.util.internal.ConcurrentSet;
3031
import lombok.NonNull;
@@ -41,6 +42,7 @@
4142

4243
import cpw.mods.fml.common.MetadataCollection;
4344
import cpw.mods.fml.common.ModMetadata;
45+
import cpw.mods.fml.fpdeploader.SystemExitBypassHelper;
4446

4547
import javax.swing.JFrame;
4648
import javax.swing.JLabel;
@@ -81,6 +83,7 @@
8183
import java.util.concurrent.atomic.AtomicBoolean;
8284
import java.util.concurrent.atomic.AtomicLong;
8385
import java.util.function.Consumer;
86+
import java.util.jar.JarFile;
8487
import java.util.jar.JarInputStream;
8588
import java.util.regex.Pattern;
8689
import java.util.stream.Collectors;
@@ -123,7 +126,7 @@ public class DependencyLoaderImpl {
123126
metadataCollectionModListAccessor.setAccessible(true);
124127
}
125128

126-
private static AtomicBoolean modDownloaded = new AtomicBoolean(false);
129+
private static AtomicBoolean needReboot = new AtomicBoolean(false);
127130

128131
private static void ensureExists(Path directory) {
129132
if (!Files.exists(directory)) {
@@ -464,17 +467,17 @@ public static void executeDependencyLoading() {
464467
baseTasks.addAll(tasks);
465468
tasks = executeArtifactLoading(baseTasks, false);
466469
}
467-
if (modDownloaded.get()) {
470+
if (needReboot.get()) {
471+
val msg = "One or more minecraft mods which have been loaded by the FP DepLoader require a game restart to fully install.";
468472
if (!SystemUtils.IS_OS_MAC) {
469473
try {
470-
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);
474+
JOptionPane.showMessageDialog(null, msg + "\nThe game will exit when you close this prompt.", "Mod Dependency Download notice", JOptionPane.WARNING_MESSAGE);
471475
} catch (Exception ignored) {}
472476
}
473-
val msg = "A minecraft mod has been downloaded by the FalsePatternLib dependency downloader, and requires a game restart to get installed properly.";
474477
for (int i = 0; i < 16; i++) {
475478
LOG.warn(msg);
476479
}
477-
System.exit(0);
480+
SystemExitBypassHelper.exit();
478481
throw new ModDependencyDownloaded(msg);
479482
}
480483
}
@@ -1113,7 +1116,7 @@ private void validateDownloadsAllowed() {
11131116
Files.delete(tmpFile);
11141117
} else {
11151118
if (isMod) {
1116-
modDownloaded.set(true);
1119+
detectModReloadRequirement(tmpFile);
11171120
}
11181121
try {
11191122
Files.move(tmpFile, file, StandardCopyOption.ATOMIC_MOVE);
@@ -1154,6 +1157,60 @@ private void validateDownloadsAllowed() {
11541157
}
11551158
}
11561159

1160+
private void detectModReloadRequirement(Path file) {
1161+
if (needReboot.get())
1162+
return;
1163+
try (val jar = new JarFile(file.toFile())) {
1164+
if (LowLevelCallMultiplexer.rfbDetected) {
1165+
//Detect RFB plugins
1166+
val entries = jar.entries();
1167+
while (entries.hasMoreElements()) {
1168+
val entry = entries.nextElement();
1169+
if (entry.getName().contains("META-INF/rfb-plugin")) {
1170+
needReboot.set(true);
1171+
return;
1172+
}
1173+
}
1174+
return;
1175+
}
1176+
//Detect coremods
1177+
val manifest = jar.getManifest().getMainAttributes();
1178+
val isCoreMod = manifest.getValue("TweakClass") != null || manifest.getValue("FMLCorePlugin") != null;
1179+
if (isCoreMod) {
1180+
needReboot.set(true);
1181+
return;
1182+
}
1183+
//Detect mixins
1184+
val entries = jar.entries();
1185+
while (entries.hasMoreElements()) {
1186+
val entry = entries.nextElement();
1187+
val name = entry.getName();
1188+
if (name.contains("META-INF/rfb-plugin")) {
1189+
needReboot.set(true);
1190+
return;
1191+
}
1192+
if (!name.endsWith(".json")) {
1193+
continue;
1194+
}
1195+
val data = jar.getInputStream(entry);
1196+
val gson = new GsonBuilder().create();
1197+
val element = gson.fromJson(new InputStreamReader(data), JsonElement.class);
1198+
if (!element.isJsonObject()) {
1199+
continue;
1200+
}
1201+
val obj = element.getAsJsonObject();
1202+
if (obj.has("refmap") || obj.has("compatibilityLevel") || obj.has("target")) {
1203+
//Most likely contains mixins, better safe than sorry
1204+
needReboot.set(true);
1205+
return;
1206+
}
1207+
}
1208+
} catch (IOException e) {
1209+
//bail out safely
1210+
needReboot.set(true);
1211+
}
1212+
}
1213+
11571214
private ChecksumStatus validateChecksum(String url) throws IOException {
11581215
for (val checksumType : CHECKSUM_TYPES) {
11591216
val checksumURL = url + "." + checksumType;

src/deploader/java/com/falsepattern/deploader/LowLevelCallMultiplexer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737

3838
@UtilityClass
3939
public class LowLevelCallMultiplexer {
40-
private static boolean rfbDetected = false;
40+
public static boolean rfbDetected = false;
4141
public static void rfbDetected() {
4242
rfbDetected = true;
4343
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* This file is part of FalsePatternLib.
3+
*
4+
* Copyright (C) 2022-2025 FalsePattern
5+
* All Rights Reserved
6+
*
7+
* The above copyright notice and this permission notice shall be included
8+
* in all copies or substantial portions of the Software.
9+
*
10+
* FalsePatternLib is free software: you can redistribute it and/or modify
11+
* it under the terms of the GNU Lesser General Public License as published by
12+
* the Free Software Foundation, only version 3 of the License.
13+
*
14+
* FalsePatternLib is distributed in the hope that it will be useful,
15+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
* GNU Lesser General Public License for more details.
18+
*
19+
* You should have received a copy of the GNU Lesser General Public License
20+
* along with FalsePatternLib. If not, see <https://www.gnu.org/licenses/>.
21+
*/
22+
23+
package cpw.mods.fml.fpdeploader;
24+
25+
public class SystemExitBypassHelper {
26+
public static void exit() {
27+
System.exit(0);
28+
}
29+
}

src/deploaderStub/java/com/falsepattern/deploader/DeploaderStub.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ public static void bootstrap(boolean rfb) {
5656
if (rfb) {
5757
rfbDetected = true;
5858
RFBUtil.preinit();
59+
} else {
60+
LaunchWrapperUtil.preinit();
5961
}
6062
Object deploader = Launch.blackboard.get(BLACKBOARD_MARKER);
6163
if (deploader != null) {
@@ -203,7 +205,7 @@ static void preinit() {
203205
try {
204206
Method exc = loader.getClass()
205207
.getDeclaredMethod("addClassLoaderExclusion", String.class);
206-
exc.invoke(loader, "com_falsepattern_deploader_".replace("_", "."));
208+
exc.invoke(loader, "com_falsepattern_deploader_".replace('_', '.'));
207209
} catch (Exception ignored) {}
208210
}
209211

@@ -221,6 +223,9 @@ static Path gameDir() {
221223
}
222224

223225
private static class LaunchWrapperUtil {
226+
static void preinit() {
227+
Launch.classLoader.addTransformerExclusion("com_falsepattern_deploader_".replace('_', '.'));
228+
}
224229
static Path gameDir() {
225230
return Launch.minecraftHome == null ? Paths.get(".") : Launch.minecraftHome.toPath();
226231
}

0 commit comments

Comments
 (0)