fix(net): honor retail graceful logout handshake
Send the active character id, drain until the authoritative server confirmation, then emit retail's zero-sequence connection disconnect with the negotiated receiver iteration. The connected gate now waits for ACE to remove the exact UDP session before reconnecting, eliminating fixed-delay races.
This commit is contained in:
parent
b03371c03d
commit
68578fa5fa
10 changed files with 372 additions and 41 deletions
|
|
@ -168,7 +168,7 @@ In-scope: 51. Implemented in acdream: 21. Phase M target delta: 30.
|
|||
| 0xEA60 | inbound | AdminEnvirons | `CPlayerSystem::Handle_Admin__Environs` | – | B | P+W | P+W | Fog presets / sound cues |
|
||||
| 0xF625 | inbound | ObjDescEvent | `SmartBox::HandleObjDescEvent` | PB | B | P+W | P+W | Per-entity appearance update |
|
||||
| 0xF643 | inbound | CharacterCreateResponse | – | PB | B | – | –defer:char-creation | Char-creation flow not yet built |
|
||||
| 0xF653 | outbound | CharacterLogOff | – | PB | P | B | PB+W | Sent on Dispose; ACE accepts |
|
||||
| 0xF653 | both | CharacterLogOff | – | PB | P | B | PB+W | Request carries active character GUID; opcode-only server confirmation gates transport disconnect |
|
||||
| 0xF655 | both | CharacterDelete | – | PB | P | – | –defer:char-mgmt | Char-management UI deferred |
|
||||
| 0xF656 | outbound | CharacterCreate | – | PB | P | – | –defer:char-creation | Char-creation flow not yet built |
|
||||
| 0xF657 | outbound | CharacterEnterWorld | `CM_Login::SendNotice_BeginEnterWorld` [^m-2] | PB | P | B | PB+W | Built; sent during handshake |
|
||||
|
|
|
|||
|
|
@ -0,0 +1,70 @@
|
|||
# Retail graceful character logout
|
||||
|
||||
## Oracle
|
||||
|
||||
- `Proto_UI::LogOffCharacter` `0x00546A20`
|
||||
- `CPlayerSystem::RequestLogOff` `0x00562DD0`
|
||||
- inbound login-message dispatch at `0x0055C963`
|
||||
- `CPlayerSystem::ExecuteLogOff` `0x0055D780`
|
||||
- `ClientNet::ExitWorldDisconnect` `0x00541E00`
|
||||
- `ClientNet::LogOffServer` `0x00543EF0`
|
||||
- `SharedNet::SendOptionalHeader` `0x00543160`
|
||||
- `CPlayerSystem::LogOffCharacter` `0x00563520`
|
||||
|
||||
Cross-checks:
|
||||
|
||||
- ACE `CharacterHandler.CharacterLogOff` begins asynchronous player removal.
|
||||
- ACE `Session.SendFinalLogOffMessages` sends the opcode-only `CharacterLogOff`
|
||||
confirmation only after the player no longer owns a landblock, then returns
|
||||
the session to `AuthConnected`.
|
||||
- Holtburger currently sends opcode-only and disconnects immediately. Its own
|
||||
comment records that AC normally waits for the server response. That shortcut
|
||||
is not the retail oracle and was the source of acdream's reconnect race.
|
||||
|
||||
## Pseudocode
|
||||
|
||||
```text
|
||||
LogOffCharacter(force):
|
||||
save player module
|
||||
if force:
|
||||
ExecuteLogOff()
|
||||
return
|
||||
if no interaction is pending:
|
||||
RequestLogOff()
|
||||
else:
|
||||
print "Logging off..."
|
||||
remember that logout must begin after the interaction
|
||||
|
||||
RequestLogOff():
|
||||
print "Logging off..."
|
||||
send_to_logon([0xF653, active_player_id])
|
||||
logOffRequested = true
|
||||
logOffRequestTime = now + 3 seconds
|
||||
if player is PK:
|
||||
logOffRequestTime += 20 seconds
|
||||
|
||||
on inbound login message 0xF653:
|
||||
ExecuteLogOff()
|
||||
|
||||
ExecuteLogOff():
|
||||
clear player-login state
|
||||
for every live ReceiverData connection:
|
||||
send a cleartext, empty DISCONNECT optional header
|
||||
use sequence 0 plus that receiver's network id and iteration
|
||||
disconnect the world connection
|
||||
clear the active player id
|
||||
```
|
||||
|
||||
## acdream close-only adaptation
|
||||
|
||||
acdream does not yet return to character selection when its native window is
|
||||
closed. It retains retail's important ordering: send the eight-byte request,
|
||||
continue receiving until the server's opcode-only confirmation arrives, then
|
||||
send the transport `DISCONNECT` and release the socket. World callbacks are not
|
||||
dispatched while shutdown drains the raw receive queue because their App owners
|
||||
may already be tearing down. A bounded 35-second wait covers retail's additional
|
||||
20-second PK logout delay and is only a transport safety
|
||||
limit; the normal path advances on the authoritative confirmation, not elapsed
|
||||
time. The connected gate separately observes ACE accepting the transport header
|
||||
before opening a replacement process; ACE removes the authenticated session on
|
||||
its world-manager tick, after the UDP sender has already completed.
|
||||
|
|
@ -181,7 +181,7 @@ is the opcode itself, followed immediately by the payload.
|
|||
| 0xF619 | PositionAndMovement | GM | S→C | unhandled | P4 | Ghost opcode — declared but never fired by ACE. |
|
||||
| 0xF625 | ObjDescEvent | GM | S→C | unhandled | P1 | `u32 guid, ObjectDescription` — full re-send of visual description (body parts, textures, palettes). Critical for seeing other players' gear changes. |
|
||||
| 0xF643 | CharacterCreateResponse / CharacterRestoreResponse | GM | S→C | unhandled | P0+ | `u32 responseCode` — login-phase, only relevant after char-create. Same opcode, two semantics (disambiguated by session state). |
|
||||
| 0xF653 | CharacterLogOff | GM | bi | partial | P0 | No payload. Client sends before Disconnect to release the character lock immediately. acdream sends it from `Dispose`. |
|
||||
| 0xF653 | CharacterLogOff | GM | bi | parsed | P0 | Client request is opcode + active character GUID; server confirmation is opcode-only. acdream waits for confirmation before transport Disconnect. |
|
||||
| 0xF655 | CharacterDelete | GM | bi | unhandled | P4 | `u32 slot` — char-select-screen deletion. |
|
||||
| 0xF656 | CharacterCreate | GM | C→S | unhandled | P4 | Full character-creation blob — heritage, gender, starting town, appearance. |
|
||||
| 0xF657 | CharacterEnterWorld | GM | C→S | done | P0 | `u32 characterGuid, string16L account`. Built by `Messages/CharacterEnterWorld.cs`. |
|
||||
|
|
@ -859,8 +859,9 @@ All the P3/P4 tails. Ship when we need them, not before.
|
|||
- **0x00A3/0x00A4 FellowshipQuit/Dismiss** — same pairing; C→S in
|
||||
GameAction, S→C in GameEvent.
|
||||
- **0xF653 CharacterLogOff** — bi-directional, same opcode, no
|
||||
envelope. Client sends to request logout; server sends the echo
|
||||
before Disconnect.
|
||||
envelope. Retail's client request carries the active character GUID;
|
||||
the server confirmation is opcode-only. The client waits for that
|
||||
confirmation before disconnecting the world transport.
|
||||
- **0x01A8 MagicRemoveSpell** — C→S is GameAction, S→C is GameEvent.
|
||||
- **0xF7DE TurbineChat** — bi-directional GameMessage. Blob layout
|
||||
includes a `ChatNetworkBlobType` discriminant (EVENT_BINARY=1,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue