fix: Forge/NeoForge split-package ResolutionException — skip version jar on classpath, fix ${classpath} variable resolution

This commit is contained in:
SashegDev
2026-07-13 13:32:34 +00:00
parent 201269efea
commit 1d069811e3
@@ -86,14 +86,19 @@ public class LaunchCommandBuilder {
classpath = filterClasspathAgainstModulePath(classpath, manifest.getJvmArguments()); classpath = filterClasspathAgainstModulePath(classpath, manifest.getJvmArguments());
} }
command.add("-cp"); command.add("-cp");
command.add(writeClasspathFile(classpath)); String cpFile = writeClasspathFile(classpath);
command.add(cpFile);
// Add JVM arguments from version.json manifest // Add JVM arguments from version.json manifest
// Includes -DignoreList, -p (module path), --add-modules, --add-opens, // Includes -DignoreList, -p (module path), --add-modules, --add-opens,
// and -DlibraryDirectory — all required for BootstrapLauncher to // and -DlibraryDirectory — all required for BootstrapLauncher to
// resolve cpw.mods.securejarhandler as a module. // resolve cpw.mods.securejarhandler as a module.
// Forge manifest JVM args also contain "-cp ${classpath}" — we resolve
// it here so the manifest's -cp takes precedence (avoids duplicate -cp
// issues and ensures the manifest controls the final classpath).
if (manifest != null) { if (manifest != null) {
Map<String, String> vars = buildVariableMap(options); Map<String, String> vars = buildVariableMap(options);
vars.put("classpath", cpFile);
for (String arg : manifest.getJvmArguments()) { for (String arg : manifest.getJvmArguments()) {
command.add(resolveVariable(arg, vars)); command.add(resolveVariable(arg, vars));
} }
@@ -500,8 +505,8 @@ public class LaunchCommandBuilder {
// through Forge's own module system (securejarhandler + module path). // through Forge's own module system (securejarhandler + module path).
// Having them on the classpath creates automatic modules that conflict // Having them on the classpath creates automatic modules that conflict
// with Forge's own module layer (split-package ResolutionException). // with Forge's own module layer (split-package ResolutionException).
if (isForgeLike && (lib.name.startsWith("net.minecraft:client") || lib.name.contains("minecraft"))) { if (isForgeLike && lib.name.startsWith("net.minecraft:")) {
System.out.println(ZAnsi.cyan(" Skipping Minecraft lib: " + lib.name)); System.out.println(ZAnsi.cyan(" Skipping Minecraft lib (loaded via module system): " + lib.name));
continue; continue;
} }
Path libPath = lib.artifactPath != null ? librariesDir.resolve(lib.artifactPath) : null; Path libPath = lib.artifactPath != null ? librariesDir.resolve(lib.artifactPath) : null;
@@ -545,6 +550,15 @@ public class LaunchCommandBuilder {
Path versionJar = findVersionJar(); Path versionJar = findVersionJar();
if (versionJar != null) { if (versionJar != null) {
// For Forge/NeoForge: do NOT add version jar to classpath.
// Forge loads Minecraft classes through its own module system
// (securejarhandler + BootstrapLauncher module layer). Having the
// version jar on the classpath creates an automatic module (e.g.
// _1._20._1) that conflicts with Forge's "minecraft" module,
// causing ResolutionException split-package errors.
if (isForgeLike) {
System.out.println(ZAnsi.cyan(" Skipping version jar for Forge/NeoForge (loaded via module system): " + versionJar.getFileName()));
} else {
if (isValidJar(versionJar)) { if (isValidJar(versionJar)) {
paths.add(0, versionJar.toAbsolutePath().toString()); paths.add(0, versionJar.toAbsolutePath().toString());
System.out.println(ZAnsi.green(" Added version jar: " + versionJar.getFileName())); System.out.println(ZAnsi.green(" Added version jar: " + versionJar.getFileName()));
@@ -553,6 +567,7 @@ public class LaunchCommandBuilder {
Files.delete(versionJar); Files.delete(versionJar);
} }
} }
}
String separator = System.getProperty("os.name").toLowerCase().contains("win") ? ";" : ":"; String separator = System.getProperty("os.name").toLowerCase().contains("win") ? ";" : ":";
return String.join(separator, paths); return String.join(separator, paths);