Skip to content

fix(server): read lobby entrants live, fix EntrantData struct size (#38) - #111

Merged
thesprockee merged 3 commits into
mainfrom
fix/38-stale-entrant-snapshot
Oct 6, 2026
Merged

thesprockee merged 3 commits into
mainfrom
fix/38-stale-entrant-snapshot

Conversation

@thesprockee

Copy link
Copy Markdown
Member

Summary

  • Closes server_context: entrant snapshot is copied once at Initialize and never refreshed #38: server_context copied the lobby's entrant list once at Initialize — before the server booted and before any player ever joined (the game calls our Initialize from LoadServerSupport, pre-boot). Every reader (smite/kick, two loadout handlers, the per-tick changed-entrant scan) got a permanently stale or empty copy.
  • Fix reads the lobby's entrant array live, on demand, from the same thread the game's own lobby update calls our Update on — matching what the game's own CNSLobby::SmiteEntrant does (reads at call time, ReVault VA 0x140616630). Confirmed safe: all 3 readers run on the thread the game serializes against; ServerDB messages are queued by the websocket thread and only handled inside Update.
  • Second bug found while fixing this: Lobby::EntrantData was declared 0xA0 bytes; the game strides the entrant array at 0xD8 bytes in 3 places confirmed via ReVault disassembly (0x14061665d, 0x1406082c9, 0x14061698f). Every entrant after slot 0 was read at the wrong offset — garbage data. Fixed the struct size with the unmapped 0x38-byte tail added and a static_assert pinning it.
  • Original "lobby pointer invalid after Initialize returns" reasoning for the snapshot (commit 1b323fc) no longer holds — a later commit (2e90317) restored the lobby pointer and other code already uses it post-Initialize.

Test plan

  • New test_server_context.cpp (7 tests). Against unfixed code: 6/7 failed, including the struct-size test (160 bytes measured vs 216 expected).
  • Mutant check: removing the null-array guard fails FreedArrayReadsAsEmpty as expected
  • just verify green (capped CMAKE_BUILD_PARALLEL_LEVEL=4)

Not done

  • No live server run exercising a real smite — found no sender of a smite message anywhere in the local nakama checkout, so this is unverified end-to-end. Unit tests + the ReVault stride evidence are the only verification.

Open question for Andrew, not blocking

  • The smite handler compares a 16-byte XPlatformId with a GUID parsed from entrant_id, but the comment above it says "matching playerSession GUID" — whether ServerDB actually fills entrant_id in a form that matches userId is unverified (no real sender exists yet to test against).

🤖 Generated with Claude Code

thesprockee and others added 2 commits October 5, 2026 19:40
echovr.exe indexes the entrant array at [lobby+0x360] with a 0xD8 stride
in every site measured in ReVault:

  CNSLobby::SmiteEntrant  0x14061665d  IMUL RAX, RDX, 0xd8
  fcn_1406082b0           0x1406082c9  IMUL RDX, RDX, 0xd8
  fcn 0x140616920 loop    0x14061698f  ADD  R8, 0xd8

The object is the same lobby IServerLib::Initialize receives:
LoadServerSupport (0x14060bb70) stores the server library at this+0x38
and calls vtable+8 (Initialize) with `this` as the lobby argument, and
SmiteEntrant reads this+0x130 (hosting) and this+0x8 (broadcaster) at
the offsets Lobby already declares.

The field offsets 0x00-0x9F still agree with echovr-reconstruction
CServerConfig.h; 0xA0 there is the mapped prefix, not the element size.
With the old sizeof, items[i] for i >= 1 read the wrong bytes. It was
latent only because ServerContext's entrant copy was empty (#38).

Adds the unmapped 0x38-byte tail and moves the static_assert to 0xD8.

Co-Authored-By: nevr-runtime <agents@sprock.io>
…ialize (#38)

ServerContext copied lobby->entrantData once in Initialize and served
GetEntrant/GetEntrantCount from that copy. The game calls
IServerLib::Initialize from CNSLobby LoadServerSupport (0x14060bb70)
while the server boots, before anyone joins, so the copy was empty or
stale for the life of the process. Every reader wanted the current
lobby:

- kLobbySmiteEntrant resolves an entrant to the slot index it hands
  the game; a stale copy can never find a player who joined later and
  could name a slot's previous occupant.
- SaveLoadout / CurrentLoadout log the player in the given slot.
- GameServerLib::Update scans entrants for the dirty flag.

The copy came from 1b323fc ("lobby pointer invalid after Initialize
returns"). That premise no longer holds in this code: 2e90317 restored
m_lobby, and GetTcpBroadcaster and UnregisterAllCallbacks dereference
it after Initialize. 2e90317 also notes FinalizeInitialization was never
called before it, so the accessors returned null during 1b323fc's
stability run and that run did not exercise entrant reads.

Reading on demand is the refresh point the binary itself uses:
CNSLobby::SmiteEntrant indexes [this+0x360] at call time, and
CNSLobby::Update calls IServerLib::Update (0x1406178c3) before it
touches the array, on the same thread. All three readers run on that
thread (the ServerDB queue is drained inside Update). GetEntrantCount
reports 0 while items is null, and the header documents that the
returned pointer must not outlive the callback.

The smite path now logs the resolved slot at Info and the entrant
count on a miss, so an empty-lobby miss can be told apart from a bad
id.

Test: test_server_context, wired into test-auth-unit. Before the fix
6/7 failed; after it 7/7 pass. Dropping the null-items guard fails
FreedArrayReadsAsEmpty.

Co-Authored-By: nevr-runtime <agents@sprock.io>
…snapshot

* origin/main:
  fix(gameserver): drop const_cast on received ServerDB payloads (#43)
  fix(winhttp): report the server's reason phrase from get_StatusText (#27)

Co-Authored-By: nevr-runtime <agents@sprock.io>

# Conflicts:
#	justfile
@thesprockee
thesprockee merged commit 88df8f5 into main Oct 6, 2026
1 check passed
@thesprockee
thesprockee deleted the fix/38-stale-entrant-snapshot branch October 6, 2026 00:59
thesprockee added a commit that referenced this pull request Oct 6, 2026
…erdb-uri

Resolve justfile and src/runtime/CMakeLists.txt list conflicts from
#111/#104/#103 landing on main: keep both test_serverdb_uri (this
branch) and test_winhttp_stub/test_server_context (main) in every
test-target list.

Co-Authored-By: nevr-runtime <agents@sprock.io>
thesprockee added a commit that referenced this pull request Oct 6, 2026
…d-registry-violation

Resolve src/runtime/CMakeLists.txt list conflict from #103/#104/#111/#113
landing on main: keep both the test_main_thread_handoff comment and the
test_winhttp_stub target block.

Co-Authored-By: nevr-runtime <agents@sprock.io>
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.

server_context: entrant snapshot is copied once at Initialize and never refreshed

1 participant