From 43afcc2adbbd8ffa3e74324035f84a562e21d6e3 Mon Sep 17 00:00:00 2001 From: Erik Date: Sun, 21 Jun 2026 18:54:25 +0200 Subject: [PATCH] =?UTF-8?q?feat(net):=20D.2b-B=20B-Wire=20=E2=80=94=20Pars?= =?UTF-8?q?eViewContents=20(0x0196)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GameEvents.ParseViewContents parses the ViewContents GameEvent payload: containerGuid + count + [guid, containerType]×count. Records ViewContentsEntry and ViewContents added. 3 unit tests added in GameEventsInventoryTests.cs (two-entry, zero-count, truncated). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/AcDream.Core.Net/Messages/GameEvents.cs | 23 ++++++++++ .../Messages/GameEventsInventoryTests.cs | 45 +++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 tests/AcDream.Core.Net.Tests/Messages/GameEventsInventoryTests.cs diff --git a/src/AcDream.Core.Net/Messages/GameEvents.cs b/src/AcDream.Core.Net/Messages/GameEvents.cs index d9131628..bd28a283 100644 --- a/src/AcDream.Core.Net/Messages/GameEvents.cs +++ b/src/AcDream.Core.Net/Messages/GameEvents.cs @@ -358,6 +358,29 @@ public static class GameEvents BinaryPrimitives.ReadUInt32LittleEndian(payload.Slice(8))); } + /// 0x0196 ViewContents: full contents list of a container you opened. + /// Layout (ACE GameEventViewContents.cs): containerGuid, count, [guid, containerType]×count. + /// Client consumer: ClientUISystem::OnViewContents (PackableList<ContentProfile>). + public readonly record struct ViewContentsEntry(uint Guid, uint ContainerType); + public readonly record struct ViewContents(uint ContainerGuid, System.Collections.Generic.IReadOnlyList Items); + + public static ViewContents? ParseViewContents(ReadOnlySpan payload) + { + if (payload.Length < 8) return null; + uint containerGuid = BinaryPrimitives.ReadUInt32LittleEndian(payload); + uint count = BinaryPrimitives.ReadUInt32LittleEndian(payload.Slice(4)); + int pos = 8; + if ((long)payload.Length - pos < (long)count * 8) return null; + var items = new ViewContentsEntry[count]; + for (int i = 0; i < count; i++) + { + uint guid = BinaryPrimitives.ReadUInt32LittleEndian(payload.Slice(pos)); pos += 4; + uint type = BinaryPrimitives.ReadUInt32LittleEndian(payload.Slice(pos)); pos += 4; + items[i] = new ViewContentsEntry(guid, type); + } + return new ViewContents(containerGuid, items); + } + // ── Other small-payload events ────────────────────────────────────────── /// 0x01C7 UseDone: the Use/UseWithTarget completion signal (WeenieError code). diff --git a/tests/AcDream.Core.Net.Tests/Messages/GameEventsInventoryTests.cs b/tests/AcDream.Core.Net.Tests/Messages/GameEventsInventoryTests.cs new file mode 100644 index 00000000..5a49b53a --- /dev/null +++ b/tests/AcDream.Core.Net.Tests/Messages/GameEventsInventoryTests.cs @@ -0,0 +1,45 @@ +using System.Buffers.Binary; +using AcDream.Core.Net.Messages; +using Xunit; + +namespace AcDream.Core.Net.Tests.Messages; + +public sealed class GameEventsInventoryTests +{ + [Fact] + public void ParseViewContents_twoEntries_returnsContainerAndItems() + { + // containerGuid + count(2) + 2×{guid, containerType} + var b = new byte[4 + 4 + 2 * 8]; + BinaryPrimitives.WriteUInt32LittleEndian(b.AsSpan(0), 0x500000C9u); // containerGuid + BinaryPrimitives.WriteUInt32LittleEndian(b.AsSpan(4), 2u); // count + BinaryPrimitives.WriteUInt32LittleEndian(b.AsSpan(8), 0x50000A01u); // guid 1 + BinaryPrimitives.WriteUInt32LittleEndian(b.AsSpan(12), 0u); // type 1 (item) + BinaryPrimitives.WriteUInt32LittleEndian(b.AsSpan(16), 0x50000A02u);// guid 2 + BinaryPrimitives.WriteUInt32LittleEndian(b.AsSpan(20), 1u); // type 2 (container) + + var p = GameEvents.ParseViewContents(b); + Assert.NotNull(p); + Assert.Equal(0x500000C9u, p!.Value.ContainerGuid); + Assert.Equal(2, p.Value.Items.Count); + Assert.Equal(0x50000A01u, p.Value.Items[0].Guid); + Assert.Equal(0u, p.Value.Items[0].ContainerType); + Assert.Equal(0x50000A02u, p.Value.Items[1].Guid); + Assert.Equal(1u, p.Value.Items[1].ContainerType); + } + + [Fact] + public void ParseViewContents_zeroCount_returnsEmptyList() + { + var b = new byte[8]; + BinaryPrimitives.WriteUInt32LittleEndian(b.AsSpan(0), 0x500000C9u); + BinaryPrimitives.WriteUInt32LittleEndian(b.AsSpan(4), 0u); + var p = GameEvents.ParseViewContents(b); + Assert.NotNull(p); + Assert.Empty(p!.Value.Items); + } + + [Fact] + public void ParseViewContents_truncated_returnsNull() + => Assert.Null(GameEvents.ParseViewContents(new byte[4])); +}