using System.Buffers.Binary; using AcDream.Core.Net.Packets; namespace AcDream.Core.Net.Messages; /// /// Retail character-delete request and server acknowledgement, both riding /// opcode 0xF655. /// /// /// Wire layout ported from retail Proto_UI::SendDeleteCharacter at /// 0x00546b30: the opcode, then AC1Legacy::PStringBase<char>::Pack /// of the account id as a String16L, then a trailing u32 written directly /// after the packed string (*(uint32_t*)var_4 = arg2): /// /// /// /// u32 opcode (0xF655) /// String16L accountName /// u32 characterSlot (NOT the character guid) /// /// /// /// The caller, CPlayerSystem::DeleteCharacter at 0x0055f830, /// resolves that trailing u32 from the target character's guid via /// CharacterSet::GetSlot(persistentData + 4, guid) before sending — /// retail deletes by **account + SLOT INDEX**, never the character guid. /// This builder takes the already-resolved slot; resolving a selected /// character to its slot is Runtime selection-state work (Campaign LA /// slice LA7b), not this file's job. /// /// /// /// The server's acknowledgement reuses the same opcode with no trailing /// payload — ACE's GameMessageCharacterDelete constructs a bare /// 4-byte body /// (ACE.Server/Network/GameMessages/Messages/GameMessageCharacterDelete.cs, /// base constructor called with bodyLength: 4 and no further /// Writer.Write calls). holtburger's inbound dispatcher /// (holtburger-protocol/src/messages/game_message/unpack.rs:50-58) /// disambiguates request vs. ack the identical way we do here — a request /// has bytes remaining after the opcode, the ack does not. /// /// /// /// Routing note for LA7b: retail transmits this request via /// Proto_UI::SendToLogon (the restore request rides /// SendToControl); ACE sends its acknowledgement and the follow-up /// refreshed CharacterList on GameMessageGroup.UIQueue. /// /// /// /// After the ack, ACE immediately follows with a fresh /// so the roster reflects the character's new pending-delete state /// (CharacterHandler.CharacterDelete, /// ACE.Server/Network/Handlers/CharacterHandler.cs:322, inside the /// SaveCharacter success callback). Requesting and re-rendering that /// refreshed roster belongs to LA7b's Runtime selection state — this file /// only builds the request and recognizes the ack. /// /// public static class CharacterDelete { public const uint Opcode = 0xF655u; /// /// Build the body bytes for an outbound CharacterDelete request. /// Layout: opcode(4) + String16L(accountName) + characterSlot(4). /// public static byte[] BuildRequestBody(string accountName, uint characterSlot) { ArgumentNullException.ThrowIfNull(accountName); var w = new PacketWriter(32); w.WriteUInt32(Opcode); w.WriteString16L(accountName); w.WriteUInt32(characterSlot); return w.ToArray(); } /// /// Returns whether a complete game-message body is the server's /// delete acknowledgement — the canonical four-byte opcode-only form /// ACE emits. A fresh follows separately /// and is not this method's concern. /// public static bool IsAcknowledgement(ReadOnlySpan body) => body.Length == sizeof(uint) && BinaryPrimitives.ReadUInt32LittleEndian(body) == Opcode; }