Skip to content

Conversation

Johni0702
Copy link
Contributor

This PR makes Essential Loader usable by third-party mods on 1.8.9-1.12.2 to have a jar-in-jar style dependency management feature and to get Mixin 0.8.x as well as MixinExtras working, without requiring the full Essential mod to be installed for that.

The PR consists of three large commits surrounded by smaller preparation / refactor / cleanup / followup commits:

  1. c3d312f enables our re-launching feature unconditionally. We've already re-launched on 1.8.9 all the time anyway, and with third-parties being able to jar-in-jar arbitrary libs, we can't have lib-specific code to check if relaunching is required any more, so we'll just re-launch all the time. This also allows us to get rid of a substantial fraction of launchwrapper stage2 code.
  2. e03ab9e is the main commit which replaces the entire launchwrapper stage2 with a new Loader which incrementally discovers jars to load by walking all jars on the classpath and looking for a essential.mod.json file with dependency info.
    It allows for dynamic dependency generation by specifying a class file which gets executed and returns more json, and that's how we now tie Essential's updater back into this new Loader.
    This also gets rid of the entire downloading/updating code in launchwrapper stage1. Instead a stage2 jar is now simply bundled in the stage1 jar, and stage2 can be updated via a stage1 update.
    Unlike before this is even possible without a reboot: When the Loader finds a jar with a newer stage1/stage2 included (e.g. if we just downloaded a new Essential update which requires a newer Loader), it will load that new stage2 and let it handle launching the game.
  3. 2974be0 adds the mixin-compat project from Essential. This produces a Mixin which has guava21 relocated (because 1.8.9 MC has and needs older guava), bundles ASM 5.2 (because mixin 0.8 needs that and 1.8.9 only provides older versions), includes a transformer to make 0.7 mixin plugins use non-relocated ASM which 0.8 uses, and has patches applied which re-introduce a bunch of internal Mixin 0.7 methods which were frequently called by mods to improve compatibility with mods that have never been updated beyond Mixin 0.7.

Johni0702 added 27 commits July 8, 2025 14:51
This plugin database file exists in multiple input jars, and if we
simply pick one of them then we'll be missing most of the plugins (e.g.
the one that loads xml configs), breaking logging.
If we exclude the file, log4j will fallback to a different path with
does discover enough plugins for it to function correctly.
Because we'll be dropping most of stage1 from our LaunchWrapper version.
So it's always the original IsolatedLaunch classpath, even when
EssentialLoader only adds Essential inside a RelaunchClassLoader.
So it can be accessed even when EssentialLoader relaunches.
We effectively always relaunch on 1.8.9 anyway, and relaunch on 1.12.2
frequently due to other mods, so we may as well just re-launch
unconditionally and clean up all the code we use to determine whether we
need to relaunch and all the old code we use to still get our classes
loaded over others when we don't relaunch.
…assLoader

This has been fixed in Essential in
ea9aa48c5e9058e4525f23200b68e90411b0dda4 (dated 2021).
Ended up deciding against going the mixin route because we had no need
for it, so this comment doesn't actually apply.
The call will be added in a later commit in which all the stage1 mod
setup code is moved to stage2.
We'll want to load stage2 in a dedicated ClassLoader soon, but
`getLogger` will return a different Logger instance depending on the
class loader of the calling class, so we need to explicitly supply the
correct class loader.
(Note that all changes talked about in this commit only apply to
EssentialLoader for LaunchWrapper. Not to EssentialLoader for
ModLauncher or Fabric, those continue operating as they used to.)

This commit rewrites most of Essential Loader for LaunchWrapper to no
longer always implicitly install Essential and instead provide a more
general dependency loading/negotiating mechanism (similar to
fabric-loader's Jar-in-Jar mechanism) useable by third-party mods via a
`essential.mod.json` metadata file.

To this end, this commit practically removes stage1 on LaunchWrapper
because stage1 necessarily interacts with Essential infra to
download/update stage2 and with the Essential mod to ask the user for
permission to do so.
Instead stage2 now has a self-update mechanism where if it finds a more
recent stage2 in any of the discovered jars (e.g. the downloaded
Essential jar), it will re-launch into that newer stage2.
Additionally, this also removes the concept of a pinned stage2 and
instead always includes the stage2 jar inside the stage1 jar. This also
allows using the existing stage1 update mechansim (dropping a file in a
specific location) to permanently update stage1 and therefore stage2 to
a new version on next boot (e.g. if we want to update the Downloading
gui).

To support Essential's auto-updating feature (as well as similar
third-party ones), in addition to plain inner jars, the
`essenital.mod.json` metadata file also supports loading a specific
class from the jar to generate additional dependency specifications.
This class can then e.g. download a newer version of the mod and emit a
dependency specification which points at it.
This commit imports Essential's mixin patches which improve backwards
compatibility of Mixin 0.8.x with mods expecting Mixin 0.7.x internals
and with Minecraft 1.8.9 which has libs older than what Mixin 0.8 needs.

This allows third-party mods to use Mixin 0.8 on 1.8.9 and 1.12.2 that
way it previously was only possible to do with full Essential.
That is, we'll automatically initialize it for mods which bundle it.

This is so mods which previously used Essential's relocated MixinExtras,
for which initialization was handled by Essential, without Essential
without any further changes.
And since we're at it, we'll also provide the same for upstream
MixinExtras to make using it convenient too.
This commit imports various patches/fixes for third-party mods from
Essential, mostly related to them doing something which breaks Mixin.
…Discoverer

