From 824263fa6ebc6264e74cab303cf1fdf607d10094 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 4 Apr 2026 22:47:48 +0200 Subject: [PATCH 01/11] Config system v1 Roughly inspired by what I've learned from my work on Space Station 14, without some of the unnecessary cruft and complexity. Implementation is relatively simple once I figured out all the template order shenanigans. --- CMakeLists.txt | 8 +- files.cmake | 6 + include/dusk/appname.hpp | 20 ++ include/dusk/config.hpp | 114 +++++++++++ include/dusk/config_var.hpp | 228 ++++++++++++++++++++++ include/dusk/io.hpp | 75 ++++++++ include/dusk/scope.hpp | 29 +++ include/dusk/settings.hpp | 24 +++ include/m_Do/m_Do_controller_pad.h | 5 +- src/d/actor/d_a_alink.cpp | 8 +- src/d/actor/d_a_alink_demo.inc | 4 +- src/d/actor/d_a_alink_dusk.cpp | 2 +- src/d/actor/d_a_alink_hvyboots.inc | 2 +- src/d/d_camera.cpp | 2 +- src/d/d_kankyo.cpp | 2 +- src/dusk/config.cpp | 230 +++++++++++++++++++++++ src/dusk/imgui/ImGuiConfig.hpp | 17 ++ src/dusk/imgui/ImGuiMenuEnhancements.cpp | 25 +-- src/dusk/imgui/ImGuiMenuEnhancements.hpp | 12 -- src/dusk/io.cpp | 143 ++++++++++++++ src/dusk/settings.cpp | 28 +++ src/m_Do/m_Do_graphic.cpp | 4 +- src/m_Do/m_Do_main.cpp | 39 +++- 23 files changed, 984 insertions(+), 43 deletions(-) create mode 100644 include/dusk/appname.hpp create mode 100644 include/dusk/config.hpp create mode 100644 include/dusk/config_var.hpp create mode 100644 include/dusk/io.hpp create mode 100644 include/dusk/scope.hpp create mode 100644 include/dusk/settings.hpp create mode 100644 src/dusk/config.cpp create mode 100644 src/dusk/imgui/ImGuiConfig.hpp create mode 100644 src/dusk/io.cpp create mode 100644 src/dusk/settings.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e0cf678839..9e2be043ee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -69,6 +69,12 @@ FetchContent_Declare( ) FetchContent_MakeAvailable(cxxopts) +FetchContent_Declare(json + URL https://github.com/nlohmann/json/releases/download/v3.12.0/json.tar.xz + URL_HASH SHA256=42f6e95cad6ec532fd372391373363b62a14af6d771056dbfc86160e6dfff7aa + DOWNLOAD_EXTRACT_TIMESTAMP TRUE +) +FetchContent_MakeAvailable(json) include(files.cmake) @@ -108,7 +114,7 @@ target_include_directories(game_debug PUBLIC extern ${CMAKE_SOURCE_DIR}/build/${DUSK_TP_VERSION}/include build/${DUSK_TP_VERSION}/include) -target_link_libraries(game_debug PUBLIC aurora::core aurora::gx aurora::gd aurora::si aurora::vi aurora::pad aurora::mtx aurora::os aurora::dvd aurora::card) +target_link_libraries(game_debug PUBLIC aurora::core aurora::gx aurora::gd aurora::si aurora::vi aurora::pad aurora::mtx aurora::os aurora::dvd aurora::card nlohmann_json::nlohmann_json) set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) add_library(game SHARED ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${SSYSTEM_FILES} ${JSYSTEM_FILES} ${REL_FILES} ${DUSK_FILES} ${DOLPHIN_FILES} diff --git a/files.cmake b/files.cmake index 26cae0f1a9..8a9e7be4d5 100644 --- a/files.cmake +++ b/files.cmake @@ -1332,16 +1332,22 @@ set(DOLPHIN_FILES set(DUSK_FILES include/dusk/endian_gx.hpp + include/dusk/config.hpp + include/dusk/settings.hpp src/d/actor/d_a_alink_dusk.cpp src/dusk/asserts.cpp + src/dusk/config.cpp + src/dusk/settings.cpp src/dusk/logging.cpp src/dusk/layout.cpp src/dusk/stubs.cpp src/dusk/endian.cpp src/dusk/extras.c src/dusk/extras.cpp + src/dusk/io.cpp src/dusk/globals.cpp #src/dusk/m_Do_ext_dusk.cpp + src/dusk/imgui/ImGuiConfig.hpp src/dusk/imgui/ImGuiConsole.hpp src/dusk/imgui/ImGuiConsole.cpp src/dusk/imgui/ImGuiMenuGame.cpp diff --git a/include/dusk/appname.hpp b/include/dusk/appname.hpp new file mode 100644 index 0000000000..d5d5f40083 --- /dev/null +++ b/include/dusk/appname.hpp @@ -0,0 +1,20 @@ +#ifndef DUSK_APPNAME_HPP +#define DUSK_APPNAME_HPP + +namespace dusk { + /** + * \brief The internal application name for the game. + * + * This gets used for file paths and such, and cannot be changed! + */ + constexpr auto AppName = "Dusk"; + + /** + * \brief The internal organization name for the game. + * + * This gets used for file paths and such, and cannot be changed! + */ + constexpr auto OrgName = "TwilitRealm"; +} + +#endif // DUSK_APPNAME_HPP diff --git a/include/dusk/config.hpp b/include/dusk/config.hpp new file mode 100644 index 0000000000..5cd0807b3b --- /dev/null +++ b/include/dusk/config.hpp @@ -0,0 +1,114 @@ +#ifndef DUSK_CONFIG_HPP +#define DUSK_CONFIG_HPP + +#include +#include "nlohmann/json.hpp" +#include "config_var.hpp" + +namespace dusk::config { + +/** + * \brief Base class containing virtual functions used for save/load of CVars. + */ +class ConfigImplBase { +protected: + virtual ~ConfigImplBase() = default; + +public: + /** + * \brief Load a JSON value into a CVar at the Value layer. + */ + virtual void loadFromJson(ConfigVarBase& cVar, const nlohmann::json& jsonValue) const = 0; + + /** + * \brief Load a simple launch argument into the CVar at the Override layer. + */ + virtual void loadFromArg(ConfigVarBase& cVar, std::string_view stringValue) const = 0; + + /** + * \brief Dump the value contained in the CVar to JSON. + */ + [[nodiscard]] virtual nlohmann::json dumpToJson(const ConfigVarBase& cVar) const = 0; +}; + +template +class ConfigImpl : public ConfigImplBase { + // Just downcasting the references... + void loadFromJson(ConfigVarBase& cVar, const nlohmann::json& jsonValue) const final { + assert(typeid(cVar) == typeid(ConfigVar)); + loadFromJson(dynamic_cast&>(cVar), jsonValue); + } + + void loadFromArg(ConfigVarBase& cVar, std::string_view stringValue) const final { + assert(typeid(cVar) == typeid(ConfigVar)); + loadFromArg(dynamic_cast&>(cVar), stringValue); + } + + [[nodiscard]] nlohmann::json dumpToJson(const ConfigVarBase& cVar) const final { + assert(typeid(cVar) == typeid(ConfigVar)); + return dumpToJson(dynamic_cast&>(cVar)); + } + + /** + * \brief Load a JSON value into a CVar at the Value layer. + */ + static void loadFromJson(ConfigVar& cVar, const nlohmann::json& jsonValue); + + /** + * \brief Load a simple launch argument into the CVar at the Override layer. + */ + static void loadFromArg(ConfigVar& cVar, std::string_view stringValue); + + /** + * \brief Dump the value contained in the CVar to JSON. + */ + [[nodiscard]] static nlohmann::json dumpToJson(const ConfigVar& cVar); +}; + +/** + * \brief Thrown by config loading functions if the value provided is invalid for the CVar. + */ +class InvalidConfigError : public std::runtime_error { +public: + explicit InvalidConfigError(const char* what) : runtime_error(what) {} +}; + +/** + * \brief Register a CVar to make the config system aware of it. + * + * This must be done on startup *before* config has been loaded. + */ +void Register(ConfigVarBase& configVar); + +/** + * \brief Indicate that all registrations have happened and everything should lock in. + */ +void FinishRegistration(); + +/** + * \brief Load config from the standard user preferences location. + */ +void LoadFromUserPreferences(); +void LoadFromFileName(const char* path); + +/** + * \brief Save the config to file. + */ +void Save(); + +/** + * \brief Get a registered CVar by name. + * + * @return null if the CVar does not exist. + */ +ConfigVarBase* GetConfigVar(std::string_view name); + +template +const ConfigImplBase* GetConfigImpl() { + static ConfigImpl config; + return &config; +} + +} // namespace dusk::config + +#endif diff --git a/include/dusk/config_var.hpp b/include/dusk/config_var.hpp new file mode 100644 index 0000000000..b775aa0584 --- /dev/null +++ b/include/dusk/config_var.hpp @@ -0,0 +1,228 @@ +#ifndef DUSK_CONFIG_VAR_HPP +#define DUSK_CONFIG_VAR_HPP + +#include "dolphin/types.h" +#include +#include +#include + +/** + * The configuration system. + * + * Configuration works via "configuration variables" aka "CVars". Each stores a single value that + * may be individually written to/from a configuration file. + * + * CVars, like ogres, have layers. Higher layers (e.g. a set value) override lower layers + * (e.g. the default value). + * + * To define a CVar, simply make a global variable of type ConfigVar, + * and make sure Register() is called on it during program startup. + * + * config_var.hpp contains the simplest "just the configuration vars themselves". + * This should be safe to include for files that need to *access* configuration, + * without blowing up compile times on implementation details. + * + * config.hpp on the other hand contains far more calls for mutating, loading, and defining CVars. + */ +namespace dusk::config { + +/** + * \brief Layers that a configuration variable can currently be at. + * + * A configuration variable can be on one of multiple *layers*, which determines where + * the current value is coming from. + */ +enum class ConfigVarLayer : u8 { + /** + * The CVar is at the default value defined by the application code. + */ + Default, + + /** + * The CVar has been modified by the user and may be saved to config. + */ + Value, + + /** + * The CVar is modified by launch argument, overruling the normal config value. + * Will not get saved to config. + */ + Override, +}; + +class ConfigImplBase; + +/** + * Base class that all CVars inherit from. + * You want the templated ConfigVar instead for actual usage. + */ +class ConfigVarBase { +protected: + /** + * The name of this CVar, used in the configuration file. + */ + const char* name; + + /** + * Whether this CVar has been registered with the global managing logic. + * If this is not done, it is not functional. + */ + bool registered; + + /** + * The layer this CVar is at. + */ + ConfigVarLayer layer; + + /** + * Pointer to an implementation struct for various load/save calls. + */ + const ConfigImplBase* impl; + + ConfigVarBase(const char* name, const ConfigImplBase* impl); + virtual ~ConfigVarBase() = default; + + /** + * Check that the CVar is registered, aborting if this is not the case. + */ + void checkRegistered() const { + if (!registered) + abort(); + } + +public: + /** + * Get the name of this CVar, used in the configuration file. + */ + [[nodiscard]] const char* getName() const noexcept; + + /** + * Get the pointer to the implementation struct. + */ + [[nodiscard]] const ConfigImplBase* getImpl() const noexcept; + + /** + * Get the layer this CVar is currently at. + */ + [[nodiscard]] constexpr ConfigVarLayer getLayer() const noexcept { + return layer; + } + + /** + * Mark this CVar as being registered with the central save/load logic. + * This is necessary to make it legal to access. + */ + void markRegistered(); +}; + +template +concept ConfigValueInteger = + std::is_same_v + || std::is_same_v + || std::is_same_v + || std::is_same_v + || std::is_same_v + || std::is_same_v + || std::is_same_v + || std::is_same_v; + +/** + * \brief Concept that defines the legal set of types that can be used for CVar values. + * + * Valid types cannot be cv-qualified and must be basic primitive types (int, float, bool), + * strings, or enums of the basic primitives. + */ +template +concept ConfigValue = + !std::is_const_v + && !std::is_volatile_v + && (std::is_same_v + || ConfigValueInteger + || std::is_same_v + || std::is_same_v + || std::is_same_v + || (std::is_enum_v && ConfigValueInteger>)); + +template +const ConfigImplBase* GetConfigImpl(); + +/** + * \brief A CVar storing values. + * + * @tparam T The type of value stored in the CVar. + */ +template +class ConfigVar : public ConfigVarBase { + T defaultValue; + T value; + T overrideValue; + +public: + /** + * \brief Construct a CVar. + * + * @param name The name of this CVar. Must be unique. + * @param arg Arguments to forward to construct the default value. + */ + template + explicit ConfigVar(const char* name, Args&&... arg) + : ConfigVarBase(name, GetConfigImpl()), defaultValue(std::forward(arg)...), + value(), overrideValue() {} + + /** + * \brief Get the current value of the CVar. + */ + [[nodiscard]] constexpr const T& getValue() const noexcept { + checkRegistered(); + switch (layer) { + case ConfigVarLayer::Default: + return defaultValue; + case ConfigVarLayer::Value: + return value; + case ConfigVarLayer::Override: + return overrideValue; + default: + abort(); + } + } + + /** + * \brief Change the value of a CVar. + * + * The new value is always stored at the Value layer. + * + * @param newValue The new value the CVar will get. + * @param replaceOverride If true, clear an existing override layer if there is one. + * If this is false and there is an override layer, + * the result of getValue() will not change immediately. + */ + void setValue(T newValue, bool replaceOverride = true) { + checkRegistered(); + value = std::move(newValue); + + if (replaceOverride) { + overrideValue = {}; + layer = ConfigVarLayer::Value; + } else if (layer != ConfigVarLayer::Override) { + layer = ConfigVarLayer::Value; + } + } + + /** + * \brief Give a CVar an override value. + * + * This overrides (but does not replace) the apparent set value of this CVar. + * The overriden value will not get saved to config. + * + * @param newValue The new value the CVar will get. + */ + void setOverrideValue(T newValue) { + checkRegistered(); + overrideValue = std::move(newValue); + layer = ConfigVarLayer::Override; + } +}; + +} + +#endif // DUSK_CONFIG_VAR_HPP diff --git a/include/dusk/io.hpp b/include/dusk/io.hpp new file mode 100644 index 0000000000..fb71a77d68 --- /dev/null +++ b/include/dusk/io.hpp @@ -0,0 +1,75 @@ +#ifndef DUSK_IO_HPP +#define DUSK_IO_HPP + +#include + +// I can't believe it's 2026 and neither SDL (no error codes) nor +// C++ (no error codes) have a file system API functional enough for me to use. +// Here you go, this one's inspired by C#. I only wrote the functions I need. + +namespace dusk::io { + +/** + * \brief A simple file stream wrapping cstdio FILE*. + * + * Methods on this class throw appropriate C++ exceptions when an error occurs. + */ +class FileStream { + void* file; + +public: + FileStream() noexcept; + + /** + * \brief Take ownership of a FILE* handle. + */ + explicit FileStream(void* file); + FileStream(const FileStream& other) = delete; + FileStream(FileStream&& other) noexcept; + + ~FileStream(); + + /** + * \brief Open a file for reading at the given path. + */ + static FileStream OpenRead(const char* utf8Path); + + /** + * \brief Create a file for writing. + * + * If there is an existing file, its contents are demolished. + */ + static FileStream Create(const char* utf8Path); + + /** + * \brief Read the byte contents of a file directly into a vector. + */ + static std::vector ReadAllBytes(const char* utf8Path); + + /** + * \brief Read the byte contents of a file directly into a vector. + */ + static void WriteAllText(const char* utf8Path, std::string_view text); + + /** + * \brief Read the remaining contents of the file directly into a vector. + */ + std::vector ReadFull(); + + /** + * Get direct access to the underlying FILE* handle. + */ + [[nodiscard]] void* GetFileHandle() const noexcept { + return file; + } + + /** + * Write data to the file. + */ + void Write(const char* data, size_t dataLen); +}; + +} + + +#endif // DUSK_IO_HPP diff --git a/include/dusk/scope.hpp b/include/dusk/scope.hpp new file mode 100644 index 0000000000..e1cfcb9a58 --- /dev/null +++ b/include/dusk/scope.hpp @@ -0,0 +1,29 @@ +#ifndef DUSK_SCOPE_HPP +#define DUSK_SCOPE_HPP + +namespace dusk { + +/** + * A simple value wrapper that will destroy the value at the end of its scope. + * @tparam T The type of value contained. + * @tparam Destructor The type of function used to destroy the value. + */ +template + struct ScopeValue { + T value; + Destructor destructor; + + explicit ScopeValue(T value, Destructor destructor) : value(value), destructor(destructor) { + } + + ~ScopeValue() { + destructor(value); + } + + constexpr operator T&() const noexcept { + return value; + } + }; +} + +#endif // DUSK_SCOPE_HPP diff --git a/include/dusk/settings.hpp b/include/dusk/settings.hpp new file mode 100644 index 0000000000..79c9981647 --- /dev/null +++ b/include/dusk/settings.hpp @@ -0,0 +1,24 @@ +#ifndef DUSK_SETTINGS_HPP +#define DUSK_SETTINGS_HPP + +#include "config_var.hpp" + +namespace dusk::settings { + using namespace dusk::config; + + namespace enhancements { + extern ConfigVar FastIronBoots; + extern ConfigVar InvertCameraXAxis; + extern ConfigVar QuickTransform; + extern ConfigVar RestoreWiiGlitches; + extern ConfigVar EnableBloom; + extern ConfigVar UseWaterProjectionOffset; + extern ConfigVar MirrorMode; + + void Register(); + } + + void Register(); +} + +#endif // DUSK_SETTINGS_HPP diff --git a/include/m_Do/m_Do_controller_pad.h b/include/m_Do/m_Do_controller_pad.h index b8cee23ca3..57de651bc0 100644 --- a/include/m_Do/m_Do_controller_pad.h +++ b/include/m_Do/m_Do_controller_pad.h @@ -3,6 +3,7 @@ #include "JSystem/JUtility/JUTGamePad.h" #include "SSystem/SComponent/c_API_controller_pad.h" +#include "dusk/settings.hpp" #include "dusk/imgui/ImGuiMenuEnhancements.hpp" @@ -57,7 +58,7 @@ public: static s16 getStickAngle3D(u32 pad) { #if TARGET_PC - if (dusk::ImGuiMenuEnhancements::m_enhancements.mirrorMode) { + if (dusk::settings::enhancements::MirrorMode.getValue()) { return -getCpadInfo(pad).mMainStickAngle; } else { return getCpadInfo(pad).mMainStickAngle; @@ -69,7 +70,7 @@ public: static f32 getSubStickX3D(u32 pad) { #if TARGET_PC - if (dusk::ImGuiMenuEnhancements::m_enhancements.mirrorMode) { + if (dusk::settings::enhancements::MirrorMode.getValue()) { return -getCpadInfo(pad).mCStickPosX; } else { return getCpadInfo(pad).mCStickPosX; diff --git a/src/d/actor/d_a_alink.cpp b/src/d/actor/d_a_alink.cpp index 2c5bcf46cd..f847fde1cb 100644 --- a/src/d/actor/d_a_alink.cpp +++ b/src/d/actor/d_a_alink.cpp @@ -54,6 +54,8 @@ #include "res/Object/Alink.h" #include +#include "dusk/settings.hpp" + static int daAlink_Create(fopAc_ac_c* i_this); static int daAlink_Delete(daAlink_c* i_this); static int daAlink_Execute(daAlink_c* i_this); @@ -7510,7 +7512,7 @@ void daAlink_c::setBlendMoveAnime(f32 i_morf) { BOOL sp24 = checkEventRun(); BOOL sp20 = checkBootsMoveAnime(1); #if TARGET_PC - if (dusk::ImGuiMenuEnhancements::m_enhancements.fastIronBoots) { + if (dusk::settings::enhancements::FastIronBoots.getValue()) { sp20 = FALSE; } #endif @@ -9475,7 +9477,7 @@ void daAlink_c::setStickData() { mHeavySpeedMultiplier = mpHIO->mItem.mIronBoots.m.mInputFactor; } #if TARGET_PC - if (dusk::ImGuiMenuEnhancements::m_enhancements.fastIronBoots) { + if (dusk::settings::enhancements::FastIronBoots.getValue()) { mHeavySpeedMultiplier = 1.0f; } #endif @@ -9487,7 +9489,7 @@ void daAlink_c::setStickData() { mHeavySpeedMultiplier = mpHIO->mItem.mIronBoots.m.mWaterInputFactor; } #if TARGET_PC - if (dusk::ImGuiMenuEnhancements::m_enhancements.fastIronBoots) { + if (dusk::settings::enhancements::FastIronBoots.getValue()) { mHeavySpeedMultiplier = 1.0f; } #endif diff --git a/src/d/actor/d_a_alink_demo.inc b/src/d/actor/d_a_alink_demo.inc index 7acccbde13..936b0128d5 100644 --- a/src/d/actor/d_a_alink_demo.inc +++ b/src/d/actor/d_a_alink_demo.inc @@ -4301,7 +4301,7 @@ bool daAlink_c::checkAcceptWarp() { */ if (mLinkAcch.ChkGroundHit() && !checkModeFlg(MODE_PLAYER_FLY) #if TARGET_PC - && (dusk::ImGuiMenuEnhancements::m_enhancements.restoreWiiGlitches || !checkNoResetFlg0(FLG0_WATER_IN_MOVE)) + && (dusk::settings::enhancements::RestoreWiiGlitches.getValue() || !checkNoResetFlg0(FLG0_WATER_IN_MOVE)) #elif VERSION != VERSION_WII_USA_R0 && !checkNoResetFlg0(FLG0_WATER_IN_MOVE) #endif @@ -4312,7 +4312,7 @@ bool daAlink_c::checkAcceptWarp() { */ if ( #if TARGET_PC - (dusk::ImGuiMenuEnhancements::m_enhancements.restoreWiiGlitches || !getSlidePolygon(&plane)) && + (dusk::settings::enhancements::RestoreWiiGlitches.getValue() || !getSlidePolygon(&plane)) && #elif VERSION != VERSION_WII_USA_R0 !getSlidePolygon(&plane) && #endif diff --git a/src/d/actor/d_a_alink_dusk.cpp b/src/d/actor/d_a_alink_dusk.cpp index 47501d4f3d..f49443a0d0 100644 --- a/src/d/actor/d_a_alink_dusk.cpp +++ b/src/d/actor/d_a_alink_dusk.cpp @@ -6,7 +6,7 @@ #include "dusk/imgui/ImGuiMenuEnhancements.hpp" void daAlink_c::handleQuickTransform() { - if (!dusk::ImGuiMenuEnhancements::m_enhancements.quickTransform) { + if (!dusk::settings::enhancements::QuickTransform.getValue()) { return; } diff --git a/src/d/actor/d_a_alink_hvyboots.inc b/src/d/actor/d_a_alink_hvyboots.inc index ed52ffcb36..af00c28b1b 100644 --- a/src/d/actor/d_a_alink_hvyboots.inc +++ b/src/d/actor/d_a_alink_hvyboots.inc @@ -351,7 +351,7 @@ int daAlink_c::procMagneBootsFly() { */ if (dComIfG_Bgsp().ChkPolySafe(mPolyInfo2) #if TARGET_PC - && (dusk::ImGuiMenuEnhancements::m_enhancements.restoreWiiGlitches || checkEquipHeavyBoots()) + && (dusk::settings::enhancements::RestoreWiiGlitches.getValue() || checkEquipHeavyBoots()) #elif PLATFORM_GCN || VERSION == VERSION_WII_KOR && checkEquipHeavyBoots() #endif diff --git a/src/d/d_camera.cpp b/src/d/d_camera.cpp index 59ed1d2c11..6e63d8b940 100644 --- a/src/d/d_camera.cpp +++ b/src/d/d_camera.cpp @@ -766,7 +766,7 @@ void dCamera_c::updatePad() { var_f31 = mDoCPd_c::getSubStickX3D(mPadID); #if TARGET_PC - if (dusk::ImGuiMenuEnhancements::m_enhancements.invertCameraXAxis) { + if (dusk::settings::enhancements::InvertCameraXAxis.getValue()) { var_f31 *= -1.0f; } #endif diff --git a/src/d/d_kankyo.cpp b/src/d/d_kankyo.cpp index a61219ffac..e772098eae 100644 --- a/src/d/d_kankyo.cpp +++ b/src/d/d_kankyo.cpp @@ -11381,7 +11381,7 @@ void dKy_bg_MAxx_proc(void* bg_model_p) { C_MTXLightPerspective(sp1D8, dComIfGd_getView()->fovy, camera_p->view.aspect, 1.0f, 1.0f, #if TARGET_PC - dusk::ImGuiMenuEnhancements::m_enhancements.useWaterProjectionOffset ? -0.01f : 0.0f, 0.0f); + dusk::settings::enhancements::UseWaterProjectionOffset.getValue() ? -0.01f : 0.0f, 0.0f); #else -0.01f, 0.0f); #endif diff --git a/src/dusk/config.cpp b/src/dusk/config.cpp new file mode 100644 index 0000000000..aeafb5ab56 --- /dev/null +++ b/src/dusk/config.cpp @@ -0,0 +1,230 @@ +#include "dusk/config.hpp" +#include "SDL3/SDL_filesystem.h" +#include "SDL3/SDL_iostream.h" +#include "dusk/appname.hpp" +#include "dusk/scope.hpp" +#include "fmt/format.h" +#include "nlohmann/json.hpp" +#include "absl/container/flat_hash_map.h" + +#include "aurora/lib/logging.hpp" +#include "dusk/io.hpp" + +#include +#include + +using namespace dusk::config; + +constexpr auto ConfigFileName = "config.json"; + +using json = nlohmann::json; + +aurora::Module DuskConfigLog("dusk::config"); + +static absl::flat_hash_map RegisteredConfigVars; +static bool RegistrationDone; + +static std::string GetConfigJsonPath() { + dusk::ScopeValue folder(SDL_GetPrefPath(dusk::OrgName, dusk::AppName), SDL_free); + if (folder.value == nullptr) { + DuskConfigLog.error("Failed to get user preference path: %s", SDL_GetError()); + return ""; + } + + return fmt::format("{}{}", folder.value, ConfigFileName); +} + +ConfigVarBase::ConfigVarBase(const char* name, const ConfigImplBase* impl) : name(name), registered(false), layer(ConfigVarLayer::Default), impl(impl) { +} + +const char* ConfigVarBase::getName() const noexcept { + return name; +} + +const ConfigImplBase* ConfigVarBase::getImpl() const noexcept { + return impl; +} + +template +void ConfigImpl::loadFromJson(ConfigVar& cVar, const json& jsonValue) { + cVar.setValue(jsonValue.get(), false); +} + +template +nlohmann::json ConfigImpl::dumpToJson(const ConfigVar& cVar) { + return cVar.getValue(); +} + +template requires std::is_integral_v && std::is_signed_v +static void loadFromArgImpl(ConfigVar& cVar, const std::string_view stringValue) { + const std::string str(stringValue); + const auto result = std::stoll(str); + if (result >= std::numeric_limits::min() && result <= std::numeric_limits::max()) { + cVar.setOverrideValue(result); + } else { + throw std::out_of_range("Value is too large"); + } +} + +template requires std::is_integral_v && std::is_unsigned_v +static void loadFromArgImpl(ConfigVar& cVar, const std::string_view stringValue) { + const std::string str(stringValue); + const auto result = std::stoull(str); + if (result <= std::numeric_limits::max()) { + cVar.setOverrideValue(result); + } else { + throw std::out_of_range("Value is too large"); + } +} + +static void loadFromArgImpl(ConfigVar& cVar, const std::string_view stringValue) { + const std::string str(stringValue); + const auto result = std::stof(str); + cVar.setOverrideValue(result); +} + +static void loadFromArgImpl(ConfigVar& cVar, const std::string_view stringValue) { + const std::string str(stringValue); + const auto result = std::stod(str); + cVar.setOverrideValue(result); +} + +static void loadFromArgImpl(ConfigVar& cVar, const std::string_view stringValue) { + cVar.setOverrideValue(std::string(stringValue)); +} + +template +void ConfigImpl::loadFromArg(ConfigVar& cVar, const std::string_view stringValue) { + loadFromArgImpl(cVar, stringValue); +} + +template<> +void ConfigImpl::loadFromArg(ConfigVar& cVar, const std::string_view stringValue) { + if (stringValue == "1" || stringValue == "TRUE" || stringValue == "true" || stringValue == "True") { + cVar.setOverrideValue(true); + } else if (stringValue == "0" || stringValue == "FALSE" || stringValue == "false" || stringValue == "False") { + cVar.setOverrideValue(false); + } else { + throw InvalidConfigError("Value cannot be parsed as boolean"); + } +} + +// My IDE is convinced this namespace is necessary. It shouldn't be AFAICT? +namespace dusk::config { + template class ConfigImpl; + template class ConfigImpl; + template class ConfigImpl; + template class ConfigImpl; + template class ConfigImpl; + template class ConfigImpl; + template class ConfigImpl; + template class ConfigImpl; + template class ConfigImpl; + template class ConfigImpl; + template class ConfigImpl; + template class ConfigImpl; +} + +void dusk::config::Register(ConfigVarBase& configVar) { + const auto& name = configVar.getName(); + if (RegistrationDone) { + DuskConfigLog.fatal("Tried to register CVar {} after registrations closed!", name); + } + + if (RegisteredConfigVars.contains(name)) { + DuskConfigLog.fatal("Tried to register CVar {} twice!", name); + } + + RegisteredConfigVars[name] = &configVar; + configVar.markRegistered(); +} + +void ConfigVarBase::markRegistered() { + if (registered) + abort(); + + registered = true; +} + +void dusk::config::FinishRegistration() { + RegistrationDone = true; +} + +void dusk::config::LoadFromUserPreferences() { + const auto configPath = GetConfigJsonPath(); + if (configPath.empty()) { + return; + } + LoadFromFileName(configPath.c_str()); +} + +static void LoadFromPath(const char* path) { + auto data = dusk::io::FileStream::ReadAllBytes(path); + + json j = json::parse(data); + if (!j.is_object()) { + DuskConfigLog.error("Config JSON is not an object!"); + return; + } + + for (const auto& el : j.items()) { + const auto& key = el.key(); + auto configVar = RegisteredConfigVars.find(key); + if (configVar == RegisteredConfigVars.end()) { + DuskConfigLog.error("Unknown key '{}' found in config!", key); + continue; + } + + try { + configVar->second->getImpl()->loadFromJson(*configVar->second, el.value()); + } catch (std::exception& e) { + DuskConfigLog.error("Failed to load key '{}' from config: {}", key, e.what()); + } + } +} + +void dusk::config::LoadFromFileName(const char* path) { + if (!RegistrationDone) { + DuskConfigLog.fatal("Registration not finished yet!"); + } + + DuskConfigLog.info("Loading config from '{}'", path); + + try { + LoadFromPath(path); + } catch (const std::system_error& e) { + if (e.code() == std::errc::no_such_file_or_directory) { + DuskConfigLog.info("Config file did not exist, staying with defaults"); + } else { + DuskConfigLog.error("Failed to load from config! {}", e.what()); + } + } +} + +void dusk::config::Save() { + const auto configPath = GetConfigJsonPath(); + if (configPath.empty()) { + return; + } + + DuskConfigLog.info("Saving config to '{}'", configPath); + + json j; + + for (const auto& pair : RegisteredConfigVars) { + if (pair.second->getLayer() == ConfigVarLayer::Value) { + j[pair.first] = pair.second->getImpl()->dumpToJson(*pair.second); + } + } + + io::FileStream::WriteAllText(configPath.c_str(), j.dump(4)); +} + +ConfigVarBase* dusk::config::GetConfigVar(std::string_view name) { + const auto configVar = RegisteredConfigVars.find(name); + if (configVar != RegisteredConfigVars.end()) { + return configVar->second; + } + + return nullptr; +} \ No newline at end of file diff --git a/src/dusk/imgui/ImGuiConfig.hpp b/src/dusk/imgui/ImGuiConfig.hpp new file mode 100644 index 0000000000..c4ecb61d77 --- /dev/null +++ b/src/dusk/imgui/ImGuiConfig.hpp @@ -0,0 +1,17 @@ +#ifndef DUSK_IMGUICONFIG_HPP +#define DUSK_IMGUICONFIG_HPP + +#include "dusk/config.hpp" +#include "imgui.h" + +namespace dusk::config { + inline void ImguiCheckbox(const char* title, ConfigVar& var) { + bool copy = var.getValue(); + if (ImGui::Checkbox(title, ©)) { + var.setValue(copy); + Save(); + } + } +} + +#endif // DUSK_IMGUICONFIG_HPP diff --git a/src/dusk/imgui/ImGuiMenuEnhancements.cpp b/src/dusk/imgui/ImGuiMenuEnhancements.cpp index 5ea315d8ec..c582bc2335 100644 --- a/src/dusk/imgui/ImGuiMenuEnhancements.cpp +++ b/src/dusk/imgui/ImGuiMenuEnhancements.cpp @@ -4,43 +4,36 @@ #include "ImGuiConsole.hpp" #include "ImGuiMenuEnhancements.hpp" +#include "ImGuiConfig.hpp" +#include "dusk/settings.hpp" #include namespace dusk { - EnhancementsSettings ImGuiMenuEnhancements::m_enhancements = { - .fastIronBoots = false, - .invertCameraXAxis = false, - .restoreWiiGlitches = false, - .enableBloom = true, - .useWaterProjectionOffset = false, - .mirrorMode = false, - }; - ImGuiMenuEnhancements::ImGuiMenuEnhancements() {} void ImGuiMenuEnhancements::draw() { if (ImGui::BeginMenu("Enhancements")) { if (ImGui::BeginMenu("Quality of Life")) { - ImGui::Checkbox("Fast Iron Boots", &m_enhancements.fastIronBoots); - ImGui::Checkbox("Invert Camera X Axis", &m_enhancements.invertCameraXAxis); - ImGui::Checkbox("Quick Transform (R+Y)", &m_enhancements.quickTransform); + config::ImguiCheckbox("Fast Iron Boots", settings::enhancements::FastIronBoots); + config::ImguiCheckbox("Invert Camera X Axis", settings::enhancements::InvertCameraXAxis); + config::ImguiCheckbox("Quick Transform (R+Y)", settings::enhancements::QuickTransform); ImGui::EndMenu(); } if (ImGui::BeginMenu("Graphics")) { - ImGui::Checkbox("Native Bloom", &m_enhancements.enableBloom); - ImGui::Checkbox("Water Projection Offset", &m_enhancements.useWaterProjectionOffset); + config::ImguiCheckbox("Native Bloom", settings::enhancements::EnableBloom); + config::ImguiCheckbox("Water Projection Offset", settings::enhancements::UseWaterProjectionOffset); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Adds GC-specific -0.01 transS offset\n" "that causes ~6px ghost artifacts in water reflections"); } - ImGui::Checkbox("Mirror Mode", &m_enhancements.mirrorMode); + config::ImguiCheckbox("Mirror Mode", settings::enhancements::MirrorMode); ImGui::EndMenu(); } if (ImGui::BeginMenu("Restorations")) { - ImGui::Checkbox("Restore Wii 1.0 Glitches", &m_enhancements.restoreWiiGlitches); + config::ImguiCheckbox("Restore Wii 1.0 Glitches", settings::enhancements::RestoreWiiGlitches); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Restores patched glitches from Wii USA 1.0, the first released version"); } diff --git a/src/dusk/imgui/ImGuiMenuEnhancements.hpp b/src/dusk/imgui/ImGuiMenuEnhancements.hpp index 0238317ce4..f40baaad65 100644 --- a/src/dusk/imgui/ImGuiMenuEnhancements.hpp +++ b/src/dusk/imgui/ImGuiMenuEnhancements.hpp @@ -8,22 +8,10 @@ #include "imgui.h" namespace dusk { - struct EnhancementsSettings { - bool fastIronBoots; - bool invertCameraXAxis; - bool quickTransform; - bool restoreWiiGlitches; - bool enableBloom; - bool useWaterProjectionOffset; - bool mirrorMode; - }; - class ImGuiMenuEnhancements { public: ImGuiMenuEnhancements(); void draw(); - - static EnhancementsSettings m_enhancements; }; } diff --git a/src/dusk/io.cpp b/src/dusk/io.cpp new file mode 100644 index 0000000000..f930c2ceaa --- /dev/null +++ b/src/dusk/io.cpp @@ -0,0 +1,143 @@ +#include "dusk/io.hpp" +#include +#include + +using namespace dusk::io; + +#if _WIN32 +#define MODE_TYPE wchar_t +#define MODE(val) L##val +#define ftell(file) _ftelli64(file) +#define fseek(a, b, c) _fseeki64(a, b, c) +#else +#define MODE_TYPE char +#define MODE(val) ##val +#endif + +static FILE* ThrowIfNotOpen(const FileStream& file) { + if (!file.GetFileHandle()) { + throw std::runtime_error("Invalid file handle!"); + } + + return static_cast(file.GetFileHandle()); +} + +[[noreturn]] static void ThrowForError(int code) { + throw std::system_error(std::make_error_code(static_cast(code))); +} + +static FILE* OpenCore(const std::filesystem::path& path, const MODE_TYPE* mode) { + FILE* file; + + int err; +#if _WIN32 + static_assert(std::is_same_v); + err = _wfopen_s(&file, path.c_str(), mode); +#else + errno = 0; + static_assert(std::is_same_v); + file = fopen(path.c_str(), mode); + err = errno; +#endif + + if (!file) { + ThrowForError(err); + } + + return file; +} + +static FILE* OpenCore(const char* path, const MODE_TYPE* mode) { + return OpenCore(reinterpret_cast(path), mode); +} + +FileStream::FileStream() noexcept : file(nullptr) { +} + +FileStream::FileStream(void* file) : file(file) { + if (!file) { + CRASH("Invalid file handle"); + } +} + +FileStream::FileStream(FileStream&& other) noexcept { + file = other.file; + other.file = nullptr; +} + +FileStream::~FileStream() { + if (file) + fclose(static_cast(file)); +} + +FileStream FileStream::OpenRead(const char* utf8Path) { + return FileStream(OpenCore(utf8Path, MODE("rb"))); +} + +FileStream FileStream::Create(const char* utf8Path) { + return FileStream(OpenCore(utf8Path, MODE("wb"))); +} + +std::vector FileStream::ReadFull() { + const auto fileHandle = ThrowIfNotOpen(*this); + + std::vector result; + + const auto startPos = ftell(fileHandle); + if (startPos == -1) { + ThrowForError(errno); + } + + auto seekRes = fseek(fileHandle, 0, SEEK_END); + if (seekRes != 0) { + ThrowForError(errno); + } + + const auto endPos = ftell(fileHandle); + if (endPos == -1) { + ThrowForError(errno); + } + + seekRes = fseek(fileHandle, startPos, SEEK_SET); + if (seekRes != 0) { + ThrowForError(errno); + } + + result.resize(endPos - startPos); + + if (result.empty()) { + return result; + } + + auto ret = fread(result.data(), 1, result.size(), fileHandle); + if (ret < result.size()) { + int err = errno; + // Error or EOF + if (feof(fileHandle)) { + result.resize(ret); + } else { + ThrowForError(err); + } + } + + return result; +} + +std::vector FileStream::ReadAllBytes(const char* utf8Path) { + auto handle = OpenRead(utf8Path); + return std::move(handle.ReadFull()); +} + +void FileStream::Write(const char* data, size_t dataLen) { + FILE* fileHandle = ThrowIfNotOpen(*this); + + const auto ret = fwrite(data, 1, dataLen, fileHandle); + if (ret < dataLen) { + ThrowForError(errno); + } +} + +void FileStream::WriteAllText(const char* utf8Path, const std::string_view text) { + auto handle = Create(utf8Path); + handle.Write(text.data(), text.size()); +} diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp new file mode 100644 index 0000000000..2be800b6a5 --- /dev/null +++ b/src/dusk/settings.cpp @@ -0,0 +1,28 @@ +#include "dusk/settings.hpp" +#include "dusk/config.hpp" + +namespace dusk::settings::enhancements { + ConfigVar FastIronBoots("enhancements.fast_iron_boots", false); + ConfigVar InvertCameraXAxis("enhancements.invert_camera_x_axis", false); + ConfigVar QuickTransform("enhancements.quick_transform", false); + ConfigVar RestoreWiiGlitches("enhancements.restore_wii_glitches", false); + ConfigVar EnableBloom("enhancements.enable_bloom", true); + ConfigVar UseWaterProjectionOffset("enhancements.use_water_projection_offset", false); + ConfigVar MirrorMode("enhancements.mirror_mode", false); + + void Register() { + Register(FastIronBoots); + Register(InvertCameraXAxis); + Register(QuickTransform); + Register(RestoreWiiGlitches); + Register(EnableBloom); + Register(UseWaterProjectionOffset); + Register(MirrorMode); + } +} + +namespace dusk::settings { + void Register() { + enhancements::Register(); + } +} diff --git a/src/m_Do/m_Do_graphic.cpp b/src/m_Do/m_Do_graphic.cpp index 06d6efd4eb..8ecc4bbb8f 100644 --- a/src/m_Do/m_Do_graphic.cpp +++ b/src/m_Do/m_Do_graphic.cpp @@ -1163,7 +1163,7 @@ void mDoGph_gInf_c::bloom_c::remove() { void mDoGph_gInf_c::bloom_c::draw() { #if TARGET_PC - if (!dusk::ImGuiMenuEnhancements::m_enhancements.enableBloom) { + if (!dusk::settings::enhancements::EnableBloom.getValue()) { return; } #endif @@ -2113,7 +2113,7 @@ int mDoGph_Painter() { #endif #if TARGET_PC - if (dusk::ImGuiMenuEnhancements::m_enhancements.mirrorMode) + if (dusk::settings::enhancements::MirrorMode.getValue()) #elif PLATFORM_WII if (data_8053a730) #endif diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index 03ff6b8c0f..b8d6fe9728 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -56,6 +56,8 @@ #include #include "cxxopts.hpp" +#include "dusk/config.hpp" +#include "dusk/settings.hpp" // --- GLOBALS --- s8 mDoMain::developmentMode = -1; @@ -225,10 +227,41 @@ static AuroraBackend ParseAuroraBackend(const std::string& value) { exit(1); } +static void ApplyCVarOverrides(const cxxopts::OptionValue& option) { + if (option.count() == 0) { + return; + } + + const auto& cVars = option.as>(); + for (const auto& cvarArg : cVars) { + const auto sep = cvarArg.find('='); + if (sep == std::string::npos) { + DuskLog.fatal("--cvar argument has no '=': '{}'", cvarArg); + continue; + } + + const auto name = std::string_view(cvarArg).substr(0, sep); + const auto value = std::string_view(cvarArg).substr(sep + 1); + + const auto cVar = dusk::config::GetConfigVar(name); + if (!cVar) { + DuskLog.fatal("Unknown --cvar name: '{}'", name); + } + + try { + cVar->getImpl()->loadFromArg(*cVar, value); + } catch (const std::exception& e) { + DuskLog.fatal("Unable to parse: '{}': {}", value, e.what()); + } + } +} + // ========================================================================= // PC ENTRY POINT // ========================================================================= int game_main(int argc, char* argv[]) { + dusk::settings::Register(); + cxxopts::ParseResult parsed_arg_options; try { @@ -238,7 +271,8 @@ int game_main(int argc, char* argv[]) { ("l,log-level", "Log level from " + std::to_string(AuroraLogLevel::LOG_DEBUG) + " to " + std::to_string(AuroraLogLevel::LOG_FATAL), cxxopts::value()->default_value("0")) ("h,help", "Print usage") ("dvd", "Path to DVD image file", cxxopts::value()->default_value("game.iso")) - ("backend", "Graphics API backend to use (auto, d3d11, d3d12, metal, vulkan, opengl, opengles, webgpu, null)", cxxopts::value()->default_value("auto")); + ("backend", "Graphics API backend to use (auto, d3d11, d3d12, metal, vulkan, opengl, opengles, webgpu, null)", cxxopts::value()->default_value("auto")) + ("cvar", "Override configuration variables without modifying config", cxxopts::value>()); arg_options.parse_positional({"dvd"}); arg_options.positional_help(""); @@ -257,6 +291,9 @@ int game_main(int argc, char* argv[]) { exit(1); } + dusk::config::LoadFromUserPreferences(); + ApplyCVarOverrides(parsed_arg_options["cvar"]); + AuroraConfig config{}; config.appName = "Dusk"; config.windowPosX = -1; From e55537f23ef10334e89e1c292057ed060462c1d5 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 4 Apr 2026 22:49:51 +0200 Subject: [PATCH 02/11] I realize that's an important comment for C++ --- include/dusk/config_var.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/dusk/config_var.hpp b/include/dusk/config_var.hpp index b775aa0584..bb1710662c 100644 --- a/include/dusk/config_var.hpp +++ b/include/dusk/config_var.hpp @@ -171,6 +171,9 @@ public: /** * \brief Get the current value of the CVar. + * + * This reference is not guaranteed to remain up-to-date after modification of the CVar. + * It will, however, remain sound to access. */ [[nodiscard]] constexpr const T& getValue() const noexcept { checkRegistered(); From 14026397e92d8f4f310da7418458c01731382f0b Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sat, 4 Apr 2026 23:00:33 +0200 Subject: [PATCH 03/11] Implicit conversion operator is probably a good idea tbh. --- include/dusk/config_var.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/dusk/config_var.hpp b/include/dusk/config_var.hpp index bb1710662c..064d37028d 100644 --- a/include/dusk/config_var.hpp +++ b/include/dusk/config_var.hpp @@ -211,6 +211,10 @@ public: } } + operator const T&() { + return getValue(); + } + /** * \brief Give a CVar an override value. * From f732eccf6c99b6703e8b34ff2b0ff40d66854531 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 5 Apr 2026 19:03:26 +0200 Subject: [PATCH 04/11] Set Aurora config path --- include/dusk/dusk.h | 1 + include/dusk/scope.hpp | 29 ----------------------------- src/dusk/config.cpp | 28 ++++++++++------------------ src/m_Do/m_Do_main.cpp | 17 ++++++++++++++++- 4 files changed, 27 insertions(+), 48 deletions(-) delete mode 100644 include/dusk/scope.hpp diff --git a/include/dusk/dusk.h b/include/dusk/dusk.h index d84e496827..7fc2a23070 100644 --- a/include/dusk/dusk.h +++ b/include/dusk/dusk.h @@ -4,5 +4,6 @@ #include extern AuroraInfo auroraInfo; +extern const char* configPath; #endif // DUSK_DUSK_H diff --git a/include/dusk/scope.hpp b/include/dusk/scope.hpp deleted file mode 100644 index e1cfcb9a58..0000000000 --- a/include/dusk/scope.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef DUSK_SCOPE_HPP -#define DUSK_SCOPE_HPP - -namespace dusk { - -/** - * A simple value wrapper that will destroy the value at the end of its scope. - * @tparam T The type of value contained. - * @tparam Destructor The type of function used to destroy the value. - */ -template - struct ScopeValue { - T value; - Destructor destructor; - - explicit ScopeValue(T value, Destructor destructor) : value(value), destructor(destructor) { - } - - ~ScopeValue() { - destructor(value); - } - - constexpr operator T&() const noexcept { - return value; - } - }; -} - -#endif // DUSK_SCOPE_HPP diff --git a/src/dusk/config.cpp b/src/dusk/config.cpp index aeafb5ab56..159706fac8 100644 --- a/src/dusk/config.cpp +++ b/src/dusk/config.cpp @@ -1,8 +1,4 @@ #include "dusk/config.hpp" -#include "SDL3/SDL_filesystem.h" -#include "SDL3/SDL_iostream.h" -#include "dusk/appname.hpp" -#include "dusk/scope.hpp" #include "fmt/format.h" #include "nlohmann/json.hpp" #include "absl/container/flat_hash_map.h" @@ -13,6 +9,8 @@ #include #include +#include "dusk/dusk.h" + using namespace dusk::config; constexpr auto ConfigFileName = "config.json"; @@ -25,13 +23,7 @@ static absl::flat_hash_map RegisteredConfigVar static bool RegistrationDone; static std::string GetConfigJsonPath() { - dusk::ScopeValue folder(SDL_GetPrefPath(dusk::OrgName, dusk::AppName), SDL_free); - if (folder.value == nullptr) { - DuskConfigLog.error("Failed to get user preference path: %s", SDL_GetError()); - return ""; - } - - return fmt::format("{}{}", folder.value, ConfigFileName); + return fmt::format("{}{}", configPath, ConfigFileName); } ConfigVarBase::ConfigVarBase(const char* name, const ConfigImplBase* impl) : name(name), registered(false), layer(ConfigVarLayer::Default), impl(impl) { @@ -151,11 +143,11 @@ void dusk::config::FinishRegistration() { } void dusk::config::LoadFromUserPreferences() { - const auto configPath = GetConfigJsonPath(); - if (configPath.empty()) { + const auto configJsonPath = GetConfigJsonPath(); + if (configJsonPath.empty()) { return; } - LoadFromFileName(configPath.c_str()); + LoadFromFileName(configJsonPath.c_str()); } static void LoadFromPath(const char* path) { @@ -202,12 +194,12 @@ void dusk::config::LoadFromFileName(const char* path) { } void dusk::config::Save() { - const auto configPath = GetConfigJsonPath(); - if (configPath.empty()) { + const auto configJsonPath = GetConfigJsonPath(); + if (configJsonPath.empty()) { return; } - DuskConfigLog.info("Saving config to '{}'", configPath); + DuskConfigLog.info("Saving config to '{}'", configJsonPath); json j; @@ -217,7 +209,7 @@ void dusk::config::Save() { } } - io::FileStream::WriteAllText(configPath.c_str(), j.dump(4)); + io::FileStream::WriteAllText(configJsonPath.c_str(), j.dump(4)); } ConfigVarBase* dusk::config::GetConfigVar(std::string_view name) { diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index ba86f17d79..16d2644f69 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -44,6 +44,7 @@ #include #include #include "SSystem/SComponent/c_API.h" +#include "dusk/appname.hpp" #include "dusk/dusk.h" #include "dusk/logging.h" #include "dusk/time.h" @@ -56,6 +57,7 @@ #include #include +#include "SDL3/SDL_filesystem.h" #include "cxxopts.hpp" #include "dusk/config.hpp" @@ -108,6 +110,7 @@ s32 LOAD_COPYDATE(void*) { } AuroraInfo auroraInfo; +const char* configPath; void main01(void) { OS_REPORT("\x1b[m"); @@ -263,6 +266,15 @@ static void ApplyCVarOverrides(const cxxopts::OptionValue& option) { } } +static const char* CalculateConfigPath() { + const auto result = SDL_GetPrefPath(dusk::OrgName, dusk::AppName); + if (!result) { + DuskLog.fatal("Unable to get PrefPath: {}", SDL_GetError()); + } + + return result; +} + // ========================================================================= // PC ENTRY POINT // ========================================================================= @@ -299,11 +311,14 @@ int game_main(int argc, char* argv[]) { exit(1); } + configPath = CalculateConfigPath(); + dusk::config::LoadFromUserPreferences(); ApplyCVarOverrides(parsed_arg_options["cvar"]); AuroraConfig config{}; - config.appName = "Dusk"; + config.appName = dusk::AppName; + config.configPath = configPath; config.windowPosX = -1; config.windowPosY = -1; config.windowWidth = 608 * 2; From 49412fae51a18f4e7c32b2a20bf9f4bd3b876d1a Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 5 Apr 2026 19:20:10 +0200 Subject: [PATCH 05/11] appname.hpp -> app_info.hpp --- include/dusk/{appname.hpp => app_info.hpp} | 0 src/m_Do/m_Do_main.cpp | 8 ++++---- 2 files changed, 4 insertions(+), 4 deletions(-) rename include/dusk/{appname.hpp => app_info.hpp} (100%) diff --git a/include/dusk/appname.hpp b/include/dusk/app_info.hpp similarity index 100% rename from include/dusk/appname.hpp rename to include/dusk/app_info.hpp diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index 16d2644f69..ffe6bb5ab6 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -44,12 +44,12 @@ #include #include #include "SSystem/SComponent/c_API.h" -#include "dusk/appname.hpp" +#include "dusk/app_info.hpp" #include "dusk/dusk.h" -#include "dusk/logging.h" -#include "dusk/time.h" -#include "dusk/main.h" #include "dusk/imgui/ImGuiEngine.hpp" +#include "dusk/logging.h" +#include "dusk/main.h" +#include "dusk/time.h" #include #include From 1da0655ec69bbb700e046c52e9922ff0b3ca29c9 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 5 Apr 2026 23:09:51 +0200 Subject: [PATCH 06/11] Un-duplicate togglefullscreen code again --- src/dusk/imgui/ImGuiConsole.cpp | 4 +--- src/dusk/imgui/ImGuiMenuGame.cpp | 3 ++- src/dusk/imgui/ImGuiMenuGame.hpp | 2 ++ 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index e1e8b83d31..95fb0d598f 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -193,9 +193,7 @@ namespace dusk { } if (ImGui::IsKeyPressed(ImGuiKey_F11)) { - settings::video::enableFullscreen.setValue(!settings::video::enableFullscreen); - VISetWindowFullscreen(settings::video::enableFullscreen); - config::Save(); + ImGuiMenuGame::ToggleFullscreen(); } if (CheckMenuViewToggle(ImGuiKey_F1, m_isHidden)) { diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index 992a111e78..ba872a3b86 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -14,9 +14,10 @@ #include "m_Do/m_Do_controller_pad.h" namespace dusk { - static void ToggleFullscreen() { + void ImGuiMenuGame::ToggleFullscreen() { settings::video::enableFullscreen.setValue(!settings::video::enableFullscreen); VISetWindowFullscreen(settings::video::enableFullscreen); + config::Save(); } ImGuiMenuGame::ImGuiMenuGame() {} diff --git a/src/dusk/imgui/ImGuiMenuGame.hpp b/src/dusk/imgui/ImGuiMenuGame.hpp index 6d7ed614aa..77e5952d1a 100644 --- a/src/dusk/imgui/ImGuiMenuGame.hpp +++ b/src/dusk/imgui/ImGuiMenuGame.hpp @@ -16,6 +16,8 @@ namespace dusk { void windowInputViewer(); void windowControllerConfig(); + static void ToggleFullscreen(); + private: struct { int m_selectedPort = 0; From 3eb843cc80d4d859401c85b082a87fa35e38678a Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Sun, 5 Apr 2026 23:13:33 +0200 Subject: [PATCH 07/11] Explanation at top of config.hpp (config_var.hpp already had one) --- include/dusk/config.hpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/include/dusk/config.hpp b/include/dusk/config.hpp index 5cd0807b3b..258e012f2b 100644 --- a/include/dusk/config.hpp +++ b/include/dusk/config.hpp @@ -7,6 +7,14 @@ namespace dusk::config { +/* + * config.hpp is a heavier "full" header for the configuration system. + * For a basic overview and the basic types (sufficient for accessing settings), + * look at config_var.hpp. + * + * Avoid including this header in the entire game, it's heavier than I'd like! + */ + /** * \brief Base class containing virtual functions used for save/load of CVars. */ From b098577ab9ae4cc7abb0f1bef890e269ac49299c Mon Sep 17 00:00:00 2001 From: Max Roncace Date: Sun, 5 Apr 2026 17:34:18 -0400 Subject: [PATCH 08/11] Reapply settings changes on top of #209 --- include/dusk/settings.h | 100 +++++++------- include/m_Do/m_Do_controller_pad.h | 4 +- src/Z2AudioLib/Z2AudioMgr.cpp | 2 +- src/Z2AudioLib/Z2SoundObjMgr.cpp | 2 +- src/d/actor/d_a_alink.cpp | 6 +- src/d/actor/d_a_alink_cut.inc | 2 +- src/d/actor/d_a_alink_damage.inc | 4 +- src/d/actor/d_a_alink_demo.inc | 6 +- src/d/actor/d_a_alink_dusk.cpp | 2 +- src/d/actor/d_a_alink_hang.inc | 24 ++-- src/d/actor/d_a_alink_hvyboots.inc | 2 +- src/d/actor/d_a_e_ym.cpp | 6 +- src/d/actor/d_a_midna.cpp | 2 +- src/d/actor/d_a_obj_drop.cpp | 8 +- src/d/d_camera.cpp | 2 +- src/d/d_kankyo.cpp | 2 +- src/d/d_s_name.cpp | 4 +- src/d/d_save.cpp | 6 +- src/dusk/imgui/ImGuiConsole.cpp | 2 +- src/dusk/imgui/ImGuiMenuEnhancements.cpp | 40 +++--- src/dusk/imgui/ImGuiMenuGame.cpp | 12 +- src/dusk/imgui/ImGuiMenuTools.cpp | 2 +- src/dusk/io.cpp | 4 + src/dusk/settings.cpp | 166 ++++++++++++----------- src/m_Do/m_Do_graphic.cpp | 4 +- src/m_Do/m_Do_main.cpp | 2 +- 26 files changed, 216 insertions(+), 200 deletions(-) diff --git a/include/dusk/settings.h b/include/dusk/settings.h index 68850a28d0..cf4d79f6ac 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -1,68 +1,76 @@ #ifndef DUSK_CONFIG_H #define DUSK_CONFIG_H -#include "config_var.hpp" +#include "dusk/config_var.hpp" + +namespace dusk { -namespace dusk::settings { using namespace config; // Persistent user settings -namespace video { -extern ConfigVar enableFullscreen; -} +struct UserSettings { + // Program settings -namespace audio { -extern ConfigVar masterVolume; -extern ConfigVar mainMusicVolume; -extern ConfigVar subMusicVolume; -extern ConfigVar soundEffectsVolume; -extern ConfigVar fanfareVolume; -extern ConfigVar enableReverb; -} + struct { + // Video + ConfigVar enableFullscreen; + } video; -namespace game { -// QoL -extern ConfigVar enableQuickTransform; -extern ConfigVar hideTvSettingsScreen; -extern ConfigVar biggerWallets; -extern ConfigVar noReturnRupees; -extern ConfigVar disableRupeeCutscenes; -extern ConfigVar noSwordRecoil; -extern ConfigVar damageMultiplier; -extern ConfigVar instantDeath; -extern ConfigVar fastClimbing; -extern ConfigVar fastTears; -extern ConfigVar noMissClimbing; + struct { + // Audio + ConfigVar masterVolume; + ConfigVar mainMusicVolume; + ConfigVar subMusicVolume; + ConfigVar soundEffectsVolume; + ConfigVar fanfareVolume; + ConfigVar enableReverb; + } audio; -// Preferences -extern ConfigVar enableMirrorMode; -extern ConfigVar invertCameraXAxis; + // Game settings -// Graphics -extern ConfigVar enableBloom; -extern ConfigVar useWaterProjectionOffset; + struct { + // QoL + ConfigVar enableQuickTransform; + ConfigVar hideTvSettingsScreen; + ConfigVar biggerWallets; + ConfigVar noReturnRupees; + ConfigVar disableRupeeCutscenes; + ConfigVar noSwordRecoil; + ConfigVar damageMultiplier; + ConfigVar instantDeath; + ConfigVar fastClimbing; + ConfigVar noMissClimbing; + ConfigVar fastTears; -// Audio -extern ConfigVar noLowHpSound; -extern ConfigVar midnasLamentNonStop; + // Preferences + ConfigVar enableMirrorMode; + ConfigVar invertCameraXAxis; -// Cheats -extern ConfigVar enableFastIronBoots; -extern ConfigVar canTransformAnywhere; + // Graphics + ConfigVar enableBloom; + ConfigVar useWaterProjectionOffset; -// Technical -extern ConfigVar restoreWiiGlitches; + // Audio + ConfigVar noLowHpSound; + ConfigVar midnasLamentNonStop; -// Controls -extern ConfigVar enableTurboKeybind; -} + // Cheats + ConfigVar enableFastIronBoots; + ConfigVar canTransformAnywhere; -void Register(); + // Technical + ConfigVar restoreWiiGlitches; -} + // Controls + ConfigVar enableTurboKeybind; + } game; +}; + +UserSettings& getSettings(); + +void registerSettings(); -namespace dusk { // Transient settings struct CollisionViewSettings { diff --git a/include/m_Do/m_Do_controller_pad.h b/include/m_Do/m_Do_controller_pad.h index f76a38cac8..e187efd17d 100644 --- a/include/m_Do/m_Do_controller_pad.h +++ b/include/m_Do/m_Do_controller_pad.h @@ -56,7 +56,7 @@ public: static s16 getStickAngle3D(u32 pad) { #if TARGET_PC - if (dusk::settings::game::enableMirrorMode) { + if (dusk::getSettings().game.enableMirrorMode) { return -getCpadInfo(pad).mMainStickAngle; } else { return getCpadInfo(pad).mMainStickAngle; @@ -68,7 +68,7 @@ public: static f32 getSubStickX3D(u32 pad) { #if TARGET_PC - if (dusk::settings::game::enableMirrorMode) { + if (dusk::getSettings().game.enableMirrorMode) { return -getCpadInfo(pad).mCStickPosX; } else { return getCpadInfo(pad).mCStickPosX; diff --git a/src/Z2AudioLib/Z2AudioMgr.cpp b/src/Z2AudioLib/Z2AudioMgr.cpp index 8c05678222..d6d78fb4b7 100644 --- a/src/Z2AudioLib/Z2AudioMgr.cpp +++ b/src/Z2AudioLib/Z2AudioMgr.cpp @@ -157,7 +157,7 @@ void Z2AudioMgr::zeldaGFrameWork() { processBgmFramework(); #if TARGET_PC - if (!dusk::settings::game::noLowHpSound) { + if (!dusk::getSettings().game.noLowHpSound) { processHeartGaugeSound(); } #else diff --git a/src/Z2AudioLib/Z2SoundObjMgr.cpp b/src/Z2AudioLib/Z2SoundObjMgr.cpp index 463040d692..19099c7675 100644 --- a/src/Z2AudioLib/Z2SoundObjMgr.cpp +++ b/src/Z2AudioLib/Z2SoundObjMgr.cpp @@ -109,7 +109,7 @@ void Z2SoundObjMgr::searchEnemy() { #if TARGET_PC if (Z2GetSeqMgr()->checkBgmIDPlaying(Z2BGM_MIDNA_SOS) && - dusk::settings::game::midnasLamentNonStop) + dusk::getSettings().game.midnasLamentNonStop) { Z2GetSeqMgr()->changeSubBgmStatus(0); return; diff --git a/src/d/actor/d_a_alink.cpp b/src/d/actor/d_a_alink.cpp index 993f78037b..48269ddc56 100644 --- a/src/d/actor/d_a_alink.cpp +++ b/src/d/actor/d_a_alink.cpp @@ -7510,7 +7510,7 @@ void daAlink_c::setBlendMoveAnime(f32 i_morf) { BOOL sp24 = checkEventRun(); BOOL sp20 = checkBootsMoveAnime(1); #if TARGET_PC - if (dusk::settings::game::enableFastIronBoots) { + if (dusk::getSettings().game.enableFastIronBoots) { sp20 = FALSE; } #endif @@ -9475,7 +9475,7 @@ void daAlink_c::setStickData() { mHeavySpeedMultiplier = mpHIO->mItem.mIronBoots.m.mInputFactor; } #if TARGET_PC - if (dusk::settings::game::enableFastIronBoots) { + if (dusk::getSettings().game.enableFastIronBoots) { mHeavySpeedMultiplier = 1.0f; } #endif @@ -9487,7 +9487,7 @@ void daAlink_c::setStickData() { mHeavySpeedMultiplier = mpHIO->mItem.mIronBoots.m.mWaterInputFactor; } #if TARGET_PC - if (dusk::settings::game::enableFastIronBoots) { + if (dusk::getSettings().game.enableFastIronBoots) { mHeavySpeedMultiplier = 1.0f; } #endif diff --git a/src/d/actor/d_a_alink_cut.inc b/src/d/actor/d_a_alink_cut.inc index 5928f7e891..8e61a262cb 100644 --- a/src/d/actor/d_a_alink_cut.inc +++ b/src/d/actor/d_a_alink_cut.inc @@ -372,7 +372,7 @@ BOOL daAlink_c::changeCutReverseProc(daAlink_c::daAlink_ANM i_anmID) { } #if TARGET_PC - if (dusk::settings::game::noSwordRecoil) { + if (dusk::getSettings().game.noSwordRecoil) { return FALSE; } #endif diff --git a/src/d/actor/d_a_alink_damage.inc b/src/d/actor/d_a_alink_damage.inc index 9bbe04206b..420011cfd6 100644 --- a/src/d/actor/d_a_alink_damage.inc +++ b/src/d/actor/d_a_alink_damage.inc @@ -153,8 +153,8 @@ f32 daAlink_c::damageMagnification(BOOL i_checkZoraMag, int param_1) { } #if TARGET_PC - base_mag *= dusk::settings::game::damageMultiplier; - if (dusk::settings::game::instantDeath) { + base_mag *= dusk::getSettings().game.damageMultiplier; + if (dusk::getSettings().game.instantDeath) { base_mag = 9999.0f; } #endif diff --git a/src/d/actor/d_a_alink_demo.inc b/src/d/actor/d_a_alink_demo.inc index c1c8c53bd2..aa77b24129 100644 --- a/src/d/actor/d_a_alink_demo.inc +++ b/src/d/actor/d_a_alink_demo.inc @@ -2233,7 +2233,7 @@ void daAlink_c::setGetSubBgm(int i_itemNo) { BOOL daAlink_c::checkTreasureRupeeReturn(int i_itemNo) const { #if TARGET_PC - if (dusk::settings::game::noReturnRupees) { + if (dusk::getSettings().game.noReturnRupees) { return FALSE; } #endif @@ -4307,7 +4307,7 @@ bool daAlink_c::checkAcceptWarp() { */ if (mLinkAcch.ChkGroundHit() && !checkModeFlg(MODE_PLAYER_FLY) #if TARGET_PC - && (dusk::settings::game::restoreWiiGlitches || !checkNoResetFlg0(FLG0_WATER_IN_MOVE)) + && (dusk::getSettings().game.restoreWiiGlitches || !checkNoResetFlg0(FLG0_WATER_IN_MOVE)) #elif VERSION != VERSION_WII_USA_R0 && !checkNoResetFlg0(FLG0_WATER_IN_MOVE) #endif @@ -4318,7 +4318,7 @@ bool daAlink_c::checkAcceptWarp() { */ if ( #if TARGET_PC - (dusk::settings::game::restoreWiiGlitches || !getSlidePolygon(&plane)) && + (dusk::getSettings().game.restoreWiiGlitches || !getSlidePolygon(&plane)) && #elif VERSION != VERSION_WII_USA_R0 !getSlidePolygon(&plane) && #endif diff --git a/src/d/actor/d_a_alink_dusk.cpp b/src/d/actor/d_a_alink_dusk.cpp index fdd309adae..95218268ac 100644 --- a/src/d/actor/d_a_alink_dusk.cpp +++ b/src/d/actor/d_a_alink_dusk.cpp @@ -5,7 +5,7 @@ #include "d/d_meter2_info.h" void daAlink_c::handleQuickTransform() { - if (!dusk::settings::game::enableQuickTransform) { + if (!dusk::getSettings().game.enableQuickTransform) { return; } diff --git a/src/d/actor/d_a_alink_hang.inc b/src/d/actor/d_a_alink_hang.inc index 73218f4b87..89f11fc940 100644 --- a/src/d/actor/d_a_alink_hang.inc +++ b/src/d/actor/d_a_alink_hang.inc @@ -15,8 +15,8 @@ f32 daAlink_c::getHangMoveAnmSpeed() { #if TARGET_PC return getAnmSpeedStickRate( - dusk::settings::game::fastClimbing ? 2.0f : mpHIO->mWallHang.mWallMove.m.mMinAnmSpeed, - dusk::settings::game::fastClimbing ? 2.0f : mpHIO->mWallHang.mWallMove.m.mMaxAnmSpeed + dusk::getSettings().game.fastClimbing ? 2.0f : mpHIO->mWallHang.mWallMove.m.mMinAnmSpeed, + dusk::getSettings().game.fastClimbing ? 2.0f : mpHIO->mWallHang.mWallMove.m.mMaxAnmSpeed ); #else return getAnmSpeedStickRate(mpHIO->mWallHang.mWallMove.m.mMinAnmSpeed, @@ -286,7 +286,7 @@ int daAlink_c::procHangStartInit() { if (checkHangFootWall()) { #if TARGET_PC - if (!is_prev_hangReady && cM_rnd() < 0.7f && !dusk::settings::game::noMissClimbing) { + if (!is_prev_hangReady && cM_rnd() < 0.7f && !dusk::getSettings().game.noMissClimbing) { #else if (!is_prev_hangReady && cM_rnd() < 0.7f) { #endif @@ -1221,7 +1221,7 @@ void daAlink_c::setLadderPos(int param_0) { f32 daAlink_c::getLadderMoveAnmSpeed() { return getAnmSpeedStickRate(mpHIO->mLadder.m.mMoveMinASpeed, #if TARGET_PC - dusk::settings::game::fastClimbing ? 1.575f : mpHIO->mLadder.m.mMoveMaxSpeed + dusk::getSettings().game.fastClimbing ? 1.575f : mpHIO->mLadder.m.mMoveMaxSpeed #else mpHIO->mLadder.m.mMoveMaxSpeed #endif @@ -1322,7 +1322,7 @@ int daAlink_c::procLadderUpStartInit() { setSingleAnimeBaseSpeed(ANM_LADDER_UP_START, #if TARGET_PC - dusk::settings::game::fastClimbing ? 1.8f : mpHIO->mLadder.m.mClimbUpStartASpeed, + dusk::getSettings().game.fastClimbing ? 1.8f : mpHIO->mLadder.m.mClimbUpStartASpeed, #else mpHIO->mLadder.m.mClimbUpStartASpeed, #endif @@ -1378,7 +1378,7 @@ int daAlink_c::procLadderUpEndInit(BOOL param_0) { setSingleAnimeBaseSpeed(anm_id, #if TARGET_PC - dusk::settings::game::fastClimbing ? 1.765f : mpHIO->mLadder.m.mClimbUpEndASpeed, + dusk::getSettings().game.fastClimbing ? 1.765f : mpHIO->mLadder.m.mClimbUpEndASpeed, #else mpHIO->mLadder.m.mClimbUpEndASpeed, #endif @@ -1443,7 +1443,7 @@ int daAlink_c::procLadderDownStartInit() { setSingleAnimeBaseSpeed(ANM_LADDER_DOWN_START, #if TARGET_PC - dusk::settings::game::fastClimbing ? 1.8f : mpHIO->mLadder.m.mClimbDownStartASpeed, + dusk::getSettings().game.fastClimbing ? 1.8f : mpHIO->mLadder.m.mClimbDownStartASpeed, #else mpHIO->mLadder.m.mClimbDownStartASpeed, #endif @@ -1505,7 +1505,7 @@ int daAlink_c::procLadderDownEndInit(BOOL param_0) { setSingleAnimeBaseSpeed(anm_id, #if TARGET_PC - dusk::settings::game::fastClimbing ? 1.8f : mpHIO->mLadder.m.mClimbDownEndASpeed, + dusk::getSettings().game.fastClimbing ? 1.8f : mpHIO->mLadder.m.mClimbDownEndASpeed, #else mpHIO->mLadder.m.mClimbDownEndASpeed, #endif @@ -1643,7 +1643,7 @@ int daAlink_c::procLadderMove() { f32 daAlink_c::getClimbMoveUpDownAnmSpeed() { return getAnmSpeedStickRate(mpHIO->mLadder.m.mWallVerticalMinAnmSpeed, #if TARGET_PC - dusk::settings::game::fastClimbing ? 1.875f : mpHIO->mLadder.m.mWallVerticalMaxAnmSpeed + dusk::getSettings().game.fastClimbing ? 1.875f : mpHIO->mLadder.m.mWallVerticalMaxAnmSpeed #else mpHIO->mLadder.m.mWallVerticalMaxAnmSpeed #endif @@ -1653,7 +1653,7 @@ f32 daAlink_c::getClimbMoveUpDownAnmSpeed() { f32 daAlink_c::getClimbMoveSideAnmSpeed() { return getAnmSpeedStickRate(mpHIO->mLadder.m.mWallHorizontalMinAnmSpeed, #if TARGET_PC - dusk::settings::game::fastClimbing ? 2.0f : mpHIO->mLadder.m.mWallHorizontalMaxAnmSpeed + dusk::getSettings().game.fastClimbing ? 2.0f : mpHIO->mLadder.m.mWallHorizontalMaxAnmSpeed #else mpHIO->mLadder.m.mWallHorizontalMaxAnmSpeed #endif @@ -2089,7 +2089,7 @@ int daAlink_c::procClimbUpStartInit(int param_0) { mNormalSpeed = 0.0f; #if TARGET_PC - if (param_0 || var_r29 || cM_rnd() < 0.3f || dusk::settings::game::noMissClimbing) { + if (param_0 || var_r29 || cM_rnd() < 0.3f || dusk::getSettings().game.noMissClimbing) { #else if (param_0 || var_r29 || cM_rnd() < 0.3f) { #endif @@ -2170,7 +2170,7 @@ int daAlink_c::procClimbDownStartInit(s16 param_0) { deleteEquipItem(TRUE, FALSE); #if TARGET_PC - if (cM_rnd() < 0.7f || dusk::settings::game::noMissClimbing) { + if (cM_rnd() < 0.7f || dusk::getSettings().game.noMissClimbing) { #else if (cM_rnd() < 0.7f) { #endif diff --git a/src/d/actor/d_a_alink_hvyboots.inc b/src/d/actor/d_a_alink_hvyboots.inc index fd9848d85c..6f7fa8da40 100644 --- a/src/d/actor/d_a_alink_hvyboots.inc +++ b/src/d/actor/d_a_alink_hvyboots.inc @@ -349,7 +349,7 @@ int daAlink_c::procMagneBootsFly() { */ if (dComIfG_Bgsp().ChkPolySafe(mPolyInfo2) #if TARGET_PC - && (dusk::settings::game::restoreWiiGlitches || checkEquipHeavyBoots()) + && (dusk::getSettings().game.restoreWiiGlitches || checkEquipHeavyBoots()) #elif PLATFORM_GCN || VERSION == VERSION_WII_KOR && checkEquipHeavyBoots() #endif diff --git a/src/d/actor/d_a_e_ym.cpp b/src/d/actor/d_a_e_ym.cpp index 0b87d419e3..758bec6269 100644 --- a/src/d/actor/d_a_e_ym.cpp +++ b/src/d/actor/d_a_e_ym.cpp @@ -1069,7 +1069,7 @@ void daE_YM_c::executeDown() { if (mAcch.ChkGroundHit()) { if (mFlyType != 1) { #if TARGET_PC - bckSet(6, 0, 0.0f, dusk::settings::game::fastTears && dComIfGp_event_getMode() == 0 ? 2.0f : 1.0f); + bckSet(6, 0, 0.0f, dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0 ? 2.0f : 1.0f); #else bckSet(6, 0, 0.0f, 1.0f); #endif @@ -1093,7 +1093,7 @@ void daE_YM_c::executeDown() { mSound.startCreatureSound(Z2SE_CM_BODYFALL_WATER_M, 0, -1); mSound.startCreatureSound(Z2SE_EN_YM_MOGAKU, 0, -1); #if TARGET_PC - bckSet(6, 0, 0.0f, dusk::settings::game::fastTears && dComIfGp_event_getMode() == 0 ? 2.0f : 1.0f); + bckSet(6, 0, 0.0f, dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0 ? 2.0f : 1.0f); #else bckSet(6, 0, 0.0f, 1.0f); #endif @@ -1115,7 +1115,7 @@ void daE_YM_c::executeDown() { || dComIfG_Bgsp().GetGroundCode(gnd_chk) == 5) { #if TARGET_PC - bckSet(6, 0, 0.0f, dusk::settings::game::fastTears && dComIfGp_event_getMode() == 0 ? 2.0f : 1.0f); + bckSet(6, 0, 0.0f, dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0 ? 2.0f : 1.0f); #else bckSet(6, 0, 0.0f, 1.0f); #endif diff --git a/src/d/actor/d_a_midna.cpp b/src/d/actor/d_a_midna.cpp index a50a3cb1f7..c63f4e5251 100644 --- a/src/d/actor/d_a_midna.cpp +++ b/src/d/actor/d_a_midna.cpp @@ -3051,7 +3051,7 @@ BOOL daMidna_c::checkMetamorphoseEnableBase() { !dComIfGs_isEventBit(0xD04) || #if TARGET_PC (fopAcIt_Judge((fopAcIt_JudgeFunc)daMidna_searchNpc, &tmp) && - !dusk::settings::game::canTransformAnywhere) + !dusk::getSettings().game.canTransformAnywhere) #else fopAcIt_Judge((fopAcIt_JudgeFunc)daMidna_searchNpc, &tmp) #endif diff --git a/src/d/actor/d_a_obj_drop.cpp b/src/d/actor/d_a_obj_drop.cpp index 1fb93a4270..275a3d6e12 100644 --- a/src/d/actor/d_a_obj_drop.cpp +++ b/src/d/actor/d_a_obj_drop.cpp @@ -265,8 +265,8 @@ int daObjDrop_c::modeParentWait() { mModeAction = 1; #if TARGET_PC - mModeTimer = dusk::settings::game::fastTears && dComIfGp_event_getMode() == 0 ? 20 : 40; - if (dusk::settings::game::fastTears && dComIfGp_event_getMode() == 0) { + mModeTimer = dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0 ? 20 : 40; + if (dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0) { current.pos.y += 100.0f; } else { current.pos.y += 300.0f; @@ -285,7 +285,7 @@ int daObjDrop_c::modeParentWait() { case 2: createBodyEffect(); #if TARGET_PC - mModeTimer = dusk::settings::game::fastTears && dComIfGp_event_getMode() == 0 ? 5 : 45; + mModeTimer = dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0 ? 5 : 45; #else mModeTimer = 45; #endif @@ -331,7 +331,7 @@ int daObjDrop_c::modeWait() { case 2: case 50: #if TARGET_PC - if (dusk::settings::game::fastTears && dComIfGp_event_getMode() == 0) { + if (dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0) { f32 player_dist = current.pos.abs(daPy_getPlayerActorClass()->current.pos); f32 home_dist = current.pos.abs(home.pos); diff --git a/src/d/d_camera.cpp b/src/d/d_camera.cpp index a946d85220..b2a5709736 100644 --- a/src/d/d_camera.cpp +++ b/src/d/d_camera.cpp @@ -764,7 +764,7 @@ void dCamera_c::updatePad() { var_f31 = mDoCPd_c::getSubStickX3D(mPadID); #if TARGET_PC - if (dusk::settings::game::invertCameraXAxis) { + if (dusk::getSettings().game.invertCameraXAxis) { var_f31 *= -1.0f; } #endif diff --git a/src/d/d_kankyo.cpp b/src/d/d_kankyo.cpp index 8b6d197624..ec35ee8bc7 100644 --- a/src/d/d_kankyo.cpp +++ b/src/d/d_kankyo.cpp @@ -11381,7 +11381,7 @@ void dKy_bg_MAxx_proc(void* bg_model_p) { C_MTXLightPerspective(sp1D8, dComIfGd_getView()->fovy, camera_p->view.aspect, 1.0f, 1.0f, #if TARGET_PC - dusk::settings::game::useWaterProjectionOffset ? -0.01f : 0.0f, 0.0f); + dusk::getSettings().game.useWaterProjectionOffset ? -0.01f : 0.0f, 0.0f); #else -0.01f, 0.0f); #endif diff --git a/src/d/d_s_name.cpp b/src/d/d_s_name.cpp index df512d8cb2..735ce4d0ca 100644 --- a/src/d/d_s_name.cpp +++ b/src/d/d_s_name.cpp @@ -301,7 +301,7 @@ void dScnName_c::FileSelectMain() { void dScnName_c::FileSelectMainNormal() { #if TARGET_PC - mShowTvSettingsScreen = !dusk::settings::game::hideTvSettingsScreen; + mShowTvSettingsScreen = !dusk::getSettings().game.hideTvSettingsScreen; #endif switch(dFs_c->isSelectEnd()) { @@ -369,7 +369,7 @@ void dScnName_c::doPreLoadSetup() { mProc = dScnName_PROC_ChangeGameScene; #if TARGET_PC - if (dusk::settings::game::disableRupeeCutscenes) { + if (dusk::getSettings().game.disableRupeeCutscenes) { return; } #endif diff --git a/src/d/d_save.cpp b/src/d/d_save.cpp index e9977bc534..3289a49c64 100644 --- a/src/d/d_save.cpp +++ b/src/d/d_save.cpp @@ -114,19 +114,19 @@ u16 dSv_player_status_a_c::getRupeeMax() const { switch (mWalletSize) { case WALLET: #if TARGET_PC - return dusk::settings::game::biggerWallets ? 500 : 300; + return dusk::getSettings().game.biggerWallets ? 500 : 300; #else return 300; #endif case BIG_WALLET: #if TARGET_PC - return dusk::settings::game::biggerWallets ? 1000 : 600; + return dusk::getSettings().game.biggerWallets ? 1000 : 600; #else return 600; #endif case GIANT_WALLET: #if TARGET_PC - return dusk::settings::game::biggerWallets ? 2000 : 1000; + return dusk::getSettings().game.biggerWallets ? 2000 : 1000; #else return 1000; #endif diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index 95fb0d598f..fe5d3e7c9a 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -184,7 +184,7 @@ namespace dusk { m_isLaunchInitialized = true; } - getTransientSettings().skipFrameRateLimit = settings::game::enableTurboKeybind && ImGui::IsKeyDown(ImGuiKey_Tab); + getTransientSettings().skipFrameRateLimit = getSettings().game.enableTurboKeybind && ImGui::IsKeyDown(ImGuiKey_Tab); if ((ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl)) && ImGui::IsKeyPressed(ImGuiKey_R)) diff --git a/src/dusk/imgui/ImGuiMenuEnhancements.cpp b/src/dusk/imgui/ImGuiMenuEnhancements.cpp index 537539cd02..3fbd55a8f2 100644 --- a/src/dusk/imgui/ImGuiMenuEnhancements.cpp +++ b/src/dusk/imgui/ImGuiMenuEnhancements.cpp @@ -10,45 +10,45 @@ namespace dusk { void ImGuiMenuEnhancements::draw() { if (ImGui::BeginMenu("Enhancements")) { if (ImGui::BeginMenu("Quality of Life")) { - config::ImGuiCheckbox("Quick Transform (R+Y)", settings::game::enableQuickTransform); + config::ImGuiCheckbox("Quick Transform (R+Y)", getSettings().game.enableQuickTransform); - config::ImGuiCheckbox("Bigger Wallets", settings::game::biggerWallets); + config::ImGuiCheckbox("Bigger Wallets", getSettings().game.biggerWallets); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Wallet sizes are like in the HD version (500, 1000, 2000)"); } - config::ImGuiCheckbox("No Rupee Returns", settings::game::noReturnRupees); + config::ImGuiCheckbox("No Rupee Returns", getSettings().game.noReturnRupees); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Always collect Rupees even if your Wallet is too full"); } - config::ImGuiCheckbox("Disable Rupee Cutscenes", settings::game::disableRupeeCutscenes); + config::ImGuiCheckbox("Disable Rupee Cutscenes", getSettings().game.disableRupeeCutscenes); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Rupees won't play cutscenes after you've collected them the first time"); } - config::ImGuiCheckbox("No Sword Recoil", settings::game::noSwordRecoil); + config::ImGuiCheckbox("No Sword Recoil", getSettings().game.noSwordRecoil); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Link won't recoil when his sword hits walls"); } - config::ImGuiCheckbox("Faster Climbing", settings::game::fastClimbing); + config::ImGuiCheckbox("Faster Climbing", getSettings().game.fastClimbing); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Quicker climbing on ladders and vines like the HD version"); } - config::ImGuiCheckbox("No Climbing Miss Animation", settings::game::noMissClimbing); + config::ImGuiCheckbox("No Climbing Miss Animation", getSettings().game.noMissClimbing); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Prevents Link from playing a struggle animation\n" "when using the Clawshot on vines at a weird angle"); } - config::ImGuiCheckbox("Faster Tears of Light", settings::game::fastTears); + config::ImGuiCheckbox("Faster Tears of Light", getSettings().game.fastTears); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Tears of Light dropped by Shadow Insects pop out faster like the HD version"); } - config::ImGuiCheckbox("Hide TV Settings Screen", settings::game::hideTvSettingsScreen); + config::ImGuiCheckbox("Hide TV Settings Screen", getSettings().game.hideTvSettingsScreen); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Hides the TV calibration screen shown when loading a save"); } @@ -57,20 +57,20 @@ namespace dusk { } if (ImGui::BeginMenu("Preferences")) { - config::ImGuiCheckbox("Mirror Mode", settings::game::enableMirrorMode); + config::ImGuiCheckbox("Mirror Mode", getSettings().game.enableMirrorMode); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Mirrors the world, matching the Wii version of the game"); } - config::ImGuiCheckbox("Invert Camera X Axis", settings::game::invertCameraXAxis); + config::ImGuiCheckbox("Invert Camera X Axis", getSettings().game.invertCameraXAxis); ImGui::EndMenu(); } if (ImGui::BeginMenu("Graphics")) { - config::ImGuiCheckbox("Native Bloom", settings::game::enableBloom); + config::ImGuiCheckbox("Native Bloom", getSettings().game.enableBloom); - config::ImGuiCheckbox("Water Projection Offset", settings::game::useWaterProjectionOffset); + config::ImGuiCheckbox("Water Projection Offset", getSettings().game.useWaterProjectionOffset); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Adds GC-specific -0.01 transS offset\n" "that causes ~6px ghost artifacts in water reflections"); @@ -80,12 +80,12 @@ namespace dusk { } if (ImGui::BeginMenu("Audio")) { - config::ImGuiCheckbox("No Low HP Sound", settings::game::noLowHpSound); + config::ImGuiCheckbox("No Low HP Sound", getSettings().game.noLowHpSound); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Disable the beeping sound when having low health"); } - config::ImGuiCheckbox("Non-Stop Midna's Lament", settings::game::midnasLamentNonStop); + config::ImGuiCheckbox("Non-Stop Midna's Lament", getSettings().game.midnasLamentNonStop); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Prevents enemy music while Midna's Lament is playing"); } @@ -94,9 +94,9 @@ namespace dusk { } if (ImGui::BeginMenu("Cheats")) { - config::ImGuiCheckbox("Fast Iron Boots", settings::game::enableFastIronBoots); + config::ImGuiCheckbox("Fast Iron Boots", getSettings().game.enableFastIronBoots); - config::ImGuiCheckbox("Can Transform Anywhere", settings::game::canTransformAnywhere); + config::ImGuiCheckbox("Can Transform Anywhere", getSettings().game.canTransformAnywhere); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Allows you to transform even if NPCs are looking"); } @@ -105,9 +105,9 @@ namespace dusk { } if (ImGui::BeginMenu("Difficulty")) { - config::ImGuiSliderInt("Damage Multiplier", settings::game::damageMultiplier, 1, 8, "x%d"); + config::ImGuiSliderInt("Damage Multiplier", getSettings().game.damageMultiplier, 1, 8, "x%d"); - config::ImGuiCheckbox("Instant Death", settings::game::instantDeath); + config::ImGuiCheckbox("Instant Death", getSettings().game.instantDeath); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Any hit will instantly kill you"); } @@ -116,7 +116,7 @@ namespace dusk { } if (ImGui::BeginMenu("Technical")) { - config::ImGuiCheckbox("Restore Wii 1.0 Glitches", settings::game::restoreWiiGlitches); + config::ImGuiCheckbox("Restore Wii 1.0 Glitches", getSettings().game.restoreWiiGlitches); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Restores patched glitches from Wii USA 1.0,\n" "the first released version"); diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index ba872a3b86..b2ed26b20a 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -15,8 +15,8 @@ namespace dusk { void ImGuiMenuGame::ToggleFullscreen() { - settings::video::enableFullscreen.setValue(!settings::video::enableFullscreen); - VISetWindowFullscreen(settings::video::enableFullscreen); + getSettings().video.enableFullscreen.setValue(!getSettings().video.enableFullscreen); + VISetWindowFullscreen(getSettings().video.enableFullscreen); config::Save(); } @@ -40,8 +40,8 @@ namespace dusk { if (ImGui::BeginMenu("Audio")) { ImGui::Text("Master Volume"); - config::ImGuiSliderInt("##masterVolume", settings::audio::masterVolume, 0, 100); - config::ImGuiCheckbox("Enable Reverb", settings::audio::enableReverb); + config::ImGuiSliderInt("##masterVolume", getSettings().audio.masterVolume, 0, 100); + config::ImGuiCheckbox("Enable Reverb", getSettings().audio.enableReverb); /* // TODO: Implement additional settings ImGui::Text("Main Music Volume"); @@ -61,8 +61,8 @@ namespace dusk { } */ - audio::SetMasterVolume(settings::audio::masterVolume / 100.0f); - audio::EnableReverb = settings::audio::enableReverb; + audio::SetMasterVolume(getSettings().audio.masterVolume / 100.0f); + audio::EnableReverb = getSettings().audio.enableReverb; ImGui::EndMenu(); } diff --git a/src/dusk/imgui/ImGuiMenuTools.cpp b/src/dusk/imgui/ImGuiMenuTools.cpp index 754f9a72e7..6a2fc2ede0 100644 --- a/src/dusk/imgui/ImGuiMenuTools.cpp +++ b/src/dusk/imgui/ImGuiMenuTools.cpp @@ -51,7 +51,7 @@ namespace dusk { ImGui::MenuItem("Audio Debug", hotkeys::SHOW_AUDIO_DEBUG, &m_showAudioDebug); ImGui::MenuItem("OSReport Force", nullptr, &OSReportReallyForceEnable); ImGui::Separator(); - config::ImGuiMenuItem("Enable Turbo Key", hotkeys::TURBO, settings::game::enableTurboKeybind); + config::ImGuiMenuItem("Enable Turbo Key", hotkeys::TURBO, getSettings().game.enableTurboKeybind); ImGui::EndMenu(); } diff --git a/src/dusk/io.cpp b/src/dusk/io.cpp index f930c2ceaa..50b5cab5bd 100644 --- a/src/dusk/io.cpp +++ b/src/dusk/io.cpp @@ -11,7 +11,11 @@ using namespace dusk::io; #define fseek(a, b, c) _fseeki64(a, b, c) #else #define MODE_TYPE char +#if _MSVC_VER #define MODE(val) ##val +#else +#define MODE(val) val +#endif #endif static FILE* ThrowIfNotOpen(const FileStream& file) { diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index 6bfc057a90..278f783dbf 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -1,95 +1,99 @@ #include "dusk/settings.h" #include "dusk/config.hpp" -namespace dusk::settings { -namespace video { -ConfigVar enableFullscreen("video.enableFullscreen", false); +namespace dusk { + +UserSettings g_userSettings = { + .video = { + .enableFullscreen {"video.enableFullscreen", false}, + }, + + .audio = { + .masterVolume {"audio.masterVolume", 80}, + .mainMusicVolume {"audio.mainMusicVolume", 100}, + .subMusicVolume {"audio.subMusicVolume", 100}, + .soundEffectsVolume {"audio.soundEffectsVolume", 100}, + .fanfareVolume {"audio.fanfareVolume", 100}, + .enableReverb {"audio.enableReverb", true}, + }, + + .game = { + // Quality of Life + .enableQuickTransform {"game.enableQuickTransform", false}, + .hideTvSettingsScreen {"game.hideTvSettingsScreen", false}, + .biggerWallets {"game.biggerWallets", false}, + .noReturnRupees {"game.noReturnRupees", false}, + .disableRupeeCutscenes {"game.disableRupeeCutscenes", false}, + .noSwordRecoil {"game.noSwordRecoil", false}, + .damageMultiplier {"game.damageMultiplier", 1}, + .instantDeath {"game.instantDeath", false}, + .fastClimbing {"game.fastClimbing", false}, + .noMissClimbing {"game.noMissClimbing", false}, + .fastTears {"game.fastTears", false}, + + // Preferences + .enableMirrorMode {"game.enableMirrorMode", false}, + .invertCameraXAxis {"game.invertCameraXAxis", false}, + + // Graphics + .enableBloom {"game.enableBloom", true}, + .useWaterProjectionOffset {"game.useWaterProjectionOffset", false}, + + // Audio + .noLowHpSound {"game.noLowHpSound", false}, + .midnasLamentNonStop {"game.midnasLamentNonStop", false}, + + // Cheats + .enableFastIronBoots {"game.enableFastIronBoots", false}, + .canTransformAnywhere {"game.canTransformAnywhere", false}, + + // Technical + .restoreWiiGlitches {"game.restoreWiiGlitches", false}, + + // Controls + .enableTurboKeybind {"game.enableTurboKeybind", true}, + }, +}; + +UserSettings& getSettings() { + return g_userSettings; } -namespace audio { -ConfigVar masterVolume("audio.masterVolume", 80); -ConfigVar mainMusicVolume("audio.mainMusicVolume", 100); -ConfigVar subMusicVolume("audio.subMusicVolume", 100); -ConfigVar soundEffectsVolume("audio.soundEffectsVolume", 100); -ConfigVar fanfareVolume("audio.fanfareVolume", 100); -ConfigVar enableReverb("audio.enableReverb", true); -} - -namespace game { -// Quality of Life -ConfigVar enableQuickTransform("game.enableQuickTransform", false); -ConfigVar hideTvSettingsScreen("game.hideTvSettingsScreen", false); -ConfigVar biggerWallets("game.biggerWallets", false); -ConfigVar noReturnRupees("game.noReturnRupees", false); -ConfigVar disableRupeeCutscenes("game.disableRupeeCutscenes", false); -ConfigVar noSwordRecoil("game.noSwordRecoil", false); -ConfigVar damageMultiplier("game.damageMultiplier", 1); -ConfigVar instantDeath("game.instantDeath", false); -ConfigVar fastClimbing("game.fastClimbing", false); -ConfigVar fastTears("game.fastTears", false); -ConfigVar noMissClimbing("game.noMissClimbing", false); - -// Preferences -ConfigVar enableMirrorMode("game.enableMirrorMode", false); -ConfigVar invertCameraXAxis("game.invertCameraXAxis", false); - -// Graphics -ConfigVar enableBloom("game.enableBloom", true); -ConfigVar useWaterProjectionOffset("game.useWaterProjectionOffset", false); - -// Audio -ConfigVar noLowHpSound("game.noLowHpSound", false); -ConfigVar midnasLamentNonStop("game.midnasLamentNonStop", false); - -// Cheats -ConfigVar enableFastIronBoots("game.enableFastIronBoots", false); -ConfigVar canTransformAnywhere("game.canTransformAnywhere", false); - -// Technical -ConfigVar restoreWiiGlitches("game.restoreWiiGlitches", false); - -// Controls -ConfigVar enableTurboKeybind("game.enableTurboKeybind", true); -} - -void Register() { +void registerSettings() { // Video - Register(video::enableFullscreen); + Register(g_userSettings.video.enableFullscreen); // Audio - Register(audio::masterVolume); - Register(audio::mainMusicVolume); - Register(audio::subMusicVolume); - Register(audio::soundEffectsVolume); - Register(audio::fanfareVolume); - Register(audio::enableReverb); + Register(g_userSettings.audio.masterVolume); + Register(g_userSettings.audio.mainMusicVolume); + Register(g_userSettings.audio.subMusicVolume); + Register(g_userSettings.audio.soundEffectsVolume); + Register(g_userSettings.audio.fanfareVolume); + Register(g_userSettings.audio.enableReverb); // Game - Register(game::enableQuickTransform); - Register(game::hideTvSettingsScreen); - Register(game::biggerWallets); - Register(game::noReturnRupees); - Register(game::disableRupeeCutscenes); - Register(game::noSwordRecoil); - Register(game::damageMultiplier); - Register(game::instantDeath); - Register(game::fastClimbing); - Register(game::fastTears); - Register(game::enableMirrorMode); - Register(game::invertCameraXAxis); - Register(game::enableBloom); - Register(game::useWaterProjectionOffset); - Register(game::enableFastIronBoots); - Register(game::canTransformAnywhere); - Register(game::restoreWiiGlitches); - Register(game::noMissClimbing); - Register(game::noLowHpSound); - Register(game::midnasLamentNonStop); - Register(game::enableTurboKeybind); + Register(g_userSettings.game.enableQuickTransform); + Register(g_userSettings.game.hideTvSettingsScreen); + Register(g_userSettings.game.biggerWallets); + Register(g_userSettings.game.noReturnRupees); + Register(g_userSettings.game.disableRupeeCutscenes); + Register(g_userSettings.game.noSwordRecoil); + Register(g_userSettings.game.damageMultiplier); + Register(g_userSettings.game.instantDeath); + Register(g_userSettings.game.fastClimbing); + Register(g_userSettings.game.fastTears); + Register(g_userSettings.game.enableMirrorMode); + Register(g_userSettings.game.invertCameraXAxis); + Register(g_userSettings.game.enableBloom); + Register(g_userSettings.game.useWaterProjectionOffset); + Register(g_userSettings.game.enableFastIronBoots); + Register(g_userSettings.game.canTransformAnywhere); + Register(g_userSettings.game.restoreWiiGlitches); + Register(g_userSettings.game.noMissClimbing); + Register(g_userSettings.game.noLowHpSound); + Register(g_userSettings.game.midnasLamentNonStop); + Register(g_userSettings.game.enableTurboKeybind); } -} - -namespace dusk { // Transient settings diff --git a/src/m_Do/m_Do_graphic.cpp b/src/m_Do/m_Do_graphic.cpp index 7ee50fc0ea..a7a09a8d87 100644 --- a/src/m_Do/m_Do_graphic.cpp +++ b/src/m_Do/m_Do_graphic.cpp @@ -1163,7 +1163,7 @@ void mDoGph_gInf_c::bloom_c::remove() { void mDoGph_gInf_c::bloom_c::draw() { #if TARGET_PC - if (!dusk::settings::game::enableBloom) { + if (!dusk::getSettings().game.enableBloom) { return; } #endif @@ -2117,7 +2117,7 @@ int mDoGph_Painter() { #endif #if TARGET_PC - if (dusk::settings::game::enableMirrorMode) + if (dusk::getSettings().game.enableMirrorMode) #elif PLATFORM_WII if (data_8053a730) #endif diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index ffe6bb5ab6..632d696ae9 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -279,7 +279,7 @@ static const char* CalculateConfigPath() { // PC ENTRY POINT // ========================================================================= int game_main(int argc, char* argv[]) { - dusk::settings::Register(); + dusk::registerSettings(); dusk::config::FinishRegistration(); cxxopts::ParseResult parsed_arg_options; From e7dc10aefbd2f1d91803486f1099943ecfcfea0f Mon Sep 17 00:00:00 2001 From: madeline Date: Sun, 5 Apr 2026 15:05:38 -0700 Subject: [PATCH 09/11] fix cloud, sun, moon, rain, fog, lens flare, etc, culling --- src/d/d_kankyo_rain.cpp | 57 +++++++++++++++++++++++++++++------------ 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/src/d/d_kankyo_rain.cpp b/src/d/d_kankyo_rain.cpp index 019f30f101..c1927c5875 100644 --- a/src/d/d_kankyo_rain.cpp +++ b/src/d/d_kankyo_rain.cpp @@ -113,7 +113,12 @@ void dKyr_lenzflare_move() { cXyz vect; cXyz proj; cXyz center; + + #if TARGET_PC + mDoLib_project(lenz_packet->mPositions, &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(lenz_packet->mPositions, &proj); + #endif center.x = FB_WIDTH / 2; center.y = FB_HEIGHT / 2; @@ -213,7 +218,12 @@ void dKyr_sun_move() { } cXyz proj; + + #if TARGET_PC + mDoLib_project(sun_packet->mPos, &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(sun_packet->mPos, &proj); + #endif for (int i = 0; i < 5; i++) { cXyz chkpnt = proj; @@ -4111,7 +4121,11 @@ void dKyr_drawStar(Mtx drawMtx, u8** tex) { } } + #if TARGET_PC + mDoLib_project(&moon_pos, &moon_proj, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&moon_pos, &moon_proj); + #endif GXSetNumChans(1); GXSetChanCtrl(GX_COLOR0, GX_DISABLE, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_CLAMP, GX_AF_NONE); @@ -4248,7 +4262,11 @@ void dKyr_drawStar(Mtx drawMtx, u8** tex) { sp68.y = spBC.y + star_pos.y; sp68.z = spBC.z + star_pos.z; + #if TARGET_PC + mDoLib_project(&sp68, &star_proj, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&sp68, &star_proj); + #endif moon_proj.z = 0.0f; star_proj.z = 0.0f; @@ -4630,7 +4648,11 @@ void drawVrkumo(Mtx drawMtx, GXColor& color, u8** tex) { } if (g_env_light.daytime > 105.0f && g_env_light.daytime < 240.0f && !dComIfGp_event_runCheck() && sun_packet != NULL && sun_packet->mSunAlpha > 0.0f) { + #if TARGET_PC + mDoLib_project(&sun_packet->mPos[0], &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&sun_packet->mPos[0], &proj); + #endif if (proj.x > 0.0f && proj.x < FB_WIDTH && proj.y > spC4 && proj.y < (458.0f - spC4)) { pass = 0; } @@ -4895,7 +4917,12 @@ void drawVrkumo(Mtx drawMtx, GXColor& color, u8** tex) { x = 100.0f; y = 100.0f; z = 100.0f; + + #if TARGET_PC + mDoLib_project(&spF0, &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&spF0, &proj); + #endif if (proj.x > -x && proj.x < (FB_WIDTH + x) && proj.y > -y && proj.y < (458.0f + z)) { break; @@ -4945,7 +4972,12 @@ void drawVrkumo(Mtx drawMtx, GXColor& color, u8** tex) { x = 100.0f; y = 100.0f; z = 100.0f; + + #if TARGET_PC + mDoLib_project(&spE4, &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&spE4, &proj); + #endif if (proj.x > -x && proj.x < (FB_WIDTH + x) && proj.y > -y && proj.y < (458.0f + z)) { break; @@ -5986,21 +6018,18 @@ static void dKyr_evil_draw2(Mtx drawMtx, u8** tex) { sp34.y = 80.0f; sp34.z = 80.0f; + #if TARGET_PC + mDoLib_project(&sp7C, &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&sp7C, &proj); + #endif -#if TARGET_PC - if (!(proj.x > -sp34.x) || !(proj.x < (dComIfGd_getViewport()->width + sp34.x)) || - !(proj.y > -sp34.y) || !(proj.y < (dComIfGd_getViewport()->height + sp34.z))) - { - continue; - } -#else if (!(proj.x > -sp34.x) || !(proj.x < (FB_WIDTH + sp34.x)) || !(proj.y > -sp34.y) || !(proj.y < (458.0f + sp34.z))) { continue; } -#endif + } f32 sp40; @@ -6219,21 +6248,17 @@ void dKyr_evil_draw(Mtx drawMtx, u8** tex) { sp44.y = 80.0f; sp44.z = 80.0f; + #if TARGET_PC + mDoLib_project(&spA4, &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&spA4, &proj); + #endif -#if TARGET_PC - if (!(proj.x > -sp44.x) || !(proj.x < (dComIfGd_getViewport()->width + sp44.x)) || - !(proj.y > -sp44.y) || !(proj.y < (dComIfGd_getViewport()->height + sp44.z))) - { - continue; - } -#else if (!(proj.x > -sp44.x) || !(proj.x < (FB_WIDTH + sp44.x)) || !(proj.y > -sp44.y) || !(proj.y < (458.0f + sp44.z))) { continue; } -#endif } f32 sp5C; From 0b5dcfc7ad2a53a60eb82e22a32e24238844072c Mon Sep 17 00:00:00 2001 From: madeline Date: Sun, 5 Apr 2026 16:31:57 -0700 Subject: [PATCH 10/11] fix every viewport projection issue fixes #123 fixes #115 --- src/d/actor/d_a_alink_kandelaar.inc | 5 +++++ src/d/actor/d_a_demo00.cpp | 5 +++++ src/d/actor/d_a_e_fk.cpp | 6 ++++++ src/d/actor/d_a_e_fs.cpp | 6 ++++++ src/d/actor/d_a_e_sm.cpp | 6 ++++++ src/d/actor/d_a_npc.cpp | 11 +++++------ src/d/actor/d_a_obj_ari.cpp | 6 ++++++ src/d/actor/d_a_obj_bhashi.cpp | 6 ++++++ src/d/actor/d_a_obj_cho.cpp | 6 ++++++ src/d/actor/d_a_obj_crvfence.cpp | 6 ++++++ src/d/actor/d_a_obj_crvhahen.cpp | 5 +++++ src/d/actor/d_a_obj_dan.cpp | 6 ++++++ src/d/actor/d_a_obj_gomikabe.cpp | 6 ++++++ src/d/actor/d_a_obj_hhashi.cpp | 6 ++++++ src/d/actor/d_a_obj_kamakiri.cpp | 6 ++++++ src/d/actor/d_a_obj_katatsumuri.cpp | 5 +++++ src/d/actor/d_a_obj_kuwagata.cpp | 5 +++++ src/d/actor/d_a_obj_ten.cpp | 6 ++++++ src/d/actor/d_a_obj_tombo.cpp | 6 ++++++ src/d/actor/d_a_obj_zra_freeze.cpp | 5 +++++ src/d/d_insect.cpp | 4 ++++ src/d/d_kankyo.cpp | 4 ++++ src/d/d_kankyo_debug.cpp | 5 +++++ src/d/d_msg_object.cpp | 12 ++++++++++++ src/d/d_msg_scrn_item.cpp | 11 +++++++++++ src/d/d_msg_scrn_talk.cpp | 13 ++++++++++++- 26 files changed, 161 insertions(+), 7 deletions(-) diff --git a/src/d/actor/d_a_alink_kandelaar.inc b/src/d/actor/d_a_alink_kandelaar.inc index 339ff90ce6..88c2152e03 100644 --- a/src/d/actor/d_a_alink_kandelaar.inc +++ b/src/d/actor/d_a_alink_kandelaar.inc @@ -180,7 +180,12 @@ void daAlink_c::preKandelaarDraw() { mat_p->setTevColor(2, &color); cXyz proj; + + #if TARGET_PC + mDoLib_project(&mKandelaarFlamePos, &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&mKandelaarFlamePos, &proj); + #endif camera_process_class* camera_p = dComIfGp_getCamera(0); f32 trimHeight; diff --git a/src/d/actor/d_a_demo00.cpp b/src/d/actor/d_a_demo00.cpp index 99ad6c1587..871e9ebca6 100644 --- a/src/d/actor/d_a_demo00.cpp +++ b/src/d/actor/d_a_demo00.cpp @@ -1658,7 +1658,12 @@ int daDemo00_c::draw() { MTXCopy(mModel.field_0x5d4->getAnmMtx(0), mDoMtx_stack_c::get()); spb0.set(0.0f, 0.0f, 0.0f); mDoMtx_stack_c::multVec(&spb0, &sp98); + + #if TARGET_PC + mDoLib_project(&sp98, &spa4, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&sp98, &spa4); + #endif if (spa4.x >= -700.0f && spa4.x < 1600.0f && spa4.y >= -200.0f && spa4.y < 600.0f) { if (mModel.mID.field_0x18 == 0 || mModel.mID.field_0x18 == 1) { diff --git a/src/d/actor/d_a_e_fk.cpp b/src/d/actor/d_a_e_fk.cpp index 5840df0495..87734da262 100644 --- a/src/d/actor/d_a_e_fk.cpp +++ b/src/d/actor/d_a_e_fk.cpp @@ -429,7 +429,13 @@ void daE_FK_c::DamageAction() { bool daE_FK_c::checkViewArea() { Vec proj; + + #if TARGET_PC + mDoLib_project(¤t.pos, &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(¤t.pos, &proj); + #endif + return (proj.x >= 0.0f && proj.x <= FB_WIDTH) && (proj.y >= 0.0f && proj.y <= FB_HEIGHT); } diff --git a/src/d/actor/d_a_e_fs.cpp b/src/d/actor/d_a_e_fs.cpp index 3ed859c686..54dd10ad81 100644 --- a/src/d/actor/d_a_e_fs.cpp +++ b/src/d/actor/d_a_e_fs.cpp @@ -463,7 +463,13 @@ static void damage_check(e_fs_class* i_this) { static bool checkViewArea(cXyz* i_pos) { Vec proj; + + #if TARGET_PC + mDoLib_project(i_pos, &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(i_pos, &proj); + #endif + bool ret = false; if (proj.x >= 0.0f && proj.x <= FB_WIDTH && proj.y >= 0.0f && proj.y <= FB_HEIGHT) { ret = true; diff --git a/src/d/actor/d_a_e_sm.cpp b/src/d/actor/d_a_e_sm.cpp index b85e22e640..8894399cec 100644 --- a/src/d/actor/d_a_e_sm.cpp +++ b/src/d/actor/d_a_e_sm.cpp @@ -1362,7 +1362,13 @@ void daE_SM_c::E_SM_C_Hook() { bool daE_SM_c::CheckViewArea() { Vec vec; + + #if TARGET_PC + mDoLib_project(¤t.pos, &vec, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(¤t.pos, &vec); + #endif + bool rv = false; if (vec.x >= 0.0f && vec.x <= FB_WIDTH && vec.y >= 0.0f && vec.y <= FB_HEIGHT) { diff --git a/src/d/actor/d_a_npc.cpp b/src/d/actor/d_a_npc.cpp index b903ce8c8f..6ec2fbe7a9 100644 --- a/src/d/actor/d_a_npc.cpp +++ b/src/d/actor/d_a_npc.cpp @@ -2694,16 +2694,15 @@ BOOL daNpcT_chkActorInScreen(fopAc_ac_c* i_ActorP, f32 param_1, f32 param_2, f32 } for (int i = 0; i < 8; i++) { + #if TARGET_PC + mDoLib_project(&pos_array[i], &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&pos_array[i], &proj); -#if TARGET_PC - if (0.0f < proj.x && proj.x < mDoGph_gInf_c::getWidth() && 0.0f < proj.y && proj.y < mDoGph_gInf_c::getHeight()) { - continue; - } -#else + #endif + if (0.0f < proj.x && proj.x < FB_WIDTH && 0.0f < proj.y && proj.y < FB_HEIGHT) { continue; } -#endif return false; } diff --git a/src/d/actor/d_a_obj_ari.cpp b/src/d/actor/d_a_obj_ari.cpp index 267b0003af..3e2727e2f2 100644 --- a/src/d/actor/d_a_obj_ari.cpp +++ b/src/d/actor/d_a_obj_ari.cpp @@ -499,7 +499,13 @@ void daObjARI_c::Z_BufferChk() { cXyz vec2, vec1; vec1 = current.pos; vec1.y += 20.0f; + + #if TARGET_PC + mDoLib_project(&vec1, &vec2, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&vec1, &vec2); + #endif + f32 trim_height; camera_process_class* camera = dComIfGp_getCamera(0); if (camera != NULL) { diff --git a/src/d/actor/d_a_obj_bhashi.cpp b/src/d/actor/d_a_obj_bhashi.cpp index 00a35bc158..33595c7a77 100644 --- a/src/d/actor/d_a_obj_bhashi.cpp +++ b/src/d/actor/d_a_obj_bhashi.cpp @@ -285,7 +285,13 @@ bool Hahen_c::CheckCull() { bool Hahen_c::checkViewArea() { Vec proj; + + #if TARGET_PC + mDoLib_project(&pos, &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&pos, &proj); + #endif + return (proj.x >= -50.0f && proj.x <= 658.0f) && (proj.y >= -50.0f && proj.y <= 498.0f); } diff --git a/src/d/actor/d_a_obj_cho.cpp b/src/d/actor/d_a_obj_cho.cpp index 8fbc2dbde8..27b0b8ce0e 100644 --- a/src/d/actor/d_a_obj_cho.cpp +++ b/src/d/actor/d_a_obj_cho.cpp @@ -289,7 +289,13 @@ void daObjCHO_c::Z_BufferChk() { cXyz vec2, vec1; vec1 = current.pos; vec1.y += 20.0f; + + #if TARGET_PC + mDoLib_project(&vec1, &vec2, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&vec1, &vec2); + #endif + f32 trim_height; camera_process_class* camera = dComIfGp_getCamera(0); if (camera != NULL) { diff --git a/src/d/actor/d_a_obj_crvfence.cpp b/src/d/actor/d_a_obj_crvfence.cpp index 9712205ea3..03ff2b1d6a 100644 --- a/src/d/actor/d_a_obj_crvfence.cpp +++ b/src/d/actor/d_a_obj_crvfence.cpp @@ -224,7 +224,13 @@ void daObjCRVFENCE_c::NormalAction() { bool daObjCRVFENCE_c::checkViewArea(cXyz* param_1) { Vec sp24; + + #if TARGET_PC + mDoLib_project(param_1, &sp24, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(param_1, &sp24); + #endif + bool rv = false; bool bVar1 = false; diff --git a/src/d/actor/d_a_obj_crvhahen.cpp b/src/d/actor/d_a_obj_crvhahen.cpp index 52d180f547..0b44a57ec8 100644 --- a/src/d/actor/d_a_obj_crvhahen.cpp +++ b/src/d/actor/d_a_obj_crvhahen.cpp @@ -137,7 +137,12 @@ void daObjCRVHAHEN_c::CheckCull() { bool daObjCRVHAHEN_c::checkViewArea(cXyz* i_this) { Vec proj; + + #if TARGET_PC + mDoLib_project(i_this, &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(i_this, &proj); + #endif bool ret = false; diff --git a/src/d/actor/d_a_obj_dan.cpp b/src/d/actor/d_a_obj_dan.cpp index cc176b9a20..53dbf4e1e4 100644 --- a/src/d/actor/d_a_obj_dan.cpp +++ b/src/d/actor/d_a_obj_dan.cpp @@ -267,7 +267,13 @@ void daObjDAN_c::Z_BufferChk() { cXyz vec2, vec1; vec1 = current.pos; vec1.y += 20.0f; + + #if TARGET_PC + mDoLib_project(&vec1, &vec2, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&vec1, &vec2); + #endif + f32 trim_height; camera_process_class* camera = dComIfGp_getCamera(0); if (camera != NULL) { diff --git a/src/d/actor/d_a_obj_gomikabe.cpp b/src/d/actor/d_a_obj_gomikabe.cpp index 66963f0efd..a5b58a44a5 100644 --- a/src/d/actor/d_a_obj_gomikabe.cpp +++ b/src/d/actor/d_a_obj_gomikabe.cpp @@ -201,7 +201,13 @@ void daObjGOMIKABE_c::CheckCull() { bool daObjGOMIKABE_c::checkViewArea(cXyz param_1) { Vec local_24; + + #if TARGET_PC + mDoLib_project(¶m_1, &local_24, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(¶m_1, &local_24); + #endif + bool rv = false; if (local_24.x >= 0.0f && local_24.x <= FB_WIDTH && local_24.y >= 0.0f && local_24.y <= FB_HEIGHT) { rv = true; diff --git a/src/d/actor/d_a_obj_hhashi.cpp b/src/d/actor/d_a_obj_hhashi.cpp index d5ab5558b0..ea45fa5ea1 100644 --- a/src/d/actor/d_a_obj_hhashi.cpp +++ b/src/d/actor/d_a_obj_hhashi.cpp @@ -214,7 +214,13 @@ void daObjHHASHI_c::CheckCull() { bool daObjHHASHI_c::checkViewArea(int param_1) { Vec local_20; + + #if TARGET_PC + mDoLib_project(&field_0x5b0[param_1], &local_20, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&field_0x5b0[param_1], &local_20); + #endif + bool rv = false; if (local_20.x >= 0.0f && local_20.x <= FB_WIDTH && local_20.y >= 0.0f && local_20.y <= FB_HEIGHT) { rv = true; diff --git a/src/d/actor/d_a_obj_kamakiri.cpp b/src/d/actor/d_a_obj_kamakiri.cpp index 2b641d7769..daa6560e6f 100644 --- a/src/d/actor/d_a_obj_kamakiri.cpp +++ b/src/d/actor/d_a_obj_kamakiri.cpp @@ -517,7 +517,13 @@ void daObjKAM_c::Z_BufferChk() { cXyz currentOffset; currentOffset = current.pos; currentOffset.y += 20.0f; + + #if TARGET_PC + mDoLib_project(¤tOffset, ¤tProj, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(¤tOffset, ¤tProj); + #endif + camera_process_class* camera = dComIfGp_getCamera(0); f32 cameraHeight; if (camera != NULL) { diff --git a/src/d/actor/d_a_obj_katatsumuri.cpp b/src/d/actor/d_a_obj_katatsumuri.cpp index 8b85644d80..03d40a1066 100644 --- a/src/d/actor/d_a_obj_katatsumuri.cpp +++ b/src/d/actor/d_a_obj_katatsumuri.cpp @@ -611,7 +611,12 @@ void daObjKAT_c::Z_BufferChk() { cXyz curWithOff; curWithOff = current.pos; curWithOff.y += 20.0f; + + #if TARGET_PC + mDoLib_project(&curWithOff, &projected, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&curWithOff, &projected); + #endif camera_process_class* camera = dComIfGp_getCamera(0); f32 unkFloat1; diff --git a/src/d/actor/d_a_obj_kuwagata.cpp b/src/d/actor/d_a_obj_kuwagata.cpp index ddb00d443d..914e25dfb1 100644 --- a/src/d/actor/d_a_obj_kuwagata.cpp +++ b/src/d/actor/d_a_obj_kuwagata.cpp @@ -528,7 +528,12 @@ void daObjKUW_c::Z_BufferChk() { cStack_68 = current.pos; cStack_68.y += 20.0f; + + #if TARGET_PC + mDoLib_project(&cStack_68, &local_5c, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&cStack_68, &local_5c); + #endif camera_process_class* cc = dComIfGp_getCamera(0); f32 trimHeight; diff --git a/src/d/actor/d_a_obj_ten.cpp b/src/d/actor/d_a_obj_ten.cpp index fbedd6a8c9..1efe358607 100644 --- a/src/d/actor/d_a_obj_ten.cpp +++ b/src/d/actor/d_a_obj_ten.cpp @@ -593,7 +593,13 @@ void daObjTEN_c::Z_BufferChk() { cXyz cStack_68; cStack_68 = current.pos; cStack_68.y += 20.0f; + + #if TARGET_PC + mDoLib_project(&cStack_68, &local_5c, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&cStack_68, &local_5c); + #endif + camera_process_class* camera = dComIfGp_getCamera(0); f32 trimHeight; if (camera != NULL) { diff --git a/src/d/actor/d_a_obj_tombo.cpp b/src/d/actor/d_a_obj_tombo.cpp index 092b1c46c3..48db14ac92 100644 --- a/src/d/actor/d_a_obj_tombo.cpp +++ b/src/d/actor/d_a_obj_tombo.cpp @@ -504,7 +504,13 @@ void daObjTOMBO_c::Z_BufferChk() { cXyz cStack_68; cStack_68 = current.pos; cStack_68.y += 20.0f; + + #if TARGET_PC + mDoLib_project(&cStack_68, &local_5c, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&cStack_68, &local_5c); + #endif + camera_process_class* pCamera = dComIfGp_getCamera(0); f32 trimHeight; if (pCamera != NULL) { diff --git a/src/d/actor/d_a_obj_zra_freeze.cpp b/src/d/actor/d_a_obj_zra_freeze.cpp index 1525bdb572..08932c439d 100644 --- a/src/d/actor/d_a_obj_zra_freeze.cpp +++ b/src/d/actor/d_a_obj_zra_freeze.cpp @@ -38,7 +38,12 @@ BOOL daZraFreeze_c::chkActorInScreen() { mDoMtx_stack_c::transM(0.0f, 0.0f, 0.0f); PSMTXMultVecArray(mDoMtx_stack_c::get(), vec, vec, 8); for (int i = 0; i < 8; i++) { + #if TARGET_PC + mDoLib_project(&vec[i], &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&vec[i], &proj); + #endif + if (0.0f < proj.x && proj.x < FB_WIDTH && 0.0f < proj.y && proj.y < FB_HEIGHT) { continue; } diff --git a/src/d/d_insect.cpp b/src/d/d_insect.cpp index 3f1d2fae11..380f545bd3 100644 --- a/src/d/d_insect.cpp +++ b/src/d/d_insect.cpp @@ -82,7 +82,11 @@ void dInsect_c::CalcZBuffer(f32 param_0) { pos = current.pos; pos.y += 20.0f; + #if TARGET_PC + mDoLib_project(&pos, &pos_projected, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&pos, &pos_projected); + #endif if (dComIfGp_getCamera(0)) { camera_trim_height = dComIfGp_getCamera(0)->mCamera.mTrimHeight; diff --git a/src/d/d_kankyo.cpp b/src/d/d_kankyo.cpp index ec35ee8bc7..50f9d716fc 100644 --- a/src/d/d_kankyo.cpp +++ b/src/d/d_kankyo.cpp @@ -10977,7 +10977,11 @@ void dKy_depth_dist_set(void* process_p) { f32 var_f31 = sp24.abs(camera_p->view.lookat.eye); if (var_f31 < 2000.0f && var_f31 < kankyo->field_0x1268) { + #if TARGET_PC + mDoLib_project(&actor_p->eyePos, &sp30, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&actor_p->eyePos, &sp30); + #endif if ((sp30.x >= 0.0f && sp30.x < FB_WIDTH) && (sp30.y >= 0.0f && #if DEBUG diff --git a/src/d/d_kankyo_debug.cpp b/src/d/d_kankyo_debug.cpp index 7fdad26ed2..03bfeac196 100644 --- a/src/d/d_kankyo_debug.cpp +++ b/src/d/d_kankyo_debug.cpp @@ -915,7 +915,12 @@ void dKydb_dungeonlight_draw() { rot.y = 0; dDbVw_drawCubeXlu(player->current.pos, size, rot, color); + #if TARGET_PC + mDoLib_project(&g_env_light.dungeonlight[i].mPosition, &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&g_env_light.dungeonlight[i].mPosition, &proj); + #endif + if (proj.x > 30.0f) { proj.x -= 30.0f; } diff --git a/src/d/d_msg_object.cpp b/src/d/d_msg_object.cpp index 8836acff10..3676639908 100644 --- a/src/d/d_msg_object.cpp +++ b/src/d/d_msg_object.cpp @@ -1464,12 +1464,24 @@ void dMsgObject_c::fukiPosCalc(bool param_1) { fopAc_ac_c* player = dComIfGp_getPlayer(0); cXyz local_3c; cXyz cStack_48; + + #if TARGET_PC + mDoLib_project(&player->eyePos, &cStack_48, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&player->eyePos, &cStack_48); + #endif + f32 temp; if ((field_0x100->pos == cXyz(0.0f, 0.0f, 0.0f))) { temp = cStack_48.y; } else { + + #if TARGET_PC + mDoLib_project(&field_0x100->pos, &local_3c, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&field_0x100->pos, &local_3c); + #endif + if (local_3c.x >= 0.0f && local_3c.x <= FB_WIDTH && local_3c.y >= 0.0f && local_3c.y <= FB_HEIGHT) { diff --git a/src/d/d_msg_scrn_item.cpp b/src/d/d_msg_scrn_item.cpp index bb4daf3927..7cba6c7b86 100644 --- a/src/d/d_msg_scrn_item.cpp +++ b/src/d/d_msg_scrn_item.cpp @@ -557,11 +557,22 @@ void dMsgScrnItem_c::fukiPosCalc(u8 param_1) { cXyz local_70; cXyz cStack_7c; f32 f3; + + #if TARGET_PC + mDoLib_project(&player->eyePos, &cStack_7c, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&player->eyePos, &cStack_7c); + #endif + if (iVar6->pos == cXyz(0.0f, 0.0f, 0.0f)) { f3 = cStack_7c.y; } else { + #if TARGET_PC + mDoLib_project(&iVar6->pos, &local_70, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&iVar6->pos, &local_70); + #endif + if (local_70.x >= 0.0f && local_70.x <= FB_WIDTH && local_70.y >= 0.0f && local_70.y <= FB_HEIGHT) { diff --git a/src/d/d_msg_scrn_talk.cpp b/src/d/d_msg_scrn_talk.cpp index 226ece0304..546e42112f 100644 --- a/src/d/d_msg_scrn_talk.cpp +++ b/src/d/d_msg_scrn_talk.cpp @@ -441,11 +441,22 @@ void dMsgScrnTalk_c::fukiPosCalc(u8 param_1) { cXyz local_70; cXyz cStack_7c; f32 f3y; + + #if TARGET_PC + mDoLib_project(&player->eyePos, &cStack_7c, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else mDoLib_project(&player->eyePos, &cStack_7c); + #endif + if (msgActor->pos == cXyz(0.0f, 0.0f, 0.0f)) { f3y = cStack_7c.y; } else { - mDoLib_project(&msgActor->pos, &local_70); + #if TARGET_PC + mDoLib_project(&msgActor->pos, &local_70, {0, 0, FB_WIDTH, FB_HEIGHT}); + #else + mDoLib_project(&msgActor->pos, &local_70,); + #endif + if (local_70.x >= 0.0f && local_70.x <= 608.0f && local_70.y >= 0.0f && local_70.y <= 448.0f) { From c41ec46c71d352201fed6a34ddf64cc4084856ea Mon Sep 17 00:00:00 2001 From: madeline Date: Mon, 6 Apr 2026 00:48:10 -0700 Subject: [PATCH 11/11] reorganize menus a bit --- src/dusk/imgui/ImGuiMenuEnhancements.cpp | 6 ++++++ src/dusk/imgui/ImGuiMenuTools.cpp | 5 +---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/dusk/imgui/ImGuiMenuEnhancements.cpp b/src/dusk/imgui/ImGuiMenuEnhancements.cpp index 3fbd55a8f2..a7a1e1d698 100644 --- a/src/dusk/imgui/ImGuiMenuEnhancements.cpp +++ b/src/dusk/imgui/ImGuiMenuEnhancements.cpp @@ -125,6 +125,12 @@ namespace dusk { ImGui::EndMenu(); } + if (ImGui::BeginMenu("Tools")) { + config::ImGuiCheckbox("Enable Turbo Key", getSettings().game.enableTurboKeybind); + + ImGui::EndMenu(); + } + ImGui::EndMenu(); } } diff --git a/src/dusk/imgui/ImGuiMenuTools.cpp b/src/dusk/imgui/ImGuiMenuTools.cpp index 6a2fc2ede0..7478adc0e5 100644 --- a/src/dusk/imgui/ImGuiMenuTools.cpp +++ b/src/dusk/imgui/ImGuiMenuTools.cpp @@ -7,7 +7,6 @@ #include "ImGuiConsole.hpp" #include "ImGuiMenuTools.hpp" -#include "ImGuiConfig.hpp" #include "d/actor/d_a_alink.h" #include "d/actor/d_a_horse.h" #include "d/d_com_inf_game.h" @@ -19,7 +18,7 @@ namespace dusk { void ImGuiMenuTools::draw() { bool isToggleDevelopmentMode = false; - if (ImGui::BeginMenu("Tools")) { + if (ImGui::BeginMenu("Debug")) { if (ImGui::Checkbox("Development Mode", &m_isDevelopmentMode)) { isToggleDevelopmentMode = true; } @@ -50,8 +49,6 @@ namespace dusk { ImGui::MenuItem("Save Editor", nullptr, &m_showSaveEditor); ImGui::MenuItem("Audio Debug", hotkeys::SHOW_AUDIO_DEBUG, &m_showAudioDebug); ImGui::MenuItem("OSReport Force", nullptr, &OSReportReallyForceEnable); - ImGui::Separator(); - config::ImGuiMenuItem("Enable Turbo Key", hotkeys::TURBO, getSettings().game.enableTurboKeybind); ImGui::EndMenu(); }