File Details
Retromod 1.2.0-Snapshot.7 (NeoForge 26.1.2)
- R
- Jul 2, 2026
- 3.14 MB
- 3
- 26.1.2
- NeoForge
File Name
retromod-1.2.0-snapshot.7+26.1.2.jar
Supported Versions
- 26.1.2
Curse Maven Snippet
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. (1) The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for Forge 1.21.1, now fully loads on NeoForge 26.2 - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). (2) YUNG's API + Better Dungeons (Fabric 1.21.1) now register AND generate on a 26.2 dedicated server: /locate finds a YUNG structure and /place builds one in-world with zero registry, parse, or generation errors.
Added
- Forge -> NeoForge: the construct + register spine. A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
FMLJavaModLoadingContextbridge (#85/#115).get().getModEventBus()is the first line of nearly every Forge@Modconstructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates toModLoadingContext.get().getActiveContainer().getEventBus(). The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on both the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.- Registry-id bridge (#87). MC 1.21.3+ requires the registry id stamped on a Block/Item
Propertiesbefore the object is built, but Forge'sDeferredRegister.register(String, Supplier)constructs it inside the supplier with no id (soRegisterEventfails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-awareregister(String, Function)overload, and thePropertiesfactories (of/ofFullCopy/ofLegacyCopy,new Item.Properties()) stampsetIdfrom it. Verified in-game (blocks/items register). - Extension-interface + lifecycle/server event migration.
IForgeItem/IForgeBlock/IForgeEntity/IForgeBlockEntity-> NeoForge'sI*Extension;FMLCommonSetupEvent,FMLClientSetupEvent(->net/neoforged/fml/event/lifecycle/*),ServerStartingEvent(-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- 26.2
ColorCollectionconsolidation (all loaders). 26.2 folded each per-color family (16 colors) ofBlocks/Itemsconstants into a singleColorCollectionfield and removed the per-color statics, so a 1.21.x mod readingBlocks.BLACK_WOOL(orItems.WHITE_CARPET, ...) hitsNoSuchFieldError. The 26.1->26.2 shim already rewrote four block families to<field>.pick(DyeColor.<COLOR>)accessors; this extends it to all 14 block families and 14 item families (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g.WOOLvsDYED_CANDLE) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (DyedBlockAccessorTest); it was the last blocker to the Macaw's loading in-game. - Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2. Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7:
IEventBusand the oldEventbase are gone (BusGroup+ per-typeEventBus<T>instead), so every old Forge mod died at construction. A syntheticLegacyEventBusinterface (so the mod's interface call sites stay verifiable) bridges the old idiom ontoBusGroup:getModEventBus()->getModBusGroup()wrap,DeferredRegister.register(bus)->register(BusGroup),MinecraftForge.EVENT_BUS-> aBusGroup.DEFAULT-backed bridge, old@SubscribeEvent-> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's ownBUSreflectively;@Mod.EventBusSubscriberclasses EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED theIForgeItem-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes fromRegistryObject.getKey()), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2. - EventBus 7: lambda setup listeners + priority overloads now bind (#101). The final piece of the EventBus 6->7 bridge.
bus.addListener(this::onSetup)compiles to aLambdaMetafactoryinvokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by aConsumer-producing indy is retargeted to a typed helper with the lambda's reified event type appended as aClassconstant, so it resolves the event's ownBUSexactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges'FMLCommonSetupEvent/FMLClientSetupEventlisteners now bind). Also added: EventBus 6's deletedEventPriorityenum is supplied as a real-enum stand-in (constants,values()/valueOf(), enum-switch all work) carrying the matching EventBus 7Prioritybyte, and the bridge gained the priority-carryingaddListeneroverloads ((EventPriority, Consumer),(EventPriority, boolean, Consumer),(EventPriority, boolean, Class, Consumer)). Tested (ForgeEventBusBridgeTest). - Forge entry point never loaded the polyfills.
RetromodForgewas missing thePolyfillRegistrywiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24DirectionPropertybridge) silently never applied on Forge hosts - surfaced on Forge 26.2 asNoSuchMethodError: EnumProperty.create(String, Direction[])killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge). - Sinytra Connector techniques (clean-room, credited). A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an offline Forge -> NeoForge mode (
--target-loader neoforge) so the migration runs from the CLI without a live NeoForge host. - Fuzzy-resolver report for offline bridge authoring (
config/retromod/fuzzy-report.tsv). The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run):NEARrows are the bridge-authoring backlog,AUTOrows are applied auto-redirects to review,SUPPRESSEDrows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (FuzzyMatchReportTest). - 1.12.2 metadata generation:
mcmod.info->mods.toml(#79). A pre-1.13 Forge mod ships onlymcmod.info(themods.tomlformat postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran.ForgeModTransformernow synthesizes amods.tomlfrommcmod.info(id/version/name/description extracted by regex, ranges relaxed to[1,)), whichpromoteToNeoForgeTomlthen renames toneoforge.mods.tomlon a NeoForge host. This is the first 1.12.2 in-game prerequisite found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (McmodInfoTomlGenTest).
Fixed
- Post-review hardening (multi-agent code + security review of this cycle). Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's
Registry.registercall would otherwise dieIncompatibleClassChangeErrorat its first registration on 26.2 targets); the 26.x became-interface opcode fixes (IntProvider/FloatProvider) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate;ForgeCorePolyfillno longer class-redirects a LIVEMinecraftForgeon real Forge hosts; the CLI syncs its default target version into the shim gates (previously only--targetruns did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets. - Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning. Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (
ClassLoader.getResources("com/example")) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based@AutoRegistersystem scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern. - Classes no redirect touches now ship byte-identical. The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to
java/lang/Objectand the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died withVerifyErrorinConstPool.readOne, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (UntouchedClassPreservationTest). IntProvider/FloatProviderbecame interfaces on 26.x, and their static range-codec factories moved.INVOKEVIRTUALon them is now auto-corrected toINVOKEINTERFACE(and, on the fast path too, static calls get theInterfaceMethodrefflag - a static method on an interface dies withIncompatibleClassChangeErrorotherwise). The removedIntProvider.codec(min,max)/FloatProvider.codec(min,max)factories are bridged to the 26.x companion dispatch codecs (IntProviders.CODEC/FloatProviders.CODEC; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.- Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server. On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the
STRUCTURE_PROCESSORregistry now holdsMapCodecs directly, so a 1.21.x mod'sStructureProcessorTypeSAM-lambda registration broke everyprocessor_listthat referenced it - a new bridge interceptsRegistry.registerinto that registry and converts the value via its oldcodec()SAM (registering the codec, returning the mod's original value so its fields still hold); (2)StructureProcessoritself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewritesextendstoObject+implements, including thesuper()constructor call; (3)IntProvider/FloatProvider's static codec factories and constants moved toIntProviders/FloatProviderswith identical signatures (plain owner redirects; also theIntProvider.CODECmiss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound. - Worldgen generation: YUNG structures now generate in-world on 26.2, not just register. Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during
/placeand/locate: (1)StructurePoolElement.getShuffledJigsawBlocksstill returns aList, but its elements are nowStructureTemplate$JigsawBlockInfowrappers (26.1) instead ofStructureBlockInfo, so a 1.21.x caller's element cast diedClassCastException- a bridge unwraps each element viaJigsawBlockInfo.info(); (2)JigsawBlock.canAttachwas re-typed from(StructureBlockInfo, StructureBlockInfo)to(JigsawBlockInfo, JigsawBlockInfo)- a bridge wraps both args viaJigsawBlockInfo.of; (3) MC renamedRegistry.getHolder/getHolderOrThrowtoget/getOrThrow(now inherited fromHolderGetter), so a 1.21.1 mod's holder lookup during structure assembly diedNoSuchMethodError- fixed as a same-owner rename. Result:/locate structurefinds a YUNG structure and/place structuregenerates one in-world with no errors. Tested (IsOverloadBridgeTest). - The fuzzy resolver no longer rewrites a reference that already resolves. The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure:
Registry.get(ResourceKey)(inherited fromHolderGetter, returns anOptional<Holder>) was rewritten to the value getterRegistry.getOptional, and YUNG's worldgen diedClassCastException: StructureTemplatePool cannot be cast to Holderat the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (FuzzyMatchReportTest). - Placement crash: 26.1 removed the single-arg
is()overloads.BlockState.is(Block)/is(TagKey),ItemStack.is(Item)/is(TagKey), and all ofFluidState.is(...)are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies withNoSuchMethodErrorat first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (Bridge_Block.onPlace). A per-mod synthetic (IsOverloadBridge) now carries faithful static reimplementations of the removed overloads (viagetBlock()/getItem()/getType()identity andbuiltInRegistryHolder().is(...)tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (IsOverloadBridgeTest). - Invisible items: 1.21.4+ client item definitions are now synthesized. MC 1.21.4 split "client item" definitions out of item models: every item id needs an
assets/<ns>/items/<id>.jsonnaming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only shipmodels/item/*.json, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (ModDataMigratorTest). - Retromod didn't initialize on NeoForge 26.2 (#90). Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for
@Modscanning, soRetromodNeoForgenever constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays;mods/Retromod/CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all. IForgeItem->ForgeItemshim conflict on NeoForge. The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration'sIForgeItem->IItemExtension(last-writer-wins across ServiceLoader shims), leaving anet/minecraftforge/common/extensions/ForgeItemreference that exists on no NeoForge classpath ->NoClassDefFoundErroron a custom Item. The forge-extension renames are now gated to Forge hosts only.MinecraftForge.EVENT_BUSNoSuchFieldErroron NeoForge. A polyfill class-redirectedMinecraftForgeto a capabilities shim with noEVENT_BUSfield, clobbering the migration's correctMinecraftForge->NeoForge(which has the field). Gated off on NeoForge so a Forge mod'sMinecraftForge.EVENT_BUS.register(this)resolves.- Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114). The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1)
ConditionalEffect.codec(Codec)dropped itsContextKeySetvalidation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and theLootItemFunctionType/LootItemConditionTypewrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON ("type"->"minecraft:entity_type",flags/location/movement/... ->minecraft:*), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing intovehicle/passenger/targeted_entityand advancement predicate slots). Two new intermediary IDs (TooltipDisplay,Weapon) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (ModDataMigratorTest). - The AOT cache now truly auto-clears when Retromod changes. The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every
.classfile inconfig/retromod/aot-cache/blindly, and the full-AOT cache (retromod-cache/full-aot/) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (.cache-stamp, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to rememberrm -rf config/retromod/aot-cache/anymore. Tested (AotCacheStampTest). - CLI transform: the "all shims" fallback didn't embed synthetics. When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7
LegacyEventBus) shipped with dangling references (NoClassDefFoundErrorat load). It now embeds on that path too, matching the version-detected path.