Skip to content

feat(edition): tell backends which players came from Bedrock - #35

Merged
lusu007 merged 1 commit into
mainfrom
feat/bedrock-edition-property
Aug 23, 2026
Merged

feat(edition): tell backends which players came from Bedrock#35
lusu007 merged 1 commit into
mainfrom
feat/bedrock-edition-property

Conversation

@hbrombeer

Copy link
Copy Markdown
Member

Backends currently cannot tell a Bedrock player from a Java one except by guessing from the UUID shape, and that guess is wrong for linked accounts. This puts the answer inside the signed forwarding payload.

The problem

Geyser terminates the Bedrock session and connects to velocity-bedrock as an ordinary Java client. Velocity's modern forwarding then carries a UUID, a username, skin properties and an address — no edition, and no free slot for one. So backends fall back to the shape of the Floodgate UUID (mostSignificantBits == 0), which is what library-gui and duel's anti-cheat exemption do today.

That is right for unlinked players and silently wrong for linked ones: Floodgate builds the forwarded profile from getCorrectUniqueId(), so a linked Bedrock player arrives under their Mojang UUID and is shaped like anyone else.

Not a corner case — the Floodgate build we pin (containers/plugin-floodgate, 2.2.5 build 140) ships:

player-link:
  enabled: true
  enable-own-linking: false
  enable-global-linking: true   # GeyserMC's central link service

Global linking needs no local database, so the emptyDir over /app/plugins does not suppress it — and a global link is one the player may have made on any Geyser server.

What this does

Stamps grounds:edition=bedrock onto the game profile on PostLoginEvent, which fires before the player is sent to a backend. The property is therefore in the payload Velocity HMACs with the forwarding secret — and in every later one, since a server switch rebuilds it from the same profile.

Being inside the signature is the whole point, not an implementation detail. Downstream this flag turns anti-cheat off, so a marker the client could choose would be a self-exemption for any modified Java client. That is exactly why the obvious alternative is unusable: Geyser does announce itself as Geyser in minecraft:brand, but brand is pure client input and Velocity forwards it unmodified — Geyser is itself the proof that a Java-protocol client writes whatever it likes there. Cookies fail the same test, being stored unsigned on the client.

Choices worth reviewing

  • Reflection, not a dependency. One method: FloodgateApi.getInstance().isFloodgatePlayer(uuid). Only one of the three proxies carries Floodgate, so a compile-time dependency would put an unresolvable class on the other two; and Floodgate publishes its API only as a moving -SNAPSHOT, which is not a version this repo can pin. library-gui avoids the dependency for the same reasons. Where Floodgate is absent the listener is not registered at all.
  • isFloodgatePlayer, not isFloodgateId. The latter is the UUID-shape test and has the same blind spot as the status quo. The former resolves through getPlayer(uuid), which falls back to scanning for a player whose getCorrectUniqueId() matches. Verified in the bytecode of the jar we actually ship (downloaded build 140, sha256 matches the pinned f5867ad7…): isFloodgatePlayer is getPlayer(uuid) != null, and getPlayer does iterate players.values() comparing getCorrectUniqueId().
  • Appends, never replaces. textures lives in the same list, and Floodgate's VelocitySkinApplier copies the list and removeIf(name == "textures") — so neither side can drop what the other wrote, whichever order they run in.
  • Fails towards "Java". Any reflection failure answers false, logged once rather than per login. False means "keep checking them" downstream; the opposite default would let a Floodgate hiccup quietly exempt players from anti-cheat.

Backend side

Not in this PR. duel keeps the UUID check and will prefer this property when present, so the two halves can ship in either order and neither breaks without the other. Background: duel/docs/bedrock-anticheat.md.

Note for whoever owns feat/permission

Branched off origin/main deliberately, so your working tree was not touched. The only overlap is one added line in velocity/build.gradle.kts and one call in GroundsPluginPlayer.onInitialize — trivial to merge either way round.

./gradlew build green; 4 new tests.

Nothing downstream of the proxy knows today. Geyser terminates the
Bedrock session and connects as an ordinary Java client, and Velocity's
modern forwarding carries a UUID, a username, skin properties and an
address — no edition. Backends make do with the shape of the Floodgate
UUID (mostSignificantBits == 0), which is right for unlinked players and
silently wrong for linked ones: they arrive under their Mojang UUID.

That is not a corner case. The Floodgate build the network ships
defaults to enable-global-linking: true, and a global link is one the
player may have made on any Geyser server.

Stamps grounds:edition=bedrock onto the game profile at PostLoginEvent,
which fires before the player reaches a backend, so it is in the payload
Velocity HMACs with the forwarding secret — and in every later one, since
a server switch rebuilds it from the same profile. Being inside the
signature is the point rather than a detail: the flag turns anti-cheat
off downstream, so a marker the client could set would be a
self-exemption for any modified Java client. It is why the client brand
is not used, despite Geyser announcing itself in it.

Floodgate is reached by reflection over one method. Only the Bedrock
proxy carries it, Floodgate publishes its API as a moving -SNAPSHOT and
nothing else, and library-gui already avoids the dependency for the same
reasons. Where Floodgate is absent the listener is not registered at all.

The stamp appends, and Floodgate's own skin applier removes only
textures, so neither can drop what the other wrote.
@hbrombeer
hbrombeer requested a review from lusu007 August 23, 2026 20:26
@lusu007
lusu007 merged commit 1474f6c into main Aug 23, 2026
3 checks passed
@lusu007
lusu007 deleted the feat/bedrock-edition-property branch August 23, 2026 20:27
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