using System; using System.Buffers.Binary; namespace AcDream.Core.Net.Messages; /// /// Inbound SetState GameMessage (opcode 0xF74B). The server /// broadcasts this whenever a previously-spawned entity's /// PhysicsState bitmask changes after CreateObject — chiefly /// when a door opens / closes (server flips ETHEREAL_PS = 0x4) or a /// spell projectile becomes ethereal post-impact. /// /// /// Wire layout (per /// references/holtburger/crates/holtburger-protocol/src/messages/object/messages/properties.rs:117-122, /// matched by every other acdream parser): /// /// /// u32 opcode — 0xF74B /// u32 objectGuid /// u32 physicsState — bitmask (acclient.h:2815 / 2819) /// u16 instanceSequence — stale-packet rejection /// u16 stateSequence — stale-packet rejection /// /// /// /// Total body size: 16 bytes (4-byte opcode + 12-byte payload). /// /// /// /// Server-side reference: /// references/ACE/Source/ACE.Server/Network/GameMessages/Messages/GameMessageSetState.cs:8-15 /// — ACE writes the same field order using its UShortSequence.CurrentBytes /// helper (which calls BitConverter.GetBytes((ushort)value) = 2 bytes per /// sequence field), so its wire output matches holtburger's 12-byte payload. /// A one-shot ACDREAM_PROBE_BUILDING hex-dump in /// 's dispatcher (added in the same commit) emits /// the first SetState body bytes for runtime confirmation. /// /// /// /// Named-retail anchor: CPhysicsObj::set_state at /// docs/research/named-retail/acclient_2013_pseudo_c.txt:283044 /// describes the runtime state-store on the in-memory object /// (this->state = arg2). The wire format for this opcode is /// confirmed by holtburger's SetStateData struct; the named-retail /// decomp does not cover the deserialization path for this opcode. /// /// public static class SetState { public const uint Opcode = 0xF74Bu; public readonly record struct Parsed( uint Guid, uint PhysicsState, ushort InstanceSequence, ushort StateSequence); /// /// Parse a 0xF74B body. must start with the /// 4-byte opcode (matches the convention used by VectorUpdate / /// UpdateMotion / UpdatePosition). Returns null on truncation or /// opcode mismatch. /// public static Parsed? TryParse(ReadOnlySpan body) { if (body.Length < 16) return null; try { uint opcode = BinaryPrimitives.ReadUInt32LittleEndian(body.Slice(0, 4)); if (opcode != Opcode) return null; uint guid = BinaryPrimitives.ReadUInt32LittleEndian(body.Slice(4, 4)); uint state = BinaryPrimitives.ReadUInt32LittleEndian(body.Slice(8, 4)); ushort instSeq = BinaryPrimitives.ReadUInt16LittleEndian(body.Slice(12, 2)); ushort stateSeq = BinaryPrimitives.ReadUInt16LittleEndian(body.Slice(14, 2)); return new Parsed(guid, state, instSeq, stateSeq); } catch { return null; } } }