fix: split classpath from module path to prevent Forge split-package error
- Build two separate classpaths: modulePath (libraries only) and classpath (libraries + version jar) - Intercept manifest -p to use modulePath (no version jar) preventing _1._20._1 vs minecraft module conflict - Intercept manifest -cp to skip (our explicit -cp already added) - Fix --version to use getVersionId() instead of instance.getName()
This commit is contained in:
+36
-28
@@ -63,44 +63,50 @@ public class LaunchCommandBuilder {
|
|||||||
// always use ModLauncher as main class (version.json might have
|
// always use ModLauncher as main class (version.json might have
|
||||||
// BootstrapLauncher which breaks with Java module system)
|
// BootstrapLauncher which breaks with Java module system)
|
||||||
if ("forge".equals(loaderType) || "neoforge".equals(loaderType)) {
|
if ("forge".equals(loaderType) || "neoforge".equals(loaderType)) {
|
||||||
// BootstrapLauncher creates the module layer ModLauncher needs
|
// Build TWO classpaths for Forge/NeoForge:
|
||||||
// for ServiceLoader discovery.
|
// 1. modulePath: libraries only (for -p module path) — version jar excluded
|
||||||
// Build classpath from manifest (shorter, ordered).
|
// to prevent split-package conflict with Forge's "minecraft" module
|
||||||
// Only JARs explicitly listed in version.json go on the classpath.
|
// 2. classpath: libraries + version jar (for -cp classpath) — version jar
|
||||||
// Do NOT append JARs from recursive scan — tool JARs like
|
// included so PreparableReloadListener and other MC classes are loadable
|
||||||
// ForgeAutoRenamingTool that contain shaded ASM packages cause
|
String modulePath;
|
||||||
// split-package conflicts with the Java module system when
|
|
||||||
// --add-modules=ALL-MODULE-PATH resolves them as automatic modules.
|
|
||||||
String classpath;
|
String classpath;
|
||||||
if (manifest != null) {
|
if (manifest != null) {
|
||||||
classpath = buildClasspathFromManifest(manifest);
|
modulePath = buildClasspathFromManifest(manifest, false);
|
||||||
|
classpath = buildClasspathFromManifest(manifest, true);
|
||||||
} else {
|
} else {
|
||||||
|
modulePath = "";
|
||||||
classpath = "";
|
classpath = "";
|
||||||
}
|
}
|
||||||
if (classpath == null || classpath.isEmpty()) {
|
if (modulePath == null || modulePath.isEmpty()) {
|
||||||
classpath = buildVanillaClasspath();
|
modulePath = buildVanillaClasspath();
|
||||||
} else {
|
|
||||||
// Safety net: remove JARs from classpath that are already
|
|
||||||
// on the module path (from manifest JVM args) to prevent
|
|
||||||
// Java module system split-package conflicts.
|
|
||||||
classpath = filterClasspathAgainstModulePath(classpath, manifest.getJvmArguments());
|
|
||||||
}
|
}
|
||||||
|
if (classpath == null || classpath.isEmpty()) {
|
||||||
|
classpath = modulePath;
|
||||||
|
}
|
||||||
|
|
||||||
command.add("-cp");
|
command.add("-cp");
|
||||||
String cpFile = writeClasspathFile(classpath);
|
String cpFile = writeClasspathFile(classpath);
|
||||||
|
String mpFile = writeClasspathFile(modulePath);
|
||||||
command.add(cpFile);
|
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,
|
// Intercept -p / --module-path to use mpFile (no version jar)
|
||||||
// and -DlibraryDirectory — all required for BootstrapLauncher to
|
// and -cp / --classpath to skip (we already added our own -cp).
|
||||||
// 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);
|
vars.put("classpath", cpFile);
|
||||||
for (String arg : manifest.getJvmArguments()) {
|
List<String> jvmArgs = manifest.getJvmArguments();
|
||||||
command.add(resolveVariable(arg, vars));
|
for (int i = 0; i < jvmArgs.size(); i++) {
|
||||||
|
String raw = jvmArgs.get(i);
|
||||||
|
if (("-p".equals(raw) || "--module-path".equals(raw)) && i + 1 < jvmArgs.size()) {
|
||||||
|
command.add(raw);
|
||||||
|
i++;
|
||||||
|
command.add(mpFile);
|
||||||
|
} else if (("-cp".equals(raw) || "--classpath".equals(raw)) && i + 1 < jvmArgs.size()) {
|
||||||
|
i++;
|
||||||
|
} else {
|
||||||
|
command.add(resolveVariable(raw, vars));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,7 +131,7 @@ public class LaunchCommandBuilder {
|
|||||||
command.addAll(getVanillaGameArguments(options));
|
command.addAll(getVanillaGameArguments(options));
|
||||||
}
|
}
|
||||||
} else if (manifest != null) {
|
} else if (manifest != null) {
|
||||||
String classpath = buildClasspathFromManifest(manifest);
|
String classpath = buildClasspathFromManifest(manifest, true);
|
||||||
|
|
||||||
// Fallback if classpath is empty
|
// Fallback if classpath is empty
|
||||||
if (classpath.isEmpty() || classpath.equals(instance.getPath().resolve("versions").resolve(getVersionId()).resolve(getVersionId() + ".jar").toAbsolutePath().toString())) {
|
if (classpath.isEmpty() || classpath.equals(instance.getPath().resolve("versions").resolve(getVersionId()).resolve(getVersionId() + ".jar").toAbsolutePath().toString())) {
|
||||||
@@ -417,7 +423,7 @@ public class LaunchCommandBuilder {
|
|||||||
List<String> args = new ArrayList<>();
|
List<String> args = new ArrayList<>();
|
||||||
|
|
||||||
args.add("--version");
|
args.add("--version");
|
||||||
args.add(instance.getName());
|
args.add(getVersionId());
|
||||||
args.add("--gameDir");
|
args.add("--gameDir");
|
||||||
args.add(instance.getPath().toAbsolutePath().toString());
|
args.add(instance.getPath().toAbsolutePath().toString());
|
||||||
args.add("--assetsDir");
|
args.add("--assetsDir");
|
||||||
@@ -491,7 +497,7 @@ public class LaunchCommandBuilder {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String buildClasspathFromManifest(VersionManifest manifest) throws Exception {
|
private String buildClasspathFromManifest(VersionManifest manifest, boolean includeVersionJar) throws Exception {
|
||||||
List<String> paths = new ArrayList<>();
|
List<String> paths = new ArrayList<>();
|
||||||
Path librariesDir = instance.getPath().resolve("libraries");
|
Path librariesDir = instance.getPath().resolve("libraries");
|
||||||
|
|
||||||
@@ -549,6 +555,7 @@ public class LaunchCommandBuilder {
|
|||||||
System.out.println(ZAnsi.cyan(" Paths dir: " + instance.getPath().resolve("versions")));
|
System.out.println(ZAnsi.cyan(" Paths dir: " + instance.getPath().resolve("versions")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (includeVersionJar) {
|
||||||
Path versionJar = findVersionJar();
|
Path versionJar = findVersionJar();
|
||||||
if (versionJar != null) {
|
if (versionJar != null) {
|
||||||
if (isValidJar(versionJar)) {
|
if (isValidJar(versionJar)) {
|
||||||
@@ -559,6 +566,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);
|
||||||
|
|||||||
Reference in New Issue
Block a user