package_root_tables.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. #include <algorithm>
  2. #include <array>
  3. #include <cstring>
  4. #include <vector>
  5. #include "../../../../state/build_data/inventory/buckets/definition.h"
  6. #include "../../../../state/build_data/runtime.h"
  7. #include "../../../../state/build_data/socket_entry_lists/definition.h"
  8. #include "internal.h"
  9. namespace sunrise::client::content::items::packages {
  10. namespace {
  11. namespace buckets = state::build_data::inventory::buckets;
  12. /** The installed equipment-slot catalogue has rows 0 through 18. */
  13. constexpr std::uint8_t kEquipmentSlotCount = 19;
  14. /** Slot 16 is not backed by an inventory bucket in the installed equipment ABI. */
  15. constexpr std::uint8_t kUnavailableBucket = 0xFF;
  16. /**
  17. * Inventory buckets are storage ranges; equipment slots are render/loadout positions. Their
  18. * installed-build ABI is independent of item definitions and routes every item through its bucket.
  19. */
  20. constexpr std::array<std::uint8_t, kEquipmentSlotCount> kBucketByEquipmentSlot{
  21. 16, 3, 4, 36, 5, 6, 7, 0, 1, 2, 10, 9, 8, 27, 41, 17, kUnavailableBucket, 47, 49};
  22. /** @return The unique descriptor carrying one bucket id, or null. */
  23. [[nodiscard]] const buckets::Descriptor*
  24. find_bucket(std::span<const buckets::Descriptor> descriptors, std::uint8_t bucketId) noexcept {
  25. const buckets::Descriptor* found = nullptr;
  26. for (const buckets::Descriptor& descriptor : descriptors) {
  27. if (descriptor.bucketId != bucketId) {
  28. continue;
  29. }
  30. if (found != nullptr) {
  31. return nullptr;
  32. }
  33. found = &descriptor;
  34. }
  35. return found;
  36. }
  37. /** The first tag a class sweep reported and how many entries carried the class. */
  38. struct BucketDefinitionTable {
  39. std::uint32_t tag{};
  40. std::size_t matches{};
  41. };
  42. /** @param context Sweep result. @param tag One matching tag. @return Always true, to count all. */
  43. bool collect_bucket_definition_tag(void* context, std::uint32_t tag) noexcept {
  44. auto* located = static_cast<BucketDefinitionTable*>(context);
  45. if (located->matches == 0) {
  46. located->tag = tag;
  47. }
  48. ++located->matches;
  49. return true;
  50. }
  51. /**
  52. * Locates the bucket-definition table by the class its entry record carries.
  53. * A tag is a package-local handle that a repack moves, so naming one pins the extraction to a
  54. * single install. The class belongs to the definition ABI, so it names the table wherever it was
  55. * packed. Entry tables are plain file data, so the sweep needs no block keys.
  56. * @param source Package source.
  57. * @param tag Receives the located tag.
  58. * @return True when exactly one installed entry carries the class.
  59. */
  60. [[nodiscard]] bool find_bucket_definition_table(const reader::Source& source,
  61. std::uint32_t& tag) noexcept {
  62. BucketDefinitionTable located{};
  63. reader::ScanResult scanned{};
  64. if (!reader::scan_class(source.directory,
  65. tables::kBucketDefinitionTableClass,
  66. &collect_bucket_definition_tag,
  67. &located,
  68. scanned)
  69. || located.matches != 1) {
  70. report_bucket_equipment_failure("table_sweep", located.matches, scanned.packages);
  71. return false;
  72. }
  73. tag = located.tag;
  74. return true;
  75. }
  76. /**
  77. * Extracts and validates the installed bucket/equipment-slot relation.
  78. * The table contains inline 72-byte records; its array elements are not index rows or tag links.
  79. */
  80. [[nodiscard]] bool read_bucket_equipment_slots(
  81. const reader::Source& source,
  82. Storage& storage,
  83. std::span<const buckets::Descriptor> descriptors,
  84. std::array<std::int8_t, buckets::kDescriptorCapacity>& equipmentSlots) noexcept {
  85. equipmentSlots.fill(buckets::kUnavailableEquipmentSlot);
  86. std::uint32_t tableTag = 0;
  87. if (!find_bucket_definition_table(source, tableTag)) {
  88. return false;
  89. }
  90. std::uint32_t tableClass = 0;
  91. tables::Array table{};
  92. if (!reader::read_tag(source, storage.scratch, tableTag, storage.child, tableClass)) {
  93. report_bucket_equipment_failure("table_read", 0, 0);
  94. return false;
  95. }
  96. // The sweep and the reader resolve the installed entry independently, so the class the reader
  97. // reports still has to agree with the one the table was selected by.
  98. if (tableClass != tables::kBucketDefinitionTableClass) {
  99. report_bucket_equipment_failure("table_class", tableClass, 0);
  100. return false;
  101. }
  102. if (!tables::find_array_at(
  103. std::span<const std::byte>{storage.child}, tables::kTableArrayDescriptor, table)) {
  104. report_bucket_equipment_failure("table_array", storage.child.size(), 0);
  105. return false;
  106. }
  107. if (table.count != tables::kBucketDefinitionCount) {
  108. report_bucket_equipment_failure("table_count", table.count, table.elementClass);
  109. return false;
  110. }
  111. const std::span<const std::byte> tableBlob{storage.child};
  112. const std::size_t tableSize = table.count * tables::kBucketDefinitionSize;
  113. if (tableSize > tableBlob.size() || table.dataOffset > tableBlob.size() - tableSize) {
  114. report_bucket_equipment_failure("table_extent", table.dataOffset, tableBlob.size());
  115. return false;
  116. }
  117. std::array<bool, kEquipmentSlotCount> seenEquipmentSlots{};
  118. std::array<bool, buckets::kDescriptorCapacity> seenBuckets{};
  119. std::size_t mappedSlots = 0;
  120. for (std::size_t index = 0; index < table.count; ++index) {
  121. const std::size_t base = table.dataOffset + index * tables::kBucketDefinitionSize;
  122. const std::uint8_t equipmentSlot = std::to_integer<std::uint8_t>(
  123. tableBlob[base + tables::kBucketDefinitionEquipmentSlotOffset]);
  124. if (equipmentSlot != tables::kBucketDefinitionUnavailableEquipmentSlot
  125. && (equipmentSlot >= kEquipmentSlotCount || seenEquipmentSlots[equipmentSlot])) {
  126. report_bucket_equipment_failure("equipment_slot", index, equipmentSlot);
  127. return false;
  128. }
  129. if (equipmentSlot == tables::kBucketDefinitionUnavailableEquipmentSlot) {
  130. continue;
  131. }
  132. const std::uint8_t bucketId = kBucketByEquipmentSlot[equipmentSlot];
  133. const buckets::Descriptor* descriptor =
  134. bucketId < buckets::kDescriptorCapacity ? find_bucket(descriptors, bucketId) : nullptr;
  135. if (descriptor == nullptr || seenBuckets[bucketId]
  136. || descriptor->arraySelector != buckets::ArraySelector::character) {
  137. report_bucket_equipment_failure("bucket_relation", equipmentSlot, bucketId);
  138. return false;
  139. }
  140. seenEquipmentSlots[equipmentSlot] = true;
  141. seenBuckets[bucketId] = true;
  142. equipmentSlots[bucketId] = static_cast<std::int8_t>(equipmentSlot);
  143. ++mappedSlots;
  144. }
  145. if (mappedSlots != 18 || seenEquipmentSlots[16]) {
  146. report_bucket_equipment_failure("mapped_slots", mappedSlots, seenEquipmentSlots[16]);
  147. return false;
  148. }
  149. report_bucket_equipment_mapping(mappedSlots);
  150. return true;
  151. }
  152. } // namespace
  153. /** Publishes the inventory bucket descriptors from the root's bucket table. */
  154. bool build_buckets(const reader::Source& source,
  155. Storage& storage,
  156. std::span<const std::byte> root) noexcept {
  157. if (state::build_data::inventory_bucket_descriptors_ready()) {
  158. return true;
  159. }
  160. storage.bucketCount = 0;
  161. storage.equipmentSlotByBucket.fill(buckets::kUnavailableEquipmentSlot);
  162. std::uint32_t tableTag = 0;
  163. if (!tables::slot_tag(root, tables::kBucketTableSlot, tableTag) || tableTag == 0
  164. || !reader::read_tag(source, storage.scratch, tableTag, storage.child)) {
  165. return false;
  166. }
  167. const std::span<const std::byte> blob{storage.child};
  168. std::int32_t count = 0;
  169. if (blob.size() < tables::kBucketFirstDescriptor + sizeof count) {
  170. return false;
  171. }
  172. std::memcpy(&count, blob.data() + tables::kBucketCountOffset, sizeof count);
  173. if (count <= 0 || static_cast<std::size_t>(count) > buckets::kDescriptorCapacity) {
  174. return false;
  175. }
  176. const std::size_t bucketCount = static_cast<std::size_t>(count);
  177. std::array<buckets::Descriptor, buckets::kDescriptorCapacity> descriptors{};
  178. for (std::size_t index = 0; index < bucketCount; ++index) {
  179. const std::size_t base =
  180. tables::kBucketFirstDescriptor + index * tables::kBucketDescriptorSize;
  181. if (base + tables::kBucketDescriptorSize > blob.size()) {
  182. return false;
  183. }
  184. std::int32_t firstSlot = 0;
  185. std::int32_t slotCount = 0;
  186. std::memcpy(
  187. &firstSlot, blob.data() + base + tables::kBucketFirstSlotOffset, sizeof firstSlot);
  188. std::memcpy(
  189. &slotCount, blob.data() + base + tables::kBucketSlotCountOffset, sizeof slotCount);
  190. descriptors[index].bucketId = std::to_integer<std::uint8_t>(blob[base]);
  191. descriptors[index].firstSlot = static_cast<std::uint16_t>(firstSlot);
  192. descriptors[index].slotCount = static_cast<std::uint16_t>(slotCount);
  193. descriptors[index].arraySelector = static_cast<buckets::ArraySelector>(
  194. std::to_integer<std::uint8_t>(blob[base + tables::kBucketArraySelectorOffset]));
  195. }
  196. std::array<std::int8_t, buckets::kDescriptorCapacity> equipmentSlots{};
  197. if (!read_bucket_equipment_slots(
  198. source, storage, std::span(descriptors).first(bucketCount), equipmentSlots)) {
  199. return false;
  200. }
  201. storage.bucketRows = descriptors;
  202. storage.bucketCount = bucketCount;
  203. storage.equipmentSlotByBucket = equipmentSlots;
  204. return true;
  205. }
  206. /** Publishes the socket entry list table from the root. */
  207. bool build_socket_entry_lists(const reader::Source& source,
  208. Storage& storage,
  209. std::span<const std::byte> root) noexcept {
  210. namespace lists = state::build_data::socket_entry_lists;
  211. if (state::build_data::socket_entry_lists_ready()) {
  212. return true;
  213. }
  214. std::uint32_t tableTag = 0;
  215. tables::Array table{};
  216. if (!tables::slot_tag(root, tables::kSocketEntryListTableSlot, tableTag) || tableTag == 0
  217. || !reader::read_tag(source, storage.scratch, tableTag, storage.child)
  218. || !tables::find_array_at(
  219. std::span<const std::byte>{storage.child}, tables::kTableArrayDescriptor, table)
  220. || table.elementClass != tables::kSocketEntryListTableClass) {
  221. return false;
  222. }
  223. const std::span<const std::byte> blob{storage.child};
  224. std::vector<lists::Definition> rows(static_cast<std::size_t>(table.count));
  225. std::vector<lists::EntryTable> entryTables;
  226. for (std::size_t index = 0; index < rows.size(); ++index) {
  227. lists::EntryTable entryTable{};
  228. bool carriesSuperLane = false;
  229. tables::IndexRow row{};
  230. if (!tables::index_row(blob, table, index, row)) {
  231. return false;
  232. }
  233. rows[index].definitionHash = row.definitionHash;
  234. rows[index].definitionIndex = static_cast<std::uint16_t>(index);
  235. if (row.targetTag == 0
  236. || !reader::read_tag(source, storage.scratch, row.targetTag, storage.definition)) {
  237. continue;
  238. }
  239. const std::span<const std::byte> target{storage.definition};
  240. tables::Array entries{};
  241. if (!tables::find_array_at(target, tables::kSocketEntryArrayDescriptor, entries)) {
  242. continue;
  243. }
  244. rows[index].entryCount = static_cast<std::uint8_t>(entries.count);
  245. for (std::uint64_t entry = 0; entry < entries.count && entry < 64U; ++entry) {
  246. const std::size_t base =
  247. entries.dataOffset + static_cast<std::size_t>(entry) * tables::kSocketEntrySize;
  248. std::uint32_t plugSource = 0;
  249. if (base + tables::kSocketEntryKind + 1 > target.size()) {
  250. break;
  251. }
  252. std::memcpy(&plugSource,
  253. target.data() + base + tables::kSocketEntryPlugSource,
  254. sizeof plugSource);
  255. if (plugSource != tables::kNoPlugSource) {
  256. rows[index].readyMask |= std::uint64_t{1} << entry;
  257. }
  258. // The group and kind decide which entries the character's selection makes active.
  259. if (entry < lists::kEntryCapacity) {
  260. lists::Entry& record = entryTable.entries[static_cast<std::size_t>(entry)];
  261. record.plugSource = plugSource;
  262. std::memcpy(&record.group,
  263. target.data() + base + tables::kSocketEntryGroup,
  264. sizeof record.group);
  265. std::memcpy(&record.kind,
  266. target.data() + base + tables::kSocketEntryKind,
  267. sizeof record.kind);
  268. carriesSuperLane = carriesSuperLane || record.kind == lists::kSuperEntryKind;
  269. }
  270. }
  271. // Only a list with a super lane belongs to a subclass, and only those are selected.
  272. if (carriesSuperLane && entryTables.size() < lists::kEntryTableCapacity) {
  273. entryTable.definitionIndex = static_cast<std::uint16_t>(index);
  274. entryTables.push_back(entryTable);
  275. }
  276. }
  277. return state::build_data::publish_socket_entry_lists(rows, entryTables);
  278. }
  279. } // namespace sunrise::client::content::items::packages