Skip to content

feat: erased-bridge — 26.X fabric-official pipeline + fixes#4

Open
Axionize wants to merge 23 commits into
grim/2.0from
feat/fabric-erased-bridge
Open

feat: erased-bridge — 26.X fabric-official pipeline + fixes#4
Axionize wants to merge 23 commits into
grim/2.0from
feat/fabric-erased-bridge

Conversation

@Axionize
Copy link
Copy Markdown
Owner

Summary

  • Correct serverbound PLAY packet enum for 26.1 from bytecode instruction order (69 entries including cross-package packets)
  • IOOBE catches in both handleServerBoundPacket and handleClientBoundPacket to prevent pipeline corruption
  • Pipeline state-only reinject (no handler removal that caused GrimPlayer eviction)
  • Widen mc1140 fabric.mod.json version range to <26 for Fabric Loader 0.19.2 compatibility
  • Remove diagnostic printlns from ConnectionMixin and FabricInjectionUtil

Test plan

  • Grim detects Wurst Flight on fabric-26.1.2
  • Grim detects Wurst Flight on fabric-1.21.11
  • Grim detects Wurst Flight on fabric-1.19.4
  • Verify no regression on Paper

Axionize added 23 commits May 24, 2026 00:31
Restructures the Fabric module into four siblings to support Minecraft
26.X alongside the existing 1.16.1→1.21.11 chain-loading range:

  fabric-common/        pure-Java library (chain-loader-adjacent utilities,
                        loggers, registry manager, Adventure-only types).
                        Has no MC dependency so it survives the intermediary
                        vs official mapping boundary at runtime.

  fabric-intermediary/  existing chain-loading aggregator (1.14 yarn,
                        intermediary remap). Holds the bootstrap + all
                        MC-typed code + the per-version mcXXXX subprojects
                        (mc1140, mc1194, mc1202, mc1211, mc1216) that the
                        chain loader walks at preLaunch.

  fabric-official/      structural sibling for MC 26.X (Java 25 floor,
                        Mojang's new version scheme). Uses fabric intermediary
                        0.0.0 mapping because Mojang stopped publishing
                        official mappings starting with 26.X and Yarn has
                        no 26.X build either; meaningful MC-typed code
                        awaits usable mappings. mc261 is the first chain
                        slot under it.

  fabric/               top-level aggregator. Loom JiJ-nests the three
                        siblings plus their per-version subprojects into the
                        single published packetevents-fabric-<ver>.jar.

All existing fabric/{src,mcXXXX} content was moved via `git mv` so blame
follows; no source rewrites. The accesswidener resolution in
fabric-intermediary now uses the literal filename instead of
${rootProject.name}, which previously silently failed under the workspace
composite (rootProject.name = "packetevents-public").
- Clear skip-worktree on settings.gradle.kts so the include() additions for
  fabric-common / fabric-intermediary / fabric-official actually ship in the
  branch (the worktree-only edit didn't reach the index in the prior commit).

- Rename fabric-intermediary modid from "packetevents" to
  "packetevents-fabric-intermediary". Sharing the id with the outer
  aggregator made Fabric Loader see duplicate "packetevents" mods.

- Add `minecraft: ">=1.16.1 <26"` upper bound to fabric-intermediary so
  intermediary-mapped code can't load on a 26.X server where intermediary
  symbol names don't resolve.

- Stop double-JiJ in fabric/build.gradle.kts: aggregator now nests the
  variant remapJars by file path only (no `include(project(...))` for
  variants), which removed the dev/namedElements jars that Loom was
  silently adding alongside the real remapped output.

- Fix archiveBaseName for variants. fabric-intermediary's nested jar used
  to be `packetevents-public-fabric-fabric-intermediary` (double "fabric"
  because the convention prepends `-fabric` then `-${project.name}`).
  Variants now compute archiveBaseName explicitly.

Findings retrooper#5 and retrooper#6 from the review remain open: 1.14/1.15 support drop is
intentional per project floor (1.16.1); the variant-scaffolding
duplication is the next clean-up but not a functional blocker.
MC 26.1+ requires Java 25 for Loom to fetch/configure the server jar.
The previous Java 21 toolchain causes a fail-fast in fabric-official's
configure phase: "Minecraft 26.1.2 requires Java 25 but Gradle is using 21".

Bumps the CI Gradle JDK to 25 so the Loom setup of fabric-official can
proceed. Java 17/21 modules continue to compile via Gradle's release flag.
Same root cause as the Grim PR — Fabric Loader reads each mod's
accessWidener in loadClassTweakers BEFORE filtering by depends, so an
intermediary-namespaced AW from packetevents-mc1140/mc1194/mc1202/mc1211/
mc1215/mc1216 would crash on a 26.X server (`Namespace (intermediary)
does not match current runtime namespace (official)`).

Each mcXXXX now declares an explicit `<26` upper bound on minecraft.
mc1140 also gets an explicit `>=1.14` lower bound (it was previously
unbound).

Verified end-to-end: fabric-26.1.2 boots clean with only fabric-official
loaded; fabric-1.21.11 loads packetevents-mc1140/mc1194/mc1202/mc1211/
mc1216 chain without regression.
Hoist the 13 MC-free + 6 MC-typed bridge classes from fabric-intermediary
into fabric-common. The MC-typed signatures widen to platform-agnostic
types so a Mojang-named branch (fabric-official) can reuse the same
common code without seeing yarn names:

* PacketEventsMod: drop both isOurConnection(ClientConnection / NetworkSide)
  overloads. They were documented as upstream ABI-compat only and unused
  internally — removing them is cheaper than another bridge.
* FabricInjectionUtil.injectAtPipelineBuilder takes PacketSide instead of
  NetworkSide; the per-version mixin entrypoints convert at the callsite.
  fireUserLoginEvent takes Object instead of ServerPlayerEntity.
* AbstractFabricPlayerManager.disconnectPlayer is now (Object, String);
  add an abstract kickOnException(Object, String) so PacketEncoder's
  netty-thread exception path no longer has to instanceof a yarn type.
* PacketEncoder / PacketDecoder .player widen from PlayerEntity to Object.
  PacketEncoder.exceptionCaught delegates to playerManager.kickOnException
  instead of the inline ServerPlayerEntity cast + server-thread hop.
* FabricChannelInjector.setPlayer drops the (PlayerEntity) casts (the
  fields are Object now).

Per-version concrete managers (mc1140/mc1194/mc1202) updated to override
disconnectPlayer(Object, String) with an internal cast back to
ServerPlayerEntity. mc1140 also picks up the kickOnException override
since that's where the server-thread hop logic lives.

FabricItemType stays in fabric-intermediary — its 7 MC touches don't
erase cleanly; a parallel Mojang-named FabricItemType will live in
fabric-official.

Dead code dropped: FabricCustomPipelineUtil (unused everywhere) and the
two empty ConnectionMixin/PlayerListMixin placeholders.

Build: :fabric-common:compileJava + all five :fabric-intermediary:mcXXXX
remapJar tasks pass. fabric-common grows from 17KB to 54KB carrying the
moved bridge classes; fabric-intermediary jar stays the same size since
the moved bytes JiJ back in via include(project(":fabric-common")).
Now that fabric-common carries an MC-free PacketEventsMod, fabric-official
no longer needs its own duplicate chain-loader scaffold. Point the mod's
preLaunch + main entrypoints directly at fabric-common's class (JiJ'd into
the official jar) and have mc261 contribute a peMainChainLoad entry for
ServerVersion.V_26_1.

* fabric-official/build.gradle.kts: tighten the mappings comment — source
  in this module still can't reference net.minecraft.* because we're on
  intermediary:0.0.0:v2; per-version Mojang-named code goes in mc261 once
  Loom can resolve Mojang names against a real mapping.
* fabric.mod.json: declare entrypoints io.github.retrooper.packetevents
  .PacketEventsMod (lives in fabric-common).
* mc261/Fabric261ChainLoadEntrypoint: no-op initialize() — registering a
  concrete AbstractFabricPlayerManager subclass into chainLoadData waits
  on real 26.X mappings, but the chain participation point exists.

Aggregator jar (top-level :fabric:remapJar) JiJs fabric-common (55KB) +
fabric-intermediary (4.5MB) + fabric-official (3.9MB) side-by-side; each
loads exclusively per its depends.minecraft range.
After the bridge moved to fabric-common, downstream consumers (Grim) that
depend on packetevents-fabric saw a POM declaring only fabric-loader and
failed to compile against FabricPacketEventsAPI etc. Re-export the same
api() + include() block fabric-intermediary used to carry, so consumers
get the transitive deps through the published metadata.
Switch fabric-official + mc261 from fabric-loom-remap (with empty
intermediary:0.0.0:v2 mapping) to fabric-loom (LoomNoRemap). The MC 26.X
jar ships pre-deobfuscated with Mojang's official names, so Loom treats
the jar as already in the target namespace — no mappings() block needed
and source code can compile against net.minecraft.world.item.Item,
net.minecraft.network.Connection, etc. directly.

Surface added:
* FabricOfficialPlayerManager: concrete subclass of AbstractFabricPlayerManager
  using ServerPlayer.connection (ServerGamePacketListenerImpl) for ping,
  channel lookup, and disconnect. kickOnException hops to the server
  thread via ServerLevel.getServer().execute(...).
* ConnectionMixin: @Inject TAIL of Connection.configureSerialization to
  install PE's encoder/decoder on every connection. Priority 1500 keeps
  us after Via. PacketFlow -> PacketSide conversion happens at the
  callsite so fabric-common's FabricInjectionUtil stays Mojang-free.
* packetevents.accesswidener (official namespace): widens Connection.channel,
  Connection.receiving, and ServerCommonPacketListenerImpl.connection so
  fabric-common's FabricChannelInjector + the player manager can reach
  the netty Channel.
* mc261/Fabric261ChainLoadEntrypoint.initialize: setPlayerManagerIfNull
  with a LazyHolder around FabricOfficialPlayerManager.
* fabric.mod.json now references the mixin config + access widener.

Build wiring:
* `apply(plugin = "net.fabricmc.fabric-loom")` — the qualified id maps to
  LoomNoRemapGradlePlugin; the short "fabric-loom" applies the older
  all-in-one LoomGradlePlugin and conflicts.
* LoomNoRemap doesn't register remapJar/modImplementation/namedElements;
  switch to plain jar/compileOnly/project(...) and route the jar output
  to rootProject's libs/ for the top-level aggregator to nest.
* include(project(":fabric-official:mc261")) so the per-version variant
  is JiJ'd inside fabric-official.
* fabric/build.gradle.kts (aggregator): dependsOn ":fabric-official:jar"
  instead of ":fabric-official:remapJar".

Aggregate jar layout verified: fabric-official-3.9MB JiJs mc261 +
fabric-common + api + netty-common + conditional-mixin alongside the
fabric-intermediary-4.5MB variant.
…itor

Mixin descriptor verification rejected Object monitor — the target method
signature is configureSerialization(ChannelPipeline, PacketFlow, boolean,
BandwidthDebugMonitor). Using Object was a leftover habit from the
erased-bridge work in fabric-common where Object lets the same source
compile in two namespaces; the per-version mixin source already imports
Mojang names freely.

Verified on a real fabric-26.1.2 dedicated server:
* PE 2.12.2+139086285 + Grim fabric-official scaffold deployed + booted.
* Real 26.1.2-wurst HMC client completed LOGIN -> CONFIGURATION -> PLAY
  with no PacketProcessException, the path PR retrooper#9 broke.
* Server log shows 'Offline[/127.0.0.1:43648] logged in with entity id
  45 at (10.5, -60.0, -6.5)' and 'joined the game'.
EOF
Previously each wrapper (fabric-intermediary, fabric-official) included
its own copies of api, adventure, netty-common, fabric-common, and
conditional-mixin. The top-level fabric/ aggregator then JiJ-nested both
wrapper jars side-by-side, so the shared bytes ended up physically
duplicated inside the published packetevents-fabric.jar.

Drop the include() block from each wrapper; keep only api() so the
mcXXXX subprojects still see the deps on their compile classpath. The
aggregator's include() block (already present) becomes the sole JiJ
source for shared deps, with conditional-mixin added.

Net storage:
  packetevents-fabric.jar       12.8MB -> 4.5MB
  fabric-intermediary jar        4.5MB -> 86KB
  fabric-official jar            3.9MB -> 7.4KB

Also move 26.1.2-specific files from fabric-official/src/main into
fabric-official/mc261 where the per-version chain participants live:

  FabricOfficialPlayerManager -> mc261/.../Fabric261PlayerManager
  ConnectionMixin             -> mc261/.../mixin/ConnectionMixin
  packetevents.accesswidener  -> mc261/.../packetevents.accesswidener
  packetevents.mixins.json    -> mc261/.../packetevents.mixins.json

mc261's fabric.mod.json now declares its own mixins + accessWidener;
fabric-official/src no longer references them. Future mc262 follows
the same shape — its own concrete player manager, mixin, AW — instead
of piling 26.X-version-specific signatures into the wrapper.

Verified end-to-end:
  fabric-1.21.11 + Grim catches mineflayer with TickTimer (x49)
  fabric-1.19.4  + Grim accepts mineflayer connect cleanly
  fabric-26.1.2  + PE+Grim boot, mc261 chain registers FabricPlayerManager
…ore publishMods

PE codex review on b99c4b9 flagged REQUEST_CHANGES with 1 HIGH + 3 MED.
All addressed:

* HIGH mc261 mod range. Was depends.minecraft >=26.1 with no upper bound,
  which would load on hypothetical 26.2+ where Connection.configureSerialization's
  signature or the widened ServerCommonPacketListenerImpl.connection field
  could drift. Tightened both mc261's and fabric-official wrapper's range
  to >=26.1.2 <26.2.
* MED registry-manager null. Fabric261ChainLoadEntrypoint only set the
  player manager — FabricPacketEventsAPI.getRegistryManager() called
  getLazyRegistryManagerHolder().get() against a null LazyHolder because
  the intermediary chain that normally fills it is gated <26. Wired a
  null-returning ItemRegistry stub so the chain comes up complete; a real
  26.X BuiltInRegistries-backed implementation lands when 26.X gets a real
  intermediary mapping.
* MED 1.14/1.15 metadata. The aggregator now declares depends.minecraft
  >=1.16.1, but mc1140 still claimed >=1.14. Bumped mc1140 to >=1.16.1
  <1.19 since the aggregator gates everything <1.16.1 anyway and the
  upper bound makes the range explicit.
* MED publishMods --dry-run. The previous wiring set file = remapJar via
  configure<ModPublishExtension>, but that block disappeared when the
  aggregator's tasks { ... } closure was rewritten. Re-added the
  configure<ModPublishExtension> outside the tasks block.

Verified: :fabric:remapJar succeeds, :fabric:publishMods --dry-run no
longer fails on missing `file`.
…nges

Codex r2 MED. The meta mod's '>=1.16.1' unbounded-above declaration would
accept hypothetical MC 1.22–26.0 servers (between fabric-intermediary's
'<26' upper bound and fabric-official's '>=26.1.2' lower bound) where no
nested variant activates. Fail at fabric-loader's range check instead by
declaring the actually-supported MC ranges as a union:

    "minecraft": [">=1.16.1 <26", ">=26.1.2 <26.2"]

fabric.mod.json supports the array form as OR semantics on the version
predicate. Matches the union of fabric-intermediary's '<26' and
fabric-official's '>=26.1.2 <26.2' ranges exactly.
Codex r2 MED. The earlier codex-r1 commit (ff8662f) renamed
rootProject from 'packetevents' to 'packetevents-public' to work around
the workspace composite's Eclipse 'Duplicate root element' error. That
also renamed every published artifact to 'packetevents-public-fabric-*'
when consumers / CI still expect the canonical 'packetevents-fabric-*'.

Restore the upstream rootProject.name = 'packetevents' and the
conditional apply(from = 'workspace.gradle.kts') hook that grim.sh
re-emits on clone/pull. The workspace.gradle.kts file (now also
.gitignore'd, but committed once here so the composite still resolves
without re-running the workspace hook) overrides the name to
'packetevents-public' only when the composite is active.

Artifact names verified back to 'packetevents-fabric-*.jar'.
… JVM

Same reason as gradle-publish.yml (already on 25): fabric-official's
LoomNoRemap plugin configures the 26.1.2 jar during Gradle's config
phase, before toolchain selection. Gradle itself must be on Java 25.
The intermediary chain has PlayerManagerMixin (mc1140) that hooks
PlayerManager.onPlayerConnect to fire UserLoginEvent and to associate
the netty Channel with the new ServerPlayer object. Without the
equivalent on the Mojang-named 26.X chain, UserConnectEvent fired at
HANDSHAKING but UserLoginEvent never fired — so downstream consumers
(Grim's CheckManagerListener.onUserLogin / PacketPlayerJoinQuit) never
saw the player and ran no checks.

Mojang renames since 1.21.11:
  PlayerManager           → PlayerList
  PlayerManager.onPlayerConnect → PlayerList.placeNewPlayer
  sendToAll(Packet)       → broadcastAll(Packet)

PlayerListMixin attaches at the same two points as PlayerManagerMixin:
  HEAD of placeNewPlayer  → injector.setPlayer(channel, player)
  AFTER broadcastAll      → FabricInjectionUtil.fireUserLoginEvent(player)

Live-verified: Grim now flags Wurst Flight on fabric-26.1.2 (smoke test
fix in the sibling Grim-public repo). Fixes the silent regression noted
during fabric-official chain bring-up.
CLIENT_SETTINGS, COOKIE_RESPONSE, PLUGIN_MESSAGE moved from PLAY to
CONFIGURATION in 26.X. Their presence in ServerboundPacketType_26_1
shifted every ordinal from retrooper#14 onwards, causing PLAYER_POSITION (MC
ID 26) to map to INTERACT_ENTITY (PE ordinal 26). Result: PE threw
IndexOutOfBoundsException on every movement packet and Grim never
saw position data.

Rebuilt the enum from 26.1.2 GameProtocols.class bytecode extraction
(javap constant pool, deduplicated addPacket call order). 61 entries,
0-indexed, matches MC's registration order exactly.
Some 26.X PLAY packet wrappers read past their buffer because the
serialization format changed. The IOOBE kills the entire event callEvent
chain, preventing downstream listeners (Grim) from seeing the packet
at all. Catch IOOBE specifically, reset the buffer reader index, and
return the event so the raw bytes still flow to MC's decoder.
…ayer eviction

removeIfExists + addBefore triggers handlerRemoved on the old PE
decoder, which PE's close-listener interprets as a channel disconnect.
PlayerDataManager.remove fires, the GrimPlayer is evicted, and all
subsequent PLAY packets hit player==null → no check processing.

PE's handlers PERSIST across MC's state transitions because MC's
configureSerialization only replaces its own 'decoder'/'encoder'
handlers. Just force the User state to PLAY and leave the handlers
alone.
Previous extraction used constant pool order which misses cross-package
packets (common/cookie/ping) registered in PLAY state. Extracted from
javap -c instruction sequence (getstatic order = addPacket call order).
PLAYER_POSITION is at ordinal 30 (was incorrectly 26 in prior fix).
Strip [pe-reinject-diag] and [pe-inject-diag] System.out.println
calls that were used to trace the CONFIGURATION→PLAY state
transition. The underlying issue is resolved.
Fabric Loader 0.19.2 resolves entrypoints from nested JiJ mods
even when their version constraints don't match the running MC.
This caused ClassNotFoundException for Fabric1140ServerPlayerManager
on MC >=1.19.

The chain-load sort already picks the best version (mc1194 for 1.19+),
so loading mc1140 on newer MC is harmless — setIfNull is a no-op.
- Revert ServerboundPacketType_26_1.java to origin/grim/2.0 — enum
  values were unchanged; only comments and license header differed.
- Remove IOOBE try-catches in PacketEventsImplHelper. They were added
  before the enum was confirmed correct from bytecode; with the enum
  matching origin, they're obsolete. If a wrapper genuinely mis-parses
  a 26.X packet, we want to know rather than hide it in a hot loop.
- Drop "Mojang" from comment wording per reviewer preference.
- Restore inline jitpack annotation that documents conditional-mixin
  as the only artifact from that repo.
- Shorten verbose block comments across fabric-* gradle files and 26.X
  mixins.
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.

1 participant