state_runtime.cpp 16 KB

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