|
@@ -50,6 +50,118 @@ template <std::size_t Size>
|
|
|
>= 0;
|
|
>= 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/** @return True when any authored or already-seeded account identity owns one SOID. */
|
|
|
|
|
+[[nodiscard]] bool identity_uses_soid(const AccountState& accountState,
|
|
|
|
|
+ std::uint64_t soid) noexcept {
|
|
|
|
|
+ if (soid == 0 || accountState.primarySoid == soid) {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ for (std::size_t index = 0; index < accountState.profileItemCount; ++index) {
|
|
|
|
|
+ if (accountState.profileItems[index].instanceSoid == soid) {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ for (std::size_t characterIndex = 0; characterIndex < accountState.characterCount;
|
|
|
|
|
+ ++characterIndex) {
|
|
|
|
|
+ const CharacterState& character = accountState.characters[characterIndex];
|
|
|
|
|
+ if (character.soid == soid) {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ for (const std::optional<account::inventory::Item>& item : character.equipment.slots) {
|
|
|
|
|
+ if (item.has_value() && item->instanceSoid == soid) {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ for (std::size_t index = 0; index < character.inventory.count; ++index) {
|
|
|
|
|
+ if (character.inventory.values[index].instanceSoid == soid) {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return false;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** Seeds canonical character row generations before installed build data is needed. */
|
|
|
|
|
+[[nodiscard]] bool seed_inventory_runtime_fields(AccountState& accountState) noexcept {
|
|
|
|
|
+ if (!account::valid_authored(accountState)) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ for (std::size_t characterIndex = 0; characterIndex < accountState.characterCount;
|
|
|
|
|
+ ++characterIndex) {
|
|
|
|
|
+ CharacterState& character = accountState.characters[characterIndex];
|
|
|
|
|
+ std::uint32_t next = 0;
|
|
|
|
|
+ for (std::optional<account::inventory::Item>& item : character.equipment.slots) {
|
|
|
|
|
+ if (item.has_value()) {
|
|
|
|
|
+ item->mutationSerial = static_cast<std::int32_t>(next++);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ for (std::size_t index = 0; index < character.inventory.count; ++index) {
|
|
|
|
|
+ character.inventory.values[index].mutationSerial = static_cast<std::int32_t>(next++);
|
|
|
|
|
+ }
|
|
|
|
|
+ character.nextInventorySerial = next;
|
|
|
|
|
+ }
|
|
|
|
|
+ return account::valid(accountState);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** Canonicalizes only profile rows which the installed socket UI materializes as action sources. */
|
|
|
|
|
+[[nodiscard]] bool canonicalize_profile_item_identities(AccountState& accountState) noexcept {
|
|
|
|
|
+ if (!account::valid(accountState) || !build_data::socket_plug_rules_ready()) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ std::array<bool, account::inventory::kProfileItemCapacity> actionSources{};
|
|
|
|
|
+ std::size_t actionSourceCount = 0;
|
|
|
|
|
+ for (std::size_t index = 0; index < accountState.profileItemCount; ++index) {
|
|
|
|
|
+ const account::inventory::ProfileItem& profileItem = accountState.profileItems[index];
|
|
|
|
|
+ build_data::items::Definition item{};
|
|
|
|
|
+ build_data::items::details::Definition detail{};
|
|
|
|
|
+ build_data::inventory::buckets::Descriptor bucket{};
|
|
|
|
|
+ if (!build_data::find_item_definition_hash(profileItem.definitionHash, item)
|
|
|
|
|
+ || item.definitionHash != profileItem.definitionHash
|
|
|
|
|
+ || !build_data::find_configured_item_detail(item.definitionIndex, detail)
|
|
|
|
|
+ || detail.definitionIndex != item.definitionIndex
|
|
|
|
|
+ || detail.definitionHash != item.definitionHash || detail.bucketId != item.bucketId
|
|
|
|
|
+ || detail.instancedDefinitionState
|
|
|
|
|
+ != build_data::items::details::InstancedDefinitionState::stackable
|
|
|
|
|
+ || !build_data::find_inventory_bucket_descriptor(item.bucketId, bucket)
|
|
|
|
|
+ || bucket.arraySelector != build_data::inventory::buckets::ArraySelector::profile) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ actionSources[index] =
|
|
|
|
|
+ build_data::is_profile_action_source(item.definitionIndex, item.bucketId);
|
|
|
|
|
+ if (actionSources[index]
|
|
|
|
|
+ && ++actionSourceCount > account::inventory::kProfileActionSourceCapacity) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Currency, material, and consumable rows are native non-instanced stacks. Clear any stale
|
|
|
|
|
+ // runtime key before allocating action-source identities so it cannot reserve the namespace.
|
|
|
|
|
+ for (std::size_t index = 0; index < accountState.profileItemCount; ++index) {
|
|
|
|
|
+ if (!actionSources[index]) {
|
|
|
|
|
+ accountState.profileItems[index].instanceSoid = 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ std::uint64_t nextProfileSoid = account::inventory::kFirstProfileItemInstanceSoid;
|
|
|
|
|
+ for (std::size_t index = 0; index < accountState.profileItemCount; ++index) {
|
|
|
|
|
+ account::inventory::ProfileItem& item = accountState.profileItems[index];
|
|
|
|
|
+ if (!actionSources[index] || item.instanceSoid != 0) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ while (identity_uses_soid(accountState, nextProfileSoid)) {
|
|
|
|
|
+ if (nextProfileSoid == (std::numeric_limits<std::uint64_t>::max)()) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ ++nextProfileSoid;
|
|
|
|
|
+ }
|
|
|
|
|
+ item.instanceSoid = nextProfileSoid;
|
|
|
|
|
+ if (nextProfileSoid != (std::numeric_limits<std::uint64_t>::max)()) {
|
|
|
|
|
+ ++nextProfileSoid;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return account::valid(accountState);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
} // namespace
|
|
} // namespace
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -72,10 +184,20 @@ bool initialize(void* module, const AccountState& initialAccount) noexcept {
|
|
|
bool initialize(void* module,
|
|
bool initialize(void* module,
|
|
|
const AccountState& initialAccount,
|
|
const AccountState& initialAccount,
|
|
|
const activity::defaults::ActivityDefaults& activityDefaults) noexcept {
|
|
const activity::defaults::ActivityDefaults& activityDefaults) noexcept {
|
|
|
- if (!account::valid(initialAccount) || !activity::defaults::valid(activityDefaults)) {
|
|
|
|
|
|
|
+ AccountState runtimeAccount = initialAccount;
|
|
|
|
|
+ if (!seed_inventory_runtime_fields(runtimeAccount)
|
|
|
|
|
+ || !activity::defaults::valid(activityDefaults)) {
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
- if (!build_data::initialize(module, runtime::equipment::configured_hash(initialAccount))) {
|
|
|
|
|
|
|
+ if (!build_data::initialize(module, runtime::equipment::configured_hash(runtimeAccount))) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ // A cache hit already has the complete plug relation, so publish canonical profile identities
|
|
|
|
|
+ // in the first State image. On a first cache build, snapshot preparation repeats this step
|
|
|
|
|
+ // after package extraction has published the relation.
|
|
|
|
|
+ if (build_data::socket_plug_rules_ready()
|
|
|
|
|
+ && !canonicalize_profile_item_identities(runtimeAccount)) {
|
|
|
|
|
+ build_data::shutdown();
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
{
|
|
{
|
|
@@ -85,8 +207,8 @@ bool initialize(void* module,
|
|
|
std::snprintf(line.data(),
|
|
std::snprintf(line.data(),
|
|
|
line.size(),
|
|
line.size(),
|
|
|
"ev=account stage=identity primary=0x%016llX characters=%zu",
|
|
"ev=account stage=identity primary=0x%016llX characters=%zu",
|
|
|
- static_cast<unsigned long long>(initialAccount.primarySoid),
|
|
|
|
|
- initialAccount.characterCount);
|
|
|
|
|
|
|
+ static_cast<unsigned long long>(runtimeAccount.primarySoid),
|
|
|
|
|
+ runtimeAccount.characterCount);
|
|
|
if (written > 0) {
|
|
if (written > 0) {
|
|
|
core::log::write(core::log::Channel::state,
|
|
core::log::write(core::log::Channel::state,
|
|
|
core::log::Level::info,
|
|
core::log::Level::info,
|
|
@@ -106,7 +228,7 @@ bool initialize(void* module,
|
|
|
// The published relay port is the one the listener binds, so both move with one setting.
|
|
// The published relay port is the one the listener binds, so both move with one setting.
|
|
|
initialized.signOn.relayPort = core::settings::get().server.bapPort;
|
|
initialized.signOn.relayPort = core::settings::get().server.bapPort;
|
|
|
initialized.signOn.tokenLifetimeSeconds = kDefaultTokenLifetimeSeconds;
|
|
initialized.signOn.tokenLifetimeSeconds = kDefaultTokenLifetimeSeconds;
|
|
|
- initialized.account = initialAccount;
|
|
|
|
|
|
|
+ initialized.account = runtimeAccount;
|
|
|
initialized.activity.defaults = activityDefaults;
|
|
initialized.activity.defaults = activityDefaults;
|
|
|
initialized.investment.family5.objectSoid = kGlobalFamily5Soid;
|
|
initialized.investment.family5.objectSoid = kGlobalFamily5Soid;
|
|
|
// Only the override lists come from settings. Identity and gate stay owned by State.
|
|
// Only the override lists come from settings. Identity and gate stay owned by State.
|
|
@@ -118,8 +240,8 @@ bool initialize(void* module,
|
|
|
// The arm is account-wide and rides the first ws-503, which goes out before any pick. Nothing
|
|
// The arm is account-wide and rides the first ws-503, which goes out before any pick. Nothing
|
|
|
// is selected at boot, so it is armed when any authored character carries the bypass. The
|
|
// is selected at boot, so it is armed when any authored character carries the bypass. The
|
|
|
// per-character objB byte is the other half, and it still decides which character it opens.
|
|
// per-character objB byte is the other half, and it still decides which character it opens.
|
|
|
- for (std::size_t index = 0; index < initialAccount.characterCount; ++index) {
|
|
|
|
|
- if (initialAccount.characters[index].contentBypass) {
|
|
|
|
|
|
|
+ for (std::size_t index = 0; index < runtimeAccount.characterCount; ++index) {
|
|
|
|
|
+ if (runtimeAccount.characters[index].contentBypass) {
|
|
|
initialized.investment.family5.contentGateArm = true;
|
|
initialized.investment.family5.contentGateArm = true;
|
|
|
break;
|
|
break;
|
|
|
}
|
|
}
|
|
@@ -146,6 +268,18 @@ const SignOnState& sign_on() noexcept {
|
|
|
return runtime::storage::g_state.signOn;
|
|
return runtime::storage::g_state.signOn;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/** Ensures every native profile action source has one unique runtime item-instance key. */
|
|
|
|
|
+bool ensure_profile_item_identities() noexcept {
|
|
|
|
|
+ AcquireSRWLockExclusive(&runtime::storage::g_stateLock);
|
|
|
|
|
+ AccountState candidate = runtime::storage::g_state.account;
|
|
|
|
|
+ const bool ready = canonicalize_profile_item_identities(candidate);
|
|
|
|
|
+ if (ready) {
|
|
|
|
|
+ runtime::storage::g_state.account = candidate;
|
|
|
|
|
+ }
|
|
|
|
|
+ ReleaseSRWLockExclusive(&runtime::storage::g_stateLock);
|
|
|
|
|
+ return ready;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* Publishes the bootstrap content-id token read from the installed client.
|
|
* Publishes the bootstrap content-id token read from the installed client.
|
|
|
* @param token Exactly 16 native bytes.
|
|
* @param token Exactly 16 native bytes.
|