feat(net): #13 heuristic inventory locator after gameplay_options blob

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-05-10 09:37:46 +02:00
parent d9a5e40203
commit 91693ea44c
2 changed files with 90 additions and 1 deletions

View file

@ -402,7 +402,17 @@ public static class PlayerDescriptionParser
if (optionFlags.HasFlag(CharacterOptionDataFlag.CharacterOptions2))
options2 = ReadU32(payload, ref pos);
if (!optionFlags.HasFlag(CharacterOptionDataFlag.GameplayOptions))
if (optionFlags.HasFlag(CharacterOptionDataFlag.GameplayOptions))
{
int gameplayStart = pos;
if (TryHeuristicInventoryStart(payload, gameplayStart, out int invStart, out int end,
inventory, equipped))
{
gameplayOptions = payload.Slice(gameplayStart, invStart - gameplayStart).ToArray();
pos = end;
}
}
else
{
// Strict path: inventory + equipped follow directly.
TryUnpackInventoryStrict(payload, ref pos, inventory, equipped);
@ -760,6 +770,43 @@ public static class PlayerDescriptionParser
return true;
}
/// <summary>4-byte-aligned forward scan from <paramref name="start"/>
/// looking for the first offset where <c>TryUnpackInventoryStrict</c>
/// consumes exactly to end-of-buffer. Mirrors holtburger
/// <c>find_inventory_start_after_gameplay_options</c> in events.rs:195-218.</summary>
private static bool TryHeuristicInventoryStart(
ReadOnlySpan<byte> src, int start,
out int invStart, out int end,
List<InventoryEntry> inventory, List<EquippedEntry> equipped)
{
invStart = end = 0;
inventory.Clear();
equipped.Clear();
if (start + 8 > src.Length) return false;
int candidate = start;
int misalign = candidate & 3;
if (misalign != 0) candidate += 4 - misalign;
int last = src.Length - 8;
while (candidate <= last)
{
int tmp = candidate;
var tmpInv = new List<InventoryEntry>();
var tmpEq = new List<EquippedEntry>();
if (TryUnpackInventoryStrict(src, ref tmp, tmpInv, tmpEq) && tmp == src.Length)
{
invStart = candidate;
end = tmp;
inventory.AddRange(tmpInv);
equipped.AddRange(tmpEq);
return true;
}
candidate += 4;
}
return false;
}
private static ushort ReadU16(ReadOnlySpan<byte> src, ref int pos)
{
if (src.Length - pos < 2) throw new FormatException("truncated u16");