| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485 |
- #include <Windows.h>
- #include <algorithm>
- #include <array>
- #include <cstddef>
- #include <cstdint>
- #include <cstdio>
- #include <limits>
- #include <string_view>
- #include <utility>
- #include "../../core/logging/log.h"
- #include "../../middleware/datagen/family4/loadout/loadout_resolver.h"
- #include "../build_data/runtime.h"
- #include "runtime.h"
- #include "state.h"
- #include "state_account_transaction_helpers.h"
- #include "storage/internal.h"
- namespace sunrise::state {
- namespace runtime::detail {
- namespace authored_inventory = account::inventory;
- namespace item_details = build_data::items::details;
- namespace inventory_buckets = build_data::inventory::buckets;
- namespace family4_loadout = middleware::datagen::family4::loadout;
- /** Writes one exhaustive equipment-transaction checkpoint to the persistent diagnostic log. */
- void report_equipment(std::string_view stage,
- std::string_view result,
- EquipmentMutationKind kind,
- std::uint64_t characterSoid,
- std::uint64_t previousSoid,
- std::uint64_t requestedSoid,
- std::size_t equipmentIndex,
- std::size_t inventoryIndex,
- std::uint8_t nativeSlot,
- std::size_t movedItemCount,
- std::uint32_t previousHash,
- std::uint32_t requestedHash) noexcept {
- const std::string_view operation = kind == EquipmentMutationKind::unequip ? "unequip" : "equip";
- std::array<char, core::log::kLineCapacity> line{};
- const int count = std::snprintf(
- line.data(),
- line.size(),
- "ev=equip operation=%.*s stage=%.*s result=%.*s character=0x%llX previous=0x%llX "
- "requested=0x%llX equipment_index=%zu inventory_index=%zu native_slot=%u "
- "moved_items=%zu previous_hash=0x%08X requested_hash=0x%08X",
- static_cast<int>(operation.size()),
- operation.data(),
- static_cast<int>(stage.size()),
- stage.data(),
- static_cast<int>(result.size()),
- result.data(),
- static_cast<unsigned long long>(characterSoid),
- static_cast<unsigned long long>(previousSoid),
- static_cast<unsigned long long>(requestedSoid),
- equipmentIndex,
- inventoryIndex,
- static_cast<unsigned>(nativeSlot),
- movedItemCount,
- previousHash,
- requestedHash);
- if (count > 0) {
- core::log::write(core::log::Channel::state,
- result == "ok" ? core::log::Level::debug : core::log::Level::warn,
- {line.data(), static_cast<std::size_t>(count)});
- }
- }
- /** Writes one exhaustive item-acquisition transaction checkpoint. */
- void report_acquisition(std::string_view stage,
- std::string_view result,
- std::string_view reason,
- std::uint32_t definitionHash,
- std::uint64_t characterSoid,
- std::uint64_t instanceSoid,
- std::size_t inventoryIndex,
- std::uint16_t inventoryRow,
- std::uint8_t equipmentSlot,
- std::uint32_t nextInventorySerial) noexcept {
- std::array<char, core::log::kLineCapacity> line{};
- const int count = std::snprintf(
- line.data(),
- line.size(),
- "ev=acquire stage=%.*s result=%.*s reason=%.*s definition_hash=0x%08X character=0x%llX "
- "instance=0x%llX inventory_index=%zu inventory_row=%u equipment_slot=%u next_serial=%u",
- static_cast<int>(stage.size()),
- stage.data(),
- static_cast<int>(result.size()),
- result.data(),
- static_cast<int>(reason.size()),
- reason.data(),
- definitionHash,
- static_cast<unsigned long long>(characterSoid),
- static_cast<unsigned long long>(instanceSoid),
- inventoryIndex,
- static_cast<unsigned>(inventoryRow),
- static_cast<unsigned>(equipmentSlot),
- nextInventorySerial);
- if (count > 0) {
- core::log::write(core::log::Channel::state,
- result == "ok" ? core::log::Level::debug : core::log::Level::warn,
- {line.data(), static_cast<std::size_t>(count)});
- }
- }
- } // namespace runtime::detail
- using namespace runtime::detail;
- /** Stores the active account key without publishing an incomplete account. */
- bool set_primary_soid(std::uint64_t primarySoid) noexcept {
- if (primarySoid == 0) {
- return false;
- }
- AcquireSRWLockExclusive(&runtime::storage::g_stateLock);
- AccountState candidate = runtime::storage::g_state.account;
- candidate.primarySoid = primarySoid;
- // Characters belong to the account key the Client uses. The reference account and its
- // characters differ only in the low byte, so the authored rows are rebased onto that key.
- for (std::size_t index = 0; index < candidate.characterCount; ++index) {
- candidate.characters[index].soid = primarySoid + 1U + index;
- }
- if (!account::valid(candidate)) {
- ReleaseSRWLockExclusive(&runtime::storage::g_stateLock);
- return false;
- }
- // Publish only after the settings and identity rules hold together.
- runtime::storage::g_state.account = candidate;
- ReleaseSRWLockExclusive(&runtime::storage::g_stateLock);
- return true;
- }
- /** Moves the selection to one authored character. */
- bool set_selected_character(std::uint64_t characterSoid, bool& changed) noexcept {
- changed = false;
- if (characterSoid == 0) {
- return false;
- }
- AcquireSRWLockExclusive(&runtime::storage::g_stateLock);
- AccountState candidate = runtime::storage::g_state.account;
- std::size_t picked = candidate.characterCount;
- for (std::size_t index = 0; index < candidate.characterCount; ++index) {
- if (candidate.characters[index].soid == characterSoid) {
- picked = index;
- }
- }
- if (picked == candidate.characterCount) {
- ReleaseSRWLockExclusive(&runtime::storage::g_stateLock);
- return false;
- }
- const bool alreadySelected = candidate.characters[picked].selected;
- for (CharacterState& character : candidate.characters) {
- character.selected = false;
- }
- candidate.characters[picked].selected = true;
- if (!account::valid(candidate)) {
- ReleaseSRWLockExclusive(&runtime::storage::g_stateLock);
- return false;
- }
- // Publish only after the whole account still meets its identity rules.
- runtime::storage::g_state.account = candidate;
- ReleaseSRWLockExclusive(&runtime::storage::g_stateLock);
- changed = !alreadySelected;
- return true;
- }
- /** Prepares one checked equip transition without changing account State. */
- bool prepare_equipment_swap(std::uint64_t requestedInstanceSoid,
- PendingEquipmentSwap& mutation) noexcept {
- mutation = {};
- const AccountState account = account_snapshot();
- if (requestedInstanceSoid == 0 || !account::valid(account)) {
- return false;
- }
- std::size_t characterIndex = account.characterCount;
- for (std::size_t index = 0; index < account.characterCount; ++index) {
- if (account.characters[index].selected) {
- characterIndex = index;
- break;
- }
- }
- if (characterIndex == account.characterCount) {
- return false;
- }
- const CharacterState& before = account.characters[characterIndex];
- family4_loadout::ResolvedLoadout beforeLoadout{};
- if (!family4_loadout::resolve(account, characterIndex, beforeLoadout)) {
- return false;
- }
- std::size_t inventoryIndex = before.inventory.count;
- for (std::size_t index = 0; index < before.inventory.count; ++index) {
- if (before.inventory.values[index].instanceSoid != requestedInstanceSoid) {
- continue;
- }
- if (inventoryIndex != before.inventory.count) {
- return false;
- }
- inventoryIndex = index;
- }
- if (inventoryIndex == before.inventory.count) {
- return false;
- }
- const authored_inventory::Item& requested = before.inventory.values[inventoryIndex];
- std::uint8_t requestedNativeSlot = 0;
- std::size_t equipmentSlotIndex = authored_inventory::kEquipmentSlotCount;
- ResolvedPosition requestedPosition{};
- if (!native_equipment_slot(requested, requestedNativeSlot)
- || !semantic_equipment_slot(requestedNativeSlot, equipmentSlotIndex)
- || !find_resolved_position(beforeLoadout, requestedInstanceSoid, requestedPosition)
- || requestedPosition.equipped || requestedPosition.equipmentSlot != requestedNativeSlot) {
- return false;
- }
- CharacterState after = before;
- auto& equipped = after.equipment.slots[equipmentSlotIndex];
- std::uint64_t previousInstanceSoid = 0;
- std::uint32_t previousDefinitionHash = 0;
- if (equipped.has_value()) {
- std::uint8_t previousNativeSlot = 0;
- ResolvedPosition previousPosition{};
- if (!native_equipment_slot(*equipped, previousNativeSlot)
- || previousNativeSlot != requestedNativeSlot
- || !find_resolved_position(beforeLoadout, equipped->instanceSoid, previousPosition)
- || !previousPosition.equipped
- || previousPosition.equipmentSlot != requestedNativeSlot) {
- return false;
- }
- previousInstanceSoid = equipped->instanceSoid;
- previousDefinitionHash = equipped->definitionHash;
- std::swap(*equipped, after.inventory.values[inventoryIndex]);
- } else {
- equipped = after.inventory.values[inventoryIndex];
- for (std::size_t index = inventoryIndex; index + 1U < after.inventory.count; ++index) {
- after.inventory.values[index] = after.inventory.values[index + 1U];
- }
- --after.inventory.count;
- after.inventory.values[after.inventory.count] = {};
- }
- std::size_t movedItemCount = 0;
- if (!finalize_equipment_transition(account,
- characterIndex,
- requestedInstanceSoid,
- EquipmentMutationKind::equip,
- requestedNativeSlot,
- beforeLoadout,
- after,
- movedItemCount)) {
- return false;
- }
- mutation.beforeCharacter = before;
- mutation.afterCharacter = after;
- mutation.characterSoid = before.soid;
- mutation.requestedInstanceSoid = requestedInstanceSoid;
- mutation.previousInstanceSoid = previousInstanceSoid;
- mutation.characterIndex = characterIndex;
- mutation.equipmentSlotIndex = equipmentSlotIndex;
- mutation.inventoryIndex = inventoryIndex;
- mutation.movedItemCount = movedItemCount;
- mutation.nativeEquipmentSlot = requestedNativeSlot;
- mutation.kind = EquipmentMutationKind::equip;
- mutation.prepared = true;
- report_equipment("prepare",
- "ok",
- mutation.kind,
- mutation.characterSoid,
- mutation.previousInstanceSoid,
- mutation.requestedInstanceSoid,
- mutation.equipmentSlotIndex,
- mutation.inventoryIndex,
- mutation.nativeEquipmentSlot,
- mutation.movedItemCount,
- previousDefinitionHash,
- requested.definitionHash);
- return true;
- }
- /** Prepares one checked equipped-to-inventory transition without changing account State. */
- bool prepare_equipment_unequip(std::uint64_t requestedInstanceSoid,
- PendingEquipmentSwap& mutation) noexcept {
- mutation = {};
- const AccountState account = account_snapshot();
- if (requestedInstanceSoid == 0 || !account::valid(account)) {
- return false;
- }
- std::size_t characterIndex = account.characterCount;
- for (std::size_t index = 0; index < account.characterCount; ++index) {
- if (account.characters[index].selected) {
- characterIndex = index;
- break;
- }
- }
- if (characterIndex == account.characterCount) {
- return false;
- }
- const CharacterState& before = account.characters[characterIndex];
- if (before.inventory.count >= before.inventory.values.size()) {
- return false;
- }
- family4_loadout::ResolvedLoadout beforeLoadout{};
- if (!family4_loadout::resolve(account, characterIndex, beforeLoadout)) {
- return false;
- }
- std::size_t equipmentSlotIndex = before.equipment.slots.size();
- for (std::size_t index = 0; index < before.equipment.slots.size(); ++index) {
- const auto& item = before.equipment.slots[index];
- if (!item.has_value() || item->instanceSoid != requestedInstanceSoid) {
- continue;
- }
- if (equipmentSlotIndex != before.equipment.slots.size()) {
- return false;
- }
- equipmentSlotIndex = index;
- }
- if (equipmentSlotIndex == before.equipment.slots.size()) {
- return false;
- }
- const authored_inventory::Item& requested = *before.equipment.slots[equipmentSlotIndex];
- std::uint8_t requestedNativeSlot = 0;
- std::uint8_t requestedBucketId = 0;
- std::size_t expectedSemanticIndex = authored_inventory::kEquipmentSlotCount;
- ResolvedPosition requestedPosition{};
- if (!native_equipment_slot(requested, requestedNativeSlot)
- || !inventory_bucket_id(requested, requestedBucketId)
- || !semantic_equipment_slot(requestedNativeSlot, expectedSemanticIndex)
- || expectedSemanticIndex != equipmentSlotIndex
- || !find_resolved_position(beforeLoadout, requestedInstanceSoid, requestedPosition)
- || !requestedPosition.equipped || requestedPosition.equipmentSlot != requestedNativeSlot) {
- return false;
- }
- std::size_t inventoryIndex = before.inventory.count;
- for (std::size_t index = 0; index < before.inventory.count; ++index) {
- std::uint8_t inventoryBucketId = 0;
- if (!inventory_bucket_id(before.inventory.values[index], inventoryBucketId)) {
- return false;
- }
- if (inventoryBucketId == requestedBucketId) {
- inventoryIndex = index;
- break;
- }
- }
- CharacterState after = before;
- const authored_inventory::Item unequipped = *after.equipment.slots[equipmentSlotIndex];
- for (std::size_t index = after.inventory.count; index > inventoryIndex; --index) {
- after.inventory.values[index] = after.inventory.values[index - 1U];
- }
- after.inventory.values[inventoryIndex] = unequipped;
- ++after.inventory.count;
- after.equipment.slots[equipmentSlotIndex].reset();
- std::size_t movedItemCount = 0;
- if (!finalize_equipment_transition(account,
- characterIndex,
- requestedInstanceSoid,
- EquipmentMutationKind::unequip,
- requestedNativeSlot,
- beforeLoadout,
- after,
- movedItemCount)) {
- return false;
- }
- mutation.beforeCharacter = before;
- mutation.afterCharacter = after;
- mutation.characterSoid = before.soid;
- mutation.requestedInstanceSoid = requestedInstanceSoid;
- mutation.characterIndex = characterIndex;
- mutation.equipmentSlotIndex = equipmentSlotIndex;
- mutation.inventoryIndex = inventoryIndex;
- mutation.movedItemCount = movedItemCount;
- mutation.nativeEquipmentSlot = requestedNativeSlot;
- mutation.kind = EquipmentMutationKind::unequip;
- mutation.prepared = true;
- report_equipment("prepare",
- "ok",
- mutation.kind,
- mutation.characterSoid,
- mutation.previousInstanceSoid,
- mutation.requestedInstanceSoid,
- mutation.equipmentSlotIndex,
- mutation.inventoryIndex,
- mutation.nativeEquipmentSlot,
- mutation.movedItemCount,
- 0,
- requested.definitionHash);
- return true;
- }
- /** Commits one prepared equipment after-image behind an exact character staleness guard. */
- bool commit_equipment_swap(PendingEquipmentSwap& mutation) noexcept {
- const PendingEquipmentSwap prepared = mutation;
- mutation = {};
- if (!prepared.prepared || prepared.characterSoid == 0 || prepared.requestedInstanceSoid == 0
- || (prepared.kind != EquipmentMutationKind::equip
- && prepared.kind != EquipmentMutationKind::unequip)
- || prepared.characterIndex >= kCharacterCapacity
- || prepared.equipmentSlotIndex >= authored_inventory::kEquipmentSlotCount
- || prepared.inventoryIndex >= authored_inventory::kCharacterItemCapacity
- || prepared.nativeEquipmentSlot >= item_details::kEquipmentSlotCount
- || prepared.beforeCharacter.soid != prepared.characterSoid
- || prepared.afterCharacter.soid != prepared.characterSoid) {
- return false;
- }
- const std::uint32_t previousDefinitionHash =
- character_item_definition_hash(prepared.afterCharacter, prepared.previousInstanceSoid);
- const std::uint32_t requestedDefinitionHash =
- character_item_definition_hash(prepared.afterCharacter, prepared.requestedInstanceSoid);
- report_equipment("commit_begin",
- "ok",
- prepared.kind,
- prepared.characterSoid,
- prepared.previousInstanceSoid,
- prepared.requestedInstanceSoid,
- prepared.equipmentSlotIndex,
- prepared.inventoryIndex,
- prepared.nativeEquipmentSlot,
- prepared.movedItemCount,
- previousDefinitionHash,
- requestedDefinitionHash);
- AcquireSRWLockExclusive(&runtime::storage::g_stateLock);
- AccountState candidate = runtime::storage::g_state.account;
- if (prepared.characterIndex >= candidate.characterCount
- || !same_character(candidate.characters[prepared.characterIndex],
- prepared.beforeCharacter)) {
- ReleaseSRWLockExclusive(&runtime::storage::g_stateLock);
- return false;
- }
- candidate.characters[prepared.characterIndex] = prepared.afterCharacter;
- family4_loadout::ResolvedLoadout checkedAfter{};
- if (!account::valid(candidate)
- || !family4_loadout::resolve(candidate, prepared.characterIndex, checkedAfter)) {
- ReleaseSRWLockExclusive(&runtime::storage::g_stateLock);
- return false;
- }
- ResolvedPosition requestedPosition{};
- const bool expectedEquipped = prepared.kind == EquipmentMutationKind::equip;
- if (!find_resolved_position(checkedAfter, prepared.requestedInstanceSoid, requestedPosition)
- || requestedPosition.equipmentSlot != prepared.nativeEquipmentSlot
- || requestedPosition.equipped != expectedEquipped) {
- ReleaseSRWLockExclusive(&runtime::storage::g_stateLock);
- return false;
- }
- runtime::storage::g_state.account = candidate;
- ReleaseSRWLockExclusive(&runtime::storage::g_stateLock);
- report_equipment("commit_end",
- "ok",
- prepared.kind,
- prepared.characterSoid,
- prepared.previousInstanceSoid,
- prepared.requestedInstanceSoid,
- prepared.equipmentSlotIndex,
- prepared.inventoryIndex,
- prepared.nativeEquipmentSlot,
- prepared.movedItemCount,
- previousDefinitionHash,
- requestedDefinitionHash);
- return true;
- }
- /** @return A copy of the active account state, read under the lock. */
- AccountState account_snapshot() noexcept {
- AcquireSRWLockShared(&runtime::storage::g_stateLock);
- const AccountState snapshot = runtime::storage::g_state.account;
- ReleaseSRWLockShared(&runtime::storage::g_stateLock);
- return snapshot;
- }
- } // namespace sunrise::state
|