Based on `MixinJarDiscoverer` from Essential.
Copy link

github-actions bot commented Jul 8, 2025

Test Results

17 files  +1  17 suites  +1   11m 26s ⏱️ -16s
90 tests  - 5  90 ✅  - 5  0 💤 ±0  0 ❌ ±0 
94 runs   - 5  94 ✅  - 5  0 💤 ±0  0 ❌ ±0 

Results for commit 225b93f. ± Comparison against base commit ee20a37.

This pull request removes 15 and adds 10 tests. Note that renamed tests count towards both.
gg.essential.loader.stage1.Stage1DevEnvTests ‑ testEssentialTweakerModsInDevWithRelaunch(Installation)
gg.essential.loader.stage1.Stage1DevEnvTests ‑ testInDevWithRelaunch(Installation)
gg.essential.loader.stage1.Stage1MixinTests ‑ testMultipleCustomTweakerModsWithMixin07(Installation)
gg.essential.loader.stage1.Stage1MixinTests ‑ testMultipleCustomTweakerModsWithMixin08(Installation)
gg.essential.loader.stage1.Stage1MixinTests ‑ testMultipleEssentialTweakerModsWithMixin07(Installation)
gg.essential.loader.stage1.Stage1MixinTests ‑ testMultipleEssentialTweakerModsWithMixin08(Installation)
gg.essential.loader.stage1.Stage1MixinTests ‑ testWithThirdPartyQueuedMixin07(Installation)
gg.essential.loader.stage1.Stage1MixinTests ‑ testWithThirdPartyQueuedMixin08(Installation)
gg.essential.loader.stage1.Stage1Tests ‑ testMultipleCustomTweakerMods(Installation)
gg.essential.loader.stage1.Stage1Tests ‑ testMultipleEssentialTweakerMods(Installation)
…
gg.essential.loader.stage1.Stage1TweakerTests ‑ testMultipleCustomTweakerMods(Installation)
gg.essential.loader.stage1.Stage1TweakerTests ‑ testMultipleEssentialTweakerMods(Installation)
gg.essential.loader.stage2.RelaunchTests ‑ testRelaunchWithNewerStage1Available(Installation)
gg.essential.loader.stage2.Stage2MixinTests ‑ testMultipleCustomTweakerModsWithMixin07(Installation)
gg.essential.loader.stage2.Stage2MixinTests ‑ testMultipleCustomTweakerModsWithMixin08(Installation)
gg.essential.loader.stage2.Stage2MixinTests ‑ testMultipleEssentialTweakerModsWithMixin07(Installation)
gg.essential.loader.stage2.Stage2MixinTests ‑ testMultipleEssentialTweakerModsWithMixin08(Installation)
gg.essential.loader.stage2.Stage2MixinTests ‑ testWithThirdPartyQueuedMixin07(Installation)
gg.essential.loader.stage2.Stage2MixinTests ‑ testWithThirdPartyQueuedMixin08(Installation)
gg.essential.loader.stage2.Stage2Tests ‑ testOurMixin(Installation)

♻️ This comment has been updated with latest results.

E.g. the `inject(CallbackBridge)` in our `CallbackInjectorCompat` mixin
uses our `CallbackBridge` type instead of Mixin's `Callback` type
because the latter is private.
We already remap `CallbackBridge` to `Callback` everywhere via
`ClassRemapper` after merging the mixin into the target, but we also
need to do that when comparing type descriptors of methods/fields in the
mixin and the target class, otherwise we won't be able to find the
target method/field.
Via a CompatMixin so
1) we can keep them organized in packages, and
2) we can create class nested inside of Mixin classes
This commit adds stubs and custom functionality as required by
MixinBooter to not crash when booting.

Mods which rely on other features only available in MixinBooter's Mixin
fork or which rely on the Mixin version shipped with MixinBooter, may of
course still not function properly. Although we are not currently aware
of any such mod.
This way if a third-party mod ships a MixinExtras version newer than
anything any EssentialLoader-using mod ships, we'll correctly use the
newer version from the third-party mod instead of simply the latest (but
older) version from all our jars.

E.g. MixinBooter currently ships MixinExtras 0.5.0-rc.1, so if an
EssentialLoader-using mod very reasonably ships 0.4.1, we'd simply be
loading 0.4.1 prior to this commit.
@Johni0702
Copy link
Contributor Author

Above commits implement compatibility with the relatively popular (at least on 1.12.2) MixinBooter mod. Since previously we didn't always relaunch on 1.12.2, this wasn't as important; but now that we do, we need to implement a bunch of methods from its Mixin fork into our patched Mixin so MixinBooter doesn't crash on boot.
The last commit also adds support for loading MixinExtras from third-party (mainly MixinBooter again) jars, so we don't just overwrite its version with our (potentially older) one.
The same cannot easily be done for Mixin, so we'll just need to keep on top of updating our Mixin whenever necessary (but Mixin updates only very rarely, so should be fine; and when it does it's often because of ModLauncher and not even relevant to LaunchWrapper).

I'll rebase before merge to have all the Release commits at the end (and to apply any fixup commits should some be necessary).

Copy link

@Traben-0 Traben-0 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm - only have a grammer comment - pending second pass by sam

* to its `EssentialLoaderTweaker` class as a newer stage1 would do.
*
* Having this wrapper is necessary because old a stage1 will add the downloaded jar to the launch and system class
* loader before executing it, which prevents the stage2 self-update mechanism from working because if it tries to
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"old a" grammar

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants