diff --git a/src/AcDream.Core.Net/Messages/PlayerDescriptionParser.cs b/src/AcDream.Core.Net/Messages/PlayerDescriptionParser.cs index 110e12b..ac88579 100644 --- a/src/AcDream.Core.Net/Messages/PlayerDescriptionParser.cs +++ b/src/AcDream.Core.Net/Messages/PlayerDescriptionParser.cs @@ -355,6 +355,29 @@ public static class PlayerDescriptionParser shortcuts.Add(new ShortcutEntry(idx, guid, spellId, layer)); } } + + if (optionFlags.HasFlag(CharacterOptionDataFlag.SpellLists8)) + { + for (int b = 0; b < 8; b++) + { + uint count = ReadU32(payload, ref pos); + if (count > 10_000) throw new FormatException("unreasonable hotbar count"); + var list = new List((int)count); + for (uint i = 0; i < count; i++) + list.Add(ReadU32(payload, ref pos)); + hotbarSpells.Add(list); + } + } + else if (payload.Length - pos >= 4) + { + // Legacy single-list fallback (holtburger events.rs:544-556). + uint count = ReadU32(payload, ref pos); + if (count > 10_000) throw new FormatException("unreasonable hotbar count"); + var list = new List((int)count); + for (uint i = 0; i < count; i++) + list.Add(ReadU32(payload, ref pos)); + hotbarSpells.Add(list); + } } } catch (FormatException ex) diff --git a/tests/AcDream.Core.Net.Tests/PlayerDescriptionParserTests.cs b/tests/AcDream.Core.Net.Tests/PlayerDescriptionParserTests.cs index 3d1d157..68ff345 100644 --- a/tests/AcDream.Core.Net.Tests/PlayerDescriptionParserTests.cs +++ b/tests/AcDream.Core.Net.Tests/PlayerDescriptionParserTests.cs @@ -438,6 +438,66 @@ public sealed class PlayerDescriptionParserTests Assert.Equal(1u, parsed.Value.Shortcuts[0].Index); } + [Fact] + public void TryParse_TrailerHotbarSpells_SpellLists8_Reads8Lists() + { + 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); // empty enchantment mask + + writer.Write(0x400u); // option_flags = SPELL_LISTS8 + writer.Write(0u); // options1 + + // 8 hotbars: counts {2,1,0,0,0,0,0,3} + writer.Write(2u); writer.Write(11u); writer.Write(12u); + writer.Write(1u); writer.Write(21u); + writer.Write(0u); + writer.Write(0u); + writer.Write(0u); + writer.Write(0u); + writer.Write(0u); + writer.Write(3u); writer.Write(81u); writer.Write(82u); writer.Write(83u); + + var parsed = PlayerDescriptionParser.TryParse(sb.ToArray()); + + Assert.NotNull(parsed); + Assert.Equal(8, parsed!.Value.HotbarSpells.Count); + Assert.Equal(new uint[] { 11u, 12u }, parsed.Value.HotbarSpells[0]); + Assert.Equal(new uint[] { 21u }, parsed.Value.HotbarSpells[1]); + Assert.Empty(parsed.Value.HotbarSpells[2]); + Assert.Equal(new uint[] { 81u, 82u, 83u }, parsed.Value.HotbarSpells[7]); + } + + [Fact] + public void TryParse_TrailerHotbarSpells_NoSpellLists8_ReadsSingleLegacyList() + { + 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); // empty enchantment mask + + writer.Write(0u); // option_flags = None (no SPELL_LISTS8) + writer.Write(0u); // options1 + + // Legacy single hotbar list: count=2, two spells. + writer.Write(2u); writer.Write(101u); writer.Write(102u); + + var parsed = PlayerDescriptionParser.TryParse(sb.ToArray()); + + Assert.NotNull(parsed); + Assert.Single(parsed!.Value.HotbarSpells); + Assert.Equal(new uint[] { 101u, 102u }, parsed.Value.HotbarSpells[0]); + } + [Fact] public void TryParse_TrailerAbsent_LessThan8BytesAfterEnchantments_PreservesUpstreamAndDoesNotFlagTruncation() {