|
@@ -3,8 +3,10 @@
|
|
|
#include <span>
|
|
#include <span>
|
|
|
|
|
|
|
|
#include "../../../../../core/logging/log.h"
|
|
#include "../../../../../core/logging/log.h"
|
|
|
|
|
+#include "../../../../../middleware/datagen/definitions.h"
|
|
|
#include "../../../../../middleware/secure_channel/runtime.h"
|
|
#include "../../../../../middleware/secure_channel/runtime.h"
|
|
|
#include "../../../../../state/account/account_state.h"
|
|
#include "../../../../../state/account/account_state.h"
|
|
|
|
|
+#include "../../../../../state/build_data/runtime.h"
|
|
|
#include "../../../../../state/runtime/runtime.h"
|
|
#include "../../../../../state/runtime/runtime.h"
|
|
|
#include "../../queuez/queuez_state_validation.h"
|
|
#include "../../queuez/queuez_state_validation.h"
|
|
|
#include "../snapshot/snapshot.h"
|
|
#include "../snapshot/snapshot.h"
|
|
@@ -34,6 +36,95 @@ void report_skip(const char* reason) noexcept {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/** Validates and appends one same-character Family-0 appearance refresh. */
|
|
|
|
|
+[[nodiscard]] bool append_appearance_frame(Scratch& scratch,
|
|
|
|
|
+ const queuez::CharacterAppearanceRefresh& refresh,
|
|
|
|
|
+ snapshot::Prepared& prepared,
|
|
|
|
|
+ const char* stage,
|
|
|
|
|
+ std::span<const std::byte, state::kAesKeySize> key,
|
|
|
|
|
+ std::array<std::byte, state::kBapNonceSize>& nonce,
|
|
|
|
|
+ std::span<std::byte> response,
|
|
|
|
|
+ std::size_t& written) noexcept {
|
|
|
|
|
+ const std::size_t objectCount = prepared.family.objects.size();
|
|
|
|
|
+ const std::size_t beforeBytes = written;
|
|
|
|
|
+ const bool replacement =
|
|
|
|
|
+ objectCount >= 2
|
|
|
|
|
+ && prepared.family.objects.front().id == middleware::datagen::kBannerCharacterObjectId
|
|
|
|
|
+ && prepared.family.objects.front().version == refresh.characterSoid
|
|
|
|
|
+ && prepared.family.objects.front().payload.empty();
|
|
|
|
|
+ const std::size_t characterIndex = replacement ? 1U : 0U;
|
|
|
|
|
+ const bool hasAnchor = objectCount == characterIndex + 2U;
|
|
|
|
|
+ if ((objectCount != characterIndex + 1U && !hasAnchor)
|
|
|
|
|
+ || prepared.family.type != queuez::kBannerFamilyType
|
|
|
|
|
+ || prepared.family.rootSoid != refresh.after.family4RootSoid
|
|
|
|
|
+ || prepared.family.version != refresh.after.family0Version || prepared.family.flags != 0
|
|
|
|
|
+ || (replacement
|
|
|
|
|
+ && prepared.family.objects.front().encoding != middleware::queuez::Encoding::raw)
|
|
|
|
|
+ || prepared.family.objects[characterIndex].id
|
|
|
|
|
+ != middleware::datagen::kBannerCharacterObjectId
|
|
|
|
|
+ || prepared.family.objects[characterIndex].version != refresh.characterSoid
|
|
|
|
|
+ || prepared.family.objects[characterIndex].encoding != middleware::queuez::Encoding::oodle
|
|
|
|
|
+ || prepared.family.objects[characterIndex].payload.empty()
|
|
|
|
|
+ || (hasAnchor
|
|
|
|
|
+ && (prepared.family.objects.back().id != middleware::datagen::kBannerAnchorObjectId
|
|
|
|
|
+ || prepared.family.objects.back().version != refresh.after.family4RootSoid
|
|
|
|
|
+ || prepared.family.objects.back().encoding != middleware::queuez::Encoding::oodle
|
|
|
|
|
+ || prepared.family.objects.back().payload.empty()))
|
|
|
|
|
+ || !queuez_frame::append(scratch,
|
|
|
|
|
+ prepared.family,
|
|
|
|
|
+ prepared.rawClearSize,
|
|
|
|
|
+ prepared.compressedClearSize,
|
|
|
|
|
+ key,
|
|
|
|
|
+ nonce,
|
|
|
|
|
+ response,
|
|
|
|
|
+ written)) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ middleware::secure_channel::advance_nonce(nonce);
|
|
|
|
|
+ queuez_report::push(stage, queuez::kBannerFamilyType, objectCount, written - beforeBytes, 1);
|
|
|
|
|
+ return true;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** Validates and appends one incremental Family-3 appearance frame. */
|
|
|
|
|
+[[nodiscard]] bool
|
|
|
|
|
+append_roster_appearance_frame(Scratch& scratch,
|
|
|
|
|
+ const queuez::RosterAppearanceRefresh& refresh,
|
|
|
|
|
+ snapshot::Prepared& prepared,
|
|
|
|
|
+ const char* stage,
|
|
|
|
|
+ std::span<const std::byte, state::kAesKeySize> key,
|
|
|
|
|
+ std::array<std::byte, state::kBapNonceSize>& nonce,
|
|
|
|
|
+ std::span<std::byte> response,
|
|
|
|
|
+ std::size_t& written) noexcept {
|
|
|
|
|
+ const std::size_t expectedObjects = refresh.includeRoster ? 2U : 1U;
|
|
|
|
|
+ const std::size_t objectCount = prepared.family.objects.size();
|
|
|
|
|
+ const std::size_t beforeBytes = written;
|
|
|
|
|
+ if (objectCount != expectedObjects || prepared.family.type != queuez::kRosterFamilyType
|
|
|
|
|
+ || prepared.family.rootSoid != refresh.after.family3RootSoid
|
|
|
|
|
+ || prepared.family.version != refresh.after.family3Version || prepared.family.flags != 0
|
|
|
|
|
+ || prepared.family.objects.front().id != middleware::datagen::kRosterCharacterObjectId
|
|
|
|
|
+ || prepared.family.objects.front().version != refresh.characterSoid
|
|
|
|
|
+ || prepared.family.objects.front().encoding != middleware::queuez::Encoding::oodle
|
|
|
|
|
+ || prepared.family.objects.front().payload.empty()
|
|
|
|
|
+ || (refresh.includeRoster
|
|
|
|
|
+ && (prepared.family.objects.back().id != middleware::datagen::kRosterObjectId
|
|
|
|
|
+ || prepared.family.objects.back().version != refresh.after.family3RootSoid
|
|
|
|
|
+ || prepared.family.objects.back().encoding != middleware::queuez::Encoding::oodle
|
|
|
|
|
+ || prepared.family.objects.back().payload.empty()))
|
|
|
|
|
+ || !queuez_frame::append(scratch,
|
|
|
|
|
+ prepared.family,
|
|
|
|
|
+ prepared.rawClearSize,
|
|
|
|
|
+ prepared.compressedClearSize,
|
|
|
|
|
+ key,
|
|
|
|
|
+ nonce,
|
|
|
|
|
+ response,
|
|
|
|
|
+ written)) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ middleware::secure_channel::advance_nonce(nonce);
|
|
|
|
|
+ queuez_report::push(stage, queuez::kRosterFamilyType, objectCount, written - beforeBytes, 1);
|
|
|
|
|
+ return true;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
} // namespace
|
|
} // namespace
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -187,4 +278,122 @@ bool append_banner_move_notification(Scratch& scratch,
|
|
|
return true;
|
|
return true;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/** Appends one same-character Family-0 appearance-record upsert after an equipment swap. */
|
|
|
|
|
+bool append_equipment_appearance_refresh_notification(
|
|
|
|
|
+ Scratch& scratch,
|
|
|
|
|
+ const queuez::CharacterAppearanceRefresh& refresh,
|
|
|
|
|
+ const state::PendingEquipmentSwap& mutation,
|
|
|
|
|
+ std::span<const std::byte, state::kAesKeySize> key,
|
|
|
|
|
+ std::array<std::byte, state::kBapNonceSize>& nonce,
|
|
|
|
|
+ std::span<std::byte> response,
|
|
|
|
|
+ std::size_t& written) noexcept {
|
|
|
|
|
+ if (!mutation.prepared || mutation.characterSoid != refresh.characterSoid) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ snapshot::Prepared prepared{};
|
|
|
|
|
+ if (!snapshot::prepare_character_appearance_refresh(scratch,
|
|
|
|
|
+ refresh,
|
|
|
|
|
+ mutation.afterCharacter,
|
|
|
|
|
+ mutation.characterIndex,
|
|
|
|
|
+ mutation.nativeEquipmentSlot,
|
|
|
|
|
+ false,
|
|
|
|
|
+ prepared)) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ return append_appearance_frame(
|
|
|
|
|
+ scratch, refresh, prepared, "equip_appearance", key, nonce, response, written);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** Appends one Family-0 record upsert after a socket change on an equipped item. */
|
|
|
|
|
+bool append_socket_appearance_refresh_notification(
|
|
|
|
|
+ Scratch& scratch,
|
|
|
|
|
+ const queuez::CharacterAppearanceRefresh& refresh,
|
|
|
|
|
+ const state::PendingSocketPlug& mutation,
|
|
|
|
|
+ std::span<const std::byte, state::kAesKeySize> key,
|
|
|
|
|
+ std::array<std::byte, state::kBapNonceSize>& nonce,
|
|
|
|
|
+ std::span<std::byte> response,
|
|
|
|
|
+ std::size_t& written) noexcept {
|
|
|
|
|
+ if (!mutation.prepared || !mutation.targetEquipped
|
|
|
|
|
+ || mutation.characterSoid != refresh.characterSoid
|
|
|
|
|
+ || mutation.itemIndex >= mutation.afterCharacter.equipment.slots.size()
|
|
|
|
|
+ || !mutation.afterCharacter.equipment.slots[mutation.itemIndex].has_value()) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ const state::account::inventory::Item& target =
|
|
|
|
|
+ *mutation.afterCharacter.equipment.slots[mutation.itemIndex];
|
|
|
|
|
+ if (target.instanceSoid != mutation.targetInstanceSoid) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ state::build_data::items::details::Definition detail{};
|
|
|
|
|
+ if (!state::build_data::find_configured_item_detail(mutation.targetDefinitionIndex, detail)
|
|
|
|
|
+ || detail.definitionIndex != mutation.targetDefinitionIndex
|
|
|
|
|
+ || detail.definitionHash != mutation.targetDefinitionHash
|
|
|
|
|
+ || detail.bucketId != mutation.targetBucketId || !detail.equipmentSlot.has_value()
|
|
|
|
|
+ || *detail.equipmentSlot < 0
|
|
|
|
|
+ || static_cast<std::size_t>(*detail.equipmentSlot)
|
|
|
|
|
+ >= state::build_data::items::details::kEquipmentSlotCount) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ snapshot::Prepared prepared{};
|
|
|
|
|
+ if (!snapshot::prepare_character_appearance_refresh(
|
|
|
|
|
+ scratch,
|
|
|
|
|
+ refresh,
|
|
|
|
|
+ mutation.afterCharacter,
|
|
|
|
|
+ mutation.characterIndex,
|
|
|
|
|
+ static_cast<std::uint8_t>(*detail.equipmentSlot),
|
|
|
|
|
+ true,
|
|
|
|
|
+ prepared)) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ return append_appearance_frame(
|
|
|
|
|
+ scratch, refresh, prepared, "socket_appearance", key, nonce, response, written);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** Appends the Family-3 character-then-roster refresh owed by one equipment mutation. */
|
|
|
|
|
+bool append_equipment_roster_refresh_notification(
|
|
|
|
|
+ Scratch& scratch,
|
|
|
|
|
+ const queuez::RosterAppearanceRefresh& refresh,
|
|
|
|
|
+ const state::PendingEquipmentSwap& mutation,
|
|
|
|
|
+ std::span<const std::byte, state::kAesKeySize> key,
|
|
|
|
|
+ std::array<std::byte, state::kBapNonceSize>& nonce,
|
|
|
|
|
+ std::span<std::byte> response,
|
|
|
|
|
+ std::size_t& written) noexcept {
|
|
|
|
|
+ if (!mutation.prepared || !refresh.includeRoster
|
|
|
|
|
+ || mutation.characterSoid != refresh.characterSoid) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ snapshot::Prepared prepared{};
|
|
|
|
|
+ if (!snapshot::prepare_roster_appearance_refresh(
|
|
|
|
|
+ scratch, refresh, mutation.afterCharacter, mutation.characterIndex, prepared)) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ return append_roster_appearance_frame(
|
|
|
|
|
+ scratch, refresh, prepared, "equip_roster", key, nonce, response, written);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** Appends the Family-3 character-only refresh owed by a socket change on equipped gear. */
|
|
|
|
|
+bool append_socket_roster_refresh_notification(Scratch& scratch,
|
|
|
|
|
+ const queuez::RosterAppearanceRefresh& refresh,
|
|
|
|
|
+ const state::PendingSocketPlug& mutation,
|
|
|
|
|
+ std::span<const std::byte, state::kAesKeySize> key,
|
|
|
|
|
+ std::array<std::byte, state::kBapNonceSize>& nonce,
|
|
|
|
|
+ std::span<std::byte> response,
|
|
|
|
|
+ std::size_t& written) noexcept {
|
|
|
|
|
+ if (!mutation.prepared || !mutation.targetEquipped || refresh.includeRoster
|
|
|
|
|
+ || mutation.characterSoid != refresh.characterSoid
|
|
|
|
|
+ || mutation.itemIndex >= mutation.afterCharacter.equipment.slots.size()
|
|
|
|
|
+ || !mutation.afterCharacter.equipment.slots[mutation.itemIndex].has_value()
|
|
|
|
|
+ || mutation.afterCharacter.equipment.slots[mutation.itemIndex]->instanceSoid
|
|
|
|
|
+ != mutation.targetInstanceSoid) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ snapshot::Prepared prepared{};
|
|
|
|
|
+ if (!snapshot::prepare_roster_appearance_refresh(
|
|
|
|
|
+ scratch, refresh, mutation.afterCharacter, mutation.characterIndex, prepared)) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ return append_roster_appearance_frame(
|
|
|
|
|
+ scratch, refresh, prepared, "socket_roster", key, nonce, response, written);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
} // namespace sunrise::server::bap::encrypted::push
|
|
} // namespace sunrise::server::bap::encrypted::push
|