promotional bannermobile promotional banner

Retromod

Retromod Is a Minecraft mod that allows you to play older mods on newer versions.

File Details

Retromod 1.2.0-snapshot.4 (NeoForge 1.21.3)

  • B
  • Jun 25, 2026
  • 3.08 MB
  • 3
  • 1.21.3
  • NeoForge

File Name

retromod-1.2.0-snapshot.4+1.21.3.jar

Supported Versions

  • 1.21.3

Curse Maven Snippet

NeoForge

implementation "curse.maven:retromod-1532616:8320422"
Curse Maven does not yet support mods that have disabled 3rd party sharing

Learn more about Curse Maven

Starts the real Forge -> NeoForge migration for 1.20.1 Forge mods on a NeoForge 26.x host (#85): the registry and event surfaces that nearly every Forge content mod touches. See the roadmap.

Added

  • Forge -> NeoForge registry migration (#85). A 1.20.1 Forge mod's registration and lookup idioms now translate onto NeoForge 26.x. ForgeRegistries.BLOCKS (and the other 13 vanilla registries) is represented as the vanilla BuiltInRegistries registry instance, which serves both Forge idioms at once: DeferredRegister.create(ForgeRegistries.BLOCKS, id) maps onto NeoForge's create(Registry, String), and ForgeRegistries.ITEMS.getValue(loc) / .getKey(v) / .containsKey(loc) map onto the matching net/minecraft/core/Registry methods (getValue is get on 26.x; the others are identical). RegistryObject becomes DeferredHolder. This also removes a pre-existing crash: three shims blanket-redirected ForgeRegistries to (three different, wrong) classes, and any such class redirect rewrote the field-read owner before the field redirect could match, so a transformed mod died at <clinit> with NoSuchFieldError: NeoForgeRegistries.BLOCKS. Those broken redirects are gone, and IForgeRegistry is supplied as an empty synthetic marker interface for stray type references. Every mapping (field types, create overloads, Registry method names) was verified against the real NeoForge 26.1 jar. NeoForge runtime only; transform-tested (ForgeRegistryMigrationTest).
  • Forge -> NeoForge event-package migration (#85). NeoForge forked Forge's event package wholesale (net.minecraftforge.event to net.neoforged.neoforge.event), keeping most class names. Retromod now class-redirects 308 cleanly-renamed event classes (core event/** and client/event/**, top-level and inner), generated by diffing the real Forge 1.20.1 and NeoForge 26.1 jars and shipped as the forge-event-renames.json data table. The handful NeoForge genuinely renamed or merged (EntityJoinWorldEvent to EntityJoinLevelEvent, LivingHurtEvent to LivingDamageEvent, world/* to level/*, and so on) stay hand-mapped and take precedence over the bulk table. Events that were reshaped rather than renamed (the TickEvent phase to Pre/Post redesign, the capability system) are out of scope: a class redirect can't rewrite a changed handler body. NeoForge runtime only; transform-tested (ForgeEventMigrationTest).
  • Forge -> NeoForge DistExecutor + Dist markers (#85). DistExecutor (the client/server-split helper, DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> clientOnly())) was deleted in the Forge -> NeoForge split, so there is no class to redirect to. Retromod now supplies it as a per-mod-embedded synthetic that reimplements all nine run/call/forDist methods (plus the safe* variants' serializable SAMs) by delegating to FMLEnvironment.getDist(). The Dist enum itself is package-renamed (net.minecraftforge.api.distmarker.Dist -> net.neoforged.api.distmarker.Dist) and @OnlyIn mapped to a no-op, both wired into the migration shim so they apply on the NeoForge runtime path (the equivalent AnnotationPolyfill redirects only run on Fabric and the CLI). NeoForge runtime only; transform-tested (ForgeNeoForgeSyntheticsTest).
  • Forge -> NeoForge FML lifecycle migration (#85). Every @Mod runs its setup through FML, which NeoForge package-renamed from net.minecraftforge.fml.* to net.neoforged.fml.*. Retromod now class-redirects the 20 mod-facing FML classes NeoForge kept under the same name: the lifecycle and config events (FMLCommonSetupEvent, FMLClientSetupEvent, FMLConstructModEvent, FMLDedicatedServerSetupEvent, FMLLoadCompleteEvent, InterModEnqueueEvent/InterModProcessEvent, ModConfigEvent and its inners) plus IExtensionPoint, InterModComms, ModList, and ModContainer. The table (forge-fml-renames.json) was generated from the NeoForge loader jar (every target verified present) and excludes the FML classes already handled (ModLoadingContext/FMLPaths/@Mod in the migration shim, the FMLJavaModLoadingContext synthetic). This closes the gap the event migration left: it covered event/** but not fml/event/**, where the setup events that every Forge mod subscribes to actually live. NeoForge runtime only; transform-tested (ForgeEventMigrationTest).

Fixed

  • Retromod's mods/Retromod/ locator crashed NeoForge 1.21.x servers at boot (#100). The locator (the #78 mods-subfolder feature) called ILaunchContext.gameDirectory(), which only exists on NeoForge loader 11.x (MC 26.x); on loader 4.x (MC 1.21.x) that method is absent, so the call threw NoSuchMethodError during mod discovery and crashed the server before any mod loaded. The game directory is now resolved version-safely (reflectively off the launch context where the method exists, otherwise FMLPaths.GAMEDIR, the same route the Forge locator and in-game screen already use), so Retromod itself loads on 1.21.x again. Verified against the real NeoForge 21.1.x and 26.x loader jars; regression-tested (RetromodModLocatorTest asserts no direct gameDirectory() call survives in the compiled locator).
  • The NeoForge in-place runtime transform applied no polyfills (now it does, safely). PolyfillRegistry.loadAndRegister ran only on the Fabric entry, so a NeoForge user transforming mods at runtime silently missed every PolyfillProvider: the Forge -> NeoForge Dist/@OnlyIn redirects (the AnnotationPolyfill ones, distinct from the hand-wired migration-shim copy added earlier this snapshot), the removed-vanilla-class bridges, and the third-party API bridges (GeckoLib 3->4, JEI, EMI, ...). RetromodNeoForge now loads them, mirroring the Fabric entry. The 36 providers were re-audited for a Mojang-named runtime, where CLAUDE.md #17 inverts (Mojang-keyed redirects that are dead against intermediary Fabric mods are LIVE against NeoForge mods): intermediary (class_XXXX) and old-MCP/Yarn-named redirects no-op, the Forge -> NeoForge migration applies, and the version-specific removed-class providers (DirectionProperty, LazyLoadedValue, the RenderType accessors, the ItemStack NBT methods) now self-gate on RetromodVersion.TARGET_MC_VERSION so they only fire where the class is actually gone, not on a pre-removal host. The neoforge-category transfer polyfills stay disabled on this path: the host-gated NeoForge_1_21_8_to_1_21_9 shim already owns the 1.21.9 transfer-API rework, so running both would conflict and the un-gated polyfill would NoClassDefFoundError on a pre-1.21.9 host. NeoForge runtime; transform-tested (RetromodNeoForgePolyfillTest, plus host-gating in BlockPropertyPolyfillTest).
  • OptiFine-compat hardening. Closed several hangs in the OptiFine compatibility path: infinite wait-loops in the compatibility dialog (and its broken cancel path) and in five config parsers, an AOT-prompt dialog that could hang, and a hardcoded version reference.

Notes

  • This is the registry + event slice of the Forge -> NeoForge migration. The Forge-host branch (a Forge 26.x runtime, which needs a Forge 26.x jar to verify against) and the deeper API redesigns remain follow-ups; see CLAUDE.md #13.