activity_sdk_native_pack_text.cpp 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717
  1. #include <algorithm>
  2. #include <array>
  3. #include <cstddef>
  4. #include <cstdint>
  5. #include <cstdio>
  6. #include <cstring>
  7. #include <limits>
  8. #include <map>
  9. #include <span>
  10. #include <string>
  11. #include <string_view>
  12. #include <unordered_map>
  13. #include <utility>
  14. #include <vector>
  15. #include "../../../core/logging/log.h"
  16. #include "../../../middleware/content/packages/tables/activity_display_name_reader.h"
  17. #include "activity_sdk_dialogue_group_index.h"
  18. #include "activity_sdk_dialogue_list.h"
  19. #include "activity_sdk_native_pack_internal.h"
  20. namespace sunrise::client::content::activity::sdk_generation::native_pack_pipeline {
  21. namespace {
  22. namespace format = state::activity_sdk::format;
  23. namespace dialogue_groups = dialogue_group_index;
  24. namespace display = middleware::content::packages::tables::activity_display_names;
  25. template <typename Value>
  26. [[nodiscard]] bool
  27. read_value(std::span<const std::byte> bytes, std::size_t offset, Value& output) noexcept {
  28. output = {};
  29. if (offset > bytes.size() || sizeof output > bytes.size() - offset) {
  30. return false;
  31. }
  32. std::memcpy(&output, bytes.data() + offset, sizeof output);
  33. return true;
  34. }
  35. /** Applies one signed blob-relative offset. @return False when the result leaves the blob. */
  36. [[nodiscard]] bool
  37. add_relative(std::size_t member, std::int64_t relative, std::size_t& target) noexcept {
  38. if (relative >= 0) {
  39. const auto distance = static_cast<std::uint64_t>(relative);
  40. if (distance > (std::numeric_limits<std::size_t>::max)() - member) {
  41. return false;
  42. }
  43. target = member + static_cast<std::size_t>(distance);
  44. return true;
  45. }
  46. const auto distance = static_cast<std::uint64_t>(-(relative + 1)) + 1U;
  47. if (distance > member) {
  48. return false;
  49. }
  50. target = member - static_cast<std::size_t>(distance);
  51. return true;
  52. }
  53. /** Logs one packed directive element the generator leaves out, and why. */
  54. void log_directive_element(std::uint32_t slotIndex,
  55. std::uint32_t nameHash,
  56. std::size_t element,
  57. std::uint32_t flags,
  58. const char* result) noexcept {
  59. std::array<char, 160> line{};
  60. const int written = std::snprintf(line.data(),
  61. line.size(),
  62. "ev=activity_sdk_directive_element result=%s slot_row=%u "
  63. "name=0x%08X element=%zu flags=0x%08X",
  64. result,
  65. static_cast<unsigned>(slotIndex),
  66. static_cast<unsigned>(nameHash),
  67. element,
  68. static_cast<unsigned>(flags));
  69. if (written > 0) {
  70. core::log::write(
  71. core::log::Channel::client,
  72. core::log::Level::debug,
  73. {line.data(), (std::min)(static_cast<std::size_t>(written), line.size() - 1U)});
  74. }
  75. }
  76. /** Reads one packed localized string reference: container tag, then string hash. */
  77. [[nodiscard]] bool read_reference(std::span<const std::byte> bytes,
  78. std::size_t offset,
  79. display::Reference& output) noexcept {
  80. return read_value(bytes, offset, output.containerTag)
  81. && read_value(bytes, offset + 4U, output.stringHash);
  82. }
  83. /** Reads one array field's data offset and count, checking its declared class and stride. */
  84. [[nodiscard]] bool read_array(std::span<const std::byte> bytes,
  85. std::size_t field,
  86. std::size_t stride,
  87. std::uint32_t expectedClass,
  88. std::size_t& data,
  89. std::size_t& count) noexcept {
  90. std::uint64_t rawCount = 0;
  91. std::int64_t relative = 0;
  92. std::size_t header = 0;
  93. std::uint64_t repeated = 0;
  94. std::uint32_t classId = 0;
  95. std::uint32_t padding = 0;
  96. if (!read_value(bytes, field, rawCount) || rawCount > format::kAbsentIndex
  97. || !read_value(bytes, field + 8U, relative)) {
  98. return false;
  99. }
  100. if (rawCount == 0 && relative == 0) {
  101. data = 0;
  102. count = 0;
  103. return true;
  104. }
  105. if (!add_relative(field + 8U, relative, header) || !read_value(bytes, header, repeated)
  106. || repeated != rawCount || !read_value(bytes, header + 8U, classId)
  107. || classId != expectedClass || !read_value(bytes, header + 12U, padding) || padding != 0
  108. || rawCount > (std::numeric_limits<std::size_t>::max)() / stride) {
  109. return false;
  110. }
  111. data = header + 16U;
  112. count = static_cast<std::size_t>(rawCount);
  113. return data <= bytes.size() && count * stride <= bytes.size() - data;
  114. }
  115. [[nodiscard]] bool copy_text(std::string_view value, authored_scene::Text& output) noexcept {
  116. output = {};
  117. if (value.empty() || value.size() >= output.value.size()
  118. || value.size() > (std::numeric_limits<std::uint16_t>::max)()) {
  119. return false;
  120. }
  121. std::copy(value.begin(), value.end(), output.value.begin());
  122. output.length = static_cast<std::uint16_t>(value.size());
  123. return true;
  124. }
  125. struct AuthoredTextCandidate final {
  126. display::Reference reference{};
  127. std::uint32_t slotIndex{};
  128. std::uint32_t cueIndex{format::kAbsentIndex};
  129. std::uint32_t definitionHash{};
  130. };
  131. /** The localized fields are one directive element, not several selectable directives. */
  132. struct AuthoredDirectiveCandidate final {
  133. display::Reference title{};
  134. display::Reference description{};
  135. display::Reference progress{};
  136. std::uint32_t flags{};
  137. std::uint32_t slotIndex{};
  138. std::uint32_t nameHash{};
  139. std::int32_t elementIndex{-1};
  140. std::uint32_t elementCount{};
  141. };
  142. /** Logs one dialogue list fact that changes which cues carry lines, and its cue context. */
  143. void log_dialogue_list(const squads::DescriptorFact& descriptor,
  144. std::uint32_t listTag,
  145. const char* result,
  146. std::uint32_t definitionHash,
  147. std::uint32_t count,
  148. core::log::Level level) noexcept {
  149. std::array<char, 192> line{};
  150. const int written =
  151. std::snprintf(line.data(),
  152. line.size(),
  153. "ev=activity_sdk_dialogue_list result=%s config=0x%08X offset=0x%X "
  154. "slot_row=%u list=0x%08X definition=0x%08X count=%u",
  155. result,
  156. static_cast<unsigned>(descriptor.configTag),
  157. static_cast<unsigned>(descriptor.descriptorOffset),
  158. static_cast<unsigned>(descriptor.slotIndex),
  159. static_cast<unsigned>(listTag),
  160. static_cast<unsigned>(definitionHash),
  161. static_cast<unsigned>(count));
  162. if (written > 0) {
  163. core::log::write(
  164. core::log::Channel::client,
  165. level,
  166. {line.data(), (std::min)(static_cast<std::size_t>(written), line.size() - 1U)});
  167. }
  168. }
  169. } // namespace
  170. /**
  171. * Keeps authored group ordinals and task counts tied to the exact objective slot.
  172. * @param topology Canonical slots.
  173. * @param facts Descriptor sources for those slots.
  174. * @param packageContext Installed package reader.
  175. * @param output Receives the native group rows.
  176. * @return False when repeated definitions disagree or the package reader fails.
  177. */
  178. bool attach_combat_objective_groups(const topology_inventory::Snapshot& topology,
  179. const squads::Facts& facts,
  180. PackageContext& packageContext,
  181. authored_scene::Snapshot& output) {
  182. // Objective descriptors contain 40-byte groups at +136 and 40-byte tasks at group +16.
  183. constexpr std::size_t kGroupsField = 136U;
  184. constexpr std::size_t kTasksField = 16U;
  185. constexpr std::size_t kRowStride = 40U;
  186. constexpr std::uint32_t kGroupClass = 0x80807D8FU;
  187. constexpr std::uint32_t kTaskClass = 0x80807D95U;
  188. std::unordered_map<std::uint64_t, std::vector<std::uint32_t>> configs{};
  189. std::map<std::uint32_t, std::vector<std::uint32_t>> slots{};
  190. try {
  191. for (const squads::DescriptorFact& descriptor : facts.descriptors) {
  192. if (descriptor.slotIndex >= topology.slots.size()
  193. || topology.slots[descriptor.slotIndex].slotType != format::kObjectiveSlotType
  194. || descriptor.componentClass != format::kObjectiveComponentClass
  195. || descriptor.senseSchema != format::kObjectiveSenseSchema
  196. || descriptor.authSchema != format::kObjectiveAuthSchema) {
  197. continue;
  198. }
  199. const std::uint64_t descriptorKey =
  200. (static_cast<std::uint64_t>(descriptor.configTag) << 32U)
  201. | descriptor.descriptorOffset;
  202. auto found = configs.find(descriptorKey);
  203. if (found == configs.end()) {
  204. std::vector<std::byte> bytes{};
  205. std::uint32_t configClass = 0;
  206. std::size_t groupData = 0;
  207. std::size_t groupCount = 0;
  208. if (!read_tag(&packageContext, descriptor.configTag, bytes, configClass)
  209. || !read_array(bytes,
  210. descriptor.descriptorOffset + kGroupsField,
  211. kRowStride,
  212. kGroupClass,
  213. groupData,
  214. groupCount)) {
  215. return false;
  216. }
  217. std::vector<std::uint32_t> taskCounts{};
  218. for (std::size_t group = 0; group < groupCount; ++group) {
  219. std::size_t taskData = 0;
  220. std::size_t taskCount = 0;
  221. if (!read_array(bytes,
  222. groupData + group * kRowStride + kTasksField,
  223. kRowStride,
  224. kTaskClass,
  225. taskData,
  226. taskCount)) {
  227. return false;
  228. }
  229. taskCounts.push_back(static_cast<std::uint32_t>(taskCount));
  230. }
  231. found = configs.emplace(descriptorKey, std::move(taskCounts)).first;
  232. }
  233. const auto [slot, inserted] = slots.emplace(descriptor.slotIndex, found->second);
  234. if (!inserted && slot->second != found->second) {
  235. return false;
  236. }
  237. if (cancelled(packageContext.cancel, packageContext.cancelContext)) {
  238. return false;
  239. }
  240. }
  241. for (const auto& [slot, taskCounts] : slots) {
  242. for (std::uint32_t group = 0; group < taskCounts.size(); ++group) {
  243. output.combatObjectiveGroups.push_back({slot, group, taskCounts[group]});
  244. }
  245. }
  246. return true;
  247. } catch (...) {
  248. return false;
  249. }
  250. }
  251. /** Extracts localized dialogue aliases and safe authored directive elements. */
  252. bool attach_authored_text(const topology_inventory::Snapshot& topology,
  253. const squads::Facts& facts,
  254. PackageContext& packageContext,
  255. authored_scene::Snapshot& output) {
  256. struct CachedTag final {
  257. std::vector<std::byte> bytes{};
  258. std::uint32_t classId{};
  259. };
  260. std::unordered_map<std::uint32_t, CachedTag> cache{};
  261. auto package = [&](std::uint32_t tag, const CachedTag*& result) -> bool {
  262. const auto found = cache.find(tag);
  263. if (found != cache.end()) {
  264. result = &found->second;
  265. return true;
  266. }
  267. CachedTag row{};
  268. if (!read_tag(&packageContext, tag, row.bytes, row.classId)) {
  269. return false;
  270. }
  271. result = &cache.emplace(tag, std::move(row)).first->second;
  272. return true;
  273. };
  274. // An absent reference has no container; a present one must name a string container.
  275. auto string_container = [&](const display::Reference& reference) -> bool {
  276. if (reference.containerTag == 0) {
  277. return true;
  278. }
  279. const CachedTag* container = nullptr;
  280. return package(reference.containerTag, container) && container != nullptr
  281. && container->classId == display::kStringContainerClass;
  282. };
  283. try {
  284. std::vector<AuthoredTextCandidate> dialogueCandidates{};
  285. std::vector<AuthoredDirectiveCandidate> directiveCandidates{};
  286. for (const squads::DescriptorFact& descriptor : facts.descriptors) {
  287. if (descriptor.slotIndex >= topology.slots.size()) {
  288. continue;
  289. }
  290. const std::uint32_t slotType = topology.slots[descriptor.slotIndex].slotType;
  291. if (slotType != format::kDialogueSlotType && slotType != 68U) {
  292. continue;
  293. }
  294. const CachedTag* config = nullptr;
  295. if (!package(descriptor.configTag, config) || config == nullptr) {
  296. continue;
  297. }
  298. const std::size_t field = static_cast<std::size_t>(descriptor.descriptorOffset)
  299. + (slotType == format::kDialogueSlotType ? 0x58U : 0x5CU);
  300. std::uint32_t resourceTag = 0;
  301. const CachedTag* resource = nullptr;
  302. if (!read_value(std::span(config->bytes), field, resourceTag)
  303. || !package(resourceTag, resource) || resource == nullptr) {
  304. continue;
  305. }
  306. const auto bytes = std::span(resource->bytes);
  307. if (slotType == format::kDialogueSlotType) {
  308. if (resource->classId != format::kDialogueAuthoredListClass) {
  309. continue;
  310. }
  311. dialogue_list::Snapshot list{};
  312. if (!dialogue_list::read(bytes, list)) {
  313. log_dialogue_list(
  314. descriptor, resourceTag, "malformed", 0, 0, core::log::Level::warn);
  315. continue;
  316. }
  317. for (const dialogue_list::SharedHash& shared : list.sharedHashes) {
  318. log_dialogue_list(descriptor,
  319. resourceTag,
  320. "shared_hash",
  321. shared.definitionHash,
  322. shared.treeCount,
  323. core::log::Level::info);
  324. }
  325. for (std::size_t cue = 0; cue < list.cues.size(); ++cue) {
  326. const dialogue_list::Cue& row = list.cues[cue];
  327. if (row.status == dialogue_list::CueStatus::ambiguous
  328. || row.status == dialogue_list::CueStatus::missing
  329. || row.status == dialogue_list::CueStatus::malformed) {
  330. log_dialogue_list(
  331. descriptor,
  332. resourceTag,
  333. row.status == dialogue_list::CueStatus::ambiguous ? "cue_ambiguous"
  334. : row.status == dialogue_list::CueStatus::missing ? "cue_missing"
  335. : "cue_malformed",
  336. row.definitionHash,
  337. static_cast<std::uint32_t>(cue),
  338. core::log::Level::warn);
  339. continue;
  340. }
  341. // Both takes of a line normally reference the same text; each distinct
  342. // reference is still a candidate, and the Lua publisher folds equal texts.
  343. for (std::uint32_t offset = 0; offset < row.lineCount; ++offset) {
  344. const dialogue_list::Line& line = list.lines[row.firstLine + offset];
  345. for (const dialogue_list::Take* take : {&line.first, &line.second}) {
  346. const CachedTag* container = nullptr;
  347. if (take->containerTag == 0
  348. || take->containerTag == format::kAbsentIndex
  349. || !package(take->containerTag, container) || container == nullptr
  350. || container->classId != display::kStringContainerClass) {
  351. continue;
  352. }
  353. dialogueCandidates.push_back({{take->containerTag, take->stringHash},
  354. descriptor.slotIndex,
  355. static_cast<std::uint32_t>(cue),
  356. row.definitionHash});
  357. }
  358. }
  359. }
  360. } else {
  361. if (resource->classId != format::kDirectiveTableClass) {
  362. continue;
  363. }
  364. std::size_t entries = 0;
  365. std::size_t entryCount = 0;
  366. if (!read_array(bytes,
  367. format::kDirectiveEntryArrayOffset,
  368. format::kDirectiveEntrySize,
  369. format::kDirectiveEntryClass,
  370. entries,
  371. entryCount)) {
  372. continue;
  373. }
  374. for (std::size_t entry = 0; entry < entryCount; ++entry) {
  375. const std::size_t row = entries + entry * format::kDirectiveEntrySize;
  376. const std::size_t elementsField = row + format::kDirectiveEntryElementsOffset;
  377. std::uint32_t nameHash = 0;
  378. std::int64_t relative = 0;
  379. std::size_t elements = 0;
  380. std::uint64_t elementCount = 0;
  381. std::uint32_t elementClass = 0;
  382. if (!read_value(bytes, row, nameHash)
  383. || !read_value(bytes, elementsField, relative)
  384. || !add_relative(elementsField, relative, elements)
  385. || !read_value(bytes, elements, elementCount) || elementCount == 0
  386. || elementCount > format::kAbsentIndex
  387. || !read_value(bytes, elements + 8U, elementClass)
  388. || elementClass != format::kDirectiveElementClass) {
  389. continue;
  390. }
  391. const std::size_t data = elements + 16U;
  392. if (data > bytes.size()
  393. || elementCount
  394. > (bytes.size() - data) / format::kDirectiveElementPackedSize) {
  395. continue;
  396. }
  397. for (std::size_t element = 0; element < elementCount; ++element) {
  398. const std::size_t elementRow =
  399. data + element * format::kDirectiveElementPackedSize;
  400. AuthoredDirectiveCandidate candidate{};
  401. candidate.slotIndex = descriptor.slotIndex;
  402. candidate.nameHash = nameHash;
  403. candidate.elementIndex = static_cast<std::int32_t>(element);
  404. candidate.elementCount = static_cast<std::uint32_t>(elementCount);
  405. if (!read_reference(bytes,
  406. elementRow + format::kDirectiveElementTitleOffset,
  407. candidate.title)
  408. || !read_reference(bytes,
  409. elementRow
  410. + format::kDirectiveElementDescriptionOffset,
  411. candidate.description)
  412. || !read_reference(bytes,
  413. elementRow + format::kDirectiveElementProgressOffset,
  414. candidate.progress)
  415. || !read_value(bytes,
  416. elementRow + format::kDirectiveElementFlagsOffset,
  417. candidate.flags)) {
  418. log_directive_element(
  419. descriptor.slotIndex, nameHash, element, candidate.flags, "read");
  420. continue;
  421. }
  422. // The title is always authored; the other two may be absent.
  423. if (candidate.title.containerTag == 0
  424. || !string_container(candidate.title)) {
  425. log_directive_element(
  426. descriptor.slotIndex, nameHash, element, candidate.flags, "title");
  427. continue;
  428. }
  429. if (!string_container(candidate.description)) {
  430. log_directive_element(descriptor.slotIndex,
  431. nameHash,
  432. element,
  433. candidate.flags,
  434. "description");
  435. continue;
  436. }
  437. if (!string_container(candidate.progress)) {
  438. log_directive_element(descriptor.slotIndex,
  439. nameHash,
  440. element,
  441. candidate.flags,
  442. "progress");
  443. continue;
  444. }
  445. directiveCandidates.push_back(candidate);
  446. }
  447. }
  448. }
  449. }
  450. std::vector<display::Reference> references{};
  451. references.reserve(dialogueCandidates.size() + directiveCandidates.size() * 3U);
  452. for (const AuthoredTextCandidate& row : dialogueCandidates) {
  453. references.push_back(row.reference);
  454. }
  455. for (const AuthoredDirectiveCandidate& row : directiveCandidates) {
  456. references.push_back(row.title);
  457. references.push_back(row.description);
  458. references.push_back(row.progress);
  459. }
  460. if (references.empty()) {
  461. return true;
  462. }
  463. display::Snapshot names{};
  464. if (!display::resolve({&packageContext, &read_localized_tag, 0, 0}, references, names)
  465. || names.names.size() != references.size()) {
  466. return true;
  467. }
  468. for (std::size_t index = 0; index < dialogueCandidates.size(); ++index) {
  469. const display::Name& name = names.names[index];
  470. if (name.authoredEmpty || name.length == 0) {
  471. continue;
  472. }
  473. const std::string_view text(name.value.data(), name.length);
  474. char id[96]{};
  475. const AuthoredTextCandidate& candidate = dialogueCandidates[index];
  476. const int length = std::snprintf(id,
  477. sizeof id,
  478. "dialogue/%08x/%u/%08x/%08x",
  479. candidate.slotIndex,
  480. candidate.cueIndex,
  481. candidate.definitionHash,
  482. candidate.reference.stringHash);
  483. authored_scene::DialogueCueText row{};
  484. if (length <= 0 || static_cast<std::size_t>(length) >= sizeof id
  485. || !copy_text(std::string_view(id, static_cast<std::size_t>(length)), row.id)
  486. || !copy_text(text, row.text)) {
  487. continue;
  488. }
  489. row.slotIndex = candidate.slotIndex;
  490. row.cueIndex = candidate.cueIndex;
  491. row.definitionHash = candidate.definitionHash;
  492. row.containerTag = candidate.reference.containerTag;
  493. row.stringHash = candidate.reference.stringHash;
  494. output.dialogueCueTexts.push_back(row);
  495. }
  496. std::size_t resolved = dialogueCandidates.size();
  497. for (const AuthoredDirectiveCandidate& candidate : directiveCandidates) {
  498. const display::Name& title = names.names[resolved++];
  499. const display::Name& description = names.names[resolved++];
  500. const display::Name& progress = names.names[resolved++];
  501. const bool hasDescription = !description.authoredEmpty && description.length != 0;
  502. const bool hasProgress = !progress.authoredEmpty && progress.length != 0;
  503. if (title.authoredEmpty || title.length == 0 || (!hasDescription && !hasProgress)) {
  504. log_directive_element(candidate.slotIndex,
  505. candidate.nameHash,
  506. static_cast<std::size_t>(candidate.elementIndex),
  507. candidate.flags,
  508. title.authoredEmpty || title.length == 0 ? "no_title"
  509. : "no_text");
  510. continue;
  511. }
  512. char id[96]{};
  513. const int length = std::snprintf(id,
  514. sizeof id,
  515. "directive/%08x/%08x/%d",
  516. candidate.slotIndex,
  517. candidate.nameHash,
  518. candidate.elementIndex);
  519. authored_scene::DirectiveElement row{};
  520. if (length <= 0 || static_cast<std::size_t>(length) >= sizeof id
  521. || !copy_text(std::string_view(id, static_cast<std::size_t>(length)), row.id)
  522. || !copy_text(std::string_view(title.value.data(), title.length), row.title)
  523. || (hasDescription
  524. && !copy_text(std::string_view(description.value.data(), description.length),
  525. row.description))
  526. || (hasProgress
  527. && !copy_text(std::string_view(progress.value.data(), progress.length),
  528. row.progress))) {
  529. continue;
  530. }
  531. row.slotIndex = candidate.slotIndex;
  532. row.nameHash = candidate.nameHash;
  533. row.elementIndex = candidate.elementIndex;
  534. row.elementCount = candidate.elementCount;
  535. row.titleContainerTag = candidate.title.containerTag;
  536. row.titleStringHash = candidate.title.stringHash;
  537. // An absent field keeps zero tags, so the row says which fields were authored.
  538. if (hasDescription) {
  539. row.descriptionContainerTag = candidate.description.containerTag;
  540. row.descriptionStringHash = candidate.description.stringHash;
  541. }
  542. if (hasProgress) {
  543. row.progressContainerTag = candidate.progress.containerTag;
  544. row.progressStringHash = candidate.progress.stringHash;
  545. }
  546. row.flags = candidate.flags;
  547. output.directiveElements.push_back(row);
  548. }
  549. auto dialogueLess = [](const auto& first, const auto& second) {
  550. return std::tie(first.slotIndex, first.cueIndex, first.definitionHash, first.stringHash)
  551. < std::tie(
  552. second.slotIndex, second.cueIndex, second.definitionHash, second.stringHash);
  553. };
  554. auto directiveLess = [](const auto& first, const auto& second) {
  555. return std::tie(first.slotIndex, first.nameHash, first.elementIndex)
  556. < std::tie(second.slotIndex, second.nameHash, second.elementIndex);
  557. };
  558. std::sort(output.dialogueCueTexts.begin(), output.dialogueCueTexts.end(), dialogueLess);
  559. std::sort(output.directiveElements.begin(), output.directiveElements.end(), directiveLess);
  560. output.dialogueCueTexts.erase(std::unique(output.dialogueCueTexts.begin(),
  561. output.dialogueCueTexts.end(),
  562. [&](const auto& left, const auto& right) {
  563. return !dialogueLess(left, right)
  564. && !dialogueLess(right, left);
  565. }),
  566. output.dialogueCueTexts.end());
  567. output.directiveElements.erase(std::unique(output.directiveElements.begin(),
  568. output.directiveElements.end(),
  569. [&](const auto& left, const auto& right) {
  570. return !directiveLess(left, right)
  571. && !directiveLess(right, left);
  572. }),
  573. output.directiveElements.end());
  574. return true;
  575. } catch (...) {
  576. return false;
  577. }
  578. }
  579. /** Resolves the native type-53 authored list and attaches its exact bound to the SDK slot row. */
  580. bool attach_dialogue_cue_counts(const topology_inventory::Snapshot& topology,
  581. const squads::Facts& facts,
  582. PackageContext& packageContext,
  583. topology_enrichment::Snapshot& enrichment,
  584. authored_scene::Snapshot& authoredRows) {
  585. if (topology.slots.size() != enrichment.slots.size()) {
  586. return false;
  587. }
  588. struct CachedTag final {
  589. std::vector<std::byte> bytes{};
  590. std::uint32_t classId{};
  591. };
  592. std::unordered_map<std::uint32_t, CachedTag> cache{};
  593. auto package = [&](std::uint32_t tag, const CachedTag*& output) -> bool {
  594. output = nullptr;
  595. const auto found = cache.find(tag);
  596. if (found != cache.end()) {
  597. output = &found->second;
  598. return true;
  599. }
  600. CachedTag row{};
  601. if (tag == 0 || tag == format::kAbsentIndex
  602. || !read_tag(&packageContext, tag, row.bytes, row.classId)) {
  603. return false;
  604. }
  605. const auto [inserted, accepted] = cache.emplace(tag, std::move(row));
  606. if (!accepted) {
  607. return false;
  608. }
  609. output = &inserted->second;
  610. return true;
  611. };
  612. try {
  613. cache.reserve(facts.descriptors.size());
  614. for (std::uint32_t slotRow = 0; slotRow < topology.slots.size(); ++slotRow) {
  615. if (topology.slots[slotRow].slotType != format::kDialogueSlotType) {
  616. continue;
  617. }
  618. topology_enrichment::Slot& enriched = enrichment.slots[slotRow];
  619. if (enriched.componentClass != format::kDialogueComponentClass
  620. || enriched.authSchema != format::kDialogueAuthSchema
  621. || (enriched.flags & format::kSlotSchemaJoinExact) == 0) {
  622. continue;
  623. }
  624. std::uint64_t agreedCount = 0;
  625. std::vector<dialogue_groups::Definition> agreedDefinitions{};
  626. bool sawDescriptor = false;
  627. bool resolved = true;
  628. for (const squads::DescriptorFact& descriptor : facts.descriptors) {
  629. if (descriptor.slotIndex != slotRow) {
  630. continue;
  631. }
  632. sawDescriptor = true;
  633. const CachedTag* config = nullptr;
  634. if (!package(descriptor.configTag, config) || config == nullptr) {
  635. resolved = false;
  636. break;
  637. }
  638. const std::size_t listField = static_cast<std::size_t>(descriptor.descriptorOffset)
  639. + format::kDialogueAuthoredListRelativeOffset;
  640. std::uint32_t listTag = 0;
  641. if (!read_value(std::span(config->bytes), listField, listTag) || listTag == 0
  642. || listTag == format::kAbsentIndex) {
  643. resolved = false;
  644. break;
  645. }
  646. const CachedTag* authored = nullptr;
  647. std::vector<dialogue_groups::Definition> definitions{};
  648. if (!package(listTag, authored) || authored == nullptr
  649. || authored->classId != format::kDialogueAuthoredListClass
  650. || !dialogue_groups::definitions(authored->bytes, definitions)
  651. || definitions.empty()
  652. || (agreedCount != 0 && agreedDefinitions != definitions)) {
  653. resolved = false;
  654. break;
  655. }
  656. agreedCount = definitions.size();
  657. agreedDefinitions = std::move(definitions);
  658. }
  659. if (cancelled(packageContext.cancel, packageContext.cancelContext)) {
  660. return false;
  661. }
  662. if (resolved && sawDescriptor && agreedCount != 0) {
  663. enriched.dialogueCueCount = static_cast<std::uint32_t>(agreedCount);
  664. enriched.flags |= format::kSlotDialogueCuesExact;
  665. for (std::uint32_t cue = 0; cue < agreedDefinitions.size(); ++cue) {
  666. const auto& definition = agreedDefinitions[cue];
  667. authoredRows.dialogueCues.push_back(
  668. {slotRow, cue, definition.hash, definition.authoredWindowSeconds});
  669. }
  670. }
  671. }
  672. return true;
  673. } catch (...) {
  674. return false;
  675. }
  676. }
  677. /** Flags the slot rows of the scenes the inventory found without a resource. */
  678. bool attach_unresourced_scenes(const topology_inventory::Snapshot& topology,
  679. const authored_scene::Snapshot& scenes,
  680. topology_enrichment::Snapshot& enrichment) noexcept {
  681. if (enrichment.slots.size() != topology.slots.size()) {
  682. return false;
  683. }
  684. for (const std::uint32_t slotRow : scenes.unresourcedSlots) {
  685. if (slotRow >= topology.slots.size()
  686. || topology.slots[slotRow].slotType != format::kAuthoredSceneSlotType) {
  687. return false;
  688. }
  689. topology_enrichment::Slot& enriched = enrichment.slots[slotRow];
  690. if (enriched.componentClass != format::kAuthoredSceneComponentClass
  691. || (enriched.flags & format::kSlotSchemaJoinExact) == 0) {
  692. return false;
  693. }
  694. enriched.flags |= format::kSlotAuthoredSceneUnresourced;
  695. }
  696. return true;
  697. }
  698. } // namespace sunrise::client::content::activity::sdk_generation::native_pack_pipeline