fix(net): Campaign LA LA7a review fixes — AD-97 register row, corrected restore justification
The Opus retail-lens review decoded the PDB-paired binary at CPlayerSystem::RestoreCharacter@0x0055d760 and refuted the uninitialized-edx justification: the two extra arguments are real push imm32 of a constant PStringBase (BN mis-renders them, but they pack to >=4 bytes each), so retail 0xF7D9 is >=16 bytes where ours is 8. The guid-only CODE stands (ACE reads only the guid; holtburger consensus) but it is an adaptation, not a corrected decompile — filed as divergence register AD-97 and the doc comment now states the true mechanism. Also from the review: the 0xF643 conditional-parse doc now names BOTH ACE flag-only failure branches (NameInUse + Corrupt); CharacterError 0x08 doc corrected (ACE misnames it ServerCrash2 — the port corrects an ACE misnaming; ACE omits three values, not four); LA7b hazard notes added (ACE silent no-reply on unknown restore guid; retail SendToLogon vs SendToControl routing; NumErrors never rendered); two review-nit tests (flag=0 Undef flag-only, non-Ok body with trailing bytes ignored). Core.Net suite: 953 passed / 0 failed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
6a32f37589
commit
4338b1c1f3
5 changed files with 100 additions and 34 deletions
|
|
@ -43,6 +43,13 @@ namespace AcDream.Core.Net.Messages;
|
|||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// Routing note for LA7b: retail transmits this request via
|
||||
/// <c>Proto_UI::SendToLogon</c> (the restore request rides
|
||||
/// <c>SendToControl</c>); ACE sends its acknowledgement and the follow-up
|
||||
/// refreshed CharacterList on <c>GameMessageGroup.UIQueue</c>.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// After the ack, ACE immediately follows with a fresh <see cref="CharacterList"/>
|
||||
/// so the roster reflects the character's new pending-delete state
|
||||
/// (<c>CharacterHandler.CharacterDelete</c>,
|
||||
|
|
|
|||
|
|
@ -43,11 +43,15 @@ namespace AcDream.Core.Net.Messages;
|
|||
/// It is a strict superset of ACE's <c>ACE.Server.Network.Enum.CharacterError</c>
|
||||
/// (<c>references/ACE/Source/ACE.Server/Network/Enum/CharacterError.cs</c>):
|
||||
/// retail additionally names 0x2 (<c>LoggedOn</c>), 0x7 (<c>NoPremade</c>),
|
||||
/// 0x8 (<c>AccountInUse</c>), and 0x16 (<c>CharacterIsBooted</c>), none of
|
||||
/// which ACE's server ever sends but all of which retail's client can
|
||||
/// receive from a genuine retail server — per the project's
|
||||
/// property-enum-divergence lesson, we port the complete oracle, not just
|
||||
/// what today's one server implementation emits. ACE's per-value doc
|
||||
/// and 0x16 (<c>CharacterIsBooted</c>) — three values ACE omits entirely,
|
||||
/// none of which ACE's server ever sends but all of which retail's client
|
||||
/// can receive from a genuine retail server. At 0x8 the port additionally
|
||||
/// CORRECTS an ACE misnaming: ACE defines 0x8 as <c>ServerCrash2</c> with a
|
||||
/// doc comment duplicating 0x4's <c>ID_CHAR_ERROR_SERVER_CRASH</c> text,
|
||||
/// but retail's header names 0x8 <c>CHAR_ERROR_ACCOUNT_IN_USE</c> — the
|
||||
/// header wins. Per the project's property-enum-divergence lesson, we port
|
||||
/// the complete oracle, not just what today's one server implementation
|
||||
/// emits. ACE's per-value doc
|
||||
/// comments (themselves sourced from the client's <c>ID_CHAR_ERROR_*</c>
|
||||
/// string table) are folded in below where they exist. One retail member,
|
||||
/// <c>FORCE_charError_32_BIT = 0x7FFFFFFF</c>, is a compiler
|
||||
|
|
@ -111,7 +115,11 @@ public static class CharacterError
|
|||
/// <summary>0x07 — CHAR_ERROR_NO_PREMADE. Retail-only; no ACE member.</summary>
|
||||
NoPremade = 0x07,
|
||||
|
||||
/// <summary>0x08 — CHAR_ERROR_ACCOUNT_IN_USE. Retail-only; no ACE member.</summary>
|
||||
/// <summary>
|
||||
/// 0x08 — CHAR_ERROR_ACCOUNT_IN_USE. ACE misnames this value
|
||||
/// <c>ServerCrash2</c> (its doc comment duplicates 0x04's text);
|
||||
/// retail's header is the authority. See the class doc comment.
|
||||
/// </summary>
|
||||
AccountInUse = 0x08,
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -228,7 +236,8 @@ public static class CharacterError
|
|||
/// sentinel (the array-bound idiom, one past the last real code) —
|
||||
/// never sent on the wire as an actual error. Kept for verbatim
|
||||
/// completeness of the enum range; do not treat a received 0x19
|
||||
/// as meaningful.
|
||||
/// as meaningful, and LA7b's error-to-string mapping must not
|
||||
/// render it as a user-facing message.
|
||||
/// </summary>
|
||||
NumErrors = 0x19,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,27 +8,36 @@ namespace AcDream.Core.Net.Messages;
|
|||
/// (opcode <c>0xF643</c>).
|
||||
///
|
||||
/// <para>
|
||||
/// <b>Request — guid-only, by reference consensus.</b> The decompiled call
|
||||
/// site (<c>Proto_UI::SendAdminRestoreCharacter</c> at <c>0x00546cf0</c>,
|
||||
/// declared with three parameters — a u32 and two <c>PStringBase<char></c>
|
||||
/// pointers — and packing two strings after the u32) LOOKS like it sends
|
||||
/// guid + two strings. It does not: its only real caller,
|
||||
/// <c>CPlayerSystem::RestoreCharacter</c> at <c>0x0055d760</c>, declares
|
||||
/// <c>class PStringBase<char>* edx;</c> as a local and passes it
|
||||
/// straight through UNINITIALIZED as the second argument, and passes
|
||||
/// <c>this</c> (a <c>CPlayerSystem*</c>, not a string) as the third. Both
|
||||
/// are textbook decompiler register-corruption artifacts (uninitialized
|
||||
/// register reuse + a mistyped extra parameter from an over-declared
|
||||
/// callee signature), not real arguments the real call site ever
|
||||
/// supplied. ACE
|
||||
/// <b>Request — guid-only, an ADAPTATION (register row AD-97).</b> Retail
|
||||
/// really does send more than the guid. The PDB-paired binary at
|
||||
/// <c>CPlayerSystem::RestoreCharacter@0x0055d760</c> is 26 bytes:
|
||||
/// <c>push 0x008173B4; push 0x008173B4; push guid;
|
||||
/// call Proto_UI::SendAdminRestoreCharacter@0x00546cf0</c> — two REAL
|
||||
/// constant <c>PStringBase<char>*</c> arguments (Binary Ninja renders
|
||||
/// them as an uninitialized <c>edx</c> local and <c>this</c>; that
|
||||
/// rendering is the artifact, the two <c>push imm32</c> are not).
|
||||
/// <c>SendAdminRestoreCharacter</c> packs both
|
||||
/// (<c>PStringBase::Pack@0x004fc6f0</c> emits ≥4 bytes even for an empty
|
||||
/// string), so retail's request is ≥16 bytes where ours is 8. We send
|
||||
/// guid-only because ACE
|
||||
/// (<c>CharacterHandler.CharacterRestore</c>,
|
||||
/// <c>ACE.Server/Network/Handlers/CharacterHandler.cs:331-385</c>, reads
|
||||
/// only <c>ReadUInt32()</c>) and holtburger
|
||||
/// <c>ACE.Server/Network/Handlers/CharacterHandler.cs:331-385</c>) reads
|
||||
/// only <c>ReadUInt32()</c> and ignores any tail, and holtburger
|
||||
/// (<c>holtburger-protocol/src/messages/character/types.rs::CharacterRestoreRequestData</c>,
|
||||
/// guid-only) independently agree on guid-only. We follow the two
|
||||
/// independent, uncorrupted references (design spec §11 item 4 — wire
|
||||
/// consensus, no divergence-register row needed: this isn't a deviation
|
||||
/// from retail, it's picking the correct reading of a corrupted decompile).
|
||||
/// sent from a real client command path) ships guid-only against ACE
|
||||
/// successfully. The omitted tail is a recorded retail deviation —
|
||||
/// divergence register AD-97.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// <b>LA7b hazards.</b> (1) ACE's restore handler has a SILENT no-reply
|
||||
/// path: an unknown guid hits
|
||||
/// <c>Characters.SingleOrDefault(...) == null → return;</c> — no 0xF643,
|
||||
/// no 0xF659. Selection state must never await a restore reply
|
||||
/// unconditionally. (2) Routing: ACE sends the response on
|
||||
/// <c>GameMessageGroup.UIQueue</c>; retail transmits the request via
|
||||
/// <c>Proto_UI::SendToControl</c> (the delete request goes via
|
||||
/// <c>SendToLogon</c>) — relevant when LA7b picks the outbound queue.
|
||||
/// </para>
|
||||
///
|
||||
/// <code>
|
||||
|
|
@ -55,12 +64,13 @@ namespace AcDream.Core.Net.Messages;
|
|||
/// </code>
|
||||
///
|
||||
/// <para>
|
||||
/// But retail's <c>CharacterRestore</c> handler can ALSO reply on this same
|
||||
/// But ACE's <c>CharacterRestore</c> handler can ALSO reply on this same
|
||||
/// opcode via the character-CREATE response path when restore itself fails
|
||||
/// (e.g. <c>SendCharacterCreateResponse(session, CharacterGenerationVerificationResponse.NameInUse)</c>
|
||||
/// when the freed name collides) — that shape is flag-only, with NO
|
||||
/// trailing fields (<c>GameMessageCharacterCreateResponse.cs</c>: the guid /
|
||||
/// name / trailing u32 are only written <c>if (response == ... .Ok)</c>).
|
||||
/// — TWO real branches: <c>NameInUse</c> (the freed name collided) and
|
||||
/// <c>Corrupt</c> (<c>SaveCharacter</c> returned false). Both shapes are
|
||||
/// flag-only, with NO trailing fields
|
||||
/// (<c>GameMessageCharacterCreateResponse.cs</c>: the guid / name /
|
||||
/// trailing u32 are only written <c>if (response == ... .Ok)</c>).
|
||||
/// <see cref="Parse"/> mirrors that conditionality: the trailing three
|
||||
/// fields are read only when <c>verificationFlag == 1</c>. Because the two
|
||||
/// message families are wire-identical when they collide, a caller cannot
|
||||
|
|
@ -95,9 +105,9 @@ public static class CharacterRestore
|
|||
|
||||
/// <summary>
|
||||
/// Build the body bytes for an outbound <c>CharacterRestore</c> request.
|
||||
/// Layout: opcode(4) + characterGuid(4). Guid-only — see the class doc
|
||||
/// comment for why the decompiled call site's apparent extra strings
|
||||
/// are not real.
|
||||
/// Layout: opcode(4) + characterGuid(4). Guid-only — an adaptation of
|
||||
/// retail's ≥16-byte shape; see the class doc comment and divergence
|
||||
/// register AD-97.
|
||||
/// </summary>
|
||||
public static byte[] BuildRequestBody(uint characterGuid)
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue