state_runtime.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. #include <Windows.h>
  2. #include <algorithm>
  3. #include <array>
  4. #include <bcrypt.h>
  5. #include <cstddef>
  6. #include <cstdint>
  7. #include <cstdio>
  8. #include <limits>
  9. #include <span>
  10. #include "../../core/logging/log.h"
  11. #include "../../core/settings/settings.h"
  12. #include "../activity/defaults/activity_defaults_validation.h"
  13. #include "../build_data/runtime.h"
  14. #include "equipment/configured_equipment_identity.h"
  15. #include "runtime.h"
  16. #include "state.h"
  17. #include "storage/internal.h"
  18. namespace sunrise::state {
  19. namespace runtime::storage {
  20. State g_state;
  21. SRWLOCK g_stateLock{SRWLOCK_INIT};
  22. } // namespace runtime::storage
  23. namespace {
  24. /** Network-order IPv4 loopback returned by the in-process SignOn route. */
  25. constexpr std::uint32_t kLoopbackAddress = 0x7F000001;
  26. /** Default one-hour lifetime for generated SignOn session tokens. */
  27. constexpr std::uint32_t kDefaultTokenLifetimeSeconds = 3600;
  28. /** Family 5 uses the largest signed 64-bit value as its process-global object key. */
  29. constexpr std::uint64_t kGlobalFamily5Soid =
  30. static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)());
  31. /**
  32. * Fills fixed secret storage with Windows system randomness.
  33. * @tparam Size Required secret byte count.
  34. * @param output Secret storage to overwrite.
  35. * @return True when Windows generates every byte.
  36. */
  37. template <std::size_t Size>
  38. [[nodiscard]] bool randomize(std::array<std::byte, Size>& output) noexcept {
  39. return BCryptGenRandom(nullptr,
  40. reinterpret_cast<PUCHAR>(output.data()),
  41. static_cast<ULONG>(output.size()),
  42. BCRYPT_USE_SYSTEM_PREFERRED_RNG)
  43. >= 0;
  44. }
  45. /** @return True when any authored or already-seeded account identity owns one SOID. */
  46. [[nodiscard]] bool identity_uses_soid(const AccountState& accountState,
  47. std::uint64_t soid) noexcept {
  48. if (soid == 0 || accountState.primarySoid == soid) {
  49. return true;
  50. }
  51. for (std::size_t index = 0; index < accountState.profileItemCount; ++index) {
  52. if (accountState.profileItems[index].instanceSoid == soid) {
  53. return true;
  54. }
  55. }
  56. for (std::size_t characterIndex = 0; characterIndex < accountState.characterCount;
  57. ++characterIndex) {
  58. const CharacterState& character = accountState.characters[characterIndex];
  59. if (character.soid == soid) {
  60. return true;
  61. }
  62. for (const std::optional<account::inventory::Item>& item : character.equipment.slots) {
  63. if (item.has_value() && item->instanceSoid == soid) {
  64. return true;
  65. }
  66. }
  67. for (std::size_t index = 0; index < character.inventory.count; ++index) {
  68. if (character.inventory.values[index].instanceSoid == soid) {
  69. return true;
  70. }
  71. }
  72. }
  73. return false;
  74. }
  75. /** Seeds canonical character row generations before installed build data is needed. */
  76. [[nodiscard]] bool seed_inventory_runtime_fields(AccountState& accountState) noexcept {
  77. if (!account::valid_authored(accountState)) {
  78. return false;
  79. }
  80. for (std::size_t characterIndex = 0; characterIndex < accountState.characterCount;
  81. ++characterIndex) {
  82. CharacterState& character = accountState.characters[characterIndex];
  83. std::uint32_t next = 0;
  84. for (std::optional<account::inventory::Item>& item : character.equipment.slots) {
  85. if (item.has_value()) {
  86. item->mutationSerial = static_cast<std::int32_t>(next++);
  87. }
  88. }
  89. for (std::size_t index = 0; index < character.inventory.count; ++index) {
  90. character.inventory.values[index].mutationSerial = static_cast<std::int32_t>(next++);
  91. }
  92. character.nextInventorySerial = next;
  93. }
  94. return account::valid(accountState);
  95. }
  96. /** Canonicalizes only profile rows which the installed socket UI materializes as action sources. */
  97. [[nodiscard]] bool canonicalize_profile_item_identities(AccountState& accountState) noexcept {
  98. if (!account::valid(accountState) || !build_data::socket_plug_rules_ready()) {
  99. return false;
  100. }
  101. std::array<bool, account::inventory::kProfileItemCapacity> actionSources{};
  102. std::size_t actionSourceCount = 0;
  103. for (std::size_t index = 0; index < accountState.profileItemCount; ++index) {
  104. const account::inventory::ProfileItem& profileItem = accountState.profileItems[index];
  105. build_data::items::Definition item{};
  106. build_data::items::details::Definition detail{};
  107. build_data::inventory::buckets::Descriptor bucket{};
  108. if (!build_data::find_item_definition_hash(profileItem.definitionHash, item)
  109. || item.definitionHash != profileItem.definitionHash
  110. || !build_data::find_configured_item_detail(item.definitionIndex, detail)
  111. || detail.definitionIndex != item.definitionIndex
  112. || detail.definitionHash != item.definitionHash || detail.bucketId != item.bucketId
  113. || detail.instancedDefinitionState
  114. != build_data::items::details::InstancedDefinitionState::stackable
  115. || !build_data::find_inventory_bucket_descriptor(item.bucketId, bucket)
  116. || bucket.arraySelector != build_data::inventory::buckets::ArraySelector::profile) {
  117. return false;
  118. }
  119. actionSources[index] =
  120. build_data::is_profile_action_source(item.definitionIndex, item.bucketId);
  121. if (actionSources[index]
  122. && ++actionSourceCount > account::inventory::kProfileActionSourceCapacity) {
  123. return false;
  124. }
  125. }
  126. // Currency, material, and consumable rows are native non-instanced stacks. Clear any stale
  127. // runtime key before allocating action-source identities so it cannot reserve the namespace.
  128. for (std::size_t index = 0; index < accountState.profileItemCount; ++index) {
  129. if (!actionSources[index]) {
  130. accountState.profileItems[index].instanceSoid = 0;
  131. }
  132. }
  133. std::uint64_t nextProfileSoid = account::inventory::kFirstProfileItemInstanceSoid;
  134. for (std::size_t index = 0; index < accountState.profileItemCount; ++index) {
  135. account::inventory::ProfileItem& item = accountState.profileItems[index];
  136. if (!actionSources[index] || item.instanceSoid != 0) {
  137. continue;
  138. }
  139. while (identity_uses_soid(accountState, nextProfileSoid)) {
  140. if (nextProfileSoid == (std::numeric_limits<std::uint64_t>::max)()) {
  141. return false;
  142. }
  143. ++nextProfileSoid;
  144. }
  145. item.instanceSoid = nextProfileSoid;
  146. if (nextProfileSoid != (std::numeric_limits<std::uint64_t>::max)()) {
  147. ++nextProfileSoid;
  148. }
  149. }
  150. return account::valid(accountState);
  151. }
  152. } // namespace
  153. /**
  154. * Loads build data and generates secrets with Sunrise's authored activity defaults.
  155. * @param module Loaded Sunrise module, or null to disable disk persistence.
  156. * @param initialAccount Empty State, or a complete checked account from Core settings.
  157. * @return True when the cached data passes its checks and every secret gets random bytes.
  158. */
  159. bool initialize(void* module, const AccountState& initialAccount) noexcept {
  160. return initialize(module, initialAccount, activity::defaults::authored());
  161. }
  162. /**
  163. * Loads build data and publishes fixed activity defaults in one step.
  164. * @param module Loaded Sunrise module, or null to disable disk persistence.
  165. * @param initialAccount Empty State, or a complete checked account from Core settings.
  166. * @param activityDefaults Complete local fallback policy from immutable Core settings.
  167. * @return True when account, defaults, cached data, and generated secrets are valid.
  168. */
  169. bool initialize(void* module,
  170. const AccountState& initialAccount,
  171. const activity::defaults::ActivityDefaults& activityDefaults) noexcept {
  172. AccountState runtimeAccount = initialAccount;
  173. if (!seed_inventory_runtime_fields(runtimeAccount)
  174. || !activity::defaults::valid(activityDefaults)) {
  175. return false;
  176. }
  177. if (!build_data::initialize(module, runtime::equipment::configured_hash(runtimeAccount))) {
  178. return false;
  179. }
  180. // A cache hit already has the complete plug relation, so publish canonical profile identities
  181. // in the first State image. On a first cache build, snapshot preparation repeats this step
  182. // after package extraction has published the relation.
  183. if (build_data::socket_plug_rules_ready()
  184. && !canonicalize_profile_item_identities(runtimeAccount)) {
  185. build_data::shutdown();
  186. return false;
  187. }
  188. {
  189. // The account key is authored, and a truncated one is consistent enough to go unnoticed.
  190. std::array<char, 96> line{};
  191. const int written =
  192. std::snprintf(line.data(),
  193. line.size(),
  194. "ev=account stage=identity primary=0x%016llX characters=%zu",
  195. static_cast<unsigned long long>(runtimeAccount.primarySoid),
  196. runtimeAccount.characterCount);
  197. if (written > 0) {
  198. core::log::write(core::log::Channel::state,
  199. core::log::Level::info,
  200. {line.data(), static_cast<std::size_t>(written)});
  201. }
  202. }
  203. State initialized{};
  204. if (!randomize(initialized.signOn.encryptionKey)
  205. || !randomize(initialized.signOn.authenticationKey)
  206. || !randomize(initialized.signOn.sessionToken) || !randomize(initialized.bap.nonce)
  207. || !randomize(initialized.bap.sessionKey) || !randomize(initialized.bap.envelopeIv)) {
  208. SecureZeroMemory(&initialized, sizeof initialized);
  209. build_data::shutdown();
  210. return false;
  211. }
  212. initialized.signOn.relayAddress = kLoopbackAddress;
  213. // The published relay port is the one the listener binds, so both move with one setting.
  214. initialized.signOn.relayPort = core::settings::get().server.bapPort;
  215. initialized.signOn.tokenLifetimeSeconds = kDefaultTokenLifetimeSeconds;
  216. initialized.account = runtimeAccount;
  217. initialized.activity.defaults = activityDefaults;
  218. initialized.investment.family5.objectSoid = kGlobalFamily5Soid;
  219. // Only the override lists come from settings. Identity and gate stay owned by State.
  220. const Family5State& authored = core::settings::get().initialFamily5;
  221. initialized.investment.family5.flags = authored.flags;
  222. initialized.investment.family5.flagCount = authored.flagCount;
  223. initialized.investment.family5.values = authored.values;
  224. initialized.investment.family5.valueCount = authored.valueCount;
  225. // The arm is account-wide and rides the first ws-503, which goes out before any pick. Nothing
  226. // is selected at boot, so it is armed when any authored character carries the bypass. The
  227. // per-character objB byte is the other half, and it still decides which character it opens.
  228. for (std::size_t index = 0; index < runtimeAccount.characterCount; ++index) {
  229. if (runtimeAccount.characters[index].contentBypass) {
  230. initialized.investment.family5.contentGateArm = true;
  231. break;
  232. }
  233. }
  234. // Publish one complete State only after every generated secret is valid.
  235. AcquireSRWLockExclusive(&runtime::storage::g_stateLock);
  236. runtime::storage::g_state = initialized;
  237. ReleaseSRWLockExclusive(&runtime::storage::g_stateLock);
  238. SecureZeroMemory(&initialized, sizeof initialized);
  239. return true;
  240. }
  241. /** Securely erases State, including activity destinations and matchmaking descriptors. */
  242. void shutdown() noexcept {
  243. AcquireSRWLockExclusive(&runtime::storage::g_stateLock);
  244. SecureZeroMemory(&runtime::storage::g_state, sizeof runtime::storage::g_state);
  245. ReleaseSRWLockExclusive(&runtime::storage::g_stateLock);
  246. build_data::shutdown();
  247. }
  248. /** @return Immutable generated SignOn session fields. */
  249. const SignOnState& sign_on() noexcept {
  250. return runtime::storage::g_state.signOn;
  251. }
  252. /** Ensures every native profile action source has one unique runtime item-instance key. */
  253. bool ensure_profile_item_identities() noexcept {
  254. AcquireSRWLockExclusive(&runtime::storage::g_stateLock);
  255. AccountState candidate = runtime::storage::g_state.account;
  256. const bool ready = canonicalize_profile_item_identities(candidate);
  257. if (ready) {
  258. runtime::storage::g_state.account = candidate;
  259. }
  260. ReleaseSRWLockExclusive(&runtime::storage::g_stateLock);
  261. return ready;
  262. }
  263. /**
  264. * Publishes the bootstrap content-id token read from the installed client.
  265. * @param token Exactly 16 native bytes.
  266. * @return True when the complete token is kept for this process.
  267. */
  268. bool publish_bootstrap_token(std::span<const std::byte> token) noexcept {
  269. SignOnState& signOn = runtime::storage::g_state.signOn;
  270. if (token.size() != signOn.bootstrapToken.size()) {
  271. return false;
  272. }
  273. std::copy(token.begin(), token.end(), signOn.bootstrapToken.begin());
  274. signOn.bootstrapTokenPresent = true;
  275. return true;
  276. }
  277. /** @return Immutable generated BAP session fields. */
  278. const BapState& bap() noexcept {
  279. return runtime::storage::g_state.bap;
  280. }
  281. /** @return A copy of the evaluated content state, read under the lock. */
  282. InvestmentState investment_snapshot() noexcept {
  283. AcquireSRWLockShared(&runtime::storage::g_stateLock);
  284. const InvestmentState snapshot = runtime::storage::g_state.investment;
  285. ReleaseSRWLockShared(&runtime::storage::g_stateLock);
  286. return snapshot;
  287. }
  288. } // namespace sunrise::state