diff --git a/src/AcDream.Core.Net/Messages/InventoryRemoveObject.cs b/src/AcDream.Core.Net/Messages/InventoryRemoveObject.cs new file mode 100644 index 00000000..b89d9cd4 --- /dev/null +++ b/src/AcDream.Core.Net/Messages/InventoryRemoveObject.cs @@ -0,0 +1,31 @@ +using System; +using System.Buffers.Binary; + +namespace AcDream.Core.Net.Messages; + +/// +/// Inbound InventoryRemoveObject (0x0024) — a top-level GameMessage (UIQueue), +/// NOT a GameEvent. The server tells the client an object left its inventory view +/// (given away / sold / destroyed). The client drops it from object maintenance. +/// +/// Wire layout (ACE GameMessageInventoryRemoveObject.cs, size hint 8): +/// +/// u32 opcode = 0x0024 +/// u32 guid +/// +/// +public static class InventoryRemoveObject +{ + public const uint Opcode = 0x0024u; + + public readonly record struct Parsed(uint Guid); + + /// Parse a raw 0x0024 body. Returns null on opcode mismatch / truncation. + public static Parsed? TryParse(ReadOnlySpan body) + { + if (body.Length < 8) return null; // 4 + 4 + if (BinaryPrimitives.ReadUInt32LittleEndian(body) != Opcode) return null; + uint guid = BinaryPrimitives.ReadUInt32LittleEndian(body[4..]); + return new Parsed(guid); + } +} diff --git a/tests/AcDream.Core.Net.Tests/Messages/InventoryRemoveObjectTests.cs b/tests/AcDream.Core.Net.Tests/Messages/InventoryRemoveObjectTests.cs new file mode 100644 index 00000000..b4588fe9 --- /dev/null +++ b/tests/AcDream.Core.Net.Tests/Messages/InventoryRemoveObjectTests.cs @@ -0,0 +1,32 @@ +using System.Buffers.Binary; +using AcDream.Core.Net.Messages; +using Xunit; + +namespace AcDream.Core.Net.Tests.Messages; + +public sealed class InventoryRemoveObjectTests +{ + private static byte[] Build(uint guid, uint opcode = 0x0024u) + { + var b = new byte[8]; + BinaryPrimitives.WriteUInt32LittleEndian(b.AsSpan(0), opcode); + BinaryPrimitives.WriteUInt32LittleEndian(b.AsSpan(4), guid); + return b; + } + + [Fact] + public void TryParse_valid_returnsGuid() + { + var p = InventoryRemoveObject.TryParse(Build(0x50000A07u)); + Assert.NotNull(p); + Assert.Equal(0x50000A07u, p!.Value.Guid); + } + + [Fact] + public void TryParse_wrongOpcode_returnsNull() + => Assert.Null(InventoryRemoveObject.TryParse(Build(1, opcode: 0x0023u))); + + [Fact] + public void TryParse_truncated_returnsNull() + => Assert.Null(InventoryRemoveObject.TryParse(new byte[7])); +}