Skip to content

fix: read SClient+0x1198 as the one-byte bool it is - #1

Closed
dan51588 wants to merge 1 commit into
Rmkrs:mainfrom
dan51588:main
Closed

dan51588 wants to merge 1 commit into
Rmkrs:mainfrom
dan51588:main

Conversation

@dan51588

@dan51588 dan51588 commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Problem

With some clients, every pilot stays "loading" from Net7 Client Manager's point of view even though they are in game. The observed lifecycle is in_game, but LoadingOrTransitionFlag never reads as zero. Everything gated on that flag stays off:

  • the in-game Client Manager tab and add-on overlays (ClientHostForm.ApplyAddonPresentationState needs !isTransitioning)
  • Auto Pilot, wormhole and target automations, the vendor companion, journals, Forge contributions, the add-on projection, and others (about 35 files read the flag)

Cause

ClientSessionObserver reads SClient +0x1198 as a uint32 (and ClientNavigationStateObserver reads the same field as FrameProcessingGateFlag). In client.exe that field is a one-byte bool. The bytes at +0x1199..+0x119B are struct padding before a std::string at +0x119C. The client never writes them, so they hold whatever the memory held before SClient was constructed.

Evidence from the client

client.exe (8,642,625 bytes, SHA-256 249f852496f341c00361be95b66e7c0becc2c39e7cba026f9227e38b602651c4). Every instruction in .text with displacement 0x1198 is a byte access. There are no 16- or 32-bit loads, stores or compares of the field:

Address Bytes Instruction Where
0x740621 c6 86 98 11 00 00 01 mov byte [esi+0x1198], 1 SClient constructor (0x7405E0): starts as true
0x737d45 c6 86 98 11 00 00 01 mov byte [esi+0x1198], 1 SERVER_HANDOFF packet handler: set again on a sector handoff
0x745160 c6 81 98 11 00 00 00 mov byte [ecx+0x1198], 0 0x745160, called from the START packet handler (0x733440): cleared when the sector starts
0x742b91 8a 86 98 11 00 00 mov al, byte [esi+0x1198] SClient::Do_Frame (0x742B60): on a dead connection
0x742d24 8a 86 98 11 00 00 mov al, byte [esi+0x1198] Do_Frame: skips the per-object aux-data aging step while set
0x742f33 8a 86 98 11 00 00 mov al, byte [esi+0x1198] Do_Frame: skips SClient::Process_AI (0x744260) and the view update while set
0x7447a2 8a 86 98 11 00 00 mov al, byte [esi+0x1198] SClient render (0x744690)
0x50cafe 8a 88 98 11 00 00 mov cl, byte [eax+0x1198] the main view (vtable slot +0x0C): returns MainView::Render_Initializing while set

This matches the name the manager already uses. The flag is true from construction and after a sector handoff until START arrives, and the client itself skips simulation and draws its loading view while it is set.

The layout, as recovered from a reconstruction of the client's source from this binary:

bool        Unknown1198;   // +0x1198
                           // +0x1199..+0x119B padding (never written)
std::string Unknown119C;   // +0x119C (the guild-confirmation dialogs' value)

Observed on live clients

Read from live clients that were in game and actively playing, with every in-game overlay hidden:

uint32 at +0x1198 flag byte padding
0x3EA30100 00 01 A3 3E
0x3F002200 00 22 00 3F
0xBEE1DB00 00 DB E1 BE
0x3D9DEA00 00 EA 9D 3D

The flag byte is 00 (in game) on every client. The padding is stale data, which happens to look like the top bytes of small floats. Whether the padding is zero depends on what that heap block held before, so the bug can come and go between machines, sessions and client start-up paths.

Fix

  • ProcessMemoryReader.TryReadByte: a one-byte read on top of TryReadBytes.
  • ClientSessionObserver and ClientNavigationStateObserver read the field with it.
  • The model properties stay uint, so nothing downstream changes. A client whose padding happens to be zero behaves exactly as before.

Testing

  • dotnet build Net7ClientManager.slnx: 0 errors, no new analyzer warnings.
  • Live, with six affected clients in game:
    • the in-game Client Manager tab now shows on every docked client
    • the gated automations and overlays work again
    • the tab still hides during a sector jump and returns on arrival, so the flag still works as a flag

🤖 Generated with Claude Code

The loading/transition flag at SClient+0x1198 was read as a uint32. In
client.exe the field is a bool: every access is a byte move (5 reads,
3 writes), and +0x1199..+0x119B are padding the client never writes.
When that padding holds stale heap data the flag never reads as zero,
so a pilot who is in game is treated as still loading: the in-game
Client Manager tab, add-on overlays, Auto Pilot and the other features
gated on LoadingOrTransitionFlag == 0 never appear.

Read one byte in ClientSessionObserver and ClientNavigationStateObserver
(the same field, FrameProcessingGateFlag). The model types stay uint.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dan51588
dan51588 marked this pull request as ready for review October 29, 2026 06:38
@dan51588

Copy link
Copy Markdown
Contributor Author

Superseded by #2 (the same commit, opened from a topic branch).

@dan51588 dan51588 closed this Oct 29, 2026
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