activity_sdk_pack_composer.cpp 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874
  1. #include "activity_sdk_pack_composer.h"
  2. #include <algorithm>
  3. #include <array>
  4. #include <cstdint>
  5. #include <cstring>
  6. #include <limits>
  7. #include <span>
  8. #include <string>
  9. #include <string_view>
  10. #include <unordered_map>
  11. #include <unordered_set>
  12. #include <utility>
  13. #include <vector>
  14. #include "../../../state/build_data/scriptables/inline_name_evidence.h"
  15. #include "activity_sdk_activity_enrichment_inventory.h"
  16. #include "activity_sdk_activity_inventory.h"
  17. #include "activity_sdk_actor_rsat_inventory.h"
  18. #include "activity_sdk_authored_scene_inventory.h"
  19. #include "activity_sdk_behavior_inventory.h"
  20. #include "activity_sdk_pack_composer_internal.h"
  21. #include "activity_sdk_policy_inventory.h"
  22. #include "activity_sdk_squad_inventory.h"
  23. #include "activity_sdk_topology_enrichment.h"
  24. #include "activity_sdk_topology_inventory.h"
  25. namespace sunrise::client::content::activity::sdk_generation::pack_composer {
  26. namespace {
  27. namespace evidence = state::build_data::scriptables::inline_name_evidence;
  28. /** Format-v9 state rows use bits zero and one for enabled and extraction-complete state. */
  29. constexpr std::uint32_t kStateEnabled = 0x1U;
  30. constexpr std::uint32_t kStateExtractionComplete = 0x2U;
  31. static_assert((kStateEnabled | kStateExtractionComplete) == format::kStateFlagMask);
  32. using detail::PreparedRanges;
  33. /** UTF-8 text is ordered by bytes, independent of the signedness of `char`. */
  34. [[nodiscard]] bool byte_less(std::string_view left, std::string_view right) noexcept {
  35. const std::size_t common = (std::min)(left.size(), right.size());
  36. for (std::size_t index = 0; index < common; ++index) {
  37. const auto leftByte = static_cast<std::uint8_t>(left[index]);
  38. const auto rightByte = static_cast<std::uint8_t>(right[index]);
  39. if (leftByte != rightByte) {
  40. return leftByte < rightByte;
  41. }
  42. }
  43. return left.size() < right.size();
  44. }
  45. /** @return True for an empty value or one canonical UTF-8 value without an embedded null. */
  46. [[nodiscard]] bool valid_text(std::string_view value) noexcept {
  47. return value.empty()
  48. || (value.find('\0') == std::string_view::npos
  49. && evidence::valid_utf8(
  50. std::as_bytes(std::span<const char>(value.data(), value.size()))));
  51. }
  52. template <typename Text>
  53. [[nodiscard]] bool text_view(const Text& input, std::string_view& output) noexcept {
  54. output = {};
  55. if (input.length >= input.value.size() || input.value[input.length] != '\0') {
  56. return false;
  57. }
  58. output = {input.value.data(), input.length};
  59. return valid_text(output);
  60. }
  61. /**
  62. * Reads one validated activity name.
  63. * @param input Source activity definition.
  64. * @param output View into `input`, or empty on failure.
  65. * @return True when the name is terminated and valid UTF-8.
  66. */
  67. [[nodiscard]] bool
  68. activity_name_view(const middleware::content::packages::tables::ActivityDefinition& input,
  69. std::string_view& output) noexcept {
  70. output = {};
  71. if (input.internalNameLength >= input.internalName.size()
  72. || input.internalName[input.internalNameLength] != '\0') {
  73. return false;
  74. }
  75. output = {input.internalName.data(), input.internalNameLength};
  76. return valid_text(output);
  77. }
  78. [[nodiscard]] bool policy_text_view(const policy_inventory::Snapshot& source,
  79. policy_inventory::Text input,
  80. std::string_view& output) noexcept {
  81. output = {};
  82. if (input.stringIndex >= source.strings.size()) {
  83. return false;
  84. }
  85. output = source.strings[input.stringIndex];
  86. return valid_text(output);
  87. }
  88. template <typename Left, typename Right>
  89. [[nodiscard]] bool same_text(const Left& left, const Right& right) noexcept {
  90. std::string_view leftView{};
  91. std::string_view rightView{};
  92. return text_view(left, leftView) && text_view(right, rightView) && leftView == rightView;
  93. }
  94. [[nodiscard]] bool same_range(format::Range left, format::Range right) noexcept {
  95. return left.first == right.first && left.count == right.count;
  96. }
  97. [[nodiscard]] bool range_inside(format::Range value, std::size_t count) noexcept {
  98. return value.first <= count && value.count <= count - value.first;
  99. }
  100. /**
  101. * Builds closed child ranges for rows grouped by owner.
  102. * @param rows Child rows in owner order.
  103. * @param ownerCount Number of possible owners.
  104. * @param owner Reads an owner index from one row.
  105. * @param output Complete range table, or partial data on failure.
  106. * @return True when every owner index and range is valid.
  107. */
  108. template <typename Row, typename Owner>
  109. [[nodiscard]] bool make_owner_ranges(std::span<const Row> rows,
  110. std::size_t ownerCount,
  111. Owner owner,
  112. std::vector<format::Range>& output) {
  113. if (ownerCount >= format::kAbsentIndex || rows.size() >= format::kAbsentIndex) {
  114. return false;
  115. }
  116. output.assign(ownerCount, {});
  117. for (std::size_t index = 0; index < rows.size(); ++index) {
  118. const std::uint32_t ownerIndex = owner(rows[index]);
  119. if (ownerIndex >= ownerCount) {
  120. return false;
  121. }
  122. format::Range& range = output[ownerIndex];
  123. if (range.count == 0) {
  124. range.first = static_cast<std::uint32_t>(index);
  125. range.count = 1;
  126. } else if (range.first + range.count == index) {
  127. ++range.count;
  128. } else {
  129. return false;
  130. }
  131. }
  132. return true;
  133. }
  134. /**
  135. * Builds closed ranges for one policy subject kind.
  136. * @param policy Accepted policy inventory.
  137. * @param kind Subject kind to include.
  138. * @param ownerCount Number of possible subjects of `kind`.
  139. * @param output Complete range table, or partial data on failure.
  140. * @return True when every matching subject and range is valid.
  141. */
  142. [[nodiscard]] bool make_capability_ranges(const policy_inventory::Snapshot& policy,
  143. format::SubjectKind kind,
  144. std::size_t ownerCount,
  145. std::vector<format::Range>& output) {
  146. if (ownerCount >= format::kAbsentIndex || policy.capabilities.size() >= format::kAbsentIndex) {
  147. return false;
  148. }
  149. output.assign(ownerCount, {});
  150. const std::uint32_t expectedKind = static_cast<std::uint32_t>(kind);
  151. for (std::size_t index = 0; index < policy.capabilities.size(); ++index) {
  152. const policy_inventory::Capability& row = policy.capabilities[index];
  153. if (row.subjectKind != expectedKind) {
  154. continue;
  155. }
  156. if (row.subjectIndex >= ownerCount) {
  157. return false;
  158. }
  159. format::Range& range = output[row.subjectIndex];
  160. if (range.count == 0) {
  161. range.first = static_cast<std::uint32_t>(index);
  162. range.count = 1;
  163. } else if (range.first + range.count == index) {
  164. ++range.count;
  165. } else {
  166. return false;
  167. }
  168. }
  169. return true;
  170. }
  171. /** One linker owns the two-tier string policy, so both tiers stay in step. */
  172. class StringLinker final {
  173. public:
  174. [[nodiscard]] bool keep_legacy(std::string_view value) {
  175. return keep(value, legacy_);
  176. }
  177. [[nodiscard]] bool keep_extension(std::string_view value) {
  178. return keep(value, extension_);
  179. }
  180. /** Builds the final byte bank and all temporary lookup references. */
  181. [[nodiscard]] bool finish(std::vector<std::byte>& output) {
  182. canonicalize(legacy_);
  183. canonicalize(extension_);
  184. extension_.erase(std::remove_if(extension_.begin(),
  185. extension_.end(),
  186. [this](std::string_view value) {
  187. return std::binary_search(
  188. legacy_.begin(), legacy_.end(), value, byte_less);
  189. }),
  190. extension_.end());
  191. std::size_t total = 0;
  192. for (const std::string_view value : legacy_) {
  193. if (value.size() > (std::numeric_limits<std::uint32_t>::max)()
  194. || total > (std::numeric_limits<std::uint32_t>::max)() - value.size()) {
  195. return false;
  196. }
  197. total += value.size();
  198. }
  199. for (const std::string_view value : extension_) {
  200. if (value.size() > (std::numeric_limits<std::uint32_t>::max)()
  201. || total > (std::numeric_limits<std::uint32_t>::max)() - value.size()) {
  202. return false;
  203. }
  204. total += value.size();
  205. }
  206. output.resize(total);
  207. references_.clear();
  208. references_.reserve(legacy_.size() + extension_.size() + 1U);
  209. references_.emplace(std::string_view{}, format::StringRef{});
  210. std::size_t cursor = 0;
  211. for (const std::string_view value : legacy_) {
  212. append(value, cursor, output);
  213. }
  214. for (const std::string_view value : extension_) {
  215. append(value, cursor, output);
  216. }
  217. return cursor == output.size();
  218. }
  219. [[nodiscard]] bool reference(std::string_view value, format::StringRef& output) const noexcept {
  220. const auto found = references_.find(value);
  221. if (found == references_.end()) {
  222. output = {};
  223. return false;
  224. }
  225. output = found->second;
  226. return true;
  227. }
  228. private:
  229. [[nodiscard]] static bool keep(std::string_view value, std::vector<std::string_view>& output) {
  230. if (!valid_text(value)) {
  231. return false;
  232. }
  233. if (!value.empty()) {
  234. output.push_back(value);
  235. }
  236. return true;
  237. }
  238. static void canonicalize(std::vector<std::string_view>& values) {
  239. std::sort(values.begin(), values.end(), byte_less);
  240. values.erase(std::unique(values.begin(), values.end()), values.end());
  241. }
  242. void append(std::string_view value, std::size_t& cursor, std::vector<std::byte>& output) {
  243. const format::StringRef reference{static_cast<std::uint32_t>(cursor),
  244. static_cast<std::uint32_t>(value.size())};
  245. if (!value.empty()) {
  246. std::memcpy(output.data() + cursor, value.data(), value.size());
  247. }
  248. references_.emplace(value, reference);
  249. cursor += value.size();
  250. }
  251. std::vector<std::string_view> legacy_{};
  252. std::vector<std::string_view> extension_{};
  253. std::unordered_map<std::string_view, format::StringRef> references_{};
  254. };
  255. [[nodiscard]] bool
  256. resolve_string(const void* context, std::string_view value, format::StringRef& output) noexcept {
  257. return static_cast<const StringLinker*>(context)->reference(value, output);
  258. }
  259. template <typename Text> [[nodiscard]] bool keep_legacy(StringLinker& linker, const Text& input) {
  260. std::string_view value{};
  261. return text_view(input, value) && linker.keep_legacy(value);
  262. }
  263. template <typename Text>
  264. [[nodiscard]] bool keep_extension(StringLinker& linker, const Text& input) {
  265. std::string_view value{};
  266. return text_view(input, value) && linker.keep_extension(value);
  267. }
  268. template <typename Text>
  269. [[nodiscard]] bool
  270. link_text(const StringLinker& linker, const Text& input, format::StringRef& output) noexcept {
  271. std::string_view value{};
  272. return text_view(input, value) && linker.reference(value, output);
  273. }
  274. [[nodiscard]] bool link_string(const StringLinker& linker,
  275. std::string_view input,
  276. format::StringRef& output) noexcept {
  277. return valid_text(input) && linker.reference(input, output);
  278. }
  279. [[nodiscard]] bool link_policy_text(const StringLinker& linker,
  280. const policy_inventory::Snapshot& source,
  281. policy_inventory::Text input,
  282. format::StringRef& output) noexcept {
  283. std::string_view value{};
  284. return policy_text_view(source, input, value) && linker.reference(value, output);
  285. }
  286. [[nodiscard]] bool all_inputs_present(const Inputs& inputs) noexcept {
  287. return inputs.activityInventory != nullptr && inputs.activityEnrichment != nullptr
  288. && inputs.topology != nullptr && inputs.topologyEnrichment != nullptr
  289. && inputs.policy != nullptr && inputs.actorRsat != nullptr
  290. && inputs.squadFacts != nullptr && inputs.squads != nullptr
  291. && inputs.authoredScenes != nullptr && inputs.behaviors != nullptr;
  292. }
  293. /** Checks accepted-snapshot markers, fixed counts, and cross-inventory parent identity. */
  294. [[nodiscard]] bool prepare_inputs(const Inputs& inputs, PreparedRanges& ranges) {
  295. if (!all_inputs_present(inputs)) {
  296. return false;
  297. }
  298. const activity_inventory::Snapshot& activityInventory = *inputs.activityInventory;
  299. const activity_enrichment_inventory::Snapshot& activityEnrichment = *inputs.activityEnrichment;
  300. const topology_inventory::Snapshot& topology = *inputs.topology;
  301. const topology_enrichment::Snapshot& topologyEnrichment = *inputs.topologyEnrichment;
  302. const policy_inventory::Snapshot& policy = *inputs.policy;
  303. const actor_rsat_inventory::Snapshot& actorRsat = *inputs.actorRsat;
  304. const squad_inventory::Facts& squadFacts = *inputs.squadFacts;
  305. const squad_inventory::Snapshot& squads = *inputs.squads;
  306. const authored_scene_inventory::Snapshot& authoredScenes = *inputs.authoredScenes;
  307. const behavior_inventory::Snapshot& behaviors = *inputs.behaviors;
  308. const bool baseReady =
  309. topology.ready && !topology.nameInventoryComplete && !topology.stringInventoryComplete
  310. && topology.nextScenario == topology.scenarios.size() && actorRsat.complete
  311. && squadFacts.complete && squadFacts.unresolvedReads == 0 && squads.ready
  312. && squads.actorLinksComplete && authoredScenes.complete && behaviors.ready;
  313. if (!baseReady || !activity_inventory::validate(activityInventory)
  314. || !activity_enrichment_inventory::validate(activityEnrichment)
  315. || activityInventory.activities.size() != topology.activities.size()
  316. || activityInventory.scenarios.size() != topology.scenarios.size()
  317. || activityEnrichment.rows.size() != topology.activities.size()
  318. || topologyEnrichment.bubbleNames.size() != topology.bubbles.size()
  319. || topologyEnrichment.slots.size() != topology.slots.size()
  320. || policy.activityAliases.size() != topology.activities.size()
  321. || policy.activityCapabilities.size() != topology.activities.size()
  322. || policy.slotAliases.size() != topology.slots.size()
  323. || policy.slotCapabilities.size() != topology.slots.size()) {
  324. return false;
  325. }
  326. for (std::size_t index = 0; index < topology.scenarios.size(); ++index) {
  327. const activity_inventory::ScenarioRoot& source = activityInventory.scenarios[index];
  328. const topology_inventory::Scenario& target = topology.scenarios[index];
  329. std::string_view sourceName{};
  330. std::string_view targetName{};
  331. if (source.nameLength >= source.name.size() || source.name[source.nameLength] != '\0'
  332. || !valid_text(sourceName = {source.name.data(), source.nameLength})
  333. || !text_view(target.name, targetName) || source.tag != target.tag
  334. || sourceName != targetName) {
  335. return false;
  336. }
  337. }
  338. for (std::size_t index = 0; index < topology.activities.size(); ++index) {
  339. const activity_inventory::ActivityVariant& source = activityInventory.activities[index];
  340. const activity_enrichment_inventory::Row& enrichment = activityEnrichment.rows[index];
  341. const topology_inventory::Activity& target = topology.activities[index];
  342. std::string_view sourceName{};
  343. std::string_view targetName{};
  344. if (!activity_name_view(source.definition, sourceName)
  345. || !text_view(target.internalName, targetName)
  346. || source.definition.activityIndex != target.activityIndex
  347. || source.definition.definitionHash != target.definitionHash
  348. || enrichment.activityIndex != target.activityIndex
  349. || enrichment.definitionHash != target.definitionHash || sourceName != targetName
  350. || !same_text(enrichment.displayName, target.displayName)) {
  351. return false;
  352. }
  353. const bool joined = source.joinStatus == activity_inventory::JoinStatus::exact;
  354. if (joined) {
  355. if (target.scenarioIndex >= topology.scenarios.size()
  356. || topology.scenarios[target.scenarioIndex].tag != source.scenarioTag
  357. || target.exactFlags != format::kActivityExactMask) {
  358. return false;
  359. }
  360. } else if (target.scenarioIndex != format::kAbsentIndex || target.exactFlags != 0) {
  361. return false;
  362. }
  363. }
  364. std::uint32_t aliasCursor = 0;
  365. for (const topology_enrichment::Slot& row : topologyEnrichment.slots) {
  366. if (row.aliases.first != aliasCursor
  367. || !range_inside(row.aliases, topologyEnrichment.slotAliases.size())) {
  368. return false;
  369. }
  370. aliasCursor += row.aliases.count;
  371. }
  372. if (aliasCursor != topologyEnrichment.slotAliases.size()) {
  373. return false;
  374. }
  375. for (const topology_inventory::Text& value : topologyEnrichment.bubbleNames) {
  376. std::string_view ignored{};
  377. if (!text_view(value, ignored)) {
  378. return false;
  379. }
  380. }
  381. for (const topology_inventory::Text& value : topologyEnrichment.slotAliases) {
  382. std::string_view alias{};
  383. if (!text_view(value, alias) || alias.empty()) {
  384. return false;
  385. }
  386. }
  387. if (policy.strings.empty() || !policy.strings.front().empty()
  388. || policy.strings.size() >= format::kAbsentIndex) {
  389. return false;
  390. }
  391. std::unordered_set<std::string_view> policyStrings{};
  392. policyStrings.reserve(policy.strings.size());
  393. for (std::size_t index = 0; index < policy.strings.size(); ++index) {
  394. const std::string_view value = policy.strings[index];
  395. if (!valid_text(value) || (index != 0 && value.empty())
  396. || !policyStrings.insert(value).second) {
  397. return false;
  398. }
  399. }
  400. if (!make_owner_ranges(
  401. std::span<const topology_inventory::Bubble>(topology.bubbles),
  402. topology.scenarios.size(),
  403. [](const topology_inventory::Bubble& row) { return row.scenarioIndex; },
  404. ranges.scenarioBubbles)
  405. || !make_owner_ranges(
  406. std::span<const topology_inventory::State>(topology.states),
  407. topology.scenarios.size(),
  408. [](const topology_inventory::State& row) { return row.scenarioIndex; },
  409. ranges.scenarioStates)
  410. || !make_owner_ranges(
  411. std::span<const topology_inventory::Occurrence>(topology.occurrences),
  412. topology.scenarios.size(),
  413. [](const topology_inventory::Occurrence& row) { return row.scenarioIndex; },
  414. ranges.scenarioOccurrences)
  415. || !make_owner_ranges(
  416. std::span<const topology_inventory::State>(topology.states),
  417. topology.bubbles.size(),
  418. [](const topology_inventory::State& row) { return row.bubbleIndex; },
  419. ranges.bubbleStates)
  420. || !make_owner_ranges(
  421. std::span<const topology_inventory::Slot>(topology.slots),
  422. topology.objects.size(),
  423. [](const topology_inventory::Slot& row) { return row.objectIndex; },
  424. ranges.objectSlots)
  425. || !make_capability_ranges(policy,
  426. format::SubjectKind::activity,
  427. topology.activities.size(),
  428. ranges.activityCapabilities)
  429. || !make_capability_ranges(
  430. policy, format::SubjectKind::slot, topology.slots.size(), ranges.slotCapabilities)
  431. || !make_capability_ranges(policy,
  432. format::SubjectKind::hostApi,
  433. policy.hostSubjects.size(),
  434. ranges.hostCapabilities)) {
  435. return false;
  436. }
  437. for (std::size_t index = 0; index < ranges.activityCapabilities.size(); ++index) {
  438. if (ranges.activityCapabilities[index].count == 0
  439. || !same_range(ranges.activityCapabilities[index],
  440. policy.activityCapabilities[index])) {
  441. return false;
  442. }
  443. }
  444. for (std::size_t index = 0; index < ranges.slotCapabilities.size(); ++index) {
  445. if (ranges.slotCapabilities[index].count == 0
  446. || !same_range(ranges.slotCapabilities[index], policy.slotCapabilities[index])) {
  447. return false;
  448. }
  449. }
  450. if (ranges.hostCapabilities.size() != policy.hostSubjects.size()) {
  451. return false;
  452. }
  453. std::string_view priorHost{};
  454. for (std::size_t index = 0; index < policy.hostSubjects.size(); ++index) {
  455. std::string_view host{};
  456. if (!policy_text_view(policy, policy.hostSubjects[index].id, host) || host.empty()
  457. || ranges.hostCapabilities[index].count == 0
  458. || !same_range(ranges.hostCapabilities[index], policy.hostSubjects[index].capabilities)
  459. || (index != 0 && !byte_less(priorHost, host))) {
  460. return false;
  461. }
  462. priorHost = host;
  463. }
  464. return true;
  465. }
  466. /** Derives only the parent ranges required to translate generation-owned rows. */
  467. [[nodiscard]] bool prepare_generated_ranges(const Inputs& inputs, PreparedRanges& ranges) {
  468. if (!all_inputs_present(inputs)) {
  469. return false;
  470. }
  471. const topology_inventory::Snapshot& topology = *inputs.topology;
  472. const policy_inventory::Snapshot& policy = *inputs.policy;
  473. return make_owner_ranges(
  474. std::span<const topology_inventory::Bubble>(topology.bubbles),
  475. topology.scenarios.size(),
  476. [](const topology_inventory::Bubble& row) { return row.scenarioIndex; },
  477. ranges.scenarioBubbles)
  478. && make_owner_ranges(
  479. std::span<const topology_inventory::State>(topology.states),
  480. topology.scenarios.size(),
  481. [](const topology_inventory::State& row) { return row.scenarioIndex; },
  482. ranges.scenarioStates)
  483. && make_owner_ranges(
  484. std::span<const topology_inventory::Occurrence>(topology.occurrences),
  485. topology.scenarios.size(),
  486. [](const topology_inventory::Occurrence& row) { return row.scenarioIndex; },
  487. ranges.scenarioOccurrences)
  488. && make_owner_ranges(
  489. std::span<const topology_inventory::State>(topology.states),
  490. topology.bubbles.size(),
  491. [](const topology_inventory::State& row) { return row.bubbleIndex; },
  492. ranges.bubbleStates)
  493. && make_owner_ranges(
  494. std::span<const topology_inventory::Slot>(topology.slots),
  495. topology.objects.size(),
  496. [](const topology_inventory::Slot& row) { return row.objectIndex; },
  497. ranges.objectSlots)
  498. && make_capability_ranges(policy,
  499. format::SubjectKind::activity,
  500. topology.activities.size(),
  501. ranges.activityCapabilities)
  502. && make_capability_ranges(
  503. policy, format::SubjectKind::slot, topology.slots.size(), ranges.slotCapabilities)
  504. && make_capability_ranges(policy,
  505. format::SubjectKind::hostApi,
  506. policy.hostSubjects.size(),
  507. ranges.hostCapabilities);
  508. }
  509. /** Collects only strings referenced by legacy sections zero through twenty-two. */
  510. [[nodiscard]] bool collect_legacy_strings(const Inputs& inputs, StringLinker& linker) {
  511. const topology_inventory::Snapshot& topology = *inputs.topology;
  512. const topology_enrichment::Snapshot& enrichment = *inputs.topologyEnrichment;
  513. const policy_inventory::Snapshot& policy = *inputs.policy;
  514. const actor_rsat_inventory::Snapshot& actorRsat = *inputs.actorRsat;
  515. const squad_inventory::Snapshot& squads = *inputs.squads;
  516. const authored_scene_inventory::Snapshot& authoredScenes = *inputs.authoredScenes;
  517. for (const topology_inventory::Activity& row : topology.activities) {
  518. if (!keep_legacy(linker, row.id) || !keep_legacy(linker, row.internalName)
  519. || !keep_legacy(linker, row.displayName)) {
  520. return false;
  521. }
  522. }
  523. for (const topology_inventory::Scenario& row : topology.scenarios) {
  524. if (!keep_legacy(linker, row.id) || !keep_legacy(linker, row.name)) {
  525. return false;
  526. }
  527. }
  528. for (std::size_t index = 0; index < topology.bubbles.size(); ++index) {
  529. if (!keep_legacy(linker, topology.bubbles[index].id)
  530. || !keep_legacy(linker, enrichment.bubbleNames[index])) {
  531. return false;
  532. }
  533. }
  534. for (const topology_inventory::State& row : topology.states) {
  535. if (!keep_legacy(linker, row.id) || !keep_legacy(linker, row.entryId)
  536. || !keep_legacy(linker, row.registryId)) {
  537. return false;
  538. }
  539. }
  540. for (const topology_inventory::Object& row : topology.objects) {
  541. if (!keep_legacy(linker, row.id)) {
  542. return false;
  543. }
  544. }
  545. for (const topology_inventory::Occurrence& row : topology.occurrences) {
  546. if (!keep_legacy(linker, row.id) || !keep_legacy(linker, row.contextRegistryKey)
  547. || !keep_legacy(linker, row.registryId) || !keep_legacy(linker, row.entryId)) {
  548. return false;
  549. }
  550. }
  551. for (std::size_t index = 0; index < topology.slots.size(); ++index) {
  552. const topology_enrichment::Slot& linked = enrichment.slots[index];
  553. if (!keep_legacy(linker, topology.slots[index].id) || !keep_legacy(linker, linked.name)
  554. || !keep_legacy(linker, linked.senseSchemaId)
  555. || !keep_legacy(linker, linked.authSchemaId)) {
  556. return false;
  557. }
  558. }
  559. const auto keepPolicy = [&policy, &linker](policy_inventory::Text text) {
  560. std::string_view value{};
  561. return policy_text_view(policy, text, value) && linker.keep_legacy(value);
  562. };
  563. for (const policy_inventory::TextRow& row : policy.texts) {
  564. if (!keepPolicy(row.value)) {
  565. return false;
  566. }
  567. }
  568. for (const policy_inventory::Capability& row : policy.capabilities) {
  569. if (!keepPolicy(row.id) || !keepPolicy(row.operation) || !keepPolicy(row.valueSchemaId)) {
  570. return false;
  571. }
  572. }
  573. for (const policy_inventory::Gate& row : policy.gates) {
  574. if (!keepPolicy(row.gate) || !keepPolicy(row.status) || !keepPolicy(row.reasonCode)
  575. || !keepPolicy(row.required) || !keepPolicy(row.observed)
  576. || !keepPolicy(row.wouldConfirm)) {
  577. return false;
  578. }
  579. }
  580. for (const policy_inventory::Refusal& row : policy.refusals) {
  581. if (!keepPolicy(row.id) || !keepPolicy(row.exposure) || !keepPolicy(row.status)) {
  582. return false;
  583. }
  584. }
  585. for (const auto& entry : actorRsat.sequenceEntries) {
  586. if (!linker.keep_extension(entry.id) || !linker.keep_extension(entry.name)
  587. || !linker.keep_extension(entry.symbol) || !linker.keep_extension(entry.sourcePath)) {
  588. return false;
  589. }
  590. }
  591. for (const actor_rsat_inventory::ActorClass& row : actorRsat.actorClasses) {
  592. if (!keep_legacy(linker, row.id)) {
  593. return false;
  594. }
  595. }
  596. for (const actor_rsat_inventory::ActorMessageSchema& row : actorRsat.messageSchemas) {
  597. if (!keep_legacy(linker, row.name)) {
  598. return false;
  599. }
  600. }
  601. for (const actor_rsat_inventory::ActorCommandDefinition& row : actorRsat.commandDefinitions) {
  602. if (!keep_legacy(linker, row.name) || !keep_legacy(linker, row.factionNoneName)
  603. || !keep_legacy(linker, row.factionRemovedName)
  604. || !keep_legacy(linker, row.factionHostileToAllName)) {
  605. return false;
  606. }
  607. }
  608. for (const actor_rsat_inventory::SimulationEventDefinition& row : actorRsat.simulationEvents) {
  609. if (!keep_legacy(linker, row.name)) {
  610. return false;
  611. }
  612. }
  613. for (const actor_rsat_inventory::RuntimeTypeDefinition& row : actorRsat.runtimeTypes) {
  614. if (!keep_legacy(linker, row.name)) {
  615. return false;
  616. }
  617. }
  618. for (const actor_rsat_inventory::EntityTypeDefinition& row : actorRsat.entityTypes) {
  619. if (!keep_legacy(linker, row.name)) {
  620. return false;
  621. }
  622. }
  623. for (const actor_rsat_inventory::RsatDescriptor& row : actorRsat.descriptors) {
  624. if (!keep_legacy(linker, row.id)) {
  625. return false;
  626. }
  627. }
  628. for (const actor_rsat_inventory::RsatSchema& row : actorRsat.schemas) {
  629. if (!keep_legacy(linker, row.id)) {
  630. return false;
  631. }
  632. }
  633. for (const squad_inventory::Squad& row : squads.squads) {
  634. if (!linker.keep_legacy(row.id)) {
  635. return false;
  636. }
  637. }
  638. for (const squad_inventory::SquadMember& row : squads.members) {
  639. if (!linker.keep_legacy(row.id)) {
  640. return false;
  641. }
  642. }
  643. for (const squad_inventory::SquadAnchor& row : squads.anchors) {
  644. if (!linker.keep_legacy(row.id)) {
  645. return false;
  646. }
  647. }
  648. for (const authored_scene_inventory::Resource& row : authoredScenes.resources) {
  649. if (!keep_legacy(linker, row.id)) {
  650. return false;
  651. }
  652. }
  653. for (const authored_scene_inventory::EventKey& row : authoredScenes.eventKeys) {
  654. if (!keep_legacy(linker, row.id)) {
  655. return false;
  656. }
  657. }
  658. for (const authored_scene_inventory::SquadEdge& row : authoredScenes.squadEdges) {
  659. if (!keep_legacy(linker, row.id)) {
  660. return false;
  661. }
  662. }
  663. for (const authored_scene_inventory::TaskTarget& row : authoredScenes.taskTargets) {
  664. if (!keep_legacy(linker, row.id)) {
  665. return false;
  666. }
  667. }
  668. for (const authored_scene_inventory::DialogueCueText& row : authoredScenes.dialogueCueTexts) {
  669. if (!keep_legacy(linker, row.id) || !keep_legacy(linker, row.text)) {
  670. return false;
  671. }
  672. }
  673. for (const authored_scene_inventory::DirectiveElement& row : authoredScenes.directiveElements) {
  674. if (!keep_legacy(linker, row.id) || !keep_legacy(linker, row.title)
  675. || !keep_legacy(linker, row.description) || !keep_legacy(linker, row.progress)) {
  676. return false;
  677. }
  678. }
  679. return true;
  680. }
  681. } // namespace
  682. /** @return Borrowed sections in exact current format order. */
  683. pack::Tables Storage::tables() const noexcept {
  684. return {
  685. strings,
  686. activities,
  687. scenarios,
  688. bubbles,
  689. states,
  690. objects,
  691. occurrences,
  692. slots,
  693. texts,
  694. capabilities,
  695. gates,
  696. refusals,
  697. actorClasses,
  698. rsatDescriptors,
  699. rsatSchemas,
  700. rsatFields,
  701. squads,
  702. squadMembers,
  703. squadAnchors,
  704. authoredSceneResources,
  705. authoredSceneSquadEdges,
  706. taskTargets,
  707. dialogueCueTexts,
  708. directiveElements,
  709. activityBindingTags,
  710. activityBindingLocators,
  711. behaviorPrograms,
  712. behaviorInputs,
  713. behaviorChannelWrites,
  714. behaviorOwners,
  715. behaviorActivityBindings,
  716. actorMessageSchemas,
  717. actorCommandDefinitions,
  718. actorBehaviorProfiles,
  719. simulationEventDefinitions,
  720. runtimeSchemas,
  721. runtimeFields,
  722. sobjectRsats,
  723. sobjectRsatDescriptors,
  724. entityTypeDefinitions,
  725. sobjectRsatFieldBindings,
  726. runtimeTypeDefinitions,
  727. actorStateNames,
  728. actorSequenceTables,
  729. actorSequenceEntries,
  730. actorSequenceBindings,
  731. dialogueCues,
  732. combatObjectiveGroups,
  733. actorAbilities,
  734. actorAbilityTargets,
  735. authoredSceneEventKeys,
  736. };
  737. }
  738. /**
  739. * Links accepted native inventories without changing `output` on failure.
  740. * @param inputs Complete accepted inventories.
  741. * @param output Owned final rows.
  742. * @return True when every row and reference closes.
  743. */
  744. bool compose(const Inputs& inputs, Storage& output) noexcept {
  745. try {
  746. PreparedRanges ranges{};
  747. if (!prepare_inputs(inputs, ranges)) {
  748. return false;
  749. }
  750. StringLinker linker{};
  751. Storage pending{};
  752. const detail::StringResolver strings{&linker, resolve_string};
  753. if (!collect_legacy_strings(inputs, linker)) {
  754. return false;
  755. }
  756. if (!linker.finish(pending.strings)) {
  757. return false;
  758. }
  759. if (!detail::translate_rows(inputs, ranges, strings, pending)) {
  760. return false;
  761. }
  762. if (!detail::validate_storage(inputs, ranges, pending)) {
  763. return false;
  764. }
  765. output = std::move(pending);
  766. return true;
  767. } catch (...) {
  768. return false;
  769. }
  770. }
  771. /** Links generation-owned inventories without running the validation pass. */
  772. bool compose_generated(const Inputs& inputs, Storage& output) noexcept {
  773. try {
  774. PreparedRanges ranges{};
  775. if (!prepare_generated_ranges(inputs, ranges)) {
  776. return false;
  777. }
  778. StringLinker linker{};
  779. Storage pending{};
  780. const detail::StringResolver strings{&linker, resolve_string};
  781. if (!collect_legacy_strings(inputs, linker) || !linker.finish(pending.strings)
  782. || !detail::translate_rows(inputs, ranges, strings, pending)) {
  783. return false;
  784. }
  785. output = std::move(pending);
  786. return true;
  787. } catch (...) {
  788. return false;
  789. }
  790. }
  791. /**
  792. * Builds a format-v12 image without changing outputs on failure.
  793. * @param identity Required pack identity.
  794. * @param inputs Complete accepted inventories.
  795. * @param tables Owned final rows.
  796. * @param image Serialized pack image.
  797. * @param payloadSha256 Digest of the serialized payload.
  798. * @return Writer status, or an input or canonical-digest failure.
  799. */
  800. pack::Status build(const pack::Identity& identity,
  801. const Inputs& inputs,
  802. Storage& tables,
  803. std::vector<std::byte>& image,
  804. pack::Digest& payloadSha256) noexcept {
  805. payloadSha256 = {};
  806. Storage pendingTables{};
  807. if (!compose(inputs, pendingTables)) {
  808. return pack::Status::invalidInput;
  809. }
  810. std::vector<std::byte> pendingImage{};
  811. pack::Digest pendingDigest{};
  812. const pack::Status status =
  813. pack::build(identity, pendingTables.tables(), pendingImage, pendingDigest);
  814. if (status != pack::Status::ready) {
  815. return status;
  816. }
  817. tables = std::move(pendingTables);
  818. image = std::move(pendingImage);
  819. payloadSha256 = pendingDigest;
  820. return pack::Status::ready;
  821. }
  822. } // namespace sunrise::client::content::activity::sdk_generation::pack_composer