diff --git a/thirdparty/rexglue-sdk/include/rex/cvar.h b/thirdparty/rexglue-sdk/include/rex/cvar.h index 20f32e14..3e01cddc 100644 --- a/thirdparty/rexglue-sdk/include/rex/cvar.h +++ b/thirdparty/rexglue-sdk/include/rex/cvar.h @@ -165,6 +165,13 @@ struct FlagEntry { bool has_session_default = false; std::string session_default; std::string session_driver; + // Set by cvar::Init when the value came from the command line. Config-file + // load skips applying to these entries so per-launch overrides keep their + // precedence (cmdline > toml > default) even for cvars consumed after + // LoadConfig; the toml value is still recorded as + // user_set/user_value so the next SaveConfig round-trips the user's saved + // choice instead of dropping it. + bool cmdline_set = false; }; std::vector& GetRegistry(); diff --git a/thirdparty/rexglue-sdk/src/core/cvar.cpp b/thirdparty/rexglue-sdk/src/core/cvar.cpp index de491464..5f8574b7 100644 --- a/thirdparty/rexglue-sdk/src/core/cvar.cpp +++ b/thirdparty/rexglue-sdk/src/core/cvar.cpp @@ -59,6 +59,30 @@ std::string FlagNameToEnvVar(std::string_view name) { return result; } +bool ValidateConstraints(const FlagEntry& entry, std::string_view value); + +// Config-load path for one flag when the command line already set it: the +// command line wins for the LIVE value (cmdline > toml > default), +// but the toml value must still become the user-owned config value or the +// next SaveConfig would silently drop the user's saved setting. +// Returns true when the flag was handled here (skip the normal set). +bool RecordConfigValueOverriddenByCmdline(const std::string& name, const std::string& value) { + std::lock_guard lock(GetRegistryMutex()); + auto it = GetRegistryIndex().find(name); + if (it == GetRegistryIndex().end()) { + return false; + } + auto& entry = GetRegistryStorage()[it->second]; + if (!entry.cmdline_set) { + return false; + } + if (ValidateConstraints(entry, value)) { + entry.user_set = true; + entry.user_value = value; + } + return true; +} + // Recursively apply TOML values void ApplyTomlTable(const toml::table& table, const std::string& prefix) { for (const auto& [key, value] : table) { @@ -81,7 +105,9 @@ void ApplyTomlTable(const toml::table& table, const std::string& prefix) { continue; } - if (SetFlagByName(full_key, value_str)) { + if (RecordConfigValueOverriddenByCmdline(full_key, value_str)) { + REXLOG_DEBUG("Config: {} = {} (command line takes precedence)", full_key, value_str); + } else if (SetFlagByName(full_key, value_str)) { REXLOG_DEBUG("Config: {} = {}", full_key, value_str); } else { REXLOG_WARN("Config: unknown cvar '{}'", full_key); @@ -592,11 +618,20 @@ std::vector Init(int argc, char** argv) { if (entry.type == FlagType::Boolean) { app.add_flag_function( "--" + entry.name + ",!--no-" + entry.name, - [&entry](int64_t count) { entry.setter(count > 0 ? "true" : "false"); }, + [&entry](int64_t count) { + if (entry.setter(count > 0 ? "true" : "false")) { + entry.cmdline_set = true; + } + }, entry.description); } else { app.add_option_function( - "--" + entry.name, [&entry](const std::string& val) { entry.setter(val); }, + "--" + entry.name, + [&entry](const std::string& val) { + if (entry.setter(val)) { + entry.cmdline_set = true; + } + }, entry.description); } } @@ -713,6 +748,7 @@ void ResetAllForTesting() { entry.has_session_default = false; entry.session_default.clear(); entry.session_driver.clear(); + entry.cmdline_set = false; } } ClearPendingRestartFlags(); diff --git a/thirdparty/rexglue-sdk/src/native/ui/rex_app.cpp b/thirdparty/rexglue-sdk/src/native/ui/rex_app.cpp index e0308d39..ad79d50d 100644 --- a/thirdparty/rexglue-sdk/src/native/ui/rex_app.cpp +++ b/thirdparty/rexglue-sdk/src/native/ui/rex_app.cpp @@ -342,39 +342,6 @@ bool ReXApp::ResolveGameSource(const std::filesystem::path& exe_dir, bool ReXApp::OnInitialize() { auto exe_dir = rex::filesystem::GetExecutableFolder(); - // Game directory: positional arg or default to exe_dir/assets - std::filesystem::path game_dir; - if (auto arg = GetArgument("game_directory")) { - game_dir = *arg; - } else { - game_dir = exe_dir / "assets"; - } - - // User data: cvar override, or platform user directory - std::filesystem::path user_dir; - std::string user_data_cvar = REXCVAR_GET(user_data_root); - if (!user_data_cvar.empty()) { - // to_path: the toml string is UTF-8, not the ANSI codepage a bare path - // construction would assume on Windows (same rule as game_iso/dlc_dir). - user_dir = rex::to_path(user_data_cvar); - } else { - user_dir = rex::filesystem::GetUserFolder() / GetName(); - } - - // Update data: cvar override, or empty (opt-in) - std::filesystem::path update_dir; - std::string update_data_cvar = REXCVAR_GET(update_data_root); - if (!update_data_cvar.empty()) { - update_dir = rex::to_path(update_data_cvar); - } - - // Allow subclass to override path defaults - PathConfig path_config{game_dir, user_dir, update_dir}; - OnConfigurePaths(path_config); - game_data_root_ = std::move(path_config.game_data_root); - user_data_root_ = std::move(path_config.user_data_root); - update_data_root_ = std::move(path_config.update_data_root); - auto config_path = exe_dir / (std::string(GetName()) + ".toml"); // Load saved config (CVARs) before anything reads them @@ -446,6 +413,47 @@ bool ReXApp::OnInitialize() { REXLOG_INFO("{} starting", GetName()); + // Data-root resolution. Runs after LoadConfig, next to the game-data + // resolution below and for the same reason: these cvars used to be + // consumed at the top of this function, BEFORE the toml was parsed, so a + // user_data_root/update_data_root set in the config file silently did + // nothing - only the command line ever worked. Nothing between + // LoadConfig and this point derives a path from the user root (log and + // crash paths come from log_file/exe_dir). + + // Game directory: positional arg or default to exe_dir/assets + std::filesystem::path game_dir; + if (auto arg = GetArgument("game_directory")) { + game_dir = *arg; + } else { + game_dir = exe_dir / "assets"; + } + + // User data: cvar override, or platform user directory + std::filesystem::path user_dir; + std::string user_data_cvar = REXCVAR_GET(user_data_root); + if (!user_data_cvar.empty()) { + // to_path: the toml string is UTF-8, not the ANSI codepage a bare path + // construction would assume on Windows (same rule as game_iso/dlc_dir). + user_dir = rex::to_path(user_data_cvar); + } else { + user_dir = rex::filesystem::GetUserFolder() / GetName(); + } + + // Update data: cvar override, or empty (opt-in) + std::filesystem::path update_dir; + std::string update_data_cvar = REXCVAR_GET(update_data_root); + if (!update_data_cvar.empty()) { + update_dir = rex::to_path(update_data_cvar); + } + + // Allow subclass to override path defaults + PathConfig path_config{game_dir, user_dir, update_dir}; + OnConfigurePaths(path_config); + game_data_root_ = std::move(path_config.game_data_root); + user_data_root_ = std::move(path_config.user_data_root); + update_data_root_ = std::move(path_config.update_data_root); + // Resolve where the game's data comes from. Runs after LoadConfig so the // game_iso / iso_direct toml overrides apply. std::filesystem::path game_image;