using System; using System.Collections.Generic; using System.Linq; using static System.Buffers.Binary.BinaryPrimitives; namespace NHSE.Core; /// /// Handles operations for parsing the player inventory. /// /// /// Player Inventory is comprised of multiple values, which we won't bother reimplementing as a convertible data structure. ///
Refer to GSavePlayerItemBaggage's ItemBag & ItemPocket in the dumped c-structure schema.
///
public static class PlayerItemSet { private const int ItemSet_Quantity = 2; // Pouch (Bag) & Pocket. private const int ItemSet_ItemCount = 20; // 20 items per item set. private const int ItemSet_ItemSize = Item.SIZE * ItemSet_ItemCount; private const int ItemSet_MetaSize = 4 + ItemSet_ItemCount; private const int ItemSet_TotalSize = (ItemSet_ItemSize + ItemSet_MetaSize) * ItemSet_Quantity; private const int ShiftToTopOfStructure = -ItemSet_MetaSize - (Item.SIZE * ItemSet_ItemCount); // shifts slot1 offset => top of data structure /// /// Gets the Offset and Size to read from based on the Item 1 RAM offset. /// /// Item Slot 1 offset in RAM /// Offset to read from /// Length of data to read from public static void GetOffsetLength(uint slot1, out uint offset, out int length) { offset = (uint)((int)slot1 + ShiftToTopOfStructure); length = ItemSet_TotalSize; } /// /// Compares the raw data to the expected data layout. /// /// Raw RAM from the game from the offset read (as per ). /// True if valid, false if not valid or corrupt. public static bool ValidateItemBinary(ReadOnlySpan data) { // Check the unlocked slot count -- expect 0,10,20 var bagCount = ReadUInt32LittleEndian(data[ItemSet_ItemSize..]); if (bagCount > ItemSet_ItemCount || bagCount % 10 != 0) // pouch21-39 count return false; var pocketCount = ReadUInt32LittleEndian(data[(ItemSet_ItemSize + ItemSet_MetaSize + ItemSet_ItemSize)..]); if (pocketCount != ItemSet_ItemCount) // pouch0-19 count should be 20. return false; // Check the item wheel binding -- expect -1 or [0,7] // Disallow duplicate binds! // Don't bother checking that bind[i] (when ! -1) is not NONE at items[i]. We don't need to check everything! var bound = new List(); if (!ValidateBindList(data, ItemSet_ItemSize + 4, bound)) return false; if (!ValidateBindList(data, ItemSet_ItemSize + 4 + (ItemSet_ItemSize + ItemSet_MetaSize), bound)) return false; return true; } private static bool ValidateBindList(ReadOnlySpan data, int bindStart, ICollection bound) { for (int i = 0; i < ItemSet_ItemCount; i++) { var bind = data[bindStart + i]; if (bind == 0xFF) // Not bound continue; if (bind > 7) // Only [0,7] permitted as the wheel has 8 spots return false; if (bound.Contains(bind)) // Wheel index is already bound to another item slot return false; bound.Add(bind); } return true; } /// /// Reads the items present in the player inventory packet and returns the list of items. /// /// Player Inventory packet public static Item[] ReadPlayerInventory(ReadOnlySpan data) { var items = GetEmptyItemArray(40); ReadPlayerInventory(data, items); return items; } private static Item[] GetEmptyItemArray(int count) { var items = new Item[count]; for (int i = 0; i < items.Length; i++) items[i] = new Item(); return items; } /// /// Reads the items present in the player inventory packet into the list of items. /// /// Player Inventory packet /// 40 Item array public static void ReadPlayerInventory(ReadOnlySpan data, IReadOnlyList destination) { var pocket2 = destination.Take(20).ToArray(); var pocket1 = destination.Skip(20).ToArray(); var p1 = Item.GetArray(data[..ItemSet_ItemSize]); var p2 = Item.GetArray(data.Slice(ItemSet_ItemSize + 0x18, ItemSet_ItemSize)); for (int i = 0; i < pocket1.Length; i++) pocket1[i].CopyFrom(p1[i]); for (int i = 0; i < pocket2.Length; i++) pocket2[i].CopyFrom(p2[i]); } /// /// Writes the items in the list of items to the player inventory packet. /// /// Player Inventory packet /// 40 Item array public static void WritePlayerInventory(Span data, IReadOnlyList source) { var pocket2 = source.Take(20).ToArray(); var pocket1 = source.Skip(20).ToArray(); var p1 = Item.SetArray(pocket1); var p2 = Item.SetArray(pocket2); p1.CopyTo(data); p2.CopyTo(data[(ItemSet_ItemSize + 0x18)..]); } }