activity_sdk_native_pack_text.cpp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587
  1. #include <algorithm>
  2. #include <array>
  3. #include <cstddef>
  4. #include <cstdint>
  5. #include <cstring>
  6. #include <limits>
  7. #include <map>
  8. #include <span>
  9. #include <string>
  10. #include <string_view>
  11. #include <unordered_map>
  12. #include <utility>
  13. #include <vector>
  14. #include "../../../middleware/content/packages/tables/activity_display_name_reader.h"
  15. #include "activity_sdk_dialogue_group_index.h"
  16. #include "activity_sdk_native_pack_internal.h"
  17. namespace sunrise::client::content::activity::sdk_generation::native_pack_pipeline {
  18. namespace {
  19. namespace format = state::activity_sdk::format;
  20. namespace dialogue_groups = dialogue_group_index;
  21. namespace display = middleware::content::packages::tables::activity_display_names;
  22. template <typename Value>
  23. [[nodiscard]] bool
  24. read_value(std::span<const std::byte> bytes, std::size_t offset, Value& output) noexcept {
  25. output = {};
  26. if (offset > bytes.size() || sizeof output > bytes.size() - offset) {
  27. return false;
  28. }
  29. std::memcpy(&output, bytes.data() + offset, sizeof output);
  30. return true;
  31. }
  32. /** Applies one signed blob-relative offset. @return False when the result leaves the blob. */
  33. [[nodiscard]] bool
  34. add_relative(std::size_t member, std::int64_t relative, std::size_t& target) noexcept {
  35. if (relative >= 0) {
  36. const auto distance = static_cast<std::uint64_t>(relative);
  37. if (distance > (std::numeric_limits<std::size_t>::max)() - member) {
  38. return false;
  39. }
  40. target = member + static_cast<std::size_t>(distance);
  41. return true;
  42. }
  43. const auto distance = static_cast<std::uint64_t>(-(relative + 1)) + 1U;
  44. if (distance > member) {
  45. return false;
  46. }
  47. target = member - static_cast<std::size_t>(distance);
  48. return true;
  49. }
  50. /** Reads one array field's data offset and count, checking its declared class and stride. */
  51. [[nodiscard]] bool read_array(std::span<const std::byte> bytes,
  52. std::size_t field,
  53. std::size_t stride,
  54. std::uint32_t expectedClass,
  55. std::size_t& data,
  56. std::size_t& count) noexcept {
  57. std::uint64_t rawCount = 0;
  58. std::int64_t relative = 0;
  59. std::size_t header = 0;
  60. std::uint64_t repeated = 0;
  61. std::uint32_t classId = 0;
  62. std::uint32_t padding = 0;
  63. if (!read_value(bytes, field, rawCount) || rawCount > format::kAbsentIndex
  64. || !read_value(bytes, field + 8U, relative)) {
  65. return false;
  66. }
  67. if (rawCount == 0 && relative == 0) {
  68. data = 0;
  69. count = 0;
  70. return true;
  71. }
  72. if (!add_relative(field + 8U, relative, header) || !read_value(bytes, header, repeated)
  73. || repeated != rawCount || !read_value(bytes, header + 8U, classId)
  74. || classId != expectedClass || !read_value(bytes, header + 12U, padding) || padding != 0
  75. || rawCount > (std::numeric_limits<std::size_t>::max)() / stride) {
  76. return false;
  77. }
  78. data = header + 16U;
  79. count = static_cast<std::size_t>(rawCount);
  80. return data <= bytes.size() && count * stride <= bytes.size() - data;
  81. }
  82. [[nodiscard]] bool copy_text(std::string_view value, authored_scene::Text& output) noexcept {
  83. output = {};
  84. if (value.empty() || value.size() >= output.value.size()
  85. || value.size() > (std::numeric_limits<std::uint16_t>::max)()) {
  86. return false;
  87. }
  88. std::copy(value.begin(), value.end(), output.value.begin());
  89. output.length = static_cast<std::uint16_t>(value.size());
  90. return true;
  91. }
  92. struct AuthoredTextCandidate final {
  93. display::Reference reference{};
  94. std::uint32_t slotIndex{};
  95. std::uint32_t cueIndex{format::kAbsentIndex};
  96. std::uint32_t definitionHash{};
  97. };
  98. /** The two localized fields are one directive element, not two selectable directives. */
  99. struct AuthoredDirectiveCandidate final {
  100. display::Reference title{};
  101. display::Reference description{};
  102. std::uint32_t slotIndex{};
  103. std::uint32_t nameHash{};
  104. std::int32_t elementIndex{-1};
  105. std::uint32_t elementCount{};
  106. };
  107. } // namespace
  108. /**
  109. * Keeps authored group ordinals and task counts tied to the exact objective slot.
  110. * @param topology Canonical slots.
  111. * @param facts Descriptor sources for those slots.
  112. * @param packageContext Installed package reader.
  113. * @param output Receives the native group rows.
  114. * @return False when repeated definitions disagree or the package reader fails.
  115. */
  116. bool attach_combat_objective_groups(const topology_inventory::Snapshot& topology,
  117. const squads::Facts& facts,
  118. PackageContext& packageContext,
  119. authored_scene::Snapshot& output) {
  120. // Objective descriptors contain 40-byte groups at +136 and 40-byte tasks at group +16.
  121. constexpr std::size_t kGroupsField = 136U;
  122. constexpr std::size_t kTasksField = 16U;
  123. constexpr std::size_t kRowStride = 40U;
  124. constexpr std::uint32_t kGroupClass = 0x80807D8FU;
  125. constexpr std::uint32_t kTaskClass = 0x80807D95U;
  126. std::unordered_map<std::uint64_t, std::vector<std::uint32_t>> configs{};
  127. std::map<std::uint32_t, std::vector<std::uint32_t>> slots{};
  128. try {
  129. for (const squads::DescriptorFact& descriptor : facts.descriptors) {
  130. if (descriptor.slotIndex >= topology.slots.size()
  131. || topology.slots[descriptor.slotIndex].slotType != format::kObjectiveSlotType
  132. || descriptor.componentClass != format::kObjectiveComponentClass
  133. || descriptor.senseSchema != format::kObjectiveSenseSchema
  134. || descriptor.authSchema != format::kObjectiveAuthSchema) {
  135. continue;
  136. }
  137. const std::uint64_t descriptorKey =
  138. (static_cast<std::uint64_t>(descriptor.configTag) << 32U)
  139. | descriptor.descriptorOffset;
  140. auto found = configs.find(descriptorKey);
  141. if (found == configs.end()) {
  142. std::vector<std::byte> bytes{};
  143. std::uint32_t configClass = 0;
  144. std::size_t groupData = 0;
  145. std::size_t groupCount = 0;
  146. if (!read_tag(&packageContext, descriptor.configTag, bytes, configClass)
  147. || !read_array(bytes,
  148. descriptor.descriptorOffset + kGroupsField,
  149. kRowStride,
  150. kGroupClass,
  151. groupData,
  152. groupCount)) {
  153. return false;
  154. }
  155. std::vector<std::uint32_t> taskCounts{};
  156. for (std::size_t group = 0; group < groupCount; ++group) {
  157. std::size_t taskData = 0;
  158. std::size_t taskCount = 0;
  159. if (!read_array(bytes,
  160. groupData + group * kRowStride + kTasksField,
  161. kRowStride,
  162. kTaskClass,
  163. taskData,
  164. taskCount)) {
  165. return false;
  166. }
  167. taskCounts.push_back(static_cast<std::uint32_t>(taskCount));
  168. }
  169. found = configs.emplace(descriptorKey, std::move(taskCounts)).first;
  170. }
  171. const auto [slot, inserted] = slots.emplace(descriptor.slotIndex, found->second);
  172. if (!inserted && slot->second != found->second) {
  173. return false;
  174. }
  175. if (cancelled(packageContext.cancel, packageContext.cancelContext)) {
  176. return false;
  177. }
  178. }
  179. for (const auto& [slot, taskCounts] : slots) {
  180. for (std::uint32_t group = 0; group < taskCounts.size(); ++group) {
  181. output.combatObjectiveGroups.push_back({slot, group, taskCounts[group]});
  182. }
  183. }
  184. return true;
  185. } catch (...) {
  186. return false;
  187. }
  188. }
  189. /** Extracts localized dialogue aliases and safe authored directive elements. */
  190. bool attach_authored_text(const topology_inventory::Snapshot& topology,
  191. const squads::Facts& facts,
  192. PackageContext& packageContext,
  193. authored_scene::Snapshot& output) {
  194. struct CachedTag final {
  195. std::vector<std::byte> bytes{};
  196. std::uint32_t classId{};
  197. };
  198. std::unordered_map<std::uint32_t, CachedTag> cache{};
  199. auto package = [&](std::uint32_t tag, const CachedTag*& result) -> bool {
  200. const auto found = cache.find(tag);
  201. if (found != cache.end()) {
  202. result = &found->second;
  203. return true;
  204. }
  205. CachedTag row{};
  206. if (!read_tag(&packageContext, tag, row.bytes, row.classId)) {
  207. return false;
  208. }
  209. result = &cache.emplace(tag, std::move(row)).first->second;
  210. return true;
  211. };
  212. try {
  213. std::vector<AuthoredTextCandidate> dialogueCandidates{};
  214. std::vector<AuthoredDirectiveCandidate> directiveCandidates{};
  215. for (const squads::DescriptorFact& descriptor : facts.descriptors) {
  216. if (descriptor.slotIndex >= topology.slots.size()) {
  217. continue;
  218. }
  219. const std::uint32_t slotType = topology.slots[descriptor.slotIndex].slotType;
  220. if (slotType != format::kDialogueSlotType && slotType != 68U) {
  221. continue;
  222. }
  223. const CachedTag* config = nullptr;
  224. if (!package(descriptor.configTag, config) || config == nullptr) {
  225. continue;
  226. }
  227. const std::size_t field = static_cast<std::size_t>(descriptor.descriptorOffset)
  228. + (slotType == format::kDialogueSlotType ? 0x58U : 0x5CU);
  229. std::uint32_t resourceTag = 0;
  230. const CachedTag* resource = nullptr;
  231. if (!read_value(std::span(config->bytes), field, resourceTag)
  232. || !package(resourceTag, resource) || resource == nullptr) {
  233. continue;
  234. }
  235. const auto bytes = std::span(resource->bytes);
  236. if (slotType == format::kDialogueSlotType) {
  237. if (resource->classId != format::kDialogueAuthoredListClass) {
  238. continue;
  239. }
  240. std::size_t groups = 0;
  241. std::size_t groupCount = 0;
  242. if (!read_array(
  243. bytes, 0x18U, 16U, format::kDialogueGroupArrayClass, groups, groupCount)) {
  244. continue;
  245. }
  246. std::size_t definitions = 0;
  247. std::size_t definitionCount = 0;
  248. if (!read_array(bytes,
  249. 8U,
  250. 8U,
  251. format::kDialogueDefinitionArrayClass,
  252. definitions,
  253. definitionCount)) {
  254. continue;
  255. }
  256. std::vector<dialogue_groups::Span> groupIndex{};
  257. if (!dialogue_groups::build(bytes, groups, groupCount, groupIndex)) {
  258. continue;
  259. }
  260. for (std::size_t cue = 0; cue < definitionCount; ++cue) {
  261. std::uint32_t definitionHash = 0;
  262. if (!read_value(bytes, definitions + cue * 8U, definitionHash)
  263. || definitionHash == 0 || definitionHash == 0x811C9DC5U) {
  264. continue;
  265. }
  266. dialogue_groups::Span group{};
  267. if (!dialogue_groups::find(groupIndex, definitionHash, group)) {
  268. continue;
  269. }
  270. for (std::size_t offset = group.begin; offset + 8U <= group.end; offset += 4U) {
  271. std::uint32_t containerTag = 0;
  272. std::uint32_t stringHash = 0;
  273. const CachedTag* container = nullptr;
  274. if (!read_value(bytes, offset, containerTag)
  275. || !read_value(bytes, offset + 4U, stringHash)
  276. || containerTag < 0x80A12000U || containerTag >= 0xC0000000U
  277. || !package(containerTag, container) || container == nullptr
  278. || container->classId != display::kStringContainerClass) {
  279. continue;
  280. }
  281. dialogueCandidates.push_back({{containerTag, stringHash},
  282. descriptor.slotIndex,
  283. static_cast<std::uint32_t>(cue),
  284. definitionHash});
  285. }
  286. }
  287. } else {
  288. if (resource->classId != 0x80804F72U) {
  289. continue;
  290. }
  291. std::size_t entries = 0;
  292. std::size_t entryCount = 0;
  293. if (!read_array(bytes, 8U, 40U, 0x80804F74U, entries, entryCount)) {
  294. continue;
  295. }
  296. for (std::size_t entry = 0; entry < entryCount; ++entry) {
  297. const std::size_t row = entries + entry * 40U;
  298. std::uint32_t nameHash = 0;
  299. std::int64_t relative = 0;
  300. std::size_t elements = 0;
  301. std::uint64_t elementCount = 0;
  302. std::uint32_t elementClass = 0;
  303. if (!read_value(bytes, row, nameHash) || !read_value(bytes, row + 24U, relative)
  304. || !add_relative(row + 24U, relative, elements)
  305. || !read_value(bytes, elements, elementCount) || elementCount == 0
  306. || elementCount > format::kAbsentIndex
  307. || !read_value(bytes, elements + 8U, elementClass)
  308. || elementClass != 0x80804F76U) {
  309. continue;
  310. }
  311. const std::size_t data = elements + 16U;
  312. if (data > bytes.size() || elementCount > (bytes.size() - data) / 36U) {
  313. continue;
  314. }
  315. for (std::size_t element = 0; element < elementCount; ++element) {
  316. const std::size_t elementRow = data + element * 36U;
  317. AuthoredDirectiveCandidate candidate{};
  318. candidate.slotIndex = descriptor.slotIndex;
  319. candidate.nameHash = nameHash;
  320. candidate.elementIndex = static_cast<std::int32_t>(element);
  321. candidate.elementCount = static_cast<std::uint32_t>(elementCount);
  322. if (!read_value(bytes, elementRow, candidate.title.containerTag)
  323. || !read_value(bytes, elementRow + 4U, candidate.title.stringHash)
  324. || !read_value(
  325. bytes, elementRow + 8U, candidate.description.containerTag)
  326. || !read_value(
  327. bytes, elementRow + 12U, candidate.description.stringHash)) {
  328. continue;
  329. }
  330. const CachedTag* title = nullptr;
  331. const CachedTag* description = nullptr;
  332. if (!package(candidate.title.containerTag, title) || title == nullptr
  333. || title->classId != display::kStringContainerClass
  334. || !package(candidate.description.containerTag, description)
  335. || description == nullptr
  336. || description->classId != display::kStringContainerClass) {
  337. continue;
  338. }
  339. directiveCandidates.push_back(candidate);
  340. }
  341. }
  342. }
  343. }
  344. std::vector<display::Reference> references{};
  345. references.reserve(dialogueCandidates.size() + directiveCandidates.size() * 2U);
  346. for (const AuthoredTextCandidate& row : dialogueCandidates) {
  347. references.push_back(row.reference);
  348. }
  349. for (const AuthoredDirectiveCandidate& row : directiveCandidates) {
  350. references.push_back(row.title);
  351. references.push_back(row.description);
  352. }
  353. if (references.empty()) {
  354. return true;
  355. }
  356. display::Snapshot names{};
  357. if (!display::resolve({&packageContext, &read_localized_tag, 0, 0}, references, names)
  358. || names.names.size() != references.size()) {
  359. return true;
  360. }
  361. for (std::size_t index = 0; index < dialogueCandidates.size(); ++index) {
  362. const display::Name& name = names.names[index];
  363. if (name.authoredEmpty || name.length == 0) {
  364. continue;
  365. }
  366. const std::string_view text(name.value.data(), name.length);
  367. char id[96]{};
  368. const AuthoredTextCandidate& candidate = dialogueCandidates[index];
  369. const int length = std::snprintf(id,
  370. sizeof id,
  371. "dialogue/%08x/%u/%08x/%08x",
  372. candidate.slotIndex,
  373. candidate.cueIndex,
  374. candidate.definitionHash,
  375. candidate.reference.stringHash);
  376. authored_scene::DialogueCueText row{};
  377. if (length <= 0 || static_cast<std::size_t>(length) >= sizeof id
  378. || !copy_text(std::string_view(id, static_cast<std::size_t>(length)), row.id)
  379. || !copy_text(text, row.text)) {
  380. continue;
  381. }
  382. row.slotIndex = candidate.slotIndex;
  383. row.cueIndex = candidate.cueIndex;
  384. row.definitionHash = candidate.definitionHash;
  385. row.containerTag = candidate.reference.containerTag;
  386. row.stringHash = candidate.reference.stringHash;
  387. output.dialogueCueTexts.push_back(row);
  388. }
  389. std::size_t resolved = dialogueCandidates.size();
  390. for (const AuthoredDirectiveCandidate& candidate : directiveCandidates) {
  391. const display::Name& title = names.names[resolved++];
  392. const display::Name& description = names.names[resolved++];
  393. if (title.authoredEmpty || title.length == 0 || description.authoredEmpty
  394. || description.length == 0) {
  395. continue;
  396. }
  397. char id[96]{};
  398. const int length = std::snprintf(id,
  399. sizeof id,
  400. "directive/%08x/%08x/%d",
  401. candidate.slotIndex,
  402. candidate.nameHash,
  403. candidate.elementIndex);
  404. authored_scene::DirectiveElement row{};
  405. if (length <= 0 || static_cast<std::size_t>(length) >= sizeof id
  406. || !copy_text(std::string_view(id, static_cast<std::size_t>(length)), row.id)
  407. || !copy_text(std::string_view(title.value.data(), title.length), row.title)
  408. || !copy_text(std::string_view(description.value.data(), description.length),
  409. row.description)) {
  410. continue;
  411. }
  412. row.slotIndex = candidate.slotIndex;
  413. row.nameHash = candidate.nameHash;
  414. row.elementIndex = candidate.elementIndex;
  415. row.elementCount = candidate.elementCount;
  416. row.titleContainerTag = candidate.title.containerTag;
  417. row.titleStringHash = candidate.title.stringHash;
  418. row.descriptionContainerTag = candidate.description.containerTag;
  419. row.descriptionStringHash = candidate.description.stringHash;
  420. output.directiveElements.push_back(row);
  421. }
  422. auto dialogueLess = [](const auto& first, const auto& second) {
  423. return std::tie(first.slotIndex, first.cueIndex, first.definitionHash, first.stringHash)
  424. < std::tie(
  425. second.slotIndex, second.cueIndex, second.definitionHash, second.stringHash);
  426. };
  427. auto directiveLess = [](const auto& first, const auto& second) {
  428. return std::tie(first.slotIndex, first.nameHash, first.elementIndex)
  429. < std::tie(second.slotIndex, second.nameHash, second.elementIndex);
  430. };
  431. std::sort(output.dialogueCueTexts.begin(), output.dialogueCueTexts.end(), dialogueLess);
  432. std::sort(output.directiveElements.begin(), output.directiveElements.end(), directiveLess);
  433. output.dialogueCueTexts.erase(std::unique(output.dialogueCueTexts.begin(),
  434. output.dialogueCueTexts.end(),
  435. [&](const auto& left, const auto& right) {
  436. return !dialogueLess(left, right)
  437. && !dialogueLess(right, left);
  438. }),
  439. output.dialogueCueTexts.end());
  440. output.directiveElements.erase(std::unique(output.directiveElements.begin(),
  441. output.directiveElements.end(),
  442. [&](const auto& left, const auto& right) {
  443. return !directiveLess(left, right)
  444. && !directiveLess(right, left);
  445. }),
  446. output.directiveElements.end());
  447. return true;
  448. } catch (...) {
  449. return false;
  450. }
  451. }
  452. /** Resolves the native type-53 authored list and attaches its exact bound to the SDK slot row. */
  453. bool attach_dialogue_cue_counts(const topology_inventory::Snapshot& topology,
  454. const squads::Facts& facts,
  455. PackageContext& packageContext,
  456. topology_enrichment::Snapshot& enrichment,
  457. authored_scene::Snapshot& authoredRows) {
  458. if (topology.slots.size() != enrichment.slots.size()) {
  459. return false;
  460. }
  461. struct CachedTag final {
  462. std::vector<std::byte> bytes{};
  463. std::uint32_t classId{};
  464. };
  465. std::unordered_map<std::uint32_t, CachedTag> cache{};
  466. auto package = [&](std::uint32_t tag, const CachedTag*& output) -> bool {
  467. output = nullptr;
  468. const auto found = cache.find(tag);
  469. if (found != cache.end()) {
  470. output = &found->second;
  471. return true;
  472. }
  473. CachedTag row{};
  474. if (tag == 0 || tag == format::kAbsentIndex
  475. || !read_tag(&packageContext, tag, row.bytes, row.classId)) {
  476. return false;
  477. }
  478. const auto [inserted, accepted] = cache.emplace(tag, std::move(row));
  479. if (!accepted) {
  480. return false;
  481. }
  482. output = &inserted->second;
  483. return true;
  484. };
  485. try {
  486. cache.reserve(facts.descriptors.size());
  487. for (std::uint32_t slotRow = 0; slotRow < topology.slots.size(); ++slotRow) {
  488. if (topology.slots[slotRow].slotType != format::kDialogueSlotType) {
  489. continue;
  490. }
  491. topology_enrichment::Slot& enriched = enrichment.slots[slotRow];
  492. if (enriched.componentClass != format::kDialogueComponentClass
  493. || enriched.authSchema != format::kDialogueAuthSchema
  494. || (enriched.flags & format::kSlotSchemaJoinExact) == 0) {
  495. continue;
  496. }
  497. std::uint64_t agreedCount = 0;
  498. std::vector<dialogue_groups::Definition> agreedDefinitions{};
  499. bool sawDescriptor = false;
  500. bool resolved = true;
  501. for (const squads::DescriptorFact& descriptor : facts.descriptors) {
  502. if (descriptor.slotIndex != slotRow) {
  503. continue;
  504. }
  505. sawDescriptor = true;
  506. const CachedTag* config = nullptr;
  507. if (!package(descriptor.configTag, config) || config == nullptr) {
  508. resolved = false;
  509. break;
  510. }
  511. const std::size_t listField = static_cast<std::size_t>(descriptor.descriptorOffset)
  512. + format::kDialogueAuthoredListRelativeOffset;
  513. std::uint32_t listTag = 0;
  514. if (!read_value(std::span(config->bytes), listField, listTag) || listTag == 0
  515. || listTag == format::kAbsentIndex) {
  516. resolved = false;
  517. break;
  518. }
  519. const CachedTag* authored = nullptr;
  520. std::vector<dialogue_groups::Definition> definitions{};
  521. if (!package(listTag, authored) || authored == nullptr
  522. || authored->classId != format::kDialogueAuthoredListClass
  523. || !dialogue_groups::definitions(authored->bytes, definitions)
  524. || definitions.empty()
  525. || (agreedCount != 0 && agreedDefinitions != definitions)) {
  526. resolved = false;
  527. break;
  528. }
  529. agreedCount = definitions.size();
  530. agreedDefinitions = std::move(definitions);
  531. }
  532. if (cancelled(packageContext.cancel, packageContext.cancelContext)) {
  533. return false;
  534. }
  535. if (resolved && sawDescriptor && agreedCount != 0) {
  536. enriched.dialogueCueCount = static_cast<std::uint32_t>(agreedCount);
  537. enriched.flags |= format::kSlotDialogueCuesExact;
  538. for (std::uint32_t cue = 0; cue < agreedDefinitions.size(); ++cue) {
  539. const auto& definition = agreedDefinitions[cue];
  540. authoredRows.dialogueCues.push_back(
  541. {slotRow, cue, definition.hash, definition.authoredWindowSeconds});
  542. }
  543. }
  544. }
  545. return true;
  546. } catch (...) {
  547. return false;
  548. }
  549. }
  550. /** Flags the slot rows of the scenes the inventory found without a resource. */
  551. bool attach_unresourced_scenes(const topology_inventory::Snapshot& topology,
  552. const authored_scene::Snapshot& scenes,
  553. topology_enrichment::Snapshot& enrichment) noexcept {
  554. if (enrichment.slots.size() != topology.slots.size()) {
  555. return false;
  556. }
  557. for (const std::uint32_t slotRow : scenes.unresourcedSlots) {
  558. if (slotRow >= topology.slots.size()
  559. || topology.slots[slotRow].slotType != format::kAuthoredSceneSlotType) {
  560. return false;
  561. }
  562. topology_enrichment::Slot& enriched = enrichment.slots[slotRow];
  563. if (enriched.componentClass != format::kAuthoredSceneComponentClass
  564. || (enriched.flags & format::kSlotSchemaJoinExact) == 0) {
  565. return false;
  566. }
  567. enriched.flags |= format::kSlotAuthoredSceneUnresourced;
  568. }
  569. return true;
  570. }
  571. } // namespace sunrise::client::content::activity::sdk_generation::native_pack_pipeline