package_detail_build.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. #include <algorithm>
  2. #include <array>
  3. #include <cstring>
  4. #include <optional>
  5. #include "../../../../state/account/account_state.h"
  6. #include "../../../../state/runtime/runtime.h"
  7. #include "internal.h"
  8. namespace sunrise::client::content::items::packages {
  9. namespace {
  10. namespace domain = state::build_data::items::details;
  11. /** @param bucketId Inventory bucket. @return Its equipment slot, or none when not equippable. */
  12. [[nodiscard]] std::optional<std::int8_t> equipment_slot(std::uint8_t bucketId) noexcept {
  13. state::build_data::inventory::buckets::Descriptor descriptor{};
  14. if (state::build_data::find_inventory_bucket_descriptor(bucketId, descriptor)
  15. && descriptor.equipmentSlot
  16. != state::build_data::inventory::buckets::kUnavailableEquipmentSlot) {
  17. return descriptor.equipmentSlot;
  18. }
  19. return std::nullopt;
  20. }
  21. /** @param row Package row. @return Its cached detail form. */
  22. [[nodiscard]] domain::Definition to_detail(const tables::items::Row& row) noexcept {
  23. domain::Definition detail{};
  24. detail.definitionIndex = row.definitionIndex;
  25. detail.definitionHash = row.definitionHash;
  26. detail.bucketId = row.bucketId;
  27. detail.maxStackSize = row.maxStackSize;
  28. detail.instancedDefinitionState = row.instanced ? domain::InstancedDefinitionState::instanced
  29. : domain::InstancedDefinitionState::stackable;
  30. detail.equipmentSlot =
  31. row.equipmentSlot.has_value() ? row.equipmentSlot : equipment_slot(row.bucketId);
  32. detail.ordinarySocketState =
  33. row.hasSockets ? domain::OrdinarySocketState::present : domain::OrdinarySocketState::absent;
  34. detail.ordinarySocketCount = row.socketCount;
  35. for (std::size_t lane = 0; lane < detail.initialPlugIndices.size(); ++lane) {
  36. detail.initialPlugIndices[lane] = row.initialPlugs[lane];
  37. detail.socketTypes[lane] = row.socketTypes[lane];
  38. }
  39. detail.socketEntryListIndex = row.socketEntryListIndex;
  40. // Every field below comes from the package blob only; the loaded definition is never read.
  41. const std::size_t stats =
  42. row.statCount < detail.stats.size() ? row.statCount : detail.stats.size();
  43. for (std::size_t entry = 0; entry < stats; ++entry) {
  44. detail.stats[entry] = {row.statRows[entry], row.statValues[entry]};
  45. }
  46. detail.statCount = static_cast<std::uint8_t>(stats);
  47. detail.gearArtIndex = row.gearArtIndex;
  48. for (std::size_t index = 0; index < detail.artArrangementIndices.size(); ++index) {
  49. detail.artArrangementIndices[index] = row.artArrangementIndices[index];
  50. }
  51. const std::size_t perks = row.sandboxPerkCount < detail.sandboxPerks.size()
  52. ? row.sandboxPerkCount
  53. : detail.sandboxPerks.size();
  54. for (std::size_t entry = 0; entry < perks; ++entry) {
  55. detail.sandboxPerks[entry] = row.sandboxPerks[entry];
  56. }
  57. detail.sandboxPerkCount = static_cast<std::uint8_t>(perks);
  58. const std::size_t overrides = row.renderOverrideCount < detail.renderOverrides.size()
  59. ? row.renderOverrideCount
  60. : detail.renderOverrides.size();
  61. for (std::size_t entry = 0; entry < overrides; ++entry) {
  62. detail.renderOverrides[entry] = {row.renderOverrides[entry].stage,
  63. row.renderOverrides[entry].key,
  64. row.renderOverrides[entry].value};
  65. }
  66. detail.renderOverrideCount = static_cast<std::uint8_t>(overrides);
  67. return detail;
  68. }
  69. /** The constants blob's own 8-byte prefix comes before every offset the client quotes. */
  70. constexpr std::size_t kConstantsPrefix = 8;
  71. /** Client offset of the stat row the banner's power number is searched by. */
  72. constexpr std::size_t kLightStatRowOffset = 592;
  73. /** Client offset of the stat row a weapon's power number is read from. */
  74. constexpr std::size_t kWeaponPowerStatRowOffset = 606;
  75. /**
  76. * Client offsets of the 6 character stat rows, in the two runs the blob stores them in.
  77. * The client reads these as 6 separate scalars, not as one array, so each is named here.
  78. */
  79. constexpr std::size_t kCharacterStatRowOffsets[]{593, 594, 595, 622, 623, 624};
  80. } // namespace
  81. /** Collects the authored equipment and plug hashes every configured character names. */
  82. bool collect_authored_hashes(AuthoredHashes& output) noexcept {
  83. output = {};
  84. const state::AccountState account = state::account_snapshot();
  85. if (!state::account::valid(account)) {
  86. return false;
  87. }
  88. const auto append = [&output](const state::account::inventory::Item& item) noexcept {
  89. if (output.count >= output.values.size()) {
  90. return false;
  91. }
  92. output.values[output.count++] = item.definitionHash;
  93. for (std::size_t lane = 0; lane < item.sockets.plugCount; ++lane) {
  94. if (!item.sockets.plugs[lane].has_value()) {
  95. continue;
  96. }
  97. if (output.count >= output.values.size()) {
  98. return false;
  99. }
  100. output.values[output.count++] = *item.sockets.plugs[lane];
  101. }
  102. return true;
  103. };
  104. for (std::size_t character = 0; character < account.characterCount; ++character) {
  105. for (const auto& item : account.characters[character].equipment.slots) {
  106. if (!item.has_value()) {
  107. continue;
  108. }
  109. if (!append(*item)) {
  110. return false;
  111. }
  112. }
  113. const state::account::inventory::CharacterItems& inventory =
  114. account.characters[character].inventory;
  115. for (std::size_t item = 0; item < inventory.count; ++item) {
  116. if (!append(inventory.values[item])) {
  117. return false;
  118. }
  119. }
  120. }
  121. const auto end = output.values.begin() + static_cast<std::ptrdiff_t>(output.count);
  122. std::sort(output.values.begin(), end);
  123. output.count =
  124. static_cast<std::size_t>(std::unique(output.values.begin(), end) - output.values.begin());
  125. return output.count != 0;
  126. }
  127. /** @param hashes Sorted authored hashes. @param hash Row hash. @return True when authored. */
  128. bool authored(const AuthoredHashes& hashes, std::uint32_t hash) noexcept {
  129. const auto begin = hashes.values.begin();
  130. const auto end = begin + static_cast<std::ptrdiff_t>(hashes.count);
  131. return std::binary_search(begin, end, hash);
  132. }
  133. /** @return True when the row's bucket maps to a supported equipment slot. */
  134. bool equippable(const tables::items::Row& row) noexcept {
  135. return row.equipmentSlot.has_value() || equipment_slot(row.bucketId).has_value();
  136. }
  137. /** Applies the bucket-definition equipment mapping and publishes the complete bucket table. */
  138. bool publish_buckets(Storage& storage) noexcept {
  139. namespace buckets = state::build_data::inventory::buckets;
  140. if (state::build_data::inventory_bucket_descriptors_ready()) {
  141. return true;
  142. }
  143. if (storage.bucketCount == 0 || storage.bucketCount > storage.bucketRows.size()) {
  144. return false;
  145. }
  146. bool hasEquipmentSlot = false;
  147. for (std::size_t index = 0; index < storage.bucketCount; ++index) {
  148. buckets::Descriptor& descriptor = storage.bucketRows[index];
  149. descriptor.equipmentSlot = storage.equipmentSlotByBucket[descriptor.bucketId];
  150. hasEquipmentSlot =
  151. hasEquipmentSlot || descriptor.equipmentSlot != buckets::kUnavailableEquipmentSlot;
  152. }
  153. return hasEquipmentSlot
  154. && state::build_data::publish_inventory_bucket_descriptors(
  155. std::span(storage.bucketRows).first(storage.bucketCount));
  156. }
  157. /** Adds one definition index to the deduplicated requested set. */
  158. void request(std::uint16_t definitionIndex, DetailRequests& requested) noexcept {
  159. if (static_cast<std::size_t>(definitionIndex) < requested.size()) {
  160. requested.set(definitionIndex);
  161. }
  162. }
  163. /** Adds every socket lane's initial plug to the requested set. */
  164. void append_initial_plugs(const tables::items::Row& row,
  165. std::uint64_t itemDefinitionCount,
  166. DetailRequests& requested) noexcept {
  167. for (std::size_t lane = 0; lane < row.socketCount; ++lane) {
  168. if (row.initialPlugs[lane] == tables::items::kUnavailablePlug
  169. || row.initialPlugs[lane] >= itemDefinitionCount) {
  170. continue;
  171. }
  172. request(row.initialPlugs[lane], requested);
  173. }
  174. }
  175. /** Materializes requested native indices in ascending order. */
  176. bool materialize_requests(const DetailRequests& requested,
  177. std::span<std::uint16_t> output,
  178. std::size_t& count) noexcept {
  179. count = 0;
  180. for (std::size_t index = 0; index < requested.size(); ++index) {
  181. if (!requested.test(index)) {
  182. continue;
  183. }
  184. if (count >= output.size()) {
  185. count = 0;
  186. return false;
  187. }
  188. output[count++] = static_cast<std::uint16_t>(index);
  189. }
  190. return true;
  191. }
  192. /** Reads one requested definition and turns it into its cached detail form. */
  193. bool build_detail(const DetailSource& source,
  194. std::uint16_t definitionIndex,
  195. domain::Definition& detail,
  196. tables::items::Row& item) noexcept {
  197. tables::IndexRow indexRow{};
  198. item = {};
  199. item.definitionIndex = definitionIndex;
  200. if (!tables::index_row(source.table, source.array, definitionIndex, indexRow)
  201. || !reader::read_tag(
  202. *source.source, *source.scratch, indexRow.targetTag, *source.definition)
  203. || !tables::items::read_definition(std::span<const std::byte>{*source.definition}, item)) {
  204. return false;
  205. }
  206. item.definitionHash = indexRow.definitionHash;
  207. detail = to_detail(item);
  208. return true;
  209. }
  210. /** Reads the stat rows the installed investment constants blob names. */
  211. bool read_investment_constants(const reader::Source& source,
  212. reader::Scratch& scratch,
  213. std::span<const std::byte> root,
  214. std::vector<std::byte>& blob,
  215. state::build_data::constants::InvestmentConstants& output) noexcept {
  216. output = {};
  217. std::uint32_t tag = 0;
  218. if (!tables::slot_tag(root, tables::kInvestmentConstantsSlot, tag) || tag == 0
  219. || !reader::read_tag(source, scratch, tag, blob)) {
  220. return false;
  221. }
  222. const std::size_t last =
  223. kConstantsPrefix + kCharacterStatRowOffsets[std::size(kCharacterStatRowOffsets) - 1];
  224. if (blob.size() <= last) {
  225. return false;
  226. }
  227. output.lightStatRow =
  228. std::to_integer<std::uint8_t>(blob[kConstantsPrefix + kLightStatRowOffset]);
  229. output.weaponPowerStatRow =
  230. std::to_integer<std::uint8_t>(blob[kConstantsPrefix + kWeaponPowerStatRowOffset]);
  231. for (std::size_t row = 0; row < std::size(kCharacterStatRowOffsets); ++row) {
  232. output.characterStatRows[row] =
  233. std::to_integer<std::uint8_t>(blob[kConstantsPrefix + kCharacterStatRowOffsets[row]]);
  234. }
  235. output.extracted = output.weaponPowerStatRow < state::build_data::constants::kStatRowCount;
  236. return output.extracted;
  237. }
  238. } // namespace sunrise::client::content::items::packages