roster_snapshot.cpp 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. #include <algorithm>
  2. #include <span>
  3. #include "../../../../../middleware/datagen/character_record/character_record_encoder.h"
  4. #include "../../../../../middleware/datagen/definitions.h"
  5. #include "../../../../../middleware/datagen/family3/family3_roster.h"
  6. #include "../../../../../middleware/datagen/family4/loadout/loadout_resolver.h"
  7. #include "../../../../../state/equipment/light/resolution/configured_equipment_light_resolver.h"
  8. #include "../../../../../state/runtime/runtime.h"
  9. #include "internal.h"
  10. #include "snapshot_storage.h"
  11. namespace sunrise::server::bap::encrypted::push::snapshot {
  12. namespace {
  13. namespace character_record = middleware::datagen::character_record;
  14. /**
  15. * Appends one family-three character record per character in use.
  16. * The roster names every character, and the select screen reads a record for each one.
  17. * @param scratch Raw object storage owned by the lock.
  18. * @param account Account State read under the lock.
  19. * @param rawExtent First unused raw byte, advanced for every record.
  20. * @param staged Snapshot that takes one descriptor per character.
  21. * @param objectCount Descriptors already staged, advanced for every record.
  22. * @return True when every character is found and its record fits raw storage.
  23. */
  24. [[nodiscard]] bool append_character_records(Scratch& scratch,
  25. const state::AccountState& account,
  26. std::size_t& rawExtent,
  27. std::size_t& compressedExtent,
  28. Prepared& staged,
  29. std::size_t& objectCount) noexcept {
  30. for (std::size_t index = 0; index < account.characterCount; ++index) {
  31. middleware::datagen::family4::loadout::ResolvedInstances instances{};
  32. std::int32_t light = 0;
  33. if (!middleware::datagen::family4::loadout::resolve_instances(account, index, instances)
  34. || !state::equipment::light::resolution::character_light(account, index, light)) {
  35. return false;
  36. }
  37. if (rawExtent > scratch.plaintext.size()
  38. || scratch.plaintext.size() - rawExtent < character_record::kFamily3RecordSize
  39. || objectCount >= staged.objects.size()) {
  40. return false;
  41. }
  42. const auto record =
  43. std::span(scratch.plaintext).subspan(rawExtent, character_record::kFamily3RecordSize);
  44. if (!character_record::encode_family3(
  45. account.characters[index], instances, light, record)) {
  46. return false;
  47. }
  48. std::size_t compressedSize = 0;
  49. if (!compress_object(scratch,
  50. record,
  51. middleware::datagen::kRosterCharacterObjectId,
  52. account.characters[index].soid,
  53. compressedExtent,
  54. staged.objects[objectCount],
  55. compressedSize)) {
  56. return false;
  57. }
  58. compressedExtent += compressedSize;
  59. ++objectCount;
  60. rawExtent += character_record::kFamily3RecordSize;
  61. }
  62. return true;
  63. }
  64. } // namespace
  65. /** Builds the family-three roster snapshot and one record per character. */
  66. bool prepare_roster(Scratch& scratch,
  67. const middleware::queuez::Subscription& subscription,
  68. std::uint32_t objectId,
  69. const Reservation& reservation,
  70. Prepared& prepared) noexcept {
  71. const state::AccountState account = state::account_snapshot();
  72. if (reservation.rawWriteOffset > scratch.plaintext.size()) {
  73. return false;
  74. }
  75. const auto destination = std::span(scratch.plaintext).subspan(reservation.rawWriteOffset);
  76. std::size_t rawSize = 0;
  77. if (!middleware::datagen::family3::encode_roster(account, destination, rawSize)) {
  78. return false;
  79. }
  80. Prepared staged{};
  81. std::size_t objectCount = 0;
  82. std::size_t compressedExtent = reservation.compressedWriteOffset;
  83. // Every queuez object goes out Oodle-encoded. The Client has no raw-object path here.
  84. std::size_t listCompressed = 0;
  85. if (!compress_object(scratch,
  86. destination.first(rawSize),
  87. objectId,
  88. account.primarySoid,
  89. compressedExtent,
  90. staged.objects[objectCount],
  91. listCompressed)) {
  92. return false;
  93. }
  94. compressedExtent += listCompressed;
  95. ++objectCount;
  96. std::size_t rawExtent = reservation.rawWriteOffset + rawSize;
  97. if (!append_character_records(
  98. scratch, account, rawExtent, compressedExtent, staged, objectCount)) {
  99. return false;
  100. }
  101. staged.rawClearSize = (std::max)(reservation.rawClearSize, rawExtent);
  102. staged.compressedClearSize = (std::max)(reservation.compressedClearSize, compressedExtent);
  103. staged.family = middleware::queuez::Family{
  104. subscription.familyType,
  105. subscription.familyRootSoid,
  106. kInitialFamilyVersion,
  107. middleware::queuez::kFullSnapshotFlag,
  108. std::span(staged.objects).first(objectCount),
  109. };
  110. return commit(staged, prepared);
  111. }
  112. /** Builds one incremental Family-3 character record and optional changed account roster. */
  113. bool prepare_roster_appearance_refresh(Scratch& scratch,
  114. const queuez::RosterAppearanceRefresh& refresh,
  115. const state::CharacterState& afterCharacter,
  116. std::size_t characterIndex,
  117. Prepared& prepared) noexcept {
  118. const Reservation reservation = reserve_prior(scratch, prepared);
  119. if (reservation.rawWriteOffset > scratch.plaintext.size()
  120. || reservation.compressedWriteOffset > scratch.sealed.size()) {
  121. return false;
  122. }
  123. state::AccountState account = state::account_snapshot();
  124. if (!refresh.after.family3Active || refresh.after.family3RootSoid == 0
  125. || refresh.after.family3Version <= kInitialFamilyVersion || refresh.characterSoid == 0
  126. || afterCharacter.soid != refresh.characterSoid || characterIndex >= account.characterCount
  127. || account.characters[characterIndex].soid != refresh.characterSoid
  128. || account.primarySoid != refresh.after.family3RootSoid) {
  129. return false;
  130. }
  131. account.characters[characterIndex] = afterCharacter;
  132. if (!state::account::valid(account)
  133. || state::account::selected_character_soid(account) != refresh.characterSoid) {
  134. return false;
  135. }
  136. middleware::datagen::family4::loadout::ResolvedInstances instances{};
  137. std::int32_t light = 0;
  138. if (!middleware::datagen::family4::loadout::resolve_instances(
  139. account, characterIndex, instances)
  140. || !state::equipment::light::resolution::character_light(account, characterIndex, light)) {
  141. return false;
  142. }
  143. const std::size_t rosterSize =
  144. refresh.includeRoster ? middleware::datagen::family3::kRosterSize : 0U;
  145. const std::size_t rawSize = character_record::kFamily3RecordSize + rosterSize;
  146. const auto rawStorage = std::span(scratch.plaintext).subspan(reservation.rawWriteOffset);
  147. if (rawSize > rawStorage.size()) {
  148. return false;
  149. }
  150. const auto characterBytes = rawStorage.first(character_record::kFamily3RecordSize);
  151. if (!character_record::encode_family3(afterCharacter, instances, light, characterBytes)) {
  152. return false;
  153. }
  154. std::span<std::byte> rosterBytes{};
  155. if (refresh.includeRoster) {
  156. rosterBytes = rawStorage.subspan(character_record::kFamily3RecordSize, rosterSize);
  157. std::size_t encodedRosterSize = 0;
  158. if (!middleware::datagen::family3::encode_roster(account, rosterBytes, encodedRosterSize)
  159. || encodedRosterSize != rosterSize) {
  160. return false;
  161. }
  162. }
  163. Prepared staged{};
  164. staged.rawClearSize =
  165. (std::max)(reservation.rawClearSize, reservation.rawWriteOffset + rawSize);
  166. std::size_t compressedExtent = reservation.compressedWriteOffset;
  167. std::size_t objectCount = 0;
  168. if (!append_object(scratch,
  169. characterBytes,
  170. middleware::datagen::kRosterCharacterObjectId,
  171. refresh.characterSoid,
  172. staged.objects[objectCount++],
  173. compressedExtent)) {
  174. clear_after(scratch, reservation);
  175. return false;
  176. }
  177. if (refresh.includeRoster
  178. && !append_object(scratch,
  179. rosterBytes,
  180. middleware::datagen::kRosterObjectId,
  181. account.primarySoid,
  182. staged.objects[objectCount++],
  183. compressedExtent)) {
  184. clear_after(scratch, reservation);
  185. return false;
  186. }
  187. staged.compressedClearSize = (std::max)(reservation.compressedClearSize, compressedExtent);
  188. staged.family = middleware::queuez::Family{
  189. kRosterFamilyType,
  190. refresh.after.family3RootSoid,
  191. refresh.after.family3Version,
  192. 0,
  193. std::span(staged.objects).first(objectCount),
  194. };
  195. if (!commit(staged, prepared)) {
  196. clear_after(scratch, reservation);
  197. return false;
  198. }
  199. return true;
  200. }
  201. } // namespace sunrise::server::bap::encrypted::push::snapshot