#include #include #include "definition_index_table.h" #include "items.h" namespace sunrise::middleware::content::packages::tables::items { namespace { /** The signed stack limit comes before the bucket id. */ constexpr std::size_t kMaxStackSizeOffset = 180; /** A nonzero predicate byte marks an instanced definition. */ constexpr std::size_t kInstancedOffset = 187; /** The equipment block is self-relative from this offset, zero when absent. */ constexpr std::size_t kEquipmentBlockOffset = 16; /** The equipment block stores its signed slot id here. */ constexpr std::size_t kEquipmentSlotOffset = 24; /** The socket entry list block is self-relative from this offset, zero when absent. */ constexpr std::size_t kSocketEntryListBlockOffset = 128; /** A present block declares 12 bytes. A shorter tail is not a block. */ constexpr std::size_t kSocketEntryListBlockSize = 12; /** Equipment slots run from 0 through 19. */ constexpr std::int32_t kEquipmentSlotCount = 20; /** A socket entry stores its type index first and its initial plug next. */ constexpr std::size_t kSocketTypeOffset = 0; constexpr std::size_t kSocketPlugOffset = 2; /** Fixed fields end after the instanced predicate. */ constexpr std::size_t kFixedFieldEnd = kInstancedOffset + 1; /** Optional plug category used to expand three native reusable plug families. */ constexpr std::size_t kPlugCategoryOffset = 392; /** Embedded reusable-list array descriptor inside one 80-byte ordinary socket entry. */ constexpr std::size_t kEmbeddedPlugListOffset = 64; /** Reusable and randomized shared plug-set row indices inside one socket entry. */ constexpr std::size_t kReusablePlugSetIndexOffset = 12; constexpr std::size_t kRandomizedPlugSetIndexOffset = 32; /** One shared plug-set table row is 24 bytes and carries its member descriptor at byte 8. */ constexpr std::size_t kPlugSetRowStride = 24; constexpr std::size_t kPlugSetMemberDescriptorOffset = 8; /** Both embedded and shared plug member rows name an item index first and occupy 32 bytes. */ constexpr std::size_t kPlugMemberStride = 32; constexpr std::size_t kPlugMemberIndexOffset = 0; /** Native arrays use 16-bit definition indices even though their serialized field is 32-bit. */ constexpr std::uint64_t kMaximumPlugMemberCount = 65535; /** @param blob Source bytes. @param offset Field offset. @param value Receives the field. */ template [[nodiscard]] bool read(std::span blob, std::size_t offset, Value& value) noexcept { if (offset > blob.size() || blob.size() - offset < sizeof value) { return false; } std::memcpy(&value, blob.data() + offset, sizeof value); return true; } /** * Reads the socket entry list index a definition declares. * @param definition Whole item definition bytes. * @param row Receives list presence and index. */ void read_socket_entry_list(std::span definition, Row& row) noexcept { row.hasSocketEntryList = false; row.socketEntryListIndex = 0; std::int64_t relative = 0; if (!read(definition, kSocketEntryListBlockOffset, relative) || relative == 0) { return; } const std::int64_t block = static_cast(kSocketEntryListBlockOffset) + relative; if (block < 0) { return; } const auto blockOffset = static_cast(block); if (blockOffset + kSocketEntryListBlockSize > definition.size()) { return; } std::uint16_t index = 0; if (read(definition, static_cast(block), index)) { row.hasSocketEntryList = true; row.socketEntryListIndex = index; } } /** * Reads the optional equipment slot a definition declares. * @param definition Whole item definition bytes. * @param slot Receives the slot when the block is present and readable. * @param raw Receives the unvalidated declared value, or -1 when the block is absent. */ void read_equipment_slot(std::span definition, std::optional& slot, std::int32_t& raw) noexcept { slot.reset(); raw = -1; std::int64_t relative = 0; if (!read(definition, kEquipmentBlockOffset, relative) || relative == 0) { return; } const std::int64_t block = static_cast(kEquipmentBlockOffset) + relative; if (block < 0) { return; } // A value outside the equippable range marks an item that takes no slot. std::int32_t value = 0; if (read(definition, static_cast(block) + kEquipmentSlotOffset, value) && value >= 0 && value < static_cast(kEquipmentSlotCount)) { slot = static_cast(value); } } /** * Reads the ordinary socket lanes a definition declares. * The lane's type index picks the overflow-hash priority the character record applies, so it is * read from the same entry as the plug rather than worked out later. * @param definition Whole item definition bytes. * @param row Receives socket presence, count, types and initial plugs. */ void read_sockets(std::span definition, Row& row) noexcept { row.hasSockets = false; row.socketCount = 0; std::fill(std::begin(row.initialPlugs), std::end(row.initialPlugs), kUnavailablePlug); std::fill(std::begin(row.socketTypes), std::end(row.socketTypes), kUnavailableSocketType); std::int64_t relative = 0; if (!read(definition, kSocketBlockOffset, relative) || relative == 0) { return; } const std::int64_t block = static_cast(kSocketBlockOffset) + relative; if (block < 0 || static_cast(block) >= definition.size()) { return; } Array array{}; if (!find_array_at(definition, static_cast(block), array) || array.elementClass != kOrdinarySocketClass) { return; } row.hasSockets = true; const auto lanes = static_cast((std::min)(array.count, std::uint64_t{kSocketCapacity})); for (std::size_t lane = 0; lane < lanes; ++lane) { const std::size_t entry = array.dataOffset + lane * kSocketEntryStride; std::uint16_t plug = kUnavailablePlug; std::uint16_t type = kUnavailableSocketType; if (!read(definition, entry + kSocketPlugOffset, plug) || !read(definition, entry + kSocketTypeOffset, type)) { return; } row.initialPlugs[lane] = plug; row.socketTypes[lane] = type; row.socketCount = static_cast(lane + 1); } } /** Walks one checked array of 32-byte plug rows. */ [[nodiscard]] bool visit_plug_array(std::span blob, const Array& array, AllowedPlugVisitor visitor, void* context) noexcept { if (array.count > kMaximumPlugMemberCount || array.dataOffset > blob.size() || array.count > (blob.size() - array.dataOffset) / kPlugMemberStride) { return false; } for (std::uint64_t index = 0; index < array.count; ++index) { std::uint32_t itemDefinitionIndex = 0; const std::size_t row = array.dataOffset + static_cast(index) * kPlugMemberStride; if (!read(blob, row + kPlugMemberIndexOffset, itemDefinitionIndex) || !visitor(context, itemDefinitionIndex)) { return false; } } return true; } /** Walks one reusable or randomized shared plug-set row, when the socket declares it. */ [[nodiscard]] bool visit_shared_plug_set(std::span definition, std::size_t socketEntry, std::size_t setIndexOffset, std::span plugSetTable, const Array& sets, AllowedPlugVisitor visitor, void* context) noexcept { std::uint16_t setIndex = kUnavailablePlug; if (!read(definition, socketEntry + setIndexOffset, setIndex)) { return false; } if (setIndex == kUnavailablePlug) { return true; } if (setIndex >= sets.count || sets.dataOffset > plugSetTable.size() || sets.count > (plugSetTable.size() - sets.dataOffset) / kPlugSetRowStride) { return false; } const std::size_t descriptor = sets.dataOffset + static_cast(setIndex) * kPlugSetRowStride + kPlugSetMemberDescriptorOffset; std::uint64_t memberCount = 0; if (!read(plugSetTable, descriptor, memberCount)) { return false; } if (memberCount == 0) { return true; } Array members{}; return find_array_at(plugSetTable, descriptor, members) && visit_plug_array(plugSetTable, members, visitor, context); } /** The block header carries its own self-relative pointer to the entries at byte 8. */ constexpr std::size_t kStatDataMember = 8; /** One stat entry is 40 blob bytes. */ constexpr std::size_t kStatEntryStride = 40; /** A stat entry names its stat table row at byte 16. */ constexpr std::size_t kStatEntryRow = 16; /** A stat entry carries its signed contribution at byte 20. */ constexpr std::size_t kStatEntryValue = 20; /** * Reads the per-item stat contributions declared inside the definition blob. * Two self-relative hops: the definition points at a header, the header points at the entries. * Both are blob offsets and are valid only against the serialized definition. * @param definition Whole item definition bytes. * @param row Receives the declared stat rows and values. */ void read_stats(std::span definition, Row& row) noexcept { row.statCount = 0; std::int64_t blockRelative = 0; if (!read(definition, kStatBlockOffset, blockRelative)) { return; } const auto header = static_cast(kStatBlockOffset) + blockRelative; if (header < 0 || static_cast(header) + kStatDataMember > definition.size()) { return; } std::uint64_t count = 0; std::int64_t dataRelative = 0; if (!read(definition, static_cast(header), count) || count == 0 || count > kStatCapacity || !read(definition, static_cast(header) + kStatDataMember, dataRelative)) { return; } const std::int64_t data = header + static_cast(kStatDataMember) + dataRelative; if (data < 0 || static_cast(data) + kStatEntryStride * count > definition.size()) { return; } for (std::uint64_t entry = 0; entry < count; ++entry) { const auto base = static_cast(data) + kStatEntryStride * entry; if (!read(definition, base + kStatEntryRow, row.statRows[entry]) || !read(definition, base + kStatEntryValue, row.statValues[entry])) { row.statCount = 0; return; } } row.statCount = static_cast(count); } } // namespace /** Reads the fixed fields of one item definition blob. */ bool read_definition(std::span definition, Row& row) noexcept { const std::uint32_t hash = row.definitionHash; const std::uint16_t index = row.definitionIndex; row = {}; row.definitionHash = hash; row.definitionIndex = index; row.insertionMaterialRequirementSetIndex = kUnavailableMaterialRequirementSetIndex; row.enabledMaterialRequirementSetIndex = kUnavailableMaterialRequirementSetIndex; std::fill(std::begin(row.initialPlugs), std::end(row.initialPlugs), kUnavailablePlug); std::fill(std::begin(row.socketTypes), std::end(row.socketTypes), kUnavailableSocketType); if (definition.size() < kFixedFieldEnd) { return false; } std::uint8_t instanced = 0; if (!read(definition, kBucketIdOffset, row.bucketId) || !read(definition, kMaxStackSizeOffset, row.maxStackSize) || !read(definition, kInstancedOffset, instanced)) { return false; } row.instanced = instanced != 0; // Short legacy definitions simply do not declare a plug category. (void)read(definition, kPlugCategoryOffset, row.plugCategoryHash); (void)read(definition, kInsertionMaterialRequirementSetIndexOffset, row.insertionMaterialRequirementSetIndex); (void)read(definition, kEnabledMaterialRequirementSetIndexOffset, row.enabledMaterialRequirementSetIndex); read_stats(definition, row); read_appearance(definition, row); read_socket_entry_list(definition, row); read_equipment_slot(definition, row.equipmentSlot, row.rawEquipmentSlot); read_sockets(definition, row); return true; } /** Visits every list-backed allowed plug for one exact ordinary socket lane. */ bool visit_allowed_plugs(std::span definition, std::span plugSetTable, std::uint8_t lane, AllowedPlugVisitor visitor, void* context) noexcept { if (visitor == nullptr || lane >= kSocketCapacity) { return false; } std::int64_t socketBlockRelative = 0; if (!read(definition, kSocketBlockOffset, socketBlockRelative) || socketBlockRelative == 0) { return false; } const std::int64_t socketBlock = static_cast(kSocketBlockOffset) + socketBlockRelative; if (socketBlock < 0 || static_cast(socketBlock) >= definition.size()) { return false; } Array sockets{}; if (!find_array_at(definition, static_cast(socketBlock), sockets) || sockets.elementClass != kOrdinarySocketClass || lane >= sockets.count || sockets.dataOffset > definition.size() || sockets.count > (definition.size() - sockets.dataOffset) / kSocketEntryStride) { return false; } Array sets{}; if (!find_array_at(plugSetTable, kTableArrayDescriptor, sets)) { return false; } const std::size_t socketEntry = sockets.dataOffset + static_cast(lane) * kSocketEntryStride; Array embedded{}; std::uint64_t embeddedCount = 0; if (!read(definition, socketEntry + kEmbeddedPlugListOffset, embeddedCount) || (embeddedCount != 0 && (!find_array_at(definition, socketEntry + kEmbeddedPlugListOffset, embedded) || !visit_plug_array(definition, embedded, visitor, context)))) { return false; } return visit_shared_plug_set(definition, socketEntry, kReusablePlugSetIndexOffset, plugSetTable, sets, visitor, context) && visit_shared_plug_set(definition, socketEntry, kRandomizedPlugSetIndexOffset, plugSetTable, sets, visitor, context); } } // namespace sunrise::middleware::content::packages::tables::items