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