From 03237f95353671c7b020b72e6d6b15518d083ab8 Mon Sep 17 00:00:00 2001 From: keton Date: Sun, 3 Mar 2024 22:21:41 +0100 Subject: [PATCH] Add ability to execute console commands from file (#96) 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. This allows flexible customization bundled in profiles that need it. Syntax and effects are the same as commands were entered in native console. Namely one command per line, space separated arguments. Comments are supported in form of whole line comments starting with '#' or ';'. Additionally inline comments are supported in form of `command param # comment` Example is Abzu where addding snippet `r.WaterSurfaceReflections 0` to `user_script.txt` disables distracting visual effects. --- src/mods/vr/CVarManager.cpp | 73 ++++++++++++++++++++++++++++++++++++- src/mods/vr/CVarManager.hpp | 3 ++ 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/src/mods/vr/CVarManager.cpp b/src/mods/vr/CVarManager.cpp index 17bbe8a7..1d2b75d0 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__); @@ -85,6 +86,11 @@ void CVarManager::on_pre_engine_tick(sdk::UGameEngine* engine, float delta) { cvar->update(); cvar->freeze(); } + + if(m_should_execute_console_script) { + execute_console_script(engine, user_script_txt_name.data()); + m_should_execute_console_script = false; + } } void CVarManager::on_draw_ui() { @@ -168,6 +174,9 @@ void CVarManager::on_config_load(const utility::Config& cfg, bool set_defaults) } // TODO: Add arbitrary cvars from the other configs the user can add. + + // calling UEngine::exec here causes a crash, defer to on_pre_engine_tick() + m_should_execute_console_script = true; } void CVarManager::dump_commands() { @@ -758,4 +767,66 @@ void CVarManager::CVarData::draw_ui() try { } } catch (...) { ImGui::TextWrapped("Failed to read cvar data: %s", utility::narrow(m_name).c_str()); -} \ No newline at end of file +} + +static inline void trim(std::string &s) { + s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) { + return !std::isspace(ch); + })); + + s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) { + return !std::isspace(ch); + }).base(), s.end()); +} + +void CVarManager::execute_console_script(sdk::UGameEngine* engine, const std::string& filename) { + ZoneScopedN(__FUNCTION__); + + if(!engine) { + spdlog::error("[execute_console_script] engine is null"); + return; + } + + 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); ) { + trim(line); + + // handle comments + if(line.starts_with('#') || line.starts_with(';')) { + continue; + } + + if(line.contains('#')) { + line = line.substr(0, line.find_first_of('#')); + trim(line); + } + + if(line.contains(';')) { + line = line.substr(0, line.find_first_of(';')); + trim(line); + } + + if(line.length() == 0) { + continue; + } + + spdlog::debug("[execute_console_script] Attempting to execute \"{}\"", line); + engine->exec(utility::widen(line)); + } + + spdlog::debug("[execute_console_script] done"); +} diff --git a/src/mods/vr/CVarManager.hpp b/src/mods/vr/CVarManager.hpp index d683d328..94d94b5e 100644 --- a/src/mods/vr/CVarManager.hpp +++ b/src/mods/vr/CVarManager.hpp @@ -23,6 +23,8 @@ class CVarManager final : public ModComponent { void dump_commands(); void spawn_console(); + void execute_console_script(sdk::UGameEngine* engine, const std::string& filename); + bool is_hzbo_frozen_and_enabled() const { if (m_hzbo == nullptr) { return false; @@ -194,6 +196,7 @@ class CVarManager final : public ModComponent { bool m_wants_display_console{false}; bool m_native_console_spawned{false}; + bool m_should_execute_console_script{false}; static inline std::vector> s_default_standard_cvars { // Bools