feat(net): #13 read OptionFlags + Options1 after enchantments
First step of the PD trailer walk. Wraps trailer reads in their own try/catch so a malformed trailer does not null out the upstream attribute/skill/spell/enchantment data we already extracted. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
65870349a8
commit
becbde60a4
2 changed files with 59 additions and 14 deletions
|
|
@ -309,26 +309,42 @@ public static class PlayerDescriptionParser
|
|||
ReadSpellTable(payload, ref pos, spells);
|
||||
|
||||
// ── Enchantments (Issue #7 / #12) ───────────────────────────────
|
||||
// Outer EnchantmentMask + per-bucket count + N×Enchantment(60-64 B).
|
||||
// Holtburger events.rs:462-501. After this block come options /
|
||||
// shortcuts / hotbars / inventory / equipped — those need a
|
||||
// heuristic walker for the variable-length gameplay_options blob.
|
||||
// Filed as ISSUES.md #13 for follow-up; stop here cleanly so
|
||||
// partial parses still populate enchantments.
|
||||
if (vectorFlags.HasFlag(DescriptionVectorFlag.Enchantment))
|
||||
ReadEnchantmentBlock(payload, ref pos, enchantments);
|
||||
|
||||
// ── Trailer (Issue #13): options + shortcuts + hotbars + inventory ──
|
||||
// Wrapped in its own try/catch — a malformed trailer must not destroy
|
||||
// the attribute / skill / spell / enchantment data we already extracted.
|
||||
CharacterOptionDataFlag optionFlags = CharacterOptionDataFlag.None;
|
||||
uint options1 = 0;
|
||||
uint options2 = 0;
|
||||
uint spellbookFilters = 0;
|
||||
List<ShortcutEntry> shortcuts = new();
|
||||
List<IReadOnlyList<uint>> hotbarSpells = new();
|
||||
List<(uint, uint)> desiredComps = new();
|
||||
ReadOnlyMemory<byte> gameplayOptions = ReadOnlyMemory<byte>.Empty;
|
||||
List<InventoryEntry> inventory = new();
|
||||
List<EquippedEntry> equipped = new();
|
||||
|
||||
try
|
||||
{
|
||||
if (payload.Length - pos >= 8)
|
||||
{
|
||||
optionFlags = (CharacterOptionDataFlag)ReadU32(payload, ref pos);
|
||||
options1 = ReadU32(payload, ref pos);
|
||||
}
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
// Trailer corrupted — keep what we have and return.
|
||||
}
|
||||
|
||||
return new Parsed(
|
||||
weenieType, propertyFlags, vectorFlags, hasHealth,
|
||||
bundle, positions, attributes, skills, spells, enchantments,
|
||||
CharacterOptionDataFlag.None, 0u, 0u,
|
||||
System.Array.Empty<ShortcutEntry>(),
|
||||
System.Array.Empty<IReadOnlyList<uint>>(),
|
||||
System.Array.Empty<(uint, uint)>(),
|
||||
0u,
|
||||
ReadOnlyMemory<byte>.Empty,
|
||||
System.Array.Empty<InventoryEntry>(),
|
||||
System.Array.Empty<EquippedEntry>());
|
||||
optionFlags, options1, options2,
|
||||
shortcuts, hotbarSpells, desiredComps, spellbookFilters,
|
||||
gameplayOptions, inventory, equipped);
|
||||
}
|
||||
catch (FormatException ex)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -334,4 +334,33 @@ public sealed class PlayerDescriptionParserTests
|
|||
Assert.Equal(2.0f, parsed.Value.Spells[1234u]);
|
||||
Assert.Equal(2.0f, parsed.Value.Spells[5678u]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryParse_TrailerOptionFlagsAndOptions1_AreReadAfterEnchantments()
|
||||
{
|
||||
// ATTRIBUTE | ENCHANTMENT vector flag; empty enchantment mask (0).
|
||||
// After mask, trailer adds u32 option_flags + u32 options1.
|
||||
var sb = new MemoryStream();
|
||||
using var writer = new BinaryWriter(sb);
|
||||
writer.Write(0u); // propertyFlags
|
||||
writer.Write(0x52u); // weenieType
|
||||
writer.Write(0x201u); // ATTRIBUTE | ENCHANTMENT
|
||||
writer.Write(1u); // has_health
|
||||
writer.Write(0u); // empty attribute_flags
|
||||
|
||||
writer.Write(0u); // EnchantmentMask = empty
|
||||
|
||||
// Trailer header: option_flags + options1
|
||||
writer.Write(0u); // option_flags = None — no further sections
|
||||
writer.Write(0xDEADBEEFu); // options1 sentinel
|
||||
|
||||
// No more bytes — spellbook_filters is optional (defaults to 0).
|
||||
var parsed = PlayerDescriptionParser.TryParse(sb.ToArray());
|
||||
|
||||
Assert.NotNull(parsed);
|
||||
Assert.Equal(PlayerDescriptionParser.CharacterOptionDataFlag.None, parsed!.Value.OptionFlags);
|
||||
Assert.Equal(0xDEADBEEFu, parsed.Value.Options1);
|
||||
Assert.Empty(parsed.Value.Shortcuts);
|
||||
Assert.Empty(parsed.Value.Inventory);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue