package_socket_plug_build.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. #include "package_socket_plug_build.h"
  2. #include <algorithm>
  3. #include <cstring>
  4. #include <limits>
  5. #include "../../../../middleware/content/packages/tables/definition_index_table.h"
  6. #include "../../../../state/build_data/runtime.h"
  7. namespace sunrise::client::content::items::packages {
  8. namespace {
  9. /** Sundial/native category families whose socket seed expands to every plug in that family. */
  10. constexpr std::array<std::uint32_t, 3> kExpandableCategories{
  11. 0xB134761EU,
  12. 0x87727F34U,
  13. 0x6C863692U,
  14. };
  15. /** Tracker sockets synthesize these three safe plug choices by socket type. */
  16. constexpr std::array<std::uint32_t, 3> kTrackerPlugHashes{
  17. 2'285'418'970U,
  18. 2'302'094'943U,
  19. 38'912'240U,
  20. };
  21. /** Native ordinary socket type whose choices are the synthetic tracker set. */
  22. constexpr std::uint16_t kTrackerSocketType = 518;
  23. /** FNV-1a constants make pool fingerprints stable and cheap. */
  24. constexpr std::uint64_t kHashOffsetBasis = 14695981039346656037ULL;
  25. constexpr std::uint64_t kHashPrime = 1099511628211ULL;
  26. /** One acquired-state rule starts with kind 1 and then its item-definition index. */
  27. constexpr std::uint32_t kAcquisitionRuleKind = 1;
  28. constexpr std::size_t kAcquisitionRuleSize = 8;
  29. /** Reads one trivially copied scalar from a bounded package blob. */
  30. template <typename Value>
  31. [[nodiscard]] bool
  32. read(std::span<const std::byte> blob, std::size_t offset, Value& value) noexcept {
  33. if (offset > blob.size() || blob.size() - offset < sizeof value) {
  34. return false;
  35. }
  36. std::memcpy(&value, blob.data() + offset, sizeof value);
  37. return true;
  38. }
  39. /** Visitor adapter that appends one list member to a bounded lane candidate. */
  40. struct VisitorContext {
  41. SocketPlugBuild* build{};
  42. std::size_t itemDefinitionCount{};
  43. };
  44. /** @return Whether the package-provided member was accepted into bounded scratch. */
  45. [[nodiscard]] bool visit_member(void* opaque, std::uint32_t itemDefinitionIndex) noexcept {
  46. auto& context = *static_cast<VisitorContext*>(opaque);
  47. return context.build != nullptr
  48. && context.build->add(itemDefinitionIndex, context.itemDefinitionCount);
  49. }
  50. } // namespace
  51. bool read_catalyst_acquisition_gates(
  52. const reader::Source& source,
  53. reader::Scratch& scratch,
  54. std::span<const std::byte> root,
  55. std::vector<std::byte>& blob,
  56. std::vector<catalysts::AcquisitionGate>& output) noexcept {
  57. output.clear();
  58. std::uint32_t tableTag = 0;
  59. tables::Array table{};
  60. if (!tables::slot_tag(root, tables::kSocketTypeTableSlot, tableTag) || tableTag == 0
  61. || !reader::read_tag(source, scratch, tableTag, blob)
  62. || !tables::find_array_at(blob, tables::kTableArrayDescriptor, table)
  63. || table.elementClass != tables::kSocketTypeTableClass || table.count == 0
  64. || table.count > (std::numeric_limits<std::uint16_t>::max)()) {
  65. return false;
  66. }
  67. const std::uint64_t tableSize = table.count * tables::kSocketTypeRowStride;
  68. if (tableSize > blob.size() || table.dataOffset > blob.size() - tableSize) {
  69. return false;
  70. }
  71. output.resize(static_cast<std::size_t>(table.count));
  72. const std::span<const std::byte> bytes{blob};
  73. for (std::size_t index = 0; index < output.size(); ++index) {
  74. catalysts::AcquisitionGate& gate = output[index];
  75. gate.socketType = static_cast<std::uint16_t>(index);
  76. const std::size_t row = table.dataOffset + index * tables::kSocketTypeRowStride;
  77. tables::Array rules{};
  78. if (!tables::find_array_at(
  79. bytes, row + tables::kSocketTypeAcquisitionDescriptor, rules)) {
  80. continue;
  81. }
  82. if (rules.count != 1 || rules.elementClass != tables::kInvestmentExpressionRowClass
  83. || rules.dataOffset > bytes.size()
  84. || bytes.size() - rules.dataOffset < kAcquisitionRuleSize) {
  85. gate.state = catalysts::AcquisitionState::ambiguous;
  86. continue;
  87. }
  88. std::uint32_t kind = 0;
  89. std::uint32_t definitionIndex = 0;
  90. if (!read(bytes, rules.dataOffset, kind)
  91. || !read(bytes, rules.dataOffset + sizeof kind, definitionIndex)
  92. || kind != kAcquisitionRuleKind
  93. || definitionIndex >= state::build_data::items::kDefinitionCapacity) {
  94. gate.state = catalysts::AcquisitionState::ambiguous;
  95. continue;
  96. }
  97. gate.definitionIndex = static_cast<std::uint16_t>(definitionIndex);
  98. gate.state = catalysts::AcquisitionState::present;
  99. }
  100. return true;
  101. }
  102. /** Returns the compact 1-based code of one native category-expansion family. */
  103. std::uint8_t special_plug_category(std::uint32_t categoryHash) noexcept {
  104. for (std::size_t index = 0; index < kExpandableCategories.size(); ++index) {
  105. if (kExpandableCategories[index] == categoryHash) {
  106. return static_cast<std::uint8_t>(index + 1);
  107. }
  108. }
  109. return 0;
  110. }
  111. /** Allocates the bounded build state and indexes expansion/tracker plug definitions. */
  112. bool SocketPlugBuild::prepare(
  113. std::span<const std::uint8_t> specialCategories,
  114. std::span<const state::build_data::items::Definition> itemDefinitions) noexcept {
  115. release();
  116. if (specialCategories.size() < itemDefinitions.size()
  117. || itemDefinitions.size() > state::build_data::items::kDefinitionCapacity) {
  118. return false;
  119. }
  120. rules_.assign(socket_plugs::kRuleCapacity, {});
  121. pools_.assign(socket_plugs::kPoolCapacity, {});
  122. members_.assign(socket_plugs::kMemberCapacity, {});
  123. candidates_.assign(state::build_data::items::kDefinitionCapacity, {});
  124. categoryMembers_.assign(kCategoryCount * state::build_data::items::kDefinitionCapacity, {});
  125. lookup_.assign(kLookupCapacity, {});
  126. pools_[socket_plugs::kEmptyPoolIndex] = {};
  127. poolCount_ = 1;
  128. for (std::size_t item = 0; item < itemDefinitions.size(); ++item) {
  129. const std::uint8_t category = specialCategories[item];
  130. if (category != 0 && category <= kCategoryCount) {
  131. const std::size_t family = category - 1;
  132. categoryMembers_[family * state::build_data::items::kDefinitionCapacity
  133. + categoryCounts_[family]++] = static_cast<std::uint16_t>(item);
  134. }
  135. for (const std::uint32_t trackerHash : kTrackerPlugHashes) {
  136. if (itemDefinitions[item].definitionHash != trackerHash) {
  137. continue;
  138. }
  139. if (trackerCount_ >= trackerMembers_.size()) {
  140. release();
  141. return false;
  142. }
  143. trackerMembers_[trackerCount_++] = static_cast<std::uint16_t>(item);
  144. }
  145. }
  146. return true;
  147. }
  148. /** Appends one package member after enforcing the installed item-table bound. */
  149. bool SocketPlugBuild::add(std::uint32_t itemDefinitionIndex,
  150. std::size_t itemDefinitionCount) noexcept {
  151. if (candidates_.empty() || itemDefinitionIndex >= itemDefinitionCount
  152. || itemDefinitionIndex >= state::build_data::items::kDefinitionCapacity
  153. || candidateCount_ >= state::build_data::items::kDefinitionCapacity) {
  154. return false;
  155. }
  156. candidates_[candidateCount_++] = static_cast<std::uint16_t>(itemDefinitionIndex);
  157. return true;
  158. }
  159. /** Expands special category seeds, sorts/deduplicates, then interns one exact pool. */
  160. bool SocketPlugBuild::intern(std::uint32_t& poolIndex) noexcept {
  161. poolIndex = socket_plugs::kEmptyPoolIndex;
  162. if (candidates_.empty() || categoryMembers_.empty() || lookup_.empty()) {
  163. return false;
  164. }
  165. std::sort(candidates_.data(), candidates_.data() + candidateCount_);
  166. candidateCount_ = static_cast<std::size_t>(
  167. std::unique(candidates_.data(), candidates_.data() + candidateCount_) - candidates_.data());
  168. std::array<bool, kCategoryCount> expand{};
  169. // Category codes were indexed by native item definition index during prepare().
  170. for (std::size_t family = 0; family < kCategoryCount; ++family) {
  171. const auto* familyMembers =
  172. categoryMembers_.data() + family * state::build_data::items::kDefinitionCapacity;
  173. for (std::size_t seed = 0; seed < candidateCount_ && !expand[family]; ++seed) {
  174. expand[family] = std::binary_search(
  175. familyMembers, familyMembers + categoryCounts_[family], candidates_[seed]);
  176. }
  177. }
  178. for (std::size_t family = 0; family < kCategoryCount; ++family) {
  179. if (!expand[family]) {
  180. continue;
  181. }
  182. if (categoryCounts_[family]
  183. > state::build_data::items::kDefinitionCapacity - candidateCount_) {
  184. return false;
  185. }
  186. const auto* first =
  187. categoryMembers_.data() + family * state::build_data::items::kDefinitionCapacity;
  188. std::copy_n(first, categoryCounts_[family], candidates_.data() + candidateCount_);
  189. candidateCount_ += categoryCounts_[family];
  190. }
  191. std::sort(candidates_.data(), candidates_.data() + candidateCount_);
  192. candidateCount_ = static_cast<std::size_t>(
  193. std::unique(candidates_.data(), candidates_.data() + candidateCount_) - candidates_.data());
  194. if (candidateCount_ == 0) {
  195. return true;
  196. }
  197. std::uint64_t fingerprint = kHashOffsetBasis;
  198. for (std::size_t member = 0; member < candidateCount_; ++member) {
  199. std::uint16_t value = candidates_[member];
  200. for (std::size_t byte = 0; byte < sizeof value; ++byte) {
  201. fingerprint ^= static_cast<std::uint8_t>(value);
  202. fingerprint *= kHashPrime;
  203. value >>= 8U;
  204. }
  205. }
  206. fingerprint ^= candidateCount_;
  207. fingerprint *= kHashPrime;
  208. static_assert((kLookupCapacity & (kLookupCapacity - 1)) == 0);
  209. const std::size_t start = static_cast<std::size_t>(fingerprint) & (kLookupCapacity - 1);
  210. for (std::size_t probe = 0; probe < kLookupCapacity; ++probe) {
  211. PoolLookup& slot = lookup_[(start + probe) & (kLookupCapacity - 1)];
  212. if (slot.poolIndex == UINT32_MAX) {
  213. if (poolCount_ >= socket_plugs::kPoolCapacity
  214. || candidateCount_ > socket_plugs::kMemberCapacity - memberCount_) {
  215. return false;
  216. }
  217. poolIndex = static_cast<std::uint32_t>(poolCount_);
  218. pools_[poolCount_++] = {static_cast<std::uint32_t>(memberCount_),
  219. static_cast<std::uint32_t>(candidateCount_)};
  220. std::copy_n(candidates_.data(), candidateCount_, members_.data() + memberCount_);
  221. memberCount_ += candidateCount_;
  222. slot = {fingerprint, poolIndex};
  223. return true;
  224. }
  225. if (slot.fingerprint != fingerprint || slot.poolIndex >= poolCount_) {
  226. continue;
  227. }
  228. const socket_plugs::Pool& pool = pools_[slot.poolIndex];
  229. if (pool.memberCount == candidateCount_
  230. && std::equal(candidates_.data(),
  231. candidates_.data() + candidateCount_,
  232. members_.data() + pool.memberOffset)) {
  233. poolIndex = slot.poolIndex;
  234. return true;
  235. }
  236. }
  237. return false;
  238. }
  239. /** Extracts every exact ordinary socket pool of one installed item definition. */
  240. bool SocketPlugBuild::append(const tables::items::Row& item,
  241. std::span<const std::byte> itemDefinition,
  242. std::span<const std::byte> plugSetTable,
  243. std::size_t itemDefinitionCount) noexcept {
  244. if (rules_.empty() || item.definitionIndex >= itemDefinitionCount
  245. || item.socketCount > socket_plugs::kLaneCapacity) {
  246. return false;
  247. }
  248. bool complete = true;
  249. for (std::uint8_t lane = 0; lane < item.socketCount; ++lane) {
  250. candidateCount_ = 0;
  251. VisitorContext visitor{this, itemDefinitionCount};
  252. bool laneValid = tables::items::visit_allowed_plugs(
  253. itemDefinition, plugSetTable, lane, visit_member, &visitor);
  254. if (laneValid && item.initialPlugs[lane] != tables::items::kUnavailablePlug) {
  255. laneValid = add(item.initialPlugs[lane], itemDefinitionCount);
  256. }
  257. if (laneValid && item.socketTypes[lane] == kTrackerSocketType) {
  258. for (std::size_t tracker = 0; tracker < trackerCount_ && laneValid; ++tracker) {
  259. laneValid = add(trackerMembers_[tracker], itemDefinitionCount);
  260. }
  261. }
  262. std::uint32_t poolIndex = socket_plugs::kEmptyPoolIndex;
  263. laneValid = laneValid && intern(poolIndex);
  264. if (!laneValid || ruleCount_ >= socket_plugs::kRuleCapacity) {
  265. ++skipped_;
  266. complete = false;
  267. continue;
  268. }
  269. rules_[ruleCount_++] = {item.definitionIndex, lane, 0, poolIndex};
  270. }
  271. return complete;
  272. }
  273. /** Publishes the bounded relation and releases all transient interning memory. */
  274. bool SocketPlugBuild::publish() noexcept {
  275. const bool published =
  276. !rules_.empty() && !pools_.empty() && !members_.empty()
  277. && state::build_data::publish_socket_plug_rules(std::span(rules_.data(), ruleCount_),
  278. std::span(pools_.data(), poolCount_),
  279. std::span(members_.data(), memberCount_));
  280. release();
  281. return published;
  282. }
  283. /** Reports how many lanes failed closed during extraction. */
  284. std::size_t SocketPlugBuild::skipped() const noexcept {
  285. return skipped_;
  286. }
  287. std::size_t SocketPlugBuild::rule_count() const noexcept {
  288. return ruleCount_;
  289. }
  290. std::size_t SocketPlugBuild::pool_count() const noexcept {
  291. return poolCount_;
  292. }
  293. std::size_t SocketPlugBuild::member_count() const noexcept {
  294. return memberCount_;
  295. }
  296. std::span<const socket_plugs::Rule> SocketPlugBuild::rules() const noexcept {
  297. return std::span(rules_).first(ruleCount_);
  298. }
  299. std::span<const socket_plugs::Pool> SocketPlugBuild::pools() const noexcept {
  300. return std::span(pools_).first(poolCount_);
  301. }
  302. std::span<const socket_plugs::Member> SocketPlugBuild::members() const noexcept {
  303. return std::span(members_).first(memberCount_);
  304. }
  305. /** Drops all heap-backed extraction scratch and resets every count. */
  306. void SocketPlugBuild::release() noexcept {
  307. rules_.clear();
  308. rules_.shrink_to_fit();
  309. pools_.clear();
  310. pools_.shrink_to_fit();
  311. members_.clear();
  312. members_.shrink_to_fit();
  313. candidates_.clear();
  314. candidates_.shrink_to_fit();
  315. categoryMembers_.clear();
  316. categoryMembers_.shrink_to_fit();
  317. lookup_.clear();
  318. lookup_.shrink_to_fit();
  319. categoryCounts_ = {};
  320. trackerMembers_ = {};
  321. trackerCount_ = 0;
  322. ruleCount_ = 0;
  323. poolCount_ = 0;
  324. memberCount_ = 0;
  325. candidateCount_ = 0;
  326. skipped_ = 0;
  327. }
  328. } // namespace sunrise::client::content::items::packages