Skip to content

Fix Android client game events failing to deserialize in cross-platform network play#10304

Merged
tool4ever merged 2 commits intoCard-Forge:masterfrom
MostCromulent:fix/mobile-event-deserialization
Apr 6, 2026
Merged

Fix Android client game events failing to deserialize in cross-platform network play#10304
tool4ever merged 2 commits intoCard-Forge:masterfrom
MostCromulent:fix/mobile-event-deserialization

Conversation

@MostCromulent
Copy link
Copy Markdown
Contributor

Summary

Fixes a bug where Android clients connecting to a desktop host cannot see or interact with their hand (and other zones). The game loads and the match screen appears, but all zone-change events (cards entering hand, battlefield, etc.) are silently dropped, leaving the mobile player with an empty/non-interactive board. The desktop host sees everything correctly.

This appears to only affect cross-platform play between desktop (JVM) and Android — both sides built from the same source.

Root Cause

The GameEventProxy inner serialization (introduced in #10018) serializes game events into byte arrays using ObjectOutputStream, then deserializes on the client with ObjectInputStream. This triggers a serialVersionUID mismatch between desktop and Android:

  • Desktop (JVM 17+): Java records get a hardcoded serialVersionUID = 0L when none is explicitly declared.
  • Android (D8 desugaring): Records are desugared to regular classes, which get an auto-computed serialVersionUID based on class structure (e.g. -841910661983749810 for GameEventZone).

The result is InvalidClassException for every event type — GameEventZone, GameEventShuffle, GameEventFlipCoin, GameEventGameStarted, GameEventCardStatsChanged, GameEventCardChangeZone. The proxy's unwrapAll() catches these and drops the events with a warning log. Since setGameView/copyChangedProps still works (it doesn't go through the proxy), the underlying data model is correct, but the UI never receives the events that trigger visual updates.

Client log showing the failure:

[WARN] GameEventProxy: Failed to unwrap GameEventProxy: forge.game.event.GameEventZone; 
  local class incompatible: stream classdesc serialVersionUID = 0, 
  local class serialVersionUID = -841910661983749810

Fix

Override readClassDescriptor() in GameEventProxy.IdResolvingInputStream to detect serialVersionUID mismatches and substitute the local class descriptor. This bypasses the false-positive UID check while preserving normal deserialization — the classes have identical fields, only the UID computation differs.

This is safe because the proxy is used for same-version communication — server and client are built from the same source. The UID mismatch is a false positive caused by JVM vs Android computing different UIDs for structurally identical classes. In the worst case (a genuinely incompatible class change between versions), deserialization would fail at the field level with an IOException rather than at the UID check — the event would still be caught and dropped by the existing error handling in unwrapAll(), same as today.

This is an established pattern in the Forge codebase, already used in:

  • SaveFileData.java — Adventure mode save file compatibility
  • CObjectInputStream.java — Netty network layer (desktop side)

Verification

Built Android APK from this branch and tested on an emulator connecting to a desktop host:

  • Hand cards visible and interactable on mobile client
  • No Failed to unwrap GameEventProxy warnings in client log
  • Desktop host unaffected

🤖 Generated with Claude Code

JVM hardcodes serialVersionUID=0 for records, but Android's D8 desugaring
computes a different UID, causing InvalidClassException for all GameEvent
types in GameEventProxy's inner deserialization. Override readClassDescriptor()
to use the local class descriptor when UIDs mismatch, matching the existing
pattern in SaveFileData and CObjectInputStream.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment thread forge-gui/src/main/java/forge/gamemodes/net/GameEventProxy.java
@tool4ever tool4ever merged commit 0a3d402 into Card-Forge:master Apr 6, 2026
2 checks passed
@MostCromulent MostCromulent deleted the fix/mobile-event-deserialization branch April 6, 2026 19:54
MostCromulent added a commit to MostCromulent/forge that referenced this pull request Apr 6, 2026
Resolve conflict on GameEventProxy.java (deleted on this branch, modified
on NetworkPlay/main by Card-Forge#10304) by porting Card-Forge#10304's serialVersionUID fix
into TrackableSerializer.ResolvingInputStream — the event deserialization
path that replaced GameEventProxy.
@Hanmac
Copy link
Copy Markdown
Contributor

Hanmac commented Apr 7, 2026

i don't have a problem with hardcoding serialVersionUID for Events
i will check if i can do this in an interface instead of record "class"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants