|
@@ -1,3 +1,5 @@
|
|
|
|
|
+#include <algorithm>
|
|
|
|
|
+#include <array>
|
|
|
#include <cstring>
|
|
#include <cstring>
|
|
|
#include <vector>
|
|
#include <vector>
|
|
|
|
|
|
|
@@ -7,15 +9,123 @@
|
|
|
#include "internal.h"
|
|
#include "internal.h"
|
|
|
|
|
|
|
|
namespace sunrise::client::content::items::packages {
|
|
namespace sunrise::client::content::items::packages {
|
|
|
|
|
+namespace {
|
|
|
|
|
+
|
|
|
|
|
+namespace buckets = state::build_data::inventory::buckets;
|
|
|
|
|
+
|
|
|
|
|
+/** The installed equipment-slot catalogue has rows 0 through 18. */
|
|
|
|
|
+constexpr std::uint8_t kEquipmentSlotCount = 19;
|
|
|
|
|
+/** Slot 16 is not backed by an inventory bucket in the installed equipment ABI. */
|
|
|
|
|
+constexpr std::uint8_t kUnavailableBucket = 0xFF;
|
|
|
|
|
+/**
|
|
|
|
|
+ * Inventory buckets are storage ranges; equipment slots are render/loadout positions. Their
|
|
|
|
|
+ * installed-build ABI is independent of item definitions and routes every item through its bucket.
|
|
|
|
|
+ */
|
|
|
|
|
+constexpr std::array<std::uint8_t, kEquipmentSlotCount> kBucketByEquipmentSlot{
|
|
|
|
|
+ 16, 3, 4, 36, 5, 6, 7, 0, 1, 2, 10, 9, 8, 27, 41, 17, kUnavailableBucket, 47, 49};
|
|
|
|
|
+
|
|
|
|
|
+/** @return The unique descriptor carrying one bucket id, or null. */
|
|
|
|
|
+[[nodiscard]] const buckets::Descriptor*
|
|
|
|
|
+find_bucket(std::span<const buckets::Descriptor> descriptors, std::uint8_t bucketId) noexcept {
|
|
|
|
|
+ const buckets::Descriptor* found = nullptr;
|
|
|
|
|
+ for (const buckets::Descriptor& descriptor : descriptors) {
|
|
|
|
|
+ if (descriptor.bucketId != bucketId) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (found != nullptr) {
|
|
|
|
|
+ return nullptr;
|
|
|
|
|
+ }
|
|
|
|
|
+ found = &descriptor;
|
|
|
|
|
+ }
|
|
|
|
|
+ return found;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Extracts and validates the installed bucket/equipment-slot relation.
|
|
|
|
|
+ * The table contains inline 72-byte records; its array elements are not index rows or tag links.
|
|
|
|
|
+ */
|
|
|
|
|
+[[nodiscard]] bool read_bucket_equipment_slots(
|
|
|
|
|
+ const reader::Source& source,
|
|
|
|
|
+ Storage& storage,
|
|
|
|
|
+ std::span<const buckets::Descriptor> descriptors,
|
|
|
|
|
+ std::array<std::int8_t, buckets::kDescriptorCapacity>& equipmentSlots) noexcept {
|
|
|
|
|
+ equipmentSlots.fill(buckets::kUnavailableEquipmentSlot);
|
|
|
|
|
+ std::uint32_t tableClass = 0;
|
|
|
|
|
+ tables::Array table{};
|
|
|
|
|
+ if (!reader::read_tag(source,
|
|
|
|
|
+ storage.scratch,
|
|
|
|
|
+ tables::kBucketDefinitionTableTag,
|
|
|
|
|
+ storage.child,
|
|
|
|
|
+ tableClass)) {
|
|
|
|
|
+ report_bucket_equipment_failure("table_read", 0, 0);
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (tableClass != tables::kBucketDefinitionTableClass) {
|
|
|
|
|
+ report_bucket_equipment_failure("table_class", tableClass, 0);
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!tables::find_array_at(
|
|
|
|
|
+ std::span<const std::byte>{storage.child}, tables::kTableArrayDescriptor, table)) {
|
|
|
|
|
+ report_bucket_equipment_failure("table_array", storage.child.size(), 0);
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (table.count != tables::kBucketDefinitionCount) {
|
|
|
|
|
+ report_bucket_equipment_failure("table_count", table.count, table.elementClass);
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ const std::span<const std::byte> tableBlob{storage.child};
|
|
|
|
|
+ const std::size_t tableSize = table.count * tables::kBucketDefinitionSize;
|
|
|
|
|
+ if (tableSize > tableBlob.size() || table.dataOffset > tableBlob.size() - tableSize) {
|
|
|
|
|
+ report_bucket_equipment_failure("table_extent", table.dataOffset, tableBlob.size());
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ std::array<bool, kEquipmentSlotCount> seenEquipmentSlots{};
|
|
|
|
|
+ std::array<bool, buckets::kDescriptorCapacity> seenBuckets{};
|
|
|
|
|
+ std::size_t mappedSlots = 0;
|
|
|
|
|
+ for (std::size_t index = 0; index < table.count; ++index) {
|
|
|
|
|
+ const std::size_t base = table.dataOffset + index * tables::kBucketDefinitionSize;
|
|
|
|
|
+ const std::uint8_t equipmentSlot = std::to_integer<std::uint8_t>(
|
|
|
|
|
+ tableBlob[base + tables::kBucketDefinitionEquipmentSlotOffset]);
|
|
|
|
|
+ if (equipmentSlot != tables::kBucketDefinitionUnavailableEquipmentSlot
|
|
|
|
|
+ && (equipmentSlot >= kEquipmentSlotCount || seenEquipmentSlots[equipmentSlot])) {
|
|
|
|
|
+ report_bucket_equipment_failure("equipment_slot", index, equipmentSlot);
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (equipmentSlot == tables::kBucketDefinitionUnavailableEquipmentSlot) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ const std::uint8_t bucketId = kBucketByEquipmentSlot[equipmentSlot];
|
|
|
|
|
+ const buckets::Descriptor* descriptor =
|
|
|
|
|
+ bucketId < buckets::kDescriptorCapacity ? find_bucket(descriptors, bucketId) : nullptr;
|
|
|
|
|
+ if (descriptor == nullptr || seenBuckets[bucketId]
|
|
|
|
|
+ || descriptor->arraySelector != buckets::ArraySelector::character) {
|
|
|
|
|
+ report_bucket_equipment_failure("bucket_relation", equipmentSlot, bucketId);
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ seenEquipmentSlots[equipmentSlot] = true;
|
|
|
|
|
+ seenBuckets[bucketId] = true;
|
|
|
|
|
+ equipmentSlots[bucketId] = static_cast<std::int8_t>(equipmentSlot);
|
|
|
|
|
+ ++mappedSlots;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (mappedSlots != 18 || seenEquipmentSlots[16]) {
|
|
|
|
|
+ report_bucket_equipment_failure("mapped_slots", mappedSlots, seenEquipmentSlots[16]);
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ report_bucket_equipment_mapping(mappedSlots);
|
|
|
|
|
+ return true;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+} // namespace
|
|
|
|
|
|
|
|
/** Publishes the inventory bucket descriptors from the root's bucket table. */
|
|
/** Publishes the inventory bucket descriptors from the root's bucket table. */
|
|
|
bool build_buckets(const reader::Source& source,
|
|
bool build_buckets(const reader::Source& source,
|
|
|
Storage& storage,
|
|
Storage& storage,
|
|
|
std::span<const std::byte> root) noexcept {
|
|
std::span<const std::byte> root) noexcept {
|
|
|
- namespace buckets = state::build_data::inventory::buckets;
|
|
|
|
|
if (state::build_data::inventory_bucket_descriptors_ready()) {
|
|
if (state::build_data::inventory_bucket_descriptors_ready()) {
|
|
|
return true;
|
|
return true;
|
|
|
}
|
|
}
|
|
|
|
|
+ storage.bucketCount = 0;
|
|
|
|
|
+ storage.equipmentSlotByBucket.fill(buckets::kUnavailableEquipmentSlot);
|
|
|
std::uint32_t tableTag = 0;
|
|
std::uint32_t tableTag = 0;
|
|
|
if (!tables::slot_tag(root, tables::kBucketTableSlot, tableTag) || tableTag == 0
|
|
if (!tables::slot_tag(root, tables::kBucketTableSlot, tableTag) || tableTag == 0
|
|
|
|| !reader::read_tag(source, storage.scratch, tableTag, storage.child)) {
|
|
|| !reader::read_tag(source, storage.scratch, tableTag, storage.child)) {
|
|
@@ -30,8 +140,9 @@ bool build_buckets(const reader::Source& source,
|
|
|
if (count <= 0 || static_cast<std::size_t>(count) > buckets::kDescriptorCapacity) {
|
|
if (count <= 0 || static_cast<std::size_t>(count) > buckets::kDescriptorCapacity) {
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
- std::vector<buckets::Descriptor> rows(static_cast<std::size_t>(count));
|
|
|
|
|
- for (std::size_t index = 0; index < rows.size(); ++index) {
|
|
|
|
|
|
|
+ const std::size_t bucketCount = static_cast<std::size_t>(count);
|
|
|
|
|
+ std::array<buckets::Descriptor, buckets::kDescriptorCapacity> descriptors{};
|
|
|
|
|
+ for (std::size_t index = 0; index < bucketCount; ++index) {
|
|
|
const std::size_t base =
|
|
const std::size_t base =
|
|
|
tables::kBucketFirstDescriptor + index * tables::kBucketDescriptorSize;
|
|
tables::kBucketFirstDescriptor + index * tables::kBucketDescriptorSize;
|
|
|
if (base + tables::kBucketDescriptorSize > blob.size()) {
|
|
if (base + tables::kBucketDescriptorSize > blob.size()) {
|
|
@@ -43,13 +154,21 @@ bool build_buckets(const reader::Source& source,
|
|
|
&firstSlot, blob.data() + base + tables::kBucketFirstSlotOffset, sizeof firstSlot);
|
|
&firstSlot, blob.data() + base + tables::kBucketFirstSlotOffset, sizeof firstSlot);
|
|
|
std::memcpy(
|
|
std::memcpy(
|
|
|
&slotCount, blob.data() + base + tables::kBucketSlotCountOffset, sizeof slotCount);
|
|
&slotCount, blob.data() + base + tables::kBucketSlotCountOffset, sizeof slotCount);
|
|
|
- rows[index].bucketId = std::to_integer<std::uint8_t>(blob[base]);
|
|
|
|
|
- rows[index].firstSlot = static_cast<std::uint16_t>(firstSlot);
|
|
|
|
|
- rows[index].slotCount = static_cast<std::uint16_t>(slotCount);
|
|
|
|
|
- rows[index].arraySelector = static_cast<buckets::ArraySelector>(
|
|
|
|
|
|
|
+ descriptors[index].bucketId = std::to_integer<std::uint8_t>(blob[base]);
|
|
|
|
|
+ descriptors[index].firstSlot = static_cast<std::uint16_t>(firstSlot);
|
|
|
|
|
+ descriptors[index].slotCount = static_cast<std::uint16_t>(slotCount);
|
|
|
|
|
+ descriptors[index].arraySelector = static_cast<buckets::ArraySelector>(
|
|
|
std::to_integer<std::uint8_t>(blob[base + tables::kBucketArraySelectorOffset]));
|
|
std::to_integer<std::uint8_t>(blob[base + tables::kBucketArraySelectorOffset]));
|
|
|
}
|
|
}
|
|
|
- return state::build_data::publish_inventory_bucket_descriptors(rows);
|
|
|
|
|
|
|
+ std::array<std::int8_t, buckets::kDescriptorCapacity> equipmentSlots{};
|
|
|
|
|
+ if (!read_bucket_equipment_slots(
|
|
|
|
|
+ source, storage, std::span(descriptors).first(bucketCount), equipmentSlots)) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ storage.bucketRows = descriptors;
|
|
|
|
|
+ storage.bucketCount = bucketCount;
|
|
|
|
|
+ storage.equipmentSlotByBucket = equipmentSlots;
|
|
|
|
|
+ return true;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/** Publishes the socket entry list table from the root. */
|
|
/** Publishes the socket entry list table from the root. */
|