family4_snapshot_preparer.cpp 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. #include <algorithm>
  2. #include <array>
  3. #include <cstddef>
  4. #include <optional>
  5. #include <span>
  6. #include "../../../../../middleware/datagen/definitions.h"
  7. #include "../../../../../middleware/datagen/family4/account/account_encoder.h"
  8. #include "../../../../../middleware/datagen/family4/account/layout.h"
  9. #include "../../../../../middleware/datagen/family4/character/character_encoder.h"
  10. #include "../../../../../middleware/datagen/family4/character/layout.h"
  11. #include "../../../../../middleware/datagen/family4/instance/layout.h"
  12. #include "../../../../../middleware/datagen/family4/loadout/loadout_resolver.h"
  13. #include "../../../../../state/runtime/runtime.h"
  14. #include "internal.h"
  15. #include "snapshot_storage.h"
  16. namespace sunrise::server::bap::encrypted::push::snapshot {
  17. namespace {
  18. namespace family4_datagen = middleware::datagen::family4;
  19. /**
  20. * Publishes the staged family metadata once every needed object is done.
  21. * @param subscription Family id the Client picked.
  22. * @param objectCount Descriptors staged for this family.
  23. * @param compressedExtent Size of the used prefix of the sealed buffer.
  24. * @param reservation Cleanup extent carried over from any prior live snapshot.
  25. * @param staged Descriptors and clear extents built by the caller.
  26. * @param output Gets the family snapshot only on success.
  27. * @return True when the staged descriptors pass the ownership check.
  28. */
  29. [[nodiscard]] bool publish(const middleware::queuez::Subscription& subscription,
  30. std::size_t objectCount,
  31. std::size_t compressedExtent,
  32. const Reservation& reservation,
  33. Prepared& staged,
  34. Prepared& output) noexcept {
  35. staged.compressedClearSize = (std::max)(reservation.compressedClearSize, compressedExtent);
  36. staged.family = middleware::queuez::Family{
  37. kAccountFamilyType,
  38. subscription.familyRootSoid,
  39. kInitialFamilyVersion,
  40. middleware::queuez::kFullSnapshotFlag,
  41. std::span(staged.objects).first(objectCount),
  42. };
  43. return commit(staged, output);
  44. }
  45. } // namespace
  46. /** Builds the Family-4 account, selected-character, and item-instance snapshot. */
  47. bool prepare(Scratch& scratch,
  48. const middleware::queuez::Subscription& subscription,
  49. std::uint32_t accountObjectId,
  50. const Reservation& reservation,
  51. std::span<const queuez::AcquisitionPresentationRow> acquisitionPresentationRows,
  52. Prepared& prepared) noexcept {
  53. if (reservation.rawWriteOffset > scratch.plaintext.size()
  54. || reservation.compressedWriteOffset > scratch.sealed.size()) {
  55. return report_failure("reservation");
  56. }
  57. const auto rawStorage = std::span(scratch.plaintext).subspan(reservation.rawWriteOffset);
  58. if (family4_datagen::account::layout::kObjectSize > rawStorage.size()) {
  59. return report_failure("account_storage");
  60. }
  61. // Package extraction may have completed after State initialization on a first cache build.
  62. // Canonicalize profile action-source identities immediately before the first Family-4 image.
  63. if (!state::ensure_profile_item_identities()) {
  64. return report_failure("profile_identities");
  65. }
  66. // The emote-collection canonicalization deliberately does not live here. Family zero and
  67. // family three build their own images from the same account and neither passes through this
  68. // function, so it runs in the shared preflight ahead of the whole dispatch instead
  69. // (push::ensure_account_canonical).
  70. const state::AccountState account = state::account_snapshot();
  71. if (!state::account::valid(account)) {
  72. return report_failure("account_state");
  73. }
  74. const std::optional<std::size_t> selectedIndex = find_character_index(account);
  75. Resolved selected{};
  76. if (selectedIndex.has_value() && !resolve(account, *selectedIndex, selected)) {
  77. return report_failure("selection");
  78. }
  79. Prepared staged{};
  80. staged.rawClearSize =
  81. (std::max)(reservation.rawClearSize,
  82. reservation.rawWriteOffset + family4_datagen::account::layout::kObjectSize);
  83. std::size_t compressedExtent = reservation.compressedWriteOffset;
  84. const auto accountBytes = rawStorage.first(family4_datagen::account::layout::kObjectSize);
  85. if (!family4_datagen::account::encode(account, accountBytes)) {
  86. return report_failure("account_object");
  87. }
  88. if (!append_object(scratch,
  89. accountBytes,
  90. accountObjectId,
  91. account.primarySoid,
  92. staged.objects[kAccountObjectIndex],
  93. compressedExtent)) {
  94. return report_failure("account_object");
  95. }
  96. // The character object is the only descriptor a selection owns. With no selection it is absent
  97. // and the items move up behind the account object, keeping the published prefix contiguous.
  98. const std::size_t itemBaseIndex =
  99. selectedIndex.has_value() ? kFirstItemObjectIndex : kFirstItemObjectIndexUnselected;
  100. if (selectedIndex.has_value()) {
  101. staged.rawClearSize = (std::max)(staged.rawClearSize,
  102. reservation.rawWriteOffset
  103. + family4_datagen::character::layout::kObjectSize);
  104. if (family4_datagen::character::layout::kObjectSize > rawStorage.size()) {
  105. return report_failure("character_storage");
  106. }
  107. const auto characterBytes =
  108. rawStorage.first(family4_datagen::character::layout::kObjectSize);
  109. const state::CharacterState& selectedCharacter =
  110. account.characters[selected.characterIndex];
  111. if (!family4_datagen::character::encode(
  112. selectedCharacter, selected.loadout, selected.lightEvaluation, characterBytes)) {
  113. return report_failure("character_encode");
  114. }
  115. if (!apply_acquisition_presentation(
  116. characterBytes, selected.loadout, acquisitionPresentationRows)) {
  117. return report_failure("character_presentation");
  118. }
  119. if (!append_object(scratch,
  120. characterBytes,
  121. selected.characterObjectId,
  122. selectedCharacter.soid,
  123. staged.objects[kCharacterObjectIndex],
  124. compressedExtent)) {
  125. return report_failure("character_object");
  126. }
  127. }
  128. // Every character in the roster needs its item records. The equip-summary reader looks up an
  129. // instance with no null check, so a missing record is a null read.
  130. std::uint32_t itemInstanceObjectId = 0;
  131. if (!middleware::datagen::object_id(
  132. kAccountFamilyType, kItemDefinitionSlotIndex, itemInstanceObjectId)) {
  133. return report_failure("item_object_id");
  134. }
  135. std::size_t itemCursor = 0;
  136. for (std::size_t characterIndex = 0; characterIndex < account.characterCount;
  137. ++characterIndex) {
  138. family4_datagen::loadout::ResolvedInstances instances{};
  139. if (!family4_datagen::loadout::resolve_owned_instances(
  140. account, characterIndex, instances)) {
  141. return report_failure("loadout");
  142. }
  143. if (instances.itemCount != 0
  144. && !append_items(scratch,
  145. rawStorage,
  146. itemInstanceObjectId,
  147. instances,
  148. itemBaseIndex,
  149. staged,
  150. itemCursor,
  151. compressedExtent)) {
  152. return report_failure("items");
  153. }
  154. }
  155. // Profile rows with nonzero SOIDs are native action sources. Their item residents follow all
  156. // character-owned instances so the full-snapshot manifest remains deterministic.
  157. if (!append_profile_items(scratch,
  158. rawStorage,
  159. itemInstanceObjectId,
  160. account,
  161. itemBaseIndex,
  162. staged,
  163. itemCursor,
  164. compressedExtent)) {
  165. return report_failure("profile_items");
  166. }
  167. const std::size_t objectCount = itemBaseIndex + itemCursor;
  168. if (itemCursor != 0) {
  169. staged.rawClearSize =
  170. (std::max)(staged.rawClearSize,
  171. reservation.rawWriteOffset + family4_datagen::instance::layout::kObjectSize);
  172. }
  173. return publish(subscription, objectCount, compressedExtent, reservation, staged, prepared);
  174. }
  175. } // namespace sunrise::server::bap::encrypted::push::snapshot