5.6 KiB
Retail live-session lifecycle pseudocode
Scope: GameWindow Slice 3 character-list, character-entry, and transport- disconnect conformance corrections.
Named-retail oracle
CharacterIdentity::UnPack @ 0x004FE880CharacterSet::UnPack @ 0x004FE340CharacterSet::GetGreyedOutFor @ 0x004FDFA0gmCharacterManagementUI::EnterGame @ 0x004ED440gmCharGenMainUI::Update @ 0x004E8460CPlayerSystem::Handle_Login__CharacterSet @ 0x0055F6D0Proto_UI::SendEnterWorldRequest @ 0x00546A00CPlayerSystem::Handle_Character__EnterGame_ServerReady @ 0x0055E4F0Proto_UI::SendEnterWorld @ 0x00546BC0CPlayerSystem::LogOnCharacter @ 0x0055F890CPlayerSystem::UseTime @ 0x00563110Proto_UI::LogOffCharacter @ 0x00546A20CPlayerSystem::RequestLogOff @ 0x00562DD0- inbound F653 dispatch
@ 0x0055C963 CPlayerSystem::ExecuteLogOff @ 0x0055D780ClientNet::ExitWorldDisconnect @ 0x00541E00ClientNet::LogOffServer @ 0x00543EF0SharedNet::SendOptionalHeader @ 0x00543160
The verbatim layouts are CharacterIdentity and CharacterSet in
docs/research/named-retail/acclient.h (types 3206 and 3209).
CharacterSet wire decode
CharacterIdentity.UnPack(cursor):
identity.guid = read_u32(cursor)
identity.name = read_PString(cursor)
identity.secondsGreyedOut = read_u32(cursor)
align cursor to 4 bytes
CharacterSet.UnPack(cursor):
result.status = read_u32(cursor)
activeCount = read_u32(cursor)
result.active = empty
repeat activeCount times:
result.active.append(CharacterIdentity.UnPack(cursor))
deletedCount = read_u32(cursor)
result.deleted = empty
repeat deletedCount times:
result.deleted.append(CharacterIdentity.UnPack(cursor))
result.numAllowedCharacters = read_u32(cursor)
result.account = read_PString(cursor)
result.useTurbineChat = read_u32(cursor)
result.hasThroneOfDestiny = read_u32(cursor)
The two values previously described as "leading/trailing padding" are the retail status and deleted-character count. ACE currently writes zero for both, which hid the schema error in acdream and in Holtburger's current reader.
Cross-checks:
- ACE
GameMessageCharacterListwrites the same active identity records, allowed-slot count, canonicalSession.Account, and booleans, but currently emits status/deleted-count as zero and no deleted records. - Holtburger
CharacterListDataagrees on active records and the tail, but likewise labels both zero values as padding. The named retailCharacterSetlayout andUnPackcontrol flow win where the references differ.
Retail selection and acdream unattended entry
gmCharacterManagementUI.EnterGame():
playerSystem = GetPlayerSystem()
if playerSystem exists
and selectedGuid != 0
and CharacterSet.GetGreyedOutFor(
CharacterSet.GetSlot(selectedGuid)) <= 0:
show entering-world dialog
playerSystem.LogOnCharacter(selectedGuid)
Retail ordinarily requires an explicit selected active character. The only
automatic retail path found is gmCharGenMainUI::Update: after character
creation it walks the active list, skips entries greyed for a positive number
of seconds, matches the newly-created name case-insensitively, and logs that
GUID on.
acdream does not yet have the retained character-management screen. Its unattended auto-entry is therefore an explicit adaptation with a narrow rule:
for each identity in active wire order:
if identity.guid != 0 and identity.secondsGreyedOut == 0:
select this active-list index
stop
if none matched:
do not enter a character
Deleted entries are never candidates. This preserves the established "first available character" behavior without treating a pending-deletion character as available.
Canonical account used by EnterWorld
CPlayerSystem.LogOnCharacter(selectedGuid), when ready:
selectedGuid = persistentData.selectedCharacter
account = playerSystem.account // populated from CharacterSet.account
Proto_UI.SendEnterWorld(account, selectedGuid)
ClientNet.EnterWorld()
The account string in the final F657 message comes from the server-returned
CharacterSet.account, not from the spelling typed into the startup login
form. acdream must therefore make WorldSession.EnterWorld consume
Characters.AccountName internally.
Logout and negotiated transport disconnect
ClientNet.LogOffServer():
packet = make optional-header packet(flags = Disconnect)
for each negotiated ReceiverData:
SharedNet.SendOptionalHeader(packet, receiver.address, receiver)
logOffSent = true
logonReceiverId = 0
ExitWorldDisconnect()
The in-world character path remains:
send F653 + active character GUID
wait for server's opcode-only F653
send negotiated transport Disconnect
release transport
ACE's GameMessageCharacterLogOff confirms the server form is exactly four
bytes. Its NetworkSession terminates a session when it receives a packet with
the transport Disconnect flag.
The transport Disconnect is connection-scoped, not character-state-scoped. Once acdream has accepted ConnectRequest receiver id/iteration, disposal sends it whether the session is still selecting a character, entering the world, in world, or failed later. F653 remains restricted to an active in-world character. A pre-negotiation failure has no receiver identity and only closes the local socket.
Deliberately unchanged registered gaps
- UN-6: the fixed 200 ms ConnectResponse delay remains registered.
- TS-28: LoginComplete readiness remains a later protocol slice.
- AD-44: closing exits rather than returning to a retained character screen.