From 21330eaa596a60c688da0329b1126a23e7b48b05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Lower?= Date: Sun, 7 Jan 2024 23:48:51 +0100 Subject: [PATCH] Add ability to execute console commands from file Certain game profiles need CVars from outside the scope provided by UEVR. Editing engine.ini makes profile sharing more complicated. This changeset adds ability to execute commands from `user_script.txt` located inside game profile folder. Syntax and effects are the same as commands were entered in console. Namely one command per line, space separated arguments. This allows flexible customization bundled in profiles that need it. Example is Abzu where addding snippet `r.WaterSurfaceReflections 0` to `user_script.txt` disables distracting visual effects. --- src/mods/vr/CVarManager.cpp | 108 ++++++++++++++++++++++++------------ src/mods/vr/CVarManager.hpp | 2 + 2 files changed, 75 insertions(+), 35 deletions(-) diff --git a/src/mods/vr/CVarManager.cpp b/src/mods/vr/CVarManager.cpp index 17bbe8a7..e8fe5bc1 100644 --- a/src/mods/vr/CVarManager.cpp +++ b/src/mods/vr/CVarManager.cpp @@ -20,6 +20,7 @@ constexpr std::string_view cvars_standard_txt_name = "cvars_standard.txt"; constexpr std::string_view cvars_data_txt_name = "cvars_data.txt"; +constexpr std::string_view user_script_txt_name = "user_script.txt"; CVarManager::CVarManager() { ZoneScopedN(__FUNCTION__); @@ -168,6 +169,7 @@ void CVarManager::on_config_load(const utility::Config& cfg, bool set_defaults) } // TODO: Add arbitrary cvars from the other configs the user can add. + execute_console_script(user_script_txt_name.data()); } void CVarManager::dump_commands() { @@ -324,41 +326,7 @@ void CVarManager::display_console() { if (m_console.input_buffer[0] != '\0') { const auto entire_command = std::string_view{ m_console.input_buffer.data() }; - // Split the command into the arguments via ' ' (space). - std::vector args{}; - - // Use getline - std::stringstream ss{ entire_command.data() }; - while (ss.good()) { - std::string arg{}; - std::getline(ss, arg, ' '); - args.push_back(arg); - } - - // Execute the command. - if (args.size() >= 2) { - auto object = console_manager->find(utility::widen(args[0])); - const auto is_command = object != nullptr && object->AsCommand() != nullptr; - - if (object != nullptr && !is_command) { - auto var = (sdk::IConsoleVariable*)object; - - GameThreadWorker::get().enqueue([var, value = utility::widen(args[1])]() { - var->Set(value.c_str()); - }); - } else if (object != nullptr && is_command) { - auto command = (sdk::IConsoleCommand*)object; - - std::vector widened_args{}; - for (auto i = 1; i < args.size(); ++i) { - widened_args.push_back(utility::widen(args[i])); - } - - GameThreadWorker::get().enqueue([command, widened_args]() { - command->Execute(widened_args); - }); - } - } + execute_console_cmd(entire_command.data()); m_console.history.push_back(m_console.input_buffer.data()); m_console.history_index = m_console.history.size(); @@ -403,6 +371,76 @@ void CVarManager::display_console() { } } +void CVarManager::execute_console_cmd(const std::string& cmd) { + ZoneScopedN(__FUNCTION__); + + const auto console_manager = sdk::FConsoleManager::get(); + if (console_manager == nullptr) { + spdlog::error("[execute_console_cmd] Failed to get console_manager"); + } + + std::vector args{}; + std::stringstream ss{ cmd }; + while (ss.good()) { + std::string arg{}; + std::getline(ss, arg, ' '); + args.push_back(arg); + } + + // Execute the command. + if (args.size() >= 2) { + auto object = console_manager->find(utility::widen(args[0])); + const auto is_command = object != nullptr && object->AsCommand() != nullptr; + + if (object != nullptr && !is_command) { + auto var = (sdk::IConsoleVariable*)object; + + GameThreadWorker::get().enqueue([var, value = utility::widen(args[1])]() { + var->Set(value.c_str()); + }); + } else if (object != nullptr && is_command) { + auto command = (sdk::IConsoleCommand*)object; + + std::vector widened_args{}; + for (auto i = 1; i < args.size(); ++i) { + widened_args.push_back(utility::widen(args[i])); + } + + GameThreadWorker::get().enqueue([command, widened_args]() { + command->Execute(widened_args); + }); + } + else { + spdlog::error("[execute_console_cmd] \"{}\" is not a valid console command", cmd); + } + } else { + spdlog::error("[execute_console_cmd] \"{}\" needs at least 2 space separated arguments", cmd); + } +} + +void CVarManager::execute_console_script(const std::string& filename) { + ZoneScopedN(__FUNCTION__); + + spdlog::info("[execute_console_script] Loading {}...", filename); + + const auto cscript_txt = Framework::get_persistent_dir(filename); + + if (!std::filesystem::exists(cscript_txt)) { + return; + } + + std::ifstream cscript_file(utility::widen(cscript_txt.string())); + + if (!cscript_file) { + spdlog::error("[execute_console_script] Failed to open file {}...", filename); + return; + } + + for (std::string line{}; getline(cscript_file, line); ) { + execute_console_cmd(line); + } +} + std::string CVarManager::CVar::get_key_name() { ZoneScopedN(__FUNCTION__); diff --git a/src/mods/vr/CVarManager.hpp b/src/mods/vr/CVarManager.hpp index 23d40933..24575261 100644 --- a/src/mods/vr/CVarManager.hpp +++ b/src/mods/vr/CVarManager.hpp @@ -22,6 +22,8 @@ class CVarManager final : public ModComponent { void dump_commands(); void spawn_console(); + void execute_console_cmd(const std::string& cmd); + void execute_console_script(const std::string& filename); bool is_hzbo_frozen_and_enabled() const { if (m_hzbo == nullptr) {