package_collectible_build.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. #include <array>
  2. #include <cstring>
  3. #include <limits>
  4. #include "../../../../state/build_data/runtime.h"
  5. #include "../../../../state/build_data/sobjects/sobject_catalog.h"
  6. #include "internal.h"
  7. namespace sunrise::client::content::items::packages {
  8. namespace {
  9. /**
  10. * Reads the one flag slot a collectible's acquired-state expression tests.
  11. * A collectible gated by anything other than one plain flag test keeps the unavailable slot.
  12. * @param table Collectible table bytes.
  13. * @param at Collectible row offset.
  14. * @param slot Receives the tested slot, or the unavailable slot.
  15. */
  16. void read_acquired_flag_slot(std::span<const std::byte> table,
  17. std::size_t at,
  18. std::uint16_t& slot) noexcept {
  19. namespace domain = state::build_data::collectibles;
  20. slot = domain::kUnavailableFlagSlot;
  21. tables::Array expression{};
  22. std::array<std::uint32_t, 2> instruction{};
  23. if (!tables::find_optional_array_at(
  24. table, at + tables::kCollectibleAcquiredExpressionField, expression)
  25. || expression.count != 1 || expression.elementClass != tables::kInvestmentExpressionRowClass
  26. || expression.dataOffset + sizeof instruction > table.size()) {
  27. return;
  28. }
  29. std::memcpy(instruction.data(), table.data() + expression.dataOffset, sizeof instruction);
  30. if (instruction[0] == tables::kUnlockReadFlagOpcode
  31. && instruction[1] < domain::kUnavailableFlagSlot) {
  32. slot = static_cast<std::uint16_t>(instruction[1]);
  33. }
  34. }
  35. /**
  36. * Resolves one unlock flag slot to the bank row it feeds.
  37. * @param table Unlock flag slot table bytes.
  38. * @param rows Located slot array, which is keyed by slot.
  39. * @param slot Slot to resolve.
  40. * @return The bank row, or the unavailable row when the slot is not in the table.
  41. */
  42. [[nodiscard]] std::uint16_t flag_bank_index(std::span<const std::byte> table,
  43. const tables::Array& rows,
  44. std::uint16_t slot) noexcept {
  45. namespace domain = state::build_data::collectibles;
  46. const std::size_t at = rows.dataOffset
  47. + static_cast<std::size_t>(slot) * tables::kUnlockSlotRowStride
  48. + tables::kUnlockSlotBankIndexOffset;
  49. std::uint16_t index = domain::kUnavailableFlagIndex;
  50. if (slot >= rows.count || at + sizeof index > table.size()) {
  51. return domain::kUnavailableFlagIndex;
  52. }
  53. std::memcpy(&index, table.data() + at, sizeof index);
  54. return index;
  55. }
  56. } // namespace
  57. /** Reads the fixed collectible table and publishes its native-index item links. */
  58. bool build_collectibles(const reader::Source& source,
  59. Storage& storage,
  60. std::span<const std::byte> root,
  61. std::uint64_t itemDefinitionCount) noexcept {
  62. namespace domain = state::build_data::collectibles;
  63. // A collectible picked up in the world arrives as an incident, and its target is a row of the
  64. // sobject table. Without that table the target is a bare number.
  65. if (!state::build_data::sobject_definitions_ready()) {
  66. namespace sobjects = state::build_data::sobjects;
  67. for (const std::uint32_t tag :
  68. {tables::kSobjectTablePrimaryTag, tables::kSobjectTableAlternateTag}) {
  69. std::vector<std::byte>& blob = storage.definition;
  70. if (!reader::read_tag(source, storage.scratch, tag, blob)
  71. || blob.size() < tables::kSobjectFirstRow) {
  72. continue;
  73. }
  74. std::uint64_t rowCount = 0;
  75. std::memcpy(&rowCount, blob.data() + tables::kSobjectCountOffset, sizeof rowCount);
  76. if (rowCount == 0 || rowCount > sobjects::kDefinitionCapacity
  77. || tables::kSobjectFirstRow
  78. + static_cast<std::size_t>(rowCount) * tables::kSobjectRowStride
  79. > blob.size()) {
  80. continue;
  81. }
  82. const auto count = static_cast<std::size_t>(rowCount);
  83. for (std::size_t row = 0; row < count; ++row) {
  84. const std::size_t at = tables::kSobjectFirstRow + row * tables::kSobjectRowStride;
  85. sobjects::Definition& definition = storage.sobjectRows[row];
  86. std::memcpy(&definition.nameHash,
  87. blob.data() + at + tables::kSobjectNameHashOffset,
  88. sizeof definition.nameHash);
  89. std::memcpy(&definition.lane4,
  90. blob.data() + at + tables::kSobjectLaneOffset,
  91. sizeof definition.lane4);
  92. std::memcpy(&definition.typeCode,
  93. blob.data() + at + tables::kSobjectTypeCodeOffset,
  94. sizeof definition.typeCode);
  95. }
  96. (void)state::build_data::publish_sobject_definitions(
  97. std::span(storage.sobjectRows).first(count));
  98. break;
  99. }
  100. }
  101. if (state::build_data::collectible_definitions_ready()) {
  102. return true;
  103. }
  104. if (itemDefinitionCount == 0
  105. || itemDefinitionCount > state::build_data::items::kDefinitionCapacity) {
  106. return false;
  107. }
  108. std::uint32_t requirementTableTag = 0;
  109. std::uint32_t requirementTableClass = 0;
  110. tables::Array requirementSets{};
  111. if (!tables::slot_tag(root, tables::kMaterialRequirementTableSlot, requirementTableTag)
  112. || requirementTableTag == 0
  113. || tables::package_of(requirementTableTag) == tables::kAbsentPackageId
  114. || !reader::read_tag(
  115. source, storage.scratch, requirementTableTag, storage.definition, requirementTableClass)
  116. || requirementTableClass != tables::kMaterialRequirementTableClass
  117. || !tables::find_array_at(std::span<const std::byte>{storage.definition},
  118. tables::kTableArrayDescriptor,
  119. requirementSets)
  120. || requirementSets.elementClass != tables::kMaterialRequirementSetRowClass
  121. || requirementSets.count == 0
  122. || requirementSets.count > (std::numeric_limits<std::uint16_t>::max)()) {
  123. return false;
  124. }
  125. const std::span<const std::byte> requirementTable{storage.definition};
  126. if (requirementSets.dataOffset > requirementTable.size()
  127. || requirementSets.count > (requirementTable.size() - requirementSets.dataOffset)
  128. / tables::kMaterialRequirementSetRowStride) {
  129. return false;
  130. }
  131. // The slot table is keyed by slot, so it resolves each collectible's acquired flag to the
  132. // bank row the object its kind names carries.
  133. std::uint32_t slotTableTag = 0;
  134. tables::Array slotRows{};
  135. if (!tables::slot_tag(root, tables::kUnlockFlagSlotTableSlot, slotTableTag) || slotTableTag == 0
  136. || !reader::read_tag(source, storage.scratch, slotTableTag, storage.unlockSlotTable)
  137. || !tables::find_array_at(std::span<const std::byte>{storage.unlockSlotTable},
  138. tables::kTableArrayDescriptor,
  139. slotRows)) {
  140. return false;
  141. }
  142. const std::span<const std::byte> slotTable{storage.unlockSlotTable};
  143. std::uint32_t tableTag = 0;
  144. std::uint32_t tableClass = 0;
  145. tables::Array rows{};
  146. if (!tables::slot_tag(root, tables::kCollectibleTableSlot, tableTag) || tableTag == 0
  147. || tables::package_of(tableTag) == tables::kAbsentPackageId
  148. || !reader::read_tag(source, storage.scratch, tableTag, storage.child, tableClass)
  149. || tableClass != tables::kCollectibleTableClass
  150. || !tables::find_array_at(
  151. std::span<const std::byte>{storage.child}, tables::kTableArrayDescriptor, rows)
  152. || rows.elementClass != tables::kCollectibleRowClass || rows.count == 0
  153. || rows.count > storage.collectibleRows.size()) {
  154. return false;
  155. }
  156. const std::span<const std::byte> table{storage.child};
  157. if (rows.dataOffset > table.size()
  158. || rows.count > (table.size() - rows.dataOffset) / tables::kCollectibleRowStride) {
  159. return false;
  160. }
  161. for (std::uint64_t row = 0; row < rows.count; ++row) {
  162. const std::size_t at =
  163. rows.dataOffset + static_cast<std::size_t>(row) * tables::kCollectibleRowStride;
  164. std::uint32_t collectibleHash = 0;
  165. std::uint16_t itemDefinitionIndex = domain::kUnavailableItemDefinitionIndex;
  166. std::uint16_t requirementSetIndex = domain::kUnavailableMaterialRequirementSetIndex;
  167. std::memcpy(&collectibleHash,
  168. table.data() + at + tables::kCollectibleHashOffset,
  169. sizeof collectibleHash);
  170. std::memcpy(&itemDefinitionIndex,
  171. table.data() + at + tables::kCollectibleItemIndexOffset,
  172. sizeof itemDefinitionIndex);
  173. std::memcpy(&requirementSetIndex,
  174. table.data() + at + tables::kCollectibleMaterialRequirementIndexOffset,
  175. sizeof requirementSetIndex);
  176. if (itemDefinitionIndex != domain::kUnavailableItemDefinitionIndex
  177. && itemDefinitionIndex >= itemDefinitionCount) {
  178. return false;
  179. }
  180. domain::Definition& output = storage.collectibleRows[static_cast<std::size_t>(row)];
  181. output = {};
  182. output.collectibleHash = collectibleHash;
  183. output.collectibleIndex = static_cast<std::uint16_t>(row);
  184. output.itemDefinitionIndex = itemDefinitionIndex;
  185. read_acquired_flag_slot(table, at, output.acquiredFlagSlot);
  186. if (output.acquiredFlagSlot != domain::kUnavailableFlagSlot) {
  187. output.acquiredFlagIndex =
  188. flag_bank_index(slotTable, slotRows, output.acquiredFlagSlot);
  189. }
  190. if (requirementSetIndex == domain::kUnavailableMaterialRequirementSetIndex) {
  191. continue;
  192. }
  193. if (requirementSetIndex >= requirementSets.count) {
  194. return false;
  195. }
  196. const std::size_t setAt = requirementSets.dataOffset
  197. + static_cast<std::size_t>(requirementSetIndex)
  198. * tables::kMaterialRequirementSetRowStride;
  199. std::uint32_t requirementSetHash = 0;
  200. std::int64_t descriptorRelative = 0;
  201. std::memcpy(&requirementSetHash,
  202. requirementTable.data() + setAt + tables::kMaterialRequirementSetHashOffset,
  203. sizeof requirementSetHash);
  204. std::memcpy(&descriptorRelative,
  205. requirementTable.data() + setAt
  206. + tables::kMaterialRequirementSetArrayPointerOffset,
  207. sizeof descriptorRelative);
  208. const std::size_t pointerAt = setAt + tables::kMaterialRequirementSetArrayPointerOffset;
  209. if (requirementSetHash == 0 || descriptorRelative < -static_cast<std::int64_t>(pointerAt)
  210. || descriptorRelative
  211. > static_cast<std::int64_t>(requirementTable.size() - pointerAt)) {
  212. return false;
  213. }
  214. const auto descriptorSigned = static_cast<std::int64_t>(pointerAt) + descriptorRelative;
  215. if (descriptorSigned < 0) {
  216. return false;
  217. }
  218. tables::Array requirements{};
  219. if (!tables::find_array_at(
  220. requirementTable, static_cast<std::size_t>(descriptorSigned), requirements)
  221. || requirements.elementClass != tables::kMaterialRequirementRowClass
  222. || requirements.count == 0 || requirements.count > output.materialRequirements.size()
  223. || requirements.dataOffset > requirementTable.size()
  224. || requirements.count > (requirementTable.size() - requirements.dataOffset)
  225. / tables::kMaterialRequirementRowStride) {
  226. return false;
  227. }
  228. output.materialRequirementSetHash = requirementSetHash;
  229. output.materialRequirementSetIndex = requirementSetIndex;
  230. output.materialRequirementCount = static_cast<std::uint8_t>(requirements.count);
  231. for (std::size_t requirement = 0; requirement < requirements.count; ++requirement) {
  232. const std::size_t requirementAt =
  233. requirements.dataOffset + requirement * tables::kMaterialRequirementRowStride;
  234. std::uint32_t nativeItemIndex = 0;
  235. std::uint32_t quantity = 0;
  236. std::uint8_t deleteOnAction = 0;
  237. std::uint8_t omitFromRequirements = 0;
  238. std::uint16_t sentinel = 0;
  239. std::memcpy(&nativeItemIndex,
  240. requirementTable.data() + requirementAt
  241. + tables::kMaterialRequirementItemIndexOffset,
  242. sizeof nativeItemIndex);
  243. std::memcpy(&quantity,
  244. requirementTable.data() + requirementAt
  245. + tables::kMaterialRequirementQuantityOffset,
  246. sizeof quantity);
  247. std::memcpy(&deleteOnAction,
  248. requirementTable.data() + requirementAt
  249. + tables::kMaterialRequirementDeleteOffset,
  250. sizeof deleteOnAction);
  251. std::memcpy(&omitFromRequirements,
  252. requirementTable.data() + requirementAt
  253. + tables::kMaterialRequirementOmitOffset,
  254. sizeof omitFromRequirements);
  255. std::memcpy(&sentinel,
  256. requirementTable.data() + requirementAt
  257. + tables::kMaterialRequirementSentinelOffset,
  258. sizeof sentinel);
  259. if (nativeItemIndex >= itemDefinitionCount
  260. || nativeItemIndex > (std::numeric_limits<std::uint16_t>::max)() || quantity == 0
  261. || deleteOnAction > 1 || omitFromRequirements > 1 || sentinel != 0xFFFFU) {
  262. return false;
  263. }
  264. output.materialRequirements[requirement] = {
  265. quantity,
  266. static_cast<std::uint16_t>(nativeItemIndex),
  267. deleteOnAction != 0,
  268. omitFromRequirements != 0,
  269. };
  270. }
  271. }
  272. return state::build_data::publish_collectible_definitions(
  273. std::span(storage.collectibleRows).first(static_cast<std::size_t>(rows.count)));
  274. }
  275. } // namespace sunrise::client::content::items::packages