Supported Platforms
Essential Loader currently supports four different platforms:LaunchWrapper
Forge 1.8.9 and 1.12.2
Fabric
1.16+ with Fabric Loader
ModLauncher 8
Forge 1.16.5
ModLauncher 9
Forge 1.17+ (also supports ModLauncher 10)
LaunchWrapper
LaunchWrapper is what Forge 1.8.9 - 1.12.2 are based on.Background
Technically LaunchWrapper is independent from Forge (it is published by Mojang), so:- Stage0 is completely independent from Forge
- Stage1 has separate code paths for pure LaunchWrapper vs LaunchWrapper+Forge
- In practice, Forge is the only big user of LaunchWrapper, so stage2 assumes Forge
Conceptually, LaunchWrapper itself is quite simple (basically just three medium-length files). This makes it fairly easy to pick up but relatively hard to master because there aren’t any mechanisms for dependency management or hard rules for what a Tweaker may or may not do.
Entrypoint
LaunchWrapper’s main entry point is anITweaker class.
Discovery process:
- The
Launchclass discovers and invokes tweakers in multiple rounds - Each round can add extra tweaker classes for the next round
- Round 1: Tweakers discovered via command line arguments (includes only Forge in production)
- Forge: Looks at the
TweakClassproperty inMANIFEST.MFof jars in the mods folder during early loading (CoreModManager) - Round 2: This is where Essential Loader gets to run
Alternative Approach: CoreMods
Alternative Approach: CoreMods
In hindsight, we could have used Forge’s CoreMod mechanism which runs during the first round. However, a Tweaker has worked well enough and changing this now would be a huge undertaking.
Hacks
Despite its conceptual simplicity, the stage2 loader for LaunchWrapper has grown to be arguably the most complex piece of essential-loader. This is due to:- The amount of workarounds necessary to run modern software (such as Mixin 0.8.x) on old Minecraft versions
- Badly written Tweakers/CoreMods in third-party code that make everyone’s lives more difficult
Relaunch
The most powerful tool available on LaunchWrapper is the “relaunch”.What is a Relaunch?
What is a Relaunch?
A relaunch creates a second, mostly clean, mostly isolated environment within the existing LaunchWrapper environment and re-launches Minecraft in there. This allows:
- More recent versions of certain libraries
- Modifications to the inner LaunchWrapper that would otherwise not be possible
- 1.8.9: Always required (default ASM library is too old for Mixin 0.8)
- 1.12.2: Could work without relaunching if no other mod pulls in an older version of our libs (Kotlin being a frequent example), but for simplicity we always relaunch
Other Hacks
There are numerous other hacks used on this platform. For now, refer to the source code for details.
Fabric
The Fabric platform is used wherever fabric-loader is used, regardless of Minecraft version.Background
With fabric-loader working across the entire range of supported Minecraft versions, the Fabric variant of essential-loader naturally does as well. Advantages:- Not stuck with old software like unsupported Forge versions
- Excellent built-in support for:
- Picking the latest version of a mod
- Bundling mods inside other mods (Jar-in-Jar, JiJ)
- First-party Mixin support
- Can’t snoop around in internals as much (updates might break our hacks)
- No way to chain-load mods or have custom code do mod discovery
- Always discovers all mods before running any third-party code
Unlike LaunchWrapper, fabric-loader was written to be a mod loader (take note ModLauncher!) and has excellent built-in functionality.
Entrypoint
We use the built-inprelaunch entrypoint for essential-loader.
Hacks
There are only three bigger hacks required on the Fabric platform, all related to chain-loading the mod:- Adding it to the class loader
- Registering it as a mod for other mods to see
- Extracting JiJ mods
Fake Mod
We make heavy use of fabric-loader internals to instantiate mod metadata for dynamically loaded mods so they appear as normal mods in ModMenu. Important caveat: Declared entrypoints will only be registered if this succeeds. If possible, mods should not rely on them (e.g., Essential uses a custom mixin for its init entrypoint instead of the fabric built-in entrypoint).Jar-in-Jar (JiJ)
By the time we run, fabric-loader has already loaded all mods. We need to handle any JiJ mods inside our dynamically loaded mods ourselves. Simple cases:- Not yet loaded: Trivial to handle
- Newer version already loaded: Trivial to handle
- Essential dynamically generates a new “Essential Dependencies” mod in the mods folder
- Prompts the user to restart the game
- On next boot, fabric-loader sees the generated jar and loads the updated version
This unfortunately means the user may need to manually restart on each update of the JiJ mod. However, this solution doesn’t rely on fabric-loader internals at all!
ModLauncher 8
ModLauncher 8 is used by Forge 1.16.5.Background
ModLauncher is Forge’s successor to LaunchWrapper. It has much bigger ambitions and is much more complex. Consequently, we more often need to resort to hacks, and those hacks are usually more complex.Entrypoint
The early entrypoint which ModLauncher provides is theTransformationService.
Discovery:
- Discovered by Forge from jars in the mods folder
- Uses a file as used by Java’s
ServiceLoader(rather than a manifest entry)
- Stage1 has its own
TransformationService(can be updated) - Stage0 forwards all methods to stage1
- Stage1 forwards all methods to stage2 (can auto-update)
ModLocator. It’s a bit of a dance because it gets loaded in a separate class loader that cannot directly communicate with the TransformationService, but otherwise fairly straightforward.
Hacks
Upgrading Third-Party Mods
Despite ModLauncher being a “Mod Loader”, it fails to do the most basic thing: provide a method to load the newer of two mods. Why we need this: On Forge, Kotlin is part of a mod called KotlinForForge (KFF), and people frequently have an old version installed. Solution: We Unsafe-clone theLanguageLoadingProvider and replace it with a different implementation (SortedLanguageLoadingProvider) that:
- Sorts jars by implementation version
- Only picks the most recent one where there are multiple jars for the same implementation name
Forge has a completely different mechanism for loading language mods vs regular mods. We only deal with the language one right now because we don’t yet need to upgrade regular ones.
Mod/Language Load Order
Modern Forge differentiates between regular Mods and Language Mods. The problem: Classes in regular mods take priority over language mods. So even after we upgrade KotlinForForge, if another mod bundles the Kotlin stdlib, we’ll get their (usually outdated) version. Solution: We mess with the Lambda used to fetch bytes for a given class name. This must be done before any classes are loaded, which is why we “register” (using Unsafe) anEssentialLaunchPluginService which has a method invoked at the right time.
Upgrading Kotlin
KotlinForForge sometimes:- Takes a while to update
- Does backwards incompatible changes (might break third-party mods)
- No longer updates at all for certain versions (anything that isn’t Forge LTS)
ModLauncher 9
ModLauncher 9 is used by Forge 1.17+. ModLauncher 10 is fairly similar and supported by the same platform code.Background
The main difference from ModLauncher 8 is that it heavily uses Java 9’s module system, which changes pretty much everything.ModLauncher 10 is fairly similar to ModLauncher 9, so it is supported by the ModLauncher 9 variant of essential-loader. There’s an inner
modlauncher10 project for the one bit that is different, bundled inside the modlauncher9 stage2 jar.Entrypoint
See the ModLauncher 8 section above—the entrypoint is the same. Why we can’t reuse stage0/1 from ML8: TheTransformationService interface now references a record class, which we can’t compile against with Java 8 required by ML8.
Hacks
Additionally, since ModLauncher 9 uses Java 9’s modules, we often can’t access even public members via reflection. Not only do we need to mess with ModLauncher internals, we also need to rely on Unsafe a lot to even access those.Chain Loading Mods
You’d think this would be easier now that language and regular mods are more uniform. But no. The problem:- We still need to implement our own
ModLocator - There’s no longer any way to actually register it
- Forge won’t call it for us
What Minecraft Version Is This Anyway?
Seems like a simple question, but you won’t get an answer from ModLauncher (even though it technically has the info). Solution: Wait until theFMLLoader has run to make the Minecraft version publicly available. Until then:
- The
EssentialLoaderimplementation in stage2 accessed by stage1 is a dummy - Gets a hard-coded
1.17.1as the Minecraft version - Only provides access to the stage2
TransformationService
ActualEssentialLoader which checks for updates and downloads the initial version.
Upgrading Third-Party Mods
Not much has changed on a high level from ModLauncher 8. ModLauncher still fails to load the more recent of two versions. What has changed internally:- Instead of file system order → HashMap iteration order (effectively random on each run)
- Java’s module system throws an exception when two modules have overlapping packages
- Luckily, if the same module is defined by two jars, it picks one without exploding
SortedJarOrPathList — a List that sorts its elements (record JarOrPath) by version. We replace the regular list for each layer with our list at the right time (configureLayerToBeSortedByVersion).
Automatic Module Names
With the above hack, all we need to do is inject a newer version with the same module name and our hack will choose the newer one. What could go wrong? The problem: If a user:- Downloads KFF from a different website
- Downloads it twice and the browser adds
(1)to the name
SelfRenamingJarMetadata — In an elegant way, this jar metadata object will:
- On-the-fly when asked for its name
- Look through all other jar metadata on the same layer
- Check if any have overlapping packages
- Steal their name
Upgrading Kotlin
KotlinForForge sometimes:- Takes a while to update
- Does backwards incompatible changes
- No longer updates at all for non-LTS versions
- Is based on the original KFF
- Has its bundled Kotlin upgraded from our bundle
- KFF still gets updated—we don’t want to downgrade Kotlin stdlibs
- No nice way to know the version in the loaded KFF jar (it’s been exploded, metadata lost)
- We get the coroutines version from a random file it has
- Get the stdlib version by loading the stdlib
KotlinVersionclass in a temporary classloader - Take a good guess at the serialization version
- It all just about works out
Platform Comparison
Simplest
Fabric - Clean APIs, minimal hacks needed
Most Hacks
LaunchWrapper - Oldest, most workarounds for modern software
Most Complex
ModLauncher 9 - Java modules + HashMap iteration = chaos
Best Designed
Fabric - Actually built to be a mod loader