queuez_select_character.cpp 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. #include <optional>
  2. #include "../../../../../middleware/datagen/definitions.h"
  3. #include "../../queuez/queuez_state_validation.h"
  4. #include "../snapshot/internal.h"
  5. #include "queuez_update_frame.h"
  6. namespace sunrise::server::bap::encrypted::push {
  7. /** Appends the opcode-504 Family-4 character-selection update. */
  8. bool append_select_character_notification(Scratch& scratch,
  9. const queuez::SelectCharacter& select,
  10. std::span<const std::byte, state::kAesKeySize> key,
  11. std::span<const std::byte, state::kBapNonceSize> nonce,
  12. std::span<std::byte> response,
  13. std::size_t& written) noexcept {
  14. snapshot::Prepared prepared{};
  15. if (!snapshot::prepare_selection_move(scratch, select, prepared)) {
  16. return false;
  17. }
  18. if (!queuez_frame::append(scratch,
  19. prepared.family,
  20. prepared.rawClearSize,
  21. prepared.compressedClearSize,
  22. key,
  23. nonce,
  24. response,
  25. written)) {
  26. return false;
  27. }
  28. return true;
  29. }
  30. /** Appends the opcode-403 character upsert as one increment above the current peer version. */
  31. bool append_equipment_swap_notification(
  32. Scratch& scratch,
  33. const queuez::EquipmentSwap& swap,
  34. const state::PendingEquipmentSwap& mutation,
  35. std::span<const queuez::AcquisitionPresentationRow> acquisitionPresentationRows,
  36. std::span<const std::byte, state::kAesKeySize> key,
  37. std::span<const std::byte, state::kBapNonceSize> nonce,
  38. std::span<std::byte> response,
  39. std::size_t& written) noexcept {
  40. snapshot::Prepared prepared{};
  41. if (!snapshot::prepare_equipment_swap(
  42. scratch, swap, mutation, acquisitionPresentationRows, prepared)) {
  43. return false;
  44. }
  45. if (!queuez_frame::append(scratch,
  46. prepared.family,
  47. prepared.rawClearSize,
  48. prepared.compressedClearSize,
  49. key,
  50. nonce,
  51. response,
  52. written)) {
  53. return false;
  54. }
  55. return true;
  56. }
  57. /** Appends one opcode-406 selected-character item-state upsert. */
  58. bool append_item_state_notification(
  59. Scratch& scratch,
  60. const queuez::EquipmentSwap& update,
  61. const state::PendingItemState& mutation,
  62. std::span<const queuez::AcquisitionPresentationRow> acquisitionPresentationRows,
  63. std::span<const std::byte, state::kAesKeySize> key,
  64. std::span<const std::byte, state::kBapNonceSize> nonce,
  65. std::span<std::byte> response,
  66. std::size_t& written) noexcept {
  67. snapshot::Prepared prepared{};
  68. if (!snapshot::prepare_item_state(
  69. scratch, update, mutation, acquisitionPresentationRows, prepared)) {
  70. return false;
  71. }
  72. const std::size_t objectCount = prepared.family.objects.size();
  73. if (objectCount != 1 || prepared.family.objects.front().id != update.characterDefinitionId
  74. || prepared.family.objects.front().version != update.characterSoid
  75. || prepared.family.objects.front().encoding != middleware::queuez::Encoding::oodle
  76. || prepared.family.objects.front().payload.empty()
  77. || !queuez_frame::append(scratch,
  78. prepared.family,
  79. prepared.rawClearSize,
  80. prepared.compressedClearSize,
  81. key,
  82. nonce,
  83. response,
  84. written)) {
  85. return false;
  86. }
  87. return true;
  88. }
  89. /** Appends one socket item upsert and its charged account balances when required. */
  90. bool append_socket_plug_notification(Scratch& scratch,
  91. const queuez::SocketPlug& socketPlug,
  92. const state::PendingSocketPlug& mutation,
  93. std::span<const std::byte, state::kAesKeySize> key,
  94. std::span<const std::byte, state::kBapNonceSize> nonce,
  95. std::span<std::byte> response,
  96. std::size_t& written) noexcept {
  97. snapshot::Prepared prepared{};
  98. if (!snapshot::prepare_socket_plug(scratch, socketPlug, mutation, prepared)) {
  99. return false;
  100. }
  101. const std::size_t objectCount = prepared.family.objects.size();
  102. const std::size_t expectedObjectCount = socketPlug.updatesAccount ? 2U : 1U;
  103. if (objectCount != expectedObjectCount
  104. || prepared.family.objects.front().id != socketPlug.itemInstanceDefinitionId
  105. || prepared.family.objects.front().version != socketPlug.targetInstanceSoid
  106. || prepared.family.objects.front().encoding != middleware::queuez::Encoding::oodle
  107. || prepared.family.objects.front().payload.empty()
  108. || (socketPlug.updatesAccount
  109. && (prepared.family.objects[1].id != socketPlug.accountDefinitionId
  110. || prepared.family.objects[1].version != socketPlug.accountSoid
  111. || prepared.family.objects[1].encoding != middleware::queuez::Encoding::oodle
  112. || prepared.family.objects[1].payload.empty()))
  113. || !queuez_frame::append(scratch,
  114. prepared.family,
  115. prepared.rawClearSize,
  116. prepared.compressedClearSize,
  117. key,
  118. nonce,
  119. response,
  120. written)) {
  121. return false;
  122. }
  123. return true;
  124. }
  125. /** Appends one subclass item-instance upsert after an opcode-801 selection. */
  126. bool append_subclass_selection_notification(Scratch& scratch,
  127. const queuez::SubclassSelection& selection,
  128. const state::PendingSubclassSelection& mutation,
  129. std::span<const std::byte, state::kAesKeySize> key,
  130. std::span<const std::byte, state::kBapNonceSize> nonce,
  131. std::span<std::byte> response,
  132. std::size_t& written) noexcept {
  133. snapshot::Prepared prepared{};
  134. if (!snapshot::prepare_subclass_selection(scratch, selection, mutation, prepared)) {
  135. return false;
  136. }
  137. const std::size_t objectCount = prepared.family.objects.size();
  138. if (objectCount != 1 || prepared.family.objects.front().id != selection.itemInstanceDefinitionId
  139. || prepared.family.objects.front().version != selection.subclassInstanceSoid
  140. || prepared.family.objects.front().encoding != middleware::queuez::Encoding::oodle
  141. || prepared.family.objects.front().payload.empty()
  142. || !queuez_frame::append(scratch,
  143. prepared.family,
  144. prepared.rawClearSize,
  145. prepared.compressedClearSize,
  146. key,
  147. nonce,
  148. response,
  149. written)) {
  150. return false;
  151. }
  152. return true;
  153. }
  154. /** Appends one atomic new-instance-before-character Family-4 acquisition update. */
  155. bool append_item_acquisition_notification(
  156. Scratch& scratch,
  157. const queuez::ItemAcquisition& acquisition,
  158. const state::PendingItemAcquisition& mutation,
  159. std::optional<std::uint16_t> pendingSeasonReward,
  160. std::span<const queuez::AcquisitionPresentationRow> acquisitionPresentationRows,
  161. std::span<const std::byte, state::kAesKeySize> key,
  162. std::span<const std::byte, state::kBapNonceSize> nonce,
  163. std::span<std::byte> response,
  164. std::size_t& written) noexcept {
  165. snapshot::Prepared prepared{};
  166. if (!snapshot::prepare_item_acquisition(scratch,
  167. acquisition,
  168. mutation,
  169. pendingSeasonReward,
  170. acquisitionPresentationRows,
  171. prepared)) {
  172. return false;
  173. }
  174. const std::size_t objectCount = prepared.family.objects.size();
  175. const std::size_t expectedObjectCount = acquisition.updatesAccount ? 3U : 2U;
  176. if (objectCount != expectedObjectCount
  177. || prepared.family.objects[0].id != acquisition.itemInstanceDefinitionId
  178. || prepared.family.objects[0].version != acquisition.acquiredInstanceSoid
  179. || prepared.family.objects[0].encoding != middleware::queuez::Encoding::oodle
  180. || prepared.family.objects[0].payload.empty()
  181. || prepared.family.objects[1].id != acquisition.characterDefinitionId
  182. || prepared.family.objects[1].version != acquisition.characterSoid
  183. || prepared.family.objects[1].encoding != middleware::queuez::Encoding::oodle
  184. || prepared.family.objects[1].payload.empty()
  185. || (acquisition.updatesAccount
  186. && (prepared.family.objects[2].id != acquisition.accountDefinitionId
  187. || prepared.family.objects[2].version != acquisition.accountSoid
  188. || prepared.family.objects[2].encoding != middleware::queuez::Encoding::oodle
  189. || prepared.family.objects[2].payload.empty()))
  190. || !queuez_frame::append(scratch,
  191. prepared.family,
  192. prepared.rawClearSize,
  193. prepared.compressedClearSize,
  194. key,
  195. nonce,
  196. response,
  197. written)) {
  198. return false;
  199. }
  200. return true;
  201. }
  202. /** Appends an optional new profile resident followed by the full account after-image. */
  203. bool append_profile_item_acquisition_notification(
  204. Scratch& scratch,
  205. const queuez::ProfileItemAcquisition& acquisition,
  206. const state::PendingProfileItemAcquisition& mutation,
  207. std::optional<std::uint16_t> pendingSeasonReward,
  208. std::span<const std::byte, state::kAesKeySize> key,
  209. std::span<const std::byte, state::kBapNonceSize> nonce,
  210. std::span<std::byte> response,
  211. std::size_t& written) noexcept {
  212. snapshot::Prepared prepared{};
  213. if (!snapshot::prepare_profile_item_acquisition(
  214. scratch, acquisition, mutation, pendingSeasonReward, prepared)) {
  215. return false;
  216. }
  217. const std::size_t objectCount = prepared.family.objects.size();
  218. const std::size_t expectedObjectCount = acquisition.appendedResident ? 2U : 1U;
  219. const std::size_t accountIndex = acquisition.appendedResident ? 1U : 0U;
  220. if (prepared.family.type != queuez::kAccountFamilyType
  221. || prepared.family.rootSoid != acquisition.after.family4RootSoid
  222. || prepared.family.version != acquisition.after.family4Version || prepared.family.flags != 0
  223. || objectCount != expectedObjectCount || accountIndex >= objectCount
  224. || prepared.family.objects[accountIndex].id != acquisition.accountDefinitionId
  225. || prepared.family.objects[accountIndex].version != acquisition.accountSoid
  226. || prepared.family.objects[accountIndex].encoding != middleware::queuez::Encoding::oodle
  227. || prepared.family.objects[accountIndex].payload.empty()
  228. || (acquisition.appendedResident
  229. && (prepared.family.objects.front().id != acquisition.itemInstanceDefinitionId
  230. || prepared.family.objects.front().version != acquisition.acquiredInstanceSoid
  231. || prepared.family.objects.front().encoding != middleware::queuez::Encoding::oodle
  232. || prepared.family.objects.front().payload.empty()))
  233. || !queuez_frame::append(scratch,
  234. prepared.family,
  235. prepared.rawClearSize,
  236. prepared.compressedClearSize,
  237. key,
  238. nonce,
  239. response,
  240. written)) {
  241. return false;
  242. }
  243. return true;
  244. }
  245. /** Publishes the transient XP row and its matching account progression in one increment. */
  246. bool append_seasonal_experience_notification(
  247. Scratch& scratch,
  248. const queuez::SessionState& before,
  249. std::int32_t amount,
  250. std::int32_t mutationSerial,
  251. std::span<const queuez::AcquisitionPresentationRow> acquisitionPresentationRows,
  252. std::span<const std::byte, state::kAesKeySize> key,
  253. std::span<const std::byte, state::kBapNonceSize> nonce,
  254. std::span<std::byte> response,
  255. std::size_t& written,
  256. queuez::SessionState& after) noexcept {
  257. after = before;
  258. snapshot::Prepared prepared{};
  259. if (!snapshot::prepare_seasonal_experience_presentation(
  260. scratch, before, amount, mutationSerial, acquisitionPresentationRows, prepared)) {
  261. return false;
  262. }
  263. const std::size_t objectCount = prepared.family.objects.size();
  264. if (objectCount != 2 || prepared.family.type != queuez::kAccountFamilyType
  265. || prepared.family.rootSoid != before.family4RootSoid
  266. || prepared.family.version != before.family4Version + 1 || prepared.family.flags != 0
  267. || prepared.family.objects[0].version != before.family4RootSoid
  268. || prepared.family.objects[0].id != before.family4Residents.front().definitionId
  269. || prepared.family.objects[0].payload.empty()
  270. || prepared.family.objects[1].version == before.family4RootSoid
  271. || prepared.family.objects[1].payload.empty()
  272. || !queuez_frame::append(scratch,
  273. prepared.family,
  274. prepared.rawClearSize,
  275. prepared.compressedClearSize,
  276. key,
  277. nonce,
  278. response,
  279. written)) {
  280. return false;
  281. }
  282. after.family4Version = prepared.family.version;
  283. if (!queuez::valid(after)) {
  284. after = before;
  285. return false;
  286. }
  287. return true;
  288. }
  289. /** Publishes every item in one rank-one class package as an ordinary acquisition. */
  290. bool append_season_pass_package_notification(
  291. Scratch& scratch,
  292. const queuez::SessionState& before,
  293. const state::PendingDirectItemBundle& mutation,
  294. std::uint16_t rewardIndex,
  295. std::span<const queuez::AcquisitionPresentationRow> acquisitionPresentationRows,
  296. std::span<const std::byte, state::kAesKeySize> key,
  297. std::span<const std::byte, state::kBapNonceSize> nonce,
  298. std::span<std::byte> response,
  299. std::size_t& written,
  300. queuez::SessionState& after) noexcept {
  301. after = before;
  302. snapshot::Prepared prepared{};
  303. const std::size_t itemCount = mutation.itemCount;
  304. if (!snapshot::prepare_season_pass_package(
  305. scratch, before, mutation, rewardIndex, acquisitionPresentationRows, prepared)
  306. || itemCount == 0 || prepared.family.objects.size() != itemCount + 2U
  307. || prepared.family.type != queuez::kAccountFamilyType
  308. || prepared.family.rootSoid != before.family4RootSoid
  309. || prepared.family.version != before.family4Version + 1 || prepared.family.flags != 0
  310. || before.family4ResidentCount + itemCount > before.family4Residents.size()) {
  311. return false;
  312. }
  313. std::uint32_t itemDefinitionId = 0;
  314. if (!middleware::datagen::object_id(
  315. queuez::kAccountFamilyType, middleware::datagen::kItemInstanceSlot, itemDefinitionId)) {
  316. return false;
  317. }
  318. for (std::size_t index = 0; index < itemCount; ++index) {
  319. const middleware::queuez::Object& object = prepared.family.objects[index];
  320. if (object.id != itemDefinitionId || object.version != mutation.firstInstanceSoid + index
  321. || object.payload.empty()) {
  322. return false;
  323. }
  324. for (std::size_t residentIndex = 0; residentIndex < before.family4ResidentCount;
  325. ++residentIndex) {
  326. if (before.family4Residents[residentIndex].objectSoid == object.version) {
  327. return false;
  328. }
  329. }
  330. after.family4Residents[after.family4ResidentCount++] =
  331. queuez::ResidentObject{object.version, object.id};
  332. }
  333. const middleware::queuez::Object& characterObject = prepared.family.objects[itemCount];
  334. const middleware::queuez::Object& accountObject = prepared.family.objects[itemCount + 1U];
  335. std::size_t characterMatches = 0;
  336. for (std::size_t index = 0; index < before.family4ResidentCount; ++index) {
  337. const queuez::ResidentObject& resident = before.family4Residents[index];
  338. characterMatches +=
  339. static_cast<std::size_t>(resident.objectSoid == characterObject.version
  340. && resident.definitionId == characterObject.id);
  341. }
  342. if (characterMatches != 1 || characterObject.payload.empty()
  343. || accountObject.version != before.family4RootSoid
  344. || accountObject.id != before.family4Residents.front().definitionId
  345. || accountObject.payload.empty()) {
  346. return false;
  347. }
  348. after.family4Version = prepared.family.version;
  349. if (!queuez::valid(after)
  350. || !queuez_frame::append(scratch,
  351. prepared.family,
  352. prepared.rawClearSize,
  353. prepared.compressedClearSize,
  354. key,
  355. nonce,
  356. response,
  357. written)) {
  358. after = before;
  359. return false;
  360. }
  361. return true;
  362. }
  363. /** Publishes one prepared record-reward batch. */
  364. bool append_record_reward_notification(
  365. Scratch& scratch,
  366. const queuez::SessionState& before,
  367. const queuez::RecordRewardGrant& update,
  368. const state::PendingRecordRewardGrant& mutation,
  369. std::span<const queuez::AcquisitionPresentationRow> acquisitionPresentationRows,
  370. std::span<const std::byte, state::kAesKeySize> key,
  371. std::span<const std::byte, state::kBapNonceSize> nonce,
  372. std::span<std::byte> response,
  373. std::size_t& written) noexcept {
  374. snapshot::Prepared prepared{};
  375. if (!snapshot::prepare_record_reward_grant(
  376. scratch, before, update, mutation, acquisitionPresentationRows, prepared)
  377. || prepared.family.type != queuez::kAccountFamilyType
  378. || prepared.family.rootSoid != before.family4RootSoid
  379. || prepared.family.version != before.family4Version + 1 || prepared.family.flags != 0
  380. || prepared.family.objects.size() != update.appendedResidentCount + 2U) {
  381. return false;
  382. }
  383. for (std::size_t index = 0; index < update.appendedResidentCount; ++index) {
  384. const auto& object = prepared.family.objects[index];
  385. const auto& resident = update.after.family4Residents[before.family4ResidentCount + index];
  386. if (object.id != update.itemInstanceDefinitionId || object.version != resident.objectSoid
  387. || object.payload.empty()) {
  388. return false;
  389. }
  390. }
  391. const auto& character = prepared.family.objects[update.appendedResidentCount];
  392. const auto& account = prepared.family.objects[update.appendedResidentCount + 1U];
  393. return character.id == update.characterDefinitionId && character.version == update.characterSoid
  394. && !character.payload.empty() && account.id == update.accountDefinitionId
  395. && account.version == update.accountSoid && !account.payload.empty()
  396. && queuez_frame::append(scratch,
  397. prepared.family,
  398. prepared.rawClearSize,
  399. prepared.compressedClearSize,
  400. key,
  401. nonce,
  402. response,
  403. written);
  404. }
  405. /** Appends one atomic dismantle update, including any account-wide material payout. */
  406. bool append_item_dismantle_notification(Scratch& scratch,
  407. const queuez::ItemDismantle& dismantle,
  408. const state::PendingItemDismantle& mutation,
  409. std::span<const std::byte, state::kAesKeySize> key,
  410. std::span<const std::byte, state::kBapNonceSize> nonce,
  411. std::span<std::byte> response,
  412. std::size_t& written) noexcept {
  413. snapshot::Prepared prepared{};
  414. if (!snapshot::prepare_item_dismantle(scratch, dismantle, mutation, prepared)) {
  415. return false;
  416. }
  417. const std::size_t objectCount = prepared.family.objects.size();
  418. const std::size_t expectedObjectCount = dismantle.updatesAccount ? 3U : 2U;
  419. if (objectCount != expectedObjectCount
  420. || prepared.family.objects[0].id != dismantle.characterDefinitionId
  421. || prepared.family.objects[0].version != dismantle.characterSoid
  422. || prepared.family.objects[1].id != dismantle.itemInstanceDefinitionId
  423. || prepared.family.objects[1].version != dismantle.dismantledInstanceSoid
  424. || prepared.family.objects[1].encoding != middleware::queuez::Encoding::oodle
  425. || !prepared.family.objects[1].payload.empty()
  426. || (dismantle.updatesAccount
  427. && (prepared.family.objects[2].id != dismantle.accountDefinitionId
  428. || prepared.family.objects[2].version != dismantle.accountSoid
  429. || prepared.family.objects[2].encoding != middleware::queuez::Encoding::oodle
  430. || prepared.family.objects[2].payload.empty()))
  431. || !queuez_frame::append(scratch,
  432. prepared.family,
  433. prepared.rawClearSize,
  434. prepared.compressedClearSize,
  435. key,
  436. nonce,
  437. response,
  438. written)) {
  439. return false;
  440. }
  441. return true;
  442. }
  443. } // namespace sunrise::server::bap::encrypted::push