fix: use patched Forge client jar instead of vanilla version jar

Vanilla 1.20.1.jar on -cp creates automatic module _1._20._1 which
conflicts with Forge minecraft module. Use forge-*-client.jar from
libraries/ instead (contains all MC classes). Falls back to vanilla.
This commit is contained in:
SashegDev
2026-07-13 15:37:20 +00:00
parent e6a6392565
commit 96e68dab89
@@ -63,14 +63,16 @@ 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)) {
// Forge manifest's -p is a HARDCODED list of 8 bootstrap JARs // Forge: vanilla version jar on -cp creates automatic module _1._20._1
// (bootstraplauncher, securejarhandler, asm-*, JarJarFileSystems) // which conflicts with Forge's "minecraft" module → split-package error.
// using ${library_directory}. Version jar is NOT on -p. // Fix: use patched Forge client jar instead of vanilla version jar.
// Put version jar on -cp only → PreparableReloadListener loadable, // The patched jar (forge-1.20.1-47.4.20-client.jar) is created by the
// no _1._20._1 vs minecraft split-package conflict. // Forge installer in libraries/ and contains all MC classes including
// PreparableReloadListener. It is NOT listed in version.json manifest,
// so we find and add it separately.
String classpath; String classpath;
if (manifest != null) { if (manifest != null) {
classpath = buildClasspathFromManifest(manifest, true); classpath = buildClasspathFromManifest(manifest, false);
} else { } else {
classpath = ""; classpath = "";
} }
@@ -78,6 +80,21 @@ public class LaunchCommandBuilder {
classpath = buildVanillaClasspath(); classpath = buildVanillaClasspath();
} }
// Find and add the patched Forge client jar
Path patchedJar = findPatchedForgeJar();
if (patchedJar != null) {
String sep = System.getProperty("os.name").toLowerCase().contains("win") ? ";" : ":";
classpath = patchedJar.toAbsolutePath().toString() + sep + classpath;
System.out.println(ZAnsi.green(" Added patched Forge jar: " + patchedJar.getFileName()));
} else {
System.out.println(ZAnsi.yellow(" No patched Forge client jar found, falling back to vanilla version jar"));
Path versionJar = findVersionJar();
if (versionJar != null && isValidJar(versionJar)) {
String sep = System.getProperty("os.name").toLowerCase().contains("win") ? ";" : ":";
classpath = versionJar.toAbsolutePath().toString() + sep + classpath;
}
}
command.add("-cp"); command.add("-cp");
String cpFile = writeClasspathFile(classpath); String cpFile = writeClasspathFile(classpath);
command.add(cpFile); command.add(cpFile);
@@ -640,6 +657,35 @@ public class LaunchCommandBuilder {
return null; return null;
} }
/**
* Find the patched Forge client jar in libraries/.
* The Forge installer creates it via binarypatcher processor:
* libraries/net/minecraftforge/forge/<version>/forge-<version>-client.jar
* This jar contains all vanilla MC classes + Forge patches.
* Using it instead of vanilla 1.20.1.jar avoids the _1._20._1 vs minecraft
* split-package conflict, because the filename produces a different automatic
* module name.
*/
private Path findPatchedForgeJar() {
Path librariesDir = instance.getPath().resolve("libraries");
String mcVersion = instance.getMinecraftVersion();
String loaderVer = instance.getLoaderVersion();
String[] candidates = {
"net/minecraftforge/forge/" + mcVersion + "-" + loaderVer + "/forge-" + mcVersion + "-" + loaderVer + "-client.jar",
"net/neoforged/neoforge/" + mcVersion + "-" + loaderVer + "/neoforge-" + mcVersion + "-" + loaderVer + "-client.jar"
};
for (String candidate : candidates) {
Path jarPath = librariesDir.resolve(candidate);
if (Files.exists(jarPath) && isValidJar(jarPath)) {
return jarPath;
}
}
return null;
}
private String mavenToPath(String mavenName) { private String mavenToPath(String mavenName) {
String[] parts = mavenName.split(":"); String[] parts = mavenName.split(":");
if (parts.length < 3) return mavenName; if (parts.length < 3) return mavenName;