findVersionJar() falls back to versions/1.20.1/1.20.1.jar when the Forge
version jar doesnt exist. Filename 1.20.1.jar produces automatic module _1._20._1
which conflicts with module minecraft (split-package on net.minecraft.server).
Fix: ensureVersionJarForForge() copies vanilla jar to versions/<versionId>/
<versionId>.jar before findVersionJar() is called. Vanilla jar has
Automatic-Module-Name: minecraft so it becomes module minecraft, not _1._20._1.
Root cause: our launcher was fighting with version.json by manually constructing
-p, -cp, --add-modules, --add-opens, -DignoreList, and -DlibraryDirectory.
AstralRinth passes version.json JVM args through with placeholder substitution,
then appends memory/GC/custom args.
Changes:
- VersionManifest: add getAllJvmArguments() and getAllGameArguments() that merge
parent (vanilla) args with child (forge) args
- LaunchCommandBuilder.build(): rewrite Forge/NeoForge branch to parse ALL
version.json JVM args and game args with placeholder substitution
- Removed: ensureVersionJarForForge(), findSrgClientJar(),
filterClasspathAgainstModulePath(), manual -Djava.library.path for Forge
- Classpath: built from version.json libraries + client jar (first position)
- Memory/GC args appended AFTER version.json args (like AstralRinth)
- Fallback to manual args if version.json has no JVM/game args
ensureVersionJarForForge() was copying the vanilla Mojang jar
(obfuscated class names) to versions/<forgeId>/<forgeId>.jar.
securejarhandler creates the minecraft module from this jar, but
obfuscated names dont match SRG names like PreparableReloadListener.
The package is claimed by the module but class is not found → crash.
Now prioritizes the SRG-mapped client jar from Forge libraries
(client-*-srg.jar) which has Automatic-Module-Name: minecraft and
correct SRG class names. Falls back to vanilla jar only if SRG jar
is not found.
Also removed redundant -DlibraryDirectory already in manifest args.
The forge patched client jar (forge-<version>-client.jar) contains all
Minecraft classes with Automatic-Module-Name: minecraft, but it is NOT
listed in the version JSON libraries array - so buildClasspathFromManifest
does not include it on the -cp. FML discovers it by scanning the library
directory, but securejarhandler only processes classpath entries to create
named modules.
This fix explicitly finds and prepends forge-<version>-client.jar to the
classpath when launching Forge/NeoForge, so securejarhandler can create
the minecraft module from it.
Instead of copying the vanilla jar to versions/<versionId>/ and adding it to
-cp (Fix 9/10), we now simply remove forge- from the -DignoreList pattern.
This allows securejarhandler to process forge-1.20.1-47.4.20-client.jar on
the classpath, which has Automatic-Module-Name: minecraft in its MANIFEST,
and create the minecraft module from it.
The forge client jar contains all patched MC classes, so PreparableReloadListener
and all other vanilla classes are accessible in the minecraft module.
No duplicate module creation, no split-package errors.
The copied version jar (1.20.1-forge-47.4.20.jar) is now on -cp so
securejarhandler can scan it and create the minecraft module. The jar
name does not match the ignoreList pattern forge- (starts with 1),
so it is included in the module layer. This ensures PreparableReloadListener
and other vanilla classes are findable in the minecraft module.
securejarhandler creates the minecraft module from versions/<versionId>/<versionId>.jar.
The Forge installer only creates the JSON there, not the jar. Without the jar,
securejarhandler cannot create the minecraft module, causing ClassNotFoundException
for PreparableReloadListener (a vanilla MC class expected in the minecraft module).
Fix: copy the vanilla version jar (e.g. versions/1.20.1/1.20.1.jar) to the
Forge version directory (e.g. versions/1.20.1-forge-47.4.20/1.20.1-forge-47.4.20.jar)
before launch. The jar is NOT put on -cp to prevent automatic module _1._20._1
from being created, which would conflict with the minecraft module (split-package).
Also removed patched forge client jar from -cp since securejarhandler excludes it
via -DignoreList (forge-* pattern).
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.
Forge manifest -p is 8 hardcoded bootstrap JARs using ${library_directory},
not ${classpath}. Intercepting it replaced those with ALL libraries causing
duplicate module errors. Version jar stays on -cp only (not -p).
- 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()
Forge version.json uses -p ${classpath} to set the module path to the same
argfile as the classpath. Previously we filtered net.minecraft:* libs from the
classpath string, which meant they were absent from the module path too, causing
ClassNotFoundException for PreparableReloadListener and other MC classes.
Now net.minecraft:* libs are included in the Forge classpath argfile so
securejarhandler can load them as modules.
- Read version from bin/.version file (reliable, no JAR locking)
- Save version to bin/.version when downloading JAR
- Use getLauncherJar() for all JAR path references
- Create binDir in main()
- Remove build.version dependency completely