diff --git a/.gitmodules b/.gitmodules index a01239c..d5ff308 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "thirdparty/dxbc"] path = thirdparty/dxbc url = https://github.com/PancakeTAS/dxbc.git +[submodule "thirdparty/toml11"] + path = thirdparty/toml11 + url = https://github.com/ToruNiina/toml11 diff --git a/CMakeLists.txt b/CMakeLists.txt index 0eb28de..38cbcd3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,8 +16,10 @@ add_compile_options(-fPIC add_subdirectory(thirdparty/dxbc EXCLUDE_FROM_ALL) add_subdirectory(thirdparty/pe-parse/pe-parser-library EXCLUDE_FROM_ALL) +add_subdirectory(thirdparty/toml11 EXCLUDE_FROM_ALL) add_subdirectory(framegen) + # main project project(lsfg-vk VERSION 0.0.1 @@ -41,7 +43,10 @@ set_target_properties(lsfg-vk PROPERTIES target_include_directories(lsfg-vk PRIVATE include) target_link_libraries(lsfg-vk PRIVATE - lsfg-vk-framegen pe-parse dxbc vulkan) + lsfg-vk-framegen pe-parse dxbc toml11 vulkan) + +get_target_property(TOML11_INCLUDE_DIRS toml11 INTERFACE_INCLUDE_DIRECTORIES) +target_include_directories(lsfg-vk SYSTEM PRIVATE ${TOML11_INCLUDE_DIRS}) if(CMAKE_BUILD_TYPE STREQUAL "Release") set_target_properties(lsfg-vk PROPERTIES diff --git a/framegen/include/common/exception.hpp b/framegen/include/common/exception.hpp index 3808ca8..a0e6a35 100644 --- a/framegen/include/common/exception.hpp +++ b/framegen/include/common/exception.hpp @@ -2,6 +2,7 @@ #include +#include #include #include @@ -31,4 +32,31 @@ namespace LSFG { VkResult result; }; + /// Simple exception class for stacking errors. + class rethrowable_error : public std::runtime_error { + public: + /// + /// Construct a new rethrowable_error with a message. + /// + /// @param message The error message. + /// @param exe The original exception to rethrow. + /// + explicit rethrowable_error(const std::string& message, + const std::exception& exe); + + /// Get the exception as a string. + [[nodiscard]] const char* what() const noexcept override { + return message.c_str(); + } + + // Trivially copyable, moveable and destructible + rethrowable_error(const rethrowable_error&) = default; + rethrowable_error(rethrowable_error&&) = default; + rethrowable_error& operator=(const rethrowable_error&) = default; + rethrowable_error& operator=(rethrowable_error&&) = default; + ~rethrowable_error() noexcept override; + private: + std::string message; + }; + } diff --git a/framegen/src/common/exception.cpp b/framegen/src/common/exception.cpp index f675833..5ea7b0d 100644 --- a/framegen/src/common/exception.cpp +++ b/framegen/src/common/exception.cpp @@ -2,6 +2,7 @@ #include +#include #include #include #include @@ -14,3 +15,10 @@ vulkan_error::vulkan_error(VkResult result, const std::string& message) result(result) {} vulkan_error::~vulkan_error() noexcept = default; + +rethrowable_error::rethrowable_error(const std::string& message, const std::exception& exe) + : std::runtime_error(message) { + this->message = std::format("{}\n- {}", message, exe.what()); +} + +rethrowable_error::~rethrowable_error() noexcept = default; diff --git a/include/config/config.hpp b/include/config/config.hpp index 733330b..fd63222 100644 --- a/include/config/config.hpp +++ b/include/config/config.hpp @@ -8,7 +8,7 @@ namespace Config { - /// lsfg-vk configuration. + /// lsfg-vk configuration struct Configuration { /// Whether lsfg-vk should be loaded in the first place. bool enable{false}; diff --git a/src/config/config.cpp b/src/config/config.cpp index c6b856f..e026c7a 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -1,15 +1,99 @@ #include "config/config.hpp" +#include "common/exception.hpp" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include using namespace Config; -const Configuration defaultConf{ - .enable = false -}; +namespace { + Configuration globalConf{}; + std::optional> gameConfs; +} bool Config::loadAndWatchConfig(const std::string& file) { + // parse config file + toml::value toml{}; + if (std::filesystem::exists(file)) { + try { + toml = toml::parse(file); + } catch (const std::exception& e) { + throw LSFG::rethrowable_error("Unable to parse configuration file", e); + } + } + + // parse global configuration + auto& global = globalConf; + const toml::value globalTable = toml::find_or_default(toml, "global"); + global.enable = toml::find_or(globalTable, "enable", false); + global.dll = toml::find_or(globalTable, "dll", std::string()); + global.multiplier = toml::find_or(globalTable, "multiplier", size_t(2)); + global.flowScale = toml::find_or(globalTable, "flow_scale", 1.0F); + global.performance = toml::find_or(globalTable, "performance_mode", false); + global.hdr = toml::find_or(globalTable, "hdr_mode", false); + global.valid = std::make_shared(true); + + // validate global configuration + if (global.multiplier < 2) + throw std::runtime_error("Multiplier cannot be less than 2"); + if (global.flowScale < 0.25F || global.flowScale > 1.0F) + throw std::runtime_error("Flow scale must be between 0.25 and 1.0"); + + // parse game-specific configuration + auto& games = gameConfs.emplace(); + const toml::value gamesList = toml::find_or_default(toml, "game"); + for (const auto& gameTable : gamesList.as_array()) { + if (!gameTable.is_table()) + throw std::runtime_error("Invalid game configuration entry"); + if (!gameTable.contains("exe")) + throw std::runtime_error("Game override missing 'exe' field"); + + const std::string exe = toml::find(gameTable, "exe"); + Configuration game{ + .enable = toml::find_or(gameTable, "enable", global.enable), + .dll = toml::find_or(gameTable, "dll", global.dll), + .multiplier = toml::find_or(gameTable, "multiplier", global.multiplier), + .flowScale = toml::find_or(gameTable, "flow_scale", global.flowScale), + .performance = toml::find_or(gameTable, "performance_mode", global.performance), + .hdr = toml::find_or(gameTable, "hdr_mode", global.hdr), + .valid = global.valid // only need a single validity flag + }; + + // validate the configuration + if (game.multiplier < 2) + throw std::runtime_error("Multiplier cannot be less than 2"); + if (game.flowScale < 0.25F || game.flowScale > 1.0F) + throw std::runtime_error("Flow scale must be between 0.25 and 1.0"); + games[exe] = std::move(game); + } + + // prepare config watcher + // (TODO) + return false; } Configuration Config::getConfig(std::string_view name) { - return defaultConf; + if (name.empty() || !gameConfs.has_value()) + return globalConf; + + const auto& games = *gameConfs; + auto it = games.find(std::string(name)); + if (it != games.end()) + return it->second; + + return globalConf; } diff --git a/src/main.cpp b/src/main.cpp index d35d624..26afa20 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -46,8 +46,8 @@ namespace { return{configFile}; const char* homePath = std::getenv("HOME"); if (homePath && *homePath != '\0') - return std::string(homePath) + "/.config/lsfg-vk.conf"; - return "/etc/lsfg-vk.conf"; + return std::string(homePath) + "/.config/lsfg-vk.toml"; + return "/etc/lsfg-vk.toml"; } __attribute__((constructor)) void lsfgvk_init() { @@ -57,6 +57,7 @@ namespace { exit(0); } + // TODO: health check, maybe? std::cerr << "lsfg-vk: This library is not meant to be preloaded, unless you are running a benchmark.\n"; exit(1); } @@ -66,8 +67,8 @@ namespace { try { Config::loadAndWatchConfig(file); } catch (const std::exception& e) { - std::cerr << "lsfg-vk: Unable to read configuration file, exiting." << '\n'; - std::cerr << e.what() << '\n'; + std::cerr << "lsfg-vk: An error occured while trying to parse the configuration, exiting:" << '\n'; + std::cerr << "- " << e.what() << '\n'; exit(0); } @@ -87,10 +88,10 @@ namespace { // print config std::cerr << "lsfg-vk: Loaded configuration for " << name << ":\n"; - std::cerr << "lsfg-vk: Using DLL from: " << conf.dll << '\n'; - std::cerr << "lsfg-vk: Multiplier: " << conf.multiplier << '\n'; - std::cerr << "lsfg-vk: Flow Scale: " << conf.flowScale << '\n'; - std::cerr << "lsfg-vk: Performance Mode: " << (conf.performance ? "Enabled" : "Disabled") << '\n'; - std::cerr << "lsfg-vk: HDR: " << (conf.hdr ? "Enabled" : "Disabled") << '\n'; + if (!conf.dll.empty()) std::cerr << " Using DLL from: " << conf.dll << '\n'; + std::cerr << " Multiplier: " << conf.multiplier << '\n'; + std::cerr << " Flow Scale: " << conf.flowScale << '\n'; + std::cerr << " Performance Mode: " << (conf.performance ? "Enabled" : "Disabled") << '\n'; + std::cerr << " HDR Mode: " << (conf.hdr ? "Enabled" : "Disabled") << '\n'; } } diff --git a/thirdparty/toml11 b/thirdparty/toml11 new file mode 160000 index 0000000..be08ba2 --- /dev/null +++ b/thirdparty/toml11 @@ -0,0 +1 @@ +Subproject commit be08ba2be2a964edcdb3d3e3ea8d100abc26f286