/
Mr.Stalin
/
FOnline-Engine
Обзор
Документация
Войти
/
Mr.Stalin
/
FOnline-Engine
Код
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
Source/Applications/ClientApp.cpp
671 строка
27 KB
cvet
Non const locals (#190)
24 июл 2026, 10:46
Не верифицирован
24 июл 2026, 10:46
4883d25
Код
Авторство
О чём код?
// __________ ___ ______ _ // / ____/ __ \____ / (_)___ ___ / ____/___ ____ _(_)___ ___ // / /_ / / / / __ \/ / / __ \/ _ \ / __/ / __ \/ __ `/ / __ \/ _ ` // / __/ / /_/ / / / / / / / / / __/ / /___/ / / / /_/ / / / / / __/ // /_/ \____/_/ /_/_/_/_/ /_/\___/ /_____/_/ /_/\__, /_/_/ /_/\___/ // /____/ // FOnline Engine // https://fonline.ru // https://github.com/cvet/fonline // // MIT License // // Copyright (c) 2006 - 2026, Anton Tsvetinskiy aka cvet <cvet@tut.by> // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // #include "Common.h" #include "Application.h" #include "Client.h" #include "ClientRuntimeApi.h" #include "MetadataRegistration.h" #include "Settings.h" #include "Updater.h" #include "WebRelated.h" #if !FO_TESTING_APP && !FO_HEADLESS_APP #include "SDL3/SDL_main.h" #endif FO_USING_NAMESPACE(); FO_BEGIN_NAMESPACE extern void ApplicationShutdownHook(); extern void ClientStartupSettingsHook(GlobalSettings& settings, int32_t client_index, bool embedded); FO_END_NAMESPACE struct ClientAppData { refcount_nptr<ClientEngine> Client {}; bool ResourcesSynced {}; bool ReloadRequested {}; string StagedRuntimePath; optional<Updater> ResourceUpdater {}; }; FO_GLOBAL_DATA(ClientAppData, Data); struct RequestedClientRuntime { string Path {}; string CompatibilityVersion {}; bool CheckCompatibilityVersion {}; bool ExplicitPath {}; bool ForceEmbedded {}; // ForceEmbeddedRuntime setting (command-line form): skip the implicit bundled-DLL load }; static auto RunEmbeddedOrLoadedClient(CommandLineArgs args) -> bool; static auto RunClientFromLibrary(CommandLineArgs args, const RequestedClientRuntime& requested_runtime) -> optional<ClientRuntimeHostResult>; static auto PromoteStagedReloadForRestart(string_view runtime_path) -> bool; static auto RunEmbeddedClient(CommandLineArgs args) -> ClientRuntimeHostResult; static auto RunClientRuntime(CommandLineArgs args) noexcept -> ClientRuntimeResult; static void MainEntry(void* data); static void CleanupClientApp() noexcept; static auto TryLoadRuntime(const RequestedClientRuntime& requested_runtime, ClientRuntimeExports& exports) -> nptr<void>; static auto ApplyStagedBinaryUpdate(string_view runtime_live_path) -> bool; static auto ResolveRequestedClientRuntime(CommandLineArgs args) -> RequestedClientRuntime; static auto ResolveBundledRuntimePath() -> string; static auto IsInstalledClientLayout() -> bool; static auto GetInstalledClientRuntimeBootstrapPath() -> optional<string>; static auto GetCurrentClientRuntimeFileName() -> string; static void CaptureRuntimeResultStrings(ClientRuntimeHostResult& runtime_result); #if !FO_TESTING_APP int main(int argc, char** argv) // Handled by SDL #else [[maybe_unused]] static auto ClientApp(CommandLineArgs args) -> int #endif { FO_STACK_TRACE_ENTRY(); CreateGlobalData(); LogToFile(GetExeLogFileName(), false); #if !FO_TESTING_APP CommandLineArgs args {numeric_cast<int32_t>(argc), argv}; #endif bool run_result = RunEmbeddedOrLoadedClient(args); ExitApp(run_result); } static auto RunEmbeddedOrLoadedClient(CommandLineArgs args) -> bool { FO_STACK_TRACE_ENTRY(); auto requested_runtime = ResolveRequestedClientRuntime(args); bool can_self_update = CanSelfUpdateNativeModules(GetCurrentUpdatePlatform()); WriteLog("Client runtime host: bundled DLL {}, compatibility check {}, explicit path {}, force embedded {}, native self-update {} for {}, embedded build {}, embedded compatibility {}", requested_runtime.Path, requested_runtime.CheckCompatibilityVersion ? "enabled" : "disabled", requested_runtime.ExplicitPath ? "yes" : "no", requested_runtime.ForceEmbedded ? "yes" : "no", can_self_update ? "enabled" : "disabled", GetCurrentBinaryUpdateTargetName(), FO_BUILD_HASH, FO_COMPATIBILITY_VERSION); // Try the bundled runtime DLL first on self-update platforms (the DLL is the authoritative // runtime); if it is absent or fails to load, RunClientFromLibrary falls back to the embedded // engine. ForceEmbeddedRuntime skips the implicit DLL load; an explicit --ClientLibPath still loads. bool can_load_bundled_runtime = requested_runtime.ExplicitPath || (!requested_runtime.ForceEmbedded && can_self_update); if (can_load_bundled_runtime) { auto loaded_runtime_result = RunClientFromLibrary(args, requested_runtime); auto loaded_result = RunClientRuntimeHostPass(loaded_runtime_result, PromoteStagedReloadForRestart); if (loaded_result.has_value()) { return loaded_result.value(); } WriteLog("Client runtime host: bundled DLL did not start, trying embedded fallback"); } else { WriteLog("Client runtime host: bundled DLL load skipped"); } if (requested_runtime.CheckCompatibilityVersion && requested_runtime.CompatibilityVersion != FO_COMPATIBILITY_VERSION) { WriteLog("Client runtime host: embedded fallback rejected, requested compatibility {}, embedded compatibility {}", requested_runtime.CompatibilityVersion, FO_COMPATIBILITY_VERSION); return false; } auto embedded_runtime_result = RunEmbeddedClient(args); auto embedded_result = RunClientRuntimeHostPass(embedded_runtime_result, PromoteStagedReloadForRestart); FO_VERIFY_AND_THROW(embedded_result.has_value(), "Embedded client runtime pass did not return a result"); return embedded_result.value(); } static auto RunClientFromLibrary(CommandLineArgs args, const RequestedClientRuntime& requested_runtime) -> optional<ClientRuntimeHostResult> { FO_STACK_TRACE_ENTRY(); WriteLog("Client runtime host: preparing DLL {}, compatibility check {}", requested_runtime.Path, requested_runtime.CheckCompatibilityVersion ? "enabled" : "disabled"); if (!ApplyStagedBinaryUpdate(requested_runtime.Path)) { WriteLog("Client runtime host: failed to apply staged binary update before loading {}", requested_runtime.Path); return std::nullopt; } if (!fs_exists(requested_runtime.Path)) { WriteLog("Client runtime host: no bundled runtime DLL at {}, using embedded runtime", requested_runtime.Path); return std::nullopt; } ClientRuntimeExports exports {}; auto runtime_module = TryLoadRuntime(requested_runtime, exports); if (!runtime_module) { WriteLog("Client runtime host: failed to load DLL {}", requested_runtime.Path); return std::nullopt; } string loaded_runtime_name = exports.Metadata.RuntimeName != nullptr ? string(exports.Metadata.RuntimeName) : string(); string loaded_build_hash = exports.Metadata.BuildHash != nullptr ? string(exports.Metadata.BuildHash) : string(); string loaded_compat = exports.Metadata.CompatibilityVersion != nullptr ? string(exports.Metadata.CompatibilityVersion) : string(); WriteLog("Client runtime host: loaded DLL {}, runtime {}, build {}, compatibility {}, ABI {}", requested_runtime.Path, loaded_runtime_name, loaded_build_hash, loaded_compat, exports.Metadata.HostAbiVersion); auto unload_runtime = scope_exit([&]() noexcept { WriteLog("Client runtime host: unloading DLL {}, runtime {}, build {}, compatibility {}", requested_runtime.Path, loaded_runtime_name, loaded_build_hash, loaded_compat); Platform::UnloadModule(runtime_module); WriteLog("Client runtime host: unloaded DLL {}", requested_runtime.Path); }); ClientRuntimeHostResult runtime_result {}; runtime_result.Result.StructSize = numeric_cast<uint32_t>(sizeof(ClientRuntimeResult)); runtime_result.LoadedBuildHash = loaded_build_hash; vector<char*> runtime_args(args.size()); for (size_t index = 0; index < args.size(); ++index) { runtime_args[index] = args[index].get(); } WriteLog("Client runtime host: entering DLL {}, runtime {}, build {}", requested_runtime.Path, loaded_runtime_name, loaded_build_hash); exports.Run(numeric_cast<int32_t>(args.size()), runtime_args.data(), &runtime_result.Result); CaptureRuntimeResultStrings(runtime_result); WriteLog("Client runtime host: DLL {} returned {}, success {}, requested path {}, requested compatibility {}", requested_runtime.Path, ClientRuntimeResultKindToString(runtime_result.Result.ResultKind), runtime_result.Result.Success ? "yes" : "no", runtime_result.RequestedRuntimePath, runtime_result.RequestedCompatibilityVersion); if (!IsValidClientRuntimeResult(runtime_result.Result)) { WriteLog("Client runtime host: DLL {} returned invalid result {}, success {}, requested path {}", requested_runtime.Path, ClientRuntimeResultKindToString(runtime_result.Result.ResultKind), runtime_result.Result.Success ? "yes" : "no", runtime_result.RequestedRuntimePath); return std::nullopt; } return runtime_result; } static auto PromoteStagedReloadForRestart(string_view runtime_path) -> bool { FO_STACK_TRACE_ENTRY(); FO_VERIFY_AND_THROW(!runtime_path.empty(), "Client runtime host received a reload result without a requested runtime path"); if (!ApplyStagedBinaryUpdate(runtime_path)) { WriteLog("Client runtime host: failed to promote staged runtime at {}", runtime_path); return false; } if (IsInstalledClientLayout()) { auto bootstrap_path = GetInstalledClientRuntimeBootstrapPath(); if (!bootstrap_path.has_value()) { WriteLog("Client runtime host: failed to resolve installed runtime bootstrap path for {}", runtime_path); return false; } string runtime_file_name = GetCurrentClientRuntimeFileName(); if (!WriteClientRuntimeBootstrapTarget(bootstrap_path.value(), runtime_path, runtime_file_name)) { WriteLog("Client runtime host: failed to persist installed runtime bootstrap {} -> {}", bootstrap_path.value(), runtime_path); return false; } WriteLog("Client runtime host: persisted installed runtime bootstrap {} -> {}", bootstrap_path.value(), runtime_path); } WriteLog("Client runtime host: staged self-update promoted at {}, exiting for user restart", runtime_path); return true; } static auto RunEmbeddedClient(CommandLineArgs args) -> ClientRuntimeHostResult { FO_STACK_TRACE_ENTRY(); WriteLog("Client runtime host: entering embedded client build {}, compatibility {}", FO_BUILD_HASH, FO_COMPATIBILITY_VERSION); ClientRuntimeHostResult runtime_result {}; runtime_result.LoadedBuildHash = FO_BUILD_HASH; runtime_result.Result = RunClientRuntime(args); CaptureRuntimeResultStrings(runtime_result); WriteLog("Client runtime host: embedded client returned {}, success {}, requested path {}, requested compatibility {}", ClientRuntimeResultKindToString(runtime_result.Result.ResultKind), runtime_result.Result.Success ? "yes" : "no", runtime_result.RequestedRuntimePath, runtime_result.RequestedCompatibilityVersion); return runtime_result; } static auto RunClientRuntime(CommandLineArgs args) noexcept -> ClientRuntimeResult { FO_STACK_TRACE_ENTRY(); ClientRuntimeResult runtime_result {}; runtime_result.StructSize = numeric_cast<uint32_t>(sizeof(ClientRuntimeResult)); runtime_result.ResultKind = ClientRuntimeResultKind::Shutdown; try { WriteLog("Client runtime embedded: starting InitApp, build {}, compatibility {}", FO_BUILD_HASH, FO_COMPATIBILITY_VERSION); InitApp(args, CombineEnum(AppInitFlags::ClientMode, AppInitFlags::ShowMessageOnException, AppInitFlags::PrebakeResources, AppInitFlags::AppendLogFile)); WriteLog("Compatibility version: {}", GetApp()->Settings.CompatibilityVersion); WriteLog("Client runtime: embedded client build {}, compatibility {}", FO_BUILD_HASH, FO_COMPATIBILITY_VERSION); #if FO_IOS MainEntry(nullptr); GetApp()->SetMainLoopCallback(MainEntry); #elif FO_WEB WebRelated::InitializePersistentData(); WebRelated::StartMainLoop(MainEntry, nullptr); #elif FO_ANDROID while (!GetApp()->IsQuitRequested()) { MainEntry(nullptr); } #else auto balancer = FrameBalancer(!GetApp()->Settings.VSync, GetApp()->Settings.Sleep, GetApp()->Settings.FixedFPS); while (!GetApp()->IsQuitRequested()) { balancer.StartLoop(); MainEntry(nullptr); balancer.EndLoop(); } #endif WriteLog("Exit from game"); WriteLog("Client runtime embedded: main loop exited"); bool quit_success = GetApp()->GetRequestedQuitSuccess(); CleanupClientApp(); if (Data->ReloadRequested) { WriteLog("Client runtime embedded: requesting reload from {}", Data->StagedRuntimePath); runtime_result.ResultKind = ClientRuntimeResultKind::ReloadRequested; runtime_result.RequestedRuntimePath = Data->StagedRuntimePath.c_str(); runtime_result.Success = true; } else { WriteLog("Client runtime embedded: returning shutdown, success {}", quit_success ? "yes" : "no"); runtime_result.Success = quit_success; } if (Data->ReloadRequested) { WriteLog("Client runtime embedded: resetting application before reload"); ResetApp(); } } catch (const std::exception& ex) { WriteLog("Client runtime embedded: exception {}", ex.what()); CleanupClientApp(); ReportExceptionAndContinue(ex); runtime_result.ResultKind = ClientRuntimeResultKind::FatalError; } catch (...) { FO_UNKNOWN_EXCEPTION(); } WriteLog("Client runtime embedded: calling application shutdown hook"); safe_call([] { ApplicationShutdownHook(); }); WriteLog("Client runtime embedded: finished with {}, success {}", ClientRuntimeResultKindToString(runtime_result.ResultKind), runtime_result.Success ? "yes" : "no"); return runtime_result; } static auto GetClient() -> ptr<ClientEngine> { FO_STACK_TRACE_ENTRY(); FO_VERIFY_AND_THROW(Data->Client, "Client engine is not created"); return Data->Client; } static void MainEntry([[maybe_unused]] void* data) { FO_STACK_TRACE_ENTRY(); if (GetApp()->IsQuitRequested()) { return; } try { if (!WebRelated::IsPersistentDataReady()) { return; } try { GetApp()->BeginFrame(); } catch (const std::exception& ex) { ReportExceptionAndExit(ex); } auto end_frame = scope_success([&]() { try { GetApp()->EndFrame(); } catch (const std::exception& ex) { ReportExceptionAndExit(ex); } }); if (!Data->Client) { try { if (!Data->ResourcesSynced) { if (!IsPackaged()) { Data->ResourcesSynced = true; return; } if (!Data->ResourceUpdater) { WriteLog("Client runtime embedded: creating updater"); Data->ResourceUpdater.emplace(&GetApp()->Settings, &GetApp()->MainWindow); } if (!Data->ResourceUpdater->Process()) { return; } auto result = Data->ResourceUpdater->GetResult(); // The updater stages the new runtime under its own binary dir (the writable root // for an installed client, the exe dir for a portable one); reload that exact path. string staged_runtime_path = Data->ResourceUpdater->GetRuntimeLivePath(); Data->ResourceUpdater.reset(); switch (result) { case UpdaterResult::ResourcesReady: WriteLog("Client runtime embedded: updater finished, resources ready"); Data->ResourcesSynced = true; break; case UpdaterResult::BinariesStaged: Data->StagedRuntimePath = staged_runtime_path; Data->ReloadRequested = true; WriteLog("Client runtime embedded: updater staged binaries at {}", Data->StagedRuntimePath); GetApp()->RequestQuit(); return; default: WriteLog("Client runtime embedded: updater failed"); ShowUpdaterFailure(result); GetApp()->RequestQuit(); return; } } ClientStartupSettingsHook(GetApp()->Settings, 1, false); auto settings = make_ptr(&GetApp()->Settings); Data->Client = SafeAlloc::MakeRefCounted<ClientEngine>(settings, GetClientResources(*settings), &GetApp()->MainWindow); #if FO_HEADLESS_APP auto client = GetClient(); client->Connect(); #endif } catch (const std::exception& ex) { ReportExceptionAndExit(ex); } } try { auto client = GetClient(); client->MainLoop(); } catch (const ResourcesOutdatedException&) { Data->ResourcesSynced = false; auto client = GetClient(); client->Shutdown(); Data->Client.reset(); } catch (const MetadataNotFoundException& ex) { ReportExceptionAndExit(ex); } catch (const std::exception& ex) { ReportExceptionAndContinue(ex); if (GetApp()->Settings.RecreateClientOnError) { auto client = GetClient(); client->Shutdown(); Data->Client.reset(); } } } catch (const std::exception& ex) { ReportExceptionAndExit(ex); } } static void CleanupClientApp() noexcept { FO_STACK_TRACE_ENTRY(); Data->ResourceUpdater.reset(); if (Data->Client) { safe_call([]() { auto client = GetClient(); client->Shutdown(); }); Data->Client.reset(); } } static auto TryLoadRuntime(const RequestedClientRuntime& requested_runtime, ClientRuntimeExports& exports) -> nptr<void> { FO_STACK_TRACE_ENTRY(); WriteLog("Client runtime host: loading DLL {}", requested_runtime.Path); auto runtime_module = Platform::LoadModule(requested_runtime.Path); if (!runtime_module) { WriteLog("Client runtime host: LoadModule failed for {}", requested_runtime.Path); return nullptr; } auto query_exports = Platform::GetFuncAddr<QueryClientRuntimeExportsFunc>(runtime_module, "FO_QueryClientRuntimeExports"); if (query_exports == nullptr) { WriteLog("Client runtime host: DLL {} does not export FO_QueryClientRuntimeExports", requested_runtime.Path); Platform::UnloadModule(runtime_module); return nullptr; } exports = {}; exports.StructSize = numeric_cast<uint32_t>(sizeof(ClientRuntimeExports)); bool query_ok = query_exports(FO_CLIENT_RUNTIME_HOST_ABI_VERSION, &exports); bool exports_valid = query_ok && IsValidClientRuntimeExports(exports); bool abi_supported = exports_valid && IsSupportedClientRuntimeAbi(exports.Metadata.HostAbiVersion); if (!query_ok || !exports_valid || !abi_supported) { WriteLog("Client runtime host: DLL {} rejected, export query {}, metadata {}, ABI {}, runtime ABI {}, host ABI {}", requested_runtime.Path, query_ok ? "ok" : "failed", exports_valid ? "valid" : "invalid", abi_supported ? "supported" : "unsupported", exports.Metadata.HostAbiVersion, FO_CLIENT_RUNTIME_HOST_ABI_VERSION); Platform::UnloadModule(runtime_module); return nullptr; } if (requested_runtime.CheckCompatibilityVersion && !IsClientRuntimeCompatibilityMatch(exports.Metadata, requested_runtime.CompatibilityVersion)) { string metadata_compat = exports.Metadata.CompatibilityVersion != nullptr ? string(exports.Metadata.CompatibilityVersion) : string(); string metadata_build = exports.Metadata.BuildHash != nullptr ? string(exports.Metadata.BuildHash) : string(); WriteLog("Client runtime host: DLL {} rejected by compatibility check, requested {}, DLL compatibility {}, DLL build {}", requested_runtime.Path, requested_runtime.CompatibilityVersion, metadata_compat, metadata_build); Platform::UnloadModule(runtime_module); return nullptr; } WriteLog("Client runtime host: accepted DLL {}, runtime {}, build {}, compatibility {}, ABI {}", requested_runtime.Path, exports.Metadata.RuntimeName, exports.Metadata.BuildHash, exports.Metadata.CompatibilityVersion, exports.Metadata.HostAbiVersion); return runtime_module; } static auto ApplyStagedBinaryUpdate(string_view runtime_live_path) -> bool { FO_STACK_TRACE_ENTRY(); // The runtime being loaded decides where staging lives: the install-dir base DLL on the initial // load, or the writable-root DLL on an installed client's reload. Portable clients use the exe dir // in both passes, so this is identical to the previous exe-dir-only behavior for them. string staged_path = MakeClientRuntimeStagingPath(runtime_live_path); string binary_dir = strex(runtime_live_path).extract_dir().str(); if (!fs_exists(staged_path)) { WriteLog("Client runtime host: no staged DLL at {}", staged_path); PromoteStagedRuntimeCompanions(binary_dir); return true; } string final_path = string(runtime_live_path); string backup_path = strex("{}.bak", final_path).str(); bool final_exists = fs_exists(final_path); WriteLog("Client runtime host: promoting staged DLL {} to {}, backup {}, live DLL exists {}", staged_path, final_path, backup_path, final_exists ? "yes" : "no"); fs_remove_file(backup_path); if (final_exists && !fs_rename(final_path, backup_path)) { WriteLog("Client runtime host: failed to move live DLL {} to backup {}", final_path, backup_path); return false; } if (!fs_rename(staged_path, final_path)) { WriteLog("Client runtime host: failed to promote staged DLL {} to {}", staged_path, final_path); if (final_exists) { fs_rename(backup_path, final_path); } return false; } if (final_exists) { fs_remove_file(backup_path); } PromoteStagedRuntimeCompanions(binary_dir); WriteLog("Client runtime host: promoted staged DLL to {}", final_path); return true; } static auto ResolveRequestedClientRuntime(CommandLineArgs args) -> RequestedClientRuntime { FO_STACK_TRACE_ENTRY(); RequestedClientRuntime requested_runtime {}; requested_runtime.Path = ResolveBundledRuntimePath(); for (size_t index = 1; index < args.size(); index++) { string_view arg = args.Get(index); if (arg.empty()) { continue; } bool has_next_arg = index + 1 < args.size(); string_view next_arg = args.Get(index + 1); if (arg == "--ClientLibPath" && has_next_arg) { requested_runtime.ExplicitPath = true; requested_runtime.Path = next_arg; } if (arg == "--ClientLibCompatibilityVersion" && has_next_arg) { requested_runtime.CompatibilityVersion = next_arg; requested_runtime.CheckCompatibilityVersion = true; } if (arg == "--ForceEmbeddedRuntime" || arg == "-ForceEmbeddedRuntime") { string_view value = has_next_arg && !CommandLineArgs::IsOption(next_arg) ? next_arg : string_view("1"); requested_runtime.ForceEmbedded = value != "0" && value != "false" && value != "False"; } } return requested_runtime; } static auto ResolveBundledRuntimePath() -> string { FO_STACK_TRACE_ENTRY(); string install_runtime_path = GetClientRuntimeLivePath(); auto bootstrap_path = GetInstalledClientRuntimeBootstrapPath(); if (!bootstrap_path.has_value()) { return install_runtime_path; } string runtime_file_name = GetCurrentClientRuntimeFileName(); string bootstrap_target = ResolveClientRuntimeBootstrapTarget(bootstrap_path.value(), runtime_file_name, install_runtime_path); if (bootstrap_target == install_runtime_path) { WriteLog("Client runtime host: installed runtime bootstrap {} selected no alternate runtime, using base runtime {}", bootstrap_path.value(), install_runtime_path); return install_runtime_path; } WriteLog("Client runtime host: selected installed runtime {} from bootstrap {}", bootstrap_target, bootstrap_path.value()); return bootstrap_target; } static auto GetInstalledClientRuntimeBootstrapPath() -> optional<string> { FO_STACK_TRACE_ENTRY(); if (!IsInstalledClientLayout()) { return std::nullopt; } string user_data_base = Platform::GetUserDataBase(); if (user_data_base.empty()) { WriteLog(LogType::Warning, "Client runtime host: installed layout detected but no user data dir is available"); return std::nullopt; } string selector_file_name = strex("{}.path", GetCurrentClientRuntimeFileName()).str(); return fs_resolve_path(strex(user_data_base).combine_path(FO_NICE_NAME).combine_path("ClientRuntimeHost").combine_path(selector_file_name).str()); } static auto IsInstalledClientLayout() -> bool { FO_STACK_TRACE_ENTRY(); optional<string> exe_path = Platform::GetExePath(); return exe_path.has_value() && fs_exists(strex(exe_path.value()).extract_dir().combine_path("INSTALLED").str()); } static auto GetCurrentClientRuntimeFileName() -> string { FO_STACK_TRACE_ENTRY(); return strex("{}{}", GetCurrentClientRuntimeLibraryName(), GetClientRuntimeLibraryExtension()).str(); } static void CaptureRuntimeResultStrings(ClientRuntimeHostResult& runtime_result) { FO_STACK_TRACE_ENTRY(); if (runtime_result.Result.RequestedRuntimePath != nullptr) { runtime_result.RequestedRuntimePath = runtime_result.Result.RequestedRuntimePath; runtime_result.Result.RequestedRuntimePath = runtime_result.RequestedRuntimePath.c_str(); } else { runtime_result.RequestedRuntimePath.clear(); } if (runtime_result.Result.RequestedCompatibilityVersion != nullptr) { runtime_result.RequestedCompatibilityVersion = runtime_result.Result.RequestedCompatibilityVersion; runtime_result.Result.RequestedCompatibilityVersion = runtime_result.RequestedCompatibilityVersion.c_str(); } else { runtime_result.RequestedCompatibilityVersion.clear(); } }