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])); +}