package_detail_build.cpp 10 KB

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