#include "core_runtime.h" #include #include #include #include #include #include #include "../../client/runtime/host/game_host_classification.h" #include "../../client/runtime/runtime.h" #include "../../middleware/runtime/middleware_runtime.h" #include "../../server/runtime/server_runtime.h" #include "../../state/content_manifest/content_manifest_state_runtime.h" #include "../../state/entitlements/entitlement_runtime.h" #include "../../state/runtime/runtime.h" #include "../../state/unlocks/unlocks_runtime.h" #include "../filesystem/path.h" #include "../logging/log.h" #include "../settings/settings.h" #include "../ui/modules/logs/logs.h" #include "../ui/modules/registry/ui_module_registry.h" #include "../ui/runtime/ui_visibility_runtime.h" namespace sunrise::core { namespace { std::atomic_bool g_initialized{false}; SRWLOCK g_runtimeLock{SRWLOCK_INIT}; /** The installed public package headers live beside the game executable. */ constexpr std::wstring_view kInstalledPackagesDirectory = L"packages"; /** * Builds the local content manifest only for the production game host. * @param module Loaded Sunrise module used for generated cache placement. * @return True when the host needs no manifest or one complete catalog is ready. */ [[nodiscard]] bool initialize_content_manifest(void* module) noexcept { if (client::runtime::host::current_requirement() == client::runtime::host::NetworkRequirement::notApplicable) { return true; } const HMODULE process = GetModuleHandleW(nullptr); path::Buffer packages; if (process == nullptr || !path::module_directory(process, packages) || !path::append(packages, kInstalledPackagesDirectory)) { log::write(log::Channel::core, log::Level::error, "ev=initialize stage=content_manifest result=fail"); return false; } const bool initialized = state::content_manifest::initialize( module, std::wstring_view(packages.chars.data(), packages.length)); if (!initialized) { log::write(log::Channel::core, log::Level::error, "ev=initialize stage=content_manifest result=fail"); } return initialized; } /** * Names the initialization stage that failed. * The logging sinks are the first stage, so their own failure has only the debugger. * @param stage Short key naming the stage. */ void report_stage_failure(const char* stage) noexcept { std::array line{}; const int written = std::snprintf(line.data(), line.size(), "ev=initialize stage=%s result=fail", stage); if (written <= 0) { return; } const std::string_view event{line.data(), static_cast(written)}; log::early(event); log::write(log::Channel::core, log::Level::error, event); } /** * Reports how long one boot boundary took, for the debug channel only. * Timing is diagnostic, so it never appears at the levels a normal run uses. * @param event Event and phase text the duration is appended to. * @param startedTick Tick count taken when the boundary began. * @param result Outcome text for the log line. */ void report_elapsed(const char* event, std::uint64_t startedTick, const char* result) noexcept { const std::uint64_t elapsed = GetTickCount64() - startedTick; std::array line{}; const int written = std::snprintf(line.data(), line.size(), "%s ms=%llu result=%s", event, static_cast(elapsed), result); if (written <= 0) { return; } const auto length = static_cast(written) < line.size() ? static_cast(written) : line.size() - 1; log::write(log::Channel::core, log::Level::debug, {line.data(), length}); } } // namespace /** Initializes every runtime layer in dependency order. */ bool initialize(void* module) noexcept { AcquireSRWLockExclusive(&g_runtimeLock); if (g_initialized.load(std::memory_order_relaxed)) { ReleaseSRWLockExclusive(&g_runtimeLock); return true; } // Taken before the first stage, so the reported duration covers settings and the sinks too. const std::uint64_t startedTick = GetTickCount64(); if (!settings::initialize(module)) { // Settings name their own failure; the sinks do not exist yet to carry a second line. ReleaseSRWLockExclusive(&g_runtimeLock); return false; } state::unlocks::publish(settings::get().initialUnlocks); // One stage per step, so a boot failure names the step instead of the whole expression. const char* stage = nullptr; if (!log::initialize(module, settings::get().logging)) { stage = "logging"; } else { // The sinks exist only from here, so this is the earliest a begin marker can reach a // channel. The duration it pairs with still counts from function entry. log::write(log::Channel::core, log::Level::debug, "ev=initialize phase=begin"); if (!ui::runtime::initialize(settings::get().client.userInterface)) { stage = "ui"; } else if (!ui::modules::logs::initialize()) { stage = "ui_logs"; } else if (!state::entitlements::publish(settings::get().server.entitlements)) { stage = "entitlements"; } else if (!state::initialize(module, settings::get().initialAccount, settings::get().initialActivityDefaults)) { stage = "state"; } else if (!initialize_content_manifest(module)) { stage = "content_manifest"; } else if (!middleware::initialize()) { stage = "middleware"; } else if (!server::initialize()) { stage = "server"; } else if (!client::initialize(module)) { stage = "client"; } } if (stage != nullptr) { report_stage_failure(stage); // Reported before the unwind, so this measures initialization alone and stays comparable // with the success line. The unwind's own quiesce waits would otherwise be counted here. // A logging-stage failure has no sinks left to carry it, and reports nothing. report_elapsed("ev=initialize phase=complete", startedTick, "fail"); // Reverse every stage because the failing expression may have completed earlier stages. (void)client::shutdown(); server::shutdown(); middleware::shutdown(); state::content_manifest::shutdown(); state::shutdown(); state::entitlements::clear(); ui::modules::logs::shutdown(); ui::modules::registry::shutdown(); ui::runtime::shutdown(); state::unlocks::clear(); log::shutdown(); settings::shutdown(); ReleaseSRWLockExclusive(&g_runtimeLock); return false; } g_initialized.store(true, std::memory_order_release); log::write(log::Channel::core, log::Level::info, "ev=initialize result=ok"); report_elapsed("ev=initialize phase=complete", startedTick, "ok"); ReleaseSRWLockExclusive(&g_runtimeLock); return true; } /** Stops every runtime layer in reverse dependency order. */ bool shutdown() noexcept { AcquireSRWLockExclusive(&g_runtimeLock); if (!g_initialized.load(std::memory_order_acquire)) { ReleaseSRWLockExclusive(&g_runtimeLock); return true; } if (!client::shutdown()) { // Server and State must remain valid while any Client hook is attached. ReleaseSRWLockExclusive(&g_runtimeLock); return false; } g_initialized.store(false, std::memory_order_release); server::shutdown(); middleware::shutdown(); state::content_manifest::shutdown(); state::shutdown(); state::entitlements::clear(); ui::modules::logs::shutdown(); ui::modules::registry::shutdown(); ui::runtime::shutdown(); log::write(log::Channel::core, log::Level::info, "ev=shutdown result=ok"); state::unlocks::clear(); log::shutdown(); settings::shutdown(); ReleaseSRWLockExclusive(&g_runtimeLock); return true; } /** @return True after every Core-owned runtime layer initializes. */ bool is_initialized() noexcept { return g_initialized.load(std::memory_order_acquire); } } // namespace sunrise::core