From 0dc475108b134dc0edb8f1d19e274b1a27656390 Mon Sep 17 00:00:00 2001 From: doop <56421834+dooplecks@users.noreply.github.com> Date: Tue, 26 May 2026 11:39:40 -0400 Subject: [PATCH 1/4] Ensure `dMdl_c` packet is in drawlist for interp (#1818) Fixes #1010. --- src/d/d_model.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/d/d_model.cpp b/src/d/d_model.cpp index 80dfbc0953..36e77e55fa 100644 --- a/src/d/d_model.cpp +++ b/src/d/d_model.cpp @@ -45,6 +45,20 @@ void dMdl_c::create(J3DModelData* i_modelData, u16 i_materialId, dKy_tevstr_c* i } void dMdl_c::entryObj(dMdl_obj_c* i_obj) { +#ifdef TARGET_PC + // if field_0x1a is false, this dMdl_c is not in the drawlist + // if true, we need to make sure with interp enabled + if (dusk::frame_interp::is_enabled() && field_0x1a) { + auto pkt = dComIfGd_getListPacket()->mpBuffer[0]; + while (pkt && pkt != this) { + pkt = pkt->getNextPacket(); + } + if (!pkt) { + field_0x1a = false; + } + } +#endif + if (!field_0x1a) { dComIfGd_getListPacket()->entryImm(this, 0); field_0x1a = true; From 2afc52772ca793ca3547a59dbbaccf97479501dd Mon Sep 17 00:00:00 2001 From: Irastris Date: Tue, 26 May 2026 22:39:07 -0400 Subject: [PATCH 2/4] Suppress Textinput line break events on BaseStringButton (#1833) * Suppress Textinput line break events on BaseStringButton * Oops, removed a comment --- src/dusk/ui/string_button.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/dusk/ui/string_button.cpp b/src/dusk/ui/string_button.cpp index 7210e87803..9a4c3ced09 100644 --- a/src/dusk/ui/string_button.cpp +++ b/src/dusk/ui/string_button.cpp @@ -7,7 +7,7 @@ namespace dusk::ui { BaseStringButton::BaseStringButton(Rml::Element* parent, Props props) : BaseControlledSelectButton(parent, {std::move(props.key)}), mType(std::move(props.type)), mMaxLength(props.maxLength) { - mInputListeners.reserve(3); + mInputListeners.reserve(4); } void BaseStringButton::update() { @@ -54,6 +54,15 @@ void BaseStringButton::start_editing() { mRoot->DispatchEvent(Rml::EventId::Submit, {{"handled", Rml::Variant{true}}}); // Register input listeners + mInputListeners.emplace_back(std::make_unique( + mInputElem, Rml::EventId::Textinput, [this](Rml::Event& event) { + if (event.GetTargetElement() == mInputElem) { + const Rml::String text = event.GetParameter("text", Rml::String{}); + if (!text.empty() && std::ranges::all_of(text, [](const char c) { return c == '\r' || c == '\n' || c == '\t'; })) { + event.StopImmediatePropagation(); + } + } + })); mInputListeners.emplace_back(std::make_unique( mInputElem, Rml::EventId::Keydown, [this](Rml::Event& event) { const auto cmd = map_nav_event(event); From dc0c868bc1bf473d49ef22dbe8f9f4a16ffb8912 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Tue, 26 May 2026 20:42:11 -0600 Subject: [PATCH 3/4] Crash handler: Properly map stack frames from modules; fix build IDs (#1848) --- src/dusk/crash_handler.cpp | 298 +++++++++++++++++++++++++++++++------ 1 file changed, 254 insertions(+), 44 deletions(-) diff --git a/src/dusk/crash_handler.cpp b/src/dusk/crash_handler.cpp index c79e2e16c8..8562d5f14b 100644 --- a/src/dusk/crash_handler.cpp +++ b/src/dusk/crash_handler.cpp @@ -64,6 +64,15 @@ struct CrashContext { }; CrashContext g_ctx; +struct ModuleInfo { + uintptr_t base = 0; + uintptr_t size = 0; + char path[1024] = {}; + uint8_t buildId[64] = {}; + unsigned buildIdLen = 0; + unsigned pdbAge = 0; +}; + void rawWrite(int fd, const char* data, size_t len) { if (fd < 0) { return; @@ -119,9 +128,29 @@ void writeHexBytes(int fd, const uint8_t* data, unsigned len) { } } -const char* moduleName() { - const char* name = g_ctx.modulePath; - for (const char* p = g_ctx.modulePath; *p != '\0'; ++p) { +void writeHexByte(int fd, uint8_t value) { + char buf[2]; + buf[0] = kHexDigits[value >> 4]; + buf[1] = kHexDigits[value & 0xF]; + rawWrite(fd, buf, 2); +} + +void writeQuoted(int fd, const char* s) { + writeStr(fd, "\""); + if (s != nullptr) { + for (const char* p = s; *p != '\0'; ++p) { + if (*p == '"' || *p == '\\') { + rawWrite(fd, "\\", 1); + } + rawWrite(fd, p, 1); + } + } + writeStr(fd, "\""); +} + +const char* baseName(const char* path) { + const char* name = path; + for (const char* p = path; p != nullptr && *p != '\0'; ++p) { if (*p == '/' || *p == '\\') { name = p + 1; } @@ -129,6 +158,40 @@ const char* moduleName() { return name[0] != '\0' ? name : "(unknown)"; } +void writeBuildId(int fd, const uint8_t* buildId, unsigned buildIdLen, unsigned pdbAge) { + if (buildIdLen == 0) { + writeStr(fd, "(unavailable)"); + return; + } +#if defined(_WIN32) + if (buildIdLen == 16) { + writeHexByte(fd, buildId[3]); + writeHexByte(fd, buildId[2]); + writeHexByte(fd, buildId[1]); + writeHexByte(fd, buildId[0]); + writeStr(fd, "-"); + writeHexByte(fd, buildId[5]); + writeHexByte(fd, buildId[4]); + writeStr(fd, "-"); + writeHexByte(fd, buildId[7]); + writeHexByte(fd, buildId[6]); + writeStr(fd, "-"); + writeHexByte(fd, buildId[8]); + writeHexByte(fd, buildId[9]); + writeStr(fd, "-"); + writeHexBytes(fd, buildId + 10, 6); + if (pdbAge != 0) { + writeStr(fd, "-"); + writeDec(fd, pdbAge); + } + return; + } +#else + (void)pdbAge; +#endif + writeHexBytes(fd, buildId, buildIdLen); +} + const char* symbolFor(uintptr_t pc, unsigned long long* disp) { #if defined(_WIN32) && defined(DUSK_CRASH_DBGHELP) alignas(SYMBOL_INFO) static char storage[sizeof(SYMBOL_INFO) + 512]; @@ -156,7 +219,47 @@ const char* symbolFor(uintptr_t pc, unsigned long long* disp) { #endif } +void fallbackModuleInfo(ModuleInfo& info) { + info = {}; + info.base = g_ctx.moduleBase; + std::strncpy(info.path, g_ctx.modulePath, sizeof(info.path) - 1); + if (g_ctx.buildIdLen > sizeof(info.buildId)) { + info.buildIdLen = sizeof(info.buildId); + } else { + info.buildIdLen = g_ctx.buildIdLen; + } + if (info.buildIdLen != 0) { + std::memcpy(info.buildId, g_ctx.buildId, info.buildIdLen); + } + info.pdbAge = g_ctx.pdbAge; +} + +bool findModuleInfo(uintptr_t pc, ModuleInfo& info); + +void emitAddressDetail(int fd, uintptr_t pc) { + ModuleInfo info; + findModuleInfo(pc, info); + const uintptr_t rva = pc >= info.base ? pc - info.base : 0ull; + writeHex(fd, pc); + writeStr(fd, " module_base="); + writeHex(fd, info.base); + if (info.size != 0) { + writeStr(fd, " image_size="); + writeHex(fd, info.size); + } + writeStr(fd, " rva="); + writeHex(fd, rva); + writeStr(fd, " module="); + writeQuoted(fd, info.path[0] != '\0' ? info.path : baseName(g_ctx.modulePath)); + writeStr(fd, " build_id="); + writeBuildId(fd, info.buildId, info.buildIdLen, info.pdbAge); +} + void emitFrame(int fd, int index, uintptr_t pc) { + ModuleInfo info; + findModuleInfo(pc, info); + const uintptr_t rva = pc >= info.base ? pc - info.base : 0ull; + writeStr(fd, "#"); if (index < 10) { writeStr(fd, "0"); @@ -164,10 +267,18 @@ void emitFrame(int fd, int index, uintptr_t pc) { writeDec(fd, static_cast(index)); writeStr(fd, " abs="); writeHex(fd, pc); + writeStr(fd, " module_base="); + writeHex(fd, info.base); + if (info.size != 0) { + writeStr(fd, " image_size="); + writeHex(fd, info.size); + } writeStr(fd, " rva="); - writeHex(fd, pc >= g_ctx.moduleBase ? pc - g_ctx.moduleBase : 0ull); - writeStr(fd, " "); - writeStr(fd, moduleName()); + writeHex(fd, rva); + writeStr(fd, " module="); + writeQuoted(fd, info.path[0] != '\0' ? info.path : baseName(g_ctx.modulePath)); + writeStr(fd, " build_id="); + writeBuildId(fd, info.buildId, info.buildIdLen, info.pdbAge); unsigned long long disp = 0; const char* sym = symbolFor(pc, &disp); if (sym != nullptr && sym[0] != '\0') { @@ -191,18 +302,7 @@ void emitHeader(int fd, const char* reason, unsigned long long code, bool hasCod writeStr(fd, "\nModule base: "); writeHex(fd, g_ctx.moduleBase); writeStr(fd, "\nBuild-ID: "); - if (g_ctx.buildIdLen != 0) { - writeHexBytes(fd, g_ctx.buildId, g_ctx.buildIdLen); -#if defined(_WIN32) - if (g_ctx.pdbAge != 0) { - writeStr(fd, " (Age="); - writeDec(fd, g_ctx.pdbAge); - writeStr(fd, ")"); - } -#endif - } else { - writeStr(fd, "(unavailable)"); - } + writeBuildId(fd, g_ctx.buildId, g_ctx.buildIdLen, g_ctx.pdbAge); writeStr(fd, "\nReason: "); writeStr(fd, reason); if (hasCode) { @@ -214,9 +314,7 @@ void emitHeader(int fd, const char* reason, unsigned long long code, bool hasCod writeHex(fd, faultAddr); writeStr(fd, "\nCrash PC: "); if (crashPcKnown) { - writeHex(fd, crashPc); - writeStr(fd, " rva="); - writeHex(fd, crashPc >= g_ctx.moduleBase ? crashPc - g_ctx.moduleBase : 0ull); + emitAddressDetail(fd, crashPc); } else { writeStr(fd, "(unavailable on this platform)"); } @@ -233,23 +331,25 @@ void emitFooter(int fd) { LONG g_inHandler = 0; LPTOP_LEVEL_EXCEPTION_FILTER g_prevFilter = nullptr; -void captureBuildId() { - const auto* base = reinterpret_cast(g_ctx.moduleBase); +bool readPeModuleInfo(uintptr_t moduleBase, ModuleInfo& info) { + const auto* base = reinterpret_cast(moduleBase); if (base == nullptr) { - return; + return false; } const auto* dos = reinterpret_cast(base); if (dos->e_magic != IMAGE_DOS_SIGNATURE) { - return; + return false; } const auto* nt = reinterpret_cast(base + dos->e_lfanew); if (nt->Signature != IMAGE_NT_SIGNATURE) { - return; + return false; } + info.base = moduleBase; + info.size = nt->OptionalHeader.SizeOfImage; const IMAGE_DATA_DIRECTORY& dir = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG]; if (dir.VirtualAddress == 0 || dir.Size == 0) { - return; + return true; } const auto* dbg = reinterpret_cast(base + dir.VirtualAddress); const unsigned count = dir.Size / sizeof(IMAGE_DEBUG_DIRECTORY); @@ -261,11 +361,40 @@ void captureBuildId() { if (std::memcmp(cv, "RSDS", 4) != 0) { continue; } - std::memcpy(g_ctx.buildId, cv + 4, sizeof(GUID)); - g_ctx.buildIdLen = sizeof(GUID); - std::memcpy(&g_ctx.pdbAge, cv + 4 + sizeof(GUID), sizeof(g_ctx.pdbAge)); + std::memcpy(info.buildId, cv + 4, sizeof(GUID)); + info.buildIdLen = sizeof(GUID); + std::memcpy(&info.pdbAge, cv + 4 + sizeof(GUID), sizeof(info.pdbAge)); break; } + return true; +} + +void captureBuildId() { + ModuleInfo info; + if (!readPeModuleInfo(g_ctx.moduleBase, info)) { + return; + } + g_ctx.buildIdLen = info.buildIdLen; + if (g_ctx.buildIdLen != 0) { + std::memcpy(g_ctx.buildId, info.buildId, g_ctx.buildIdLen); + } + g_ctx.pdbAge = info.pdbAge; +} + +bool findModuleInfo(uintptr_t pc, ModuleInfo& info) { + fallbackModuleInfo(info); + MEMORY_BASIC_INFORMATION mbi; + if (VirtualQuery(reinterpret_cast(pc), &mbi, sizeof(mbi)) == 0 || + mbi.AllocationBase == nullptr) { + return false; + } + const auto moduleBase = reinterpret_cast(mbi.AllocationBase); + info = {}; + info.base = moduleBase; + GetModuleFileNameA(reinterpret_cast(moduleBase), info.path, + static_cast(sizeof(info.path) - 1)); + readPeModuleInfo(moduleBase, info); + return true; } const char* exceptionName(DWORD code) { @@ -512,23 +641,35 @@ void prewarmUnwinder() { #if defined(__APPLE__) -void captureBuildId() { - const auto* header = reinterpret_cast(g_ctx.moduleBase); +bool readMachBuildId(uintptr_t moduleBase, ModuleInfo& info) { + const auto* header = reinterpret_cast(moduleBase); if (header == nullptr || header->magic != MH_MAGIC_64) { - return; + return false; } const auto* lc = reinterpret_cast( reinterpret_cast(header) + sizeof(struct mach_header_64)); for (uint32_t i = 0; i < header->ncmds; ++i) { if (lc->cmd == LC_UUID) { const auto* uuid = reinterpret_cast(lc); - std::memcpy(g_ctx.buildId, uuid->uuid, sizeof(uuid->uuid)); - g_ctx.buildIdLen = sizeof(uuid->uuid); - return; + std::memcpy(info.buildId, uuid->uuid, sizeof(uuid->uuid)); + info.buildIdLen = sizeof(uuid->uuid); + return true; } lc = reinterpret_cast( reinterpret_cast(lc) + lc->cmdsize); } + return true; +} + +void captureBuildId() { + ModuleInfo info; + if (!readMachBuildId(g_ctx.moduleBase, info)) { + return; + } + g_ctx.buildIdLen = info.buildIdLen; + if (g_ctx.buildIdLen != 0) { + std::memcpy(g_ctx.buildId, info.buildId, g_ctx.buildIdLen); + } } #else @@ -547,7 +688,28 @@ bool segmentContains(const dl_phdr_info* info, uintptr_t addr) { return false; } -bool readGnuBuildId(const dl_phdr_info* info) { +void readElfModuleInfo(const dl_phdr_info* info, ModuleInfo& module) { + uintptr_t minAddr = ~static_cast(0); + uintptr_t maxAddr = 0; + for (int i = 0; i < info->dlpi_phnum; ++i) { + const ElfW(Phdr)& ph = info->dlpi_phdr[i]; + if (ph.p_type != PT_LOAD) { + continue; + } + const uintptr_t start = info->dlpi_addr + ph.p_vaddr; + const uintptr_t end = start + ph.p_memsz; + if (start < minAddr) { + minAddr = start; + } + if (end > maxAddr) { + maxAddr = end; + } + } + if (minAddr <= maxAddr && maxAddr != 0) { + module.base = minAddr; + module.size = maxAddr - minAddr; + } + for (int i = 0; i < info->dlpi_phnum; ++i) { const ElfW(Phdr)& ph = info->dlpi_phdr[i]; if (ph.p_type != PT_NOTE) { @@ -563,17 +725,16 @@ bool readGnuBuildId(const dl_phdr_info* info) { if (nh->n_type == NT_GNU_BUILD_ID && nh->n_namesz == 4 && std::memcmp(name, "GNU", 4) == 0) { unsigned n = nh->n_descsz; - if (n > sizeof(g_ctx.buildId)) { - n = sizeof(g_ctx.buildId); + if (n > sizeof(module.buildId)) { + n = sizeof(module.buildId); } - std::memcpy(g_ctx.buildId, desc, n); - g_ctx.buildIdLen = n; - return true; + std::memcpy(module.buildId, desc, n); + module.buildIdLen = n; + return; } p = desc + ((nh->n_descsz + 3) & ~3u); } } - return false; } int elfBuildIdCallback(dl_phdr_info* info, size_t, void* arg) { @@ -581,7 +742,12 @@ int elfBuildIdCallback(dl_phdr_info* info, size_t, void* arg) { if (!segmentContains(info, self)) { return 0; } - readGnuBuildId(info); + ModuleInfo module; + readElfModuleInfo(info, module); + g_ctx.buildIdLen = module.buildIdLen; + if (g_ctx.buildIdLen != 0) { + std::memcpy(g_ctx.buildId, module.buildId, g_ctx.buildIdLen); + } return 1; } @@ -592,6 +758,50 @@ void captureBuildId() { #endif +#if !defined(__APPLE__) +struct ElfModuleSearch { + uintptr_t pc; + ModuleInfo* module; +}; + +int elfModuleInfoCallback(dl_phdr_info* info, size_t, void* arg) { + auto* search = static_cast(arg); + if (!segmentContains(info, search->pc)) { + return 0; + } + if (info->dlpi_name != nullptr && info->dlpi_name[0] != '\0') { + std::strncpy(search->module->path, info->dlpi_name, + sizeof(search->module->path) - 1); + } + readElfModuleInfo(info, *search->module); + return 1; +} +#endif + +bool findModuleInfo(uintptr_t pc, ModuleInfo& info) { + fallbackModuleInfo(info); + Dl_info moduleInfo; + if (dladdr(reinterpret_cast(pc), &moduleInfo) == 0) { + return false; + } + if (moduleInfo.dli_fbase != nullptr) { + info.base = reinterpret_cast(moduleInfo.dli_fbase); + } + if (moduleInfo.dli_fname != nullptr && moduleInfo.dli_fname[0] != '\0') { + info.path[0] = '\0'; + std::strncpy(info.path, moduleInfo.dli_fname, sizeof(info.path) - 1); + } + info.buildIdLen = 0; + info.pdbAge = 0; +#if defined(__APPLE__) + readMachBuildId(info.base, info); +#else + ElfModuleSearch search{pc, &info}; + dl_iterate_phdr(&elfModuleInfoCallback, &search); +#endif + return true; +} + const char* signalName(int sig) { switch (sig) { case SIGSEGV: From a85f8c203d0d68de948f7e5fd79538a226551203 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Tue, 26 May 2026 20:42:26 -0600 Subject: [PATCH 4/4] Better config saving handling (#1846) --- include/dusk/io.hpp | 5 +++++ src/dusk/config.cpp | 40 +++++++++++++++++++++++++++++----- src/dusk/io.cpp | 16 +++++++++++++- src/dusk/ui/graphics_tuner.cpp | 2 +- src/dusk/ui/settings.cpp | 5 +++++ src/dusk/ui/settings.hpp | 1 + src/dusk/ui/ui.cpp | 1 - 7 files changed, 62 insertions(+), 8 deletions(-) diff --git a/include/dusk/io.hpp b/include/dusk/io.hpp index 2efc4a8d3b..aba6bf8a48 100644 --- a/include/dusk/io.hpp +++ b/include/dusk/io.hpp @@ -30,6 +30,11 @@ public: ~FileStream(); + /** + * \brief Flush buffered writes and throw if the flush fails. + */ + void Flush(); + /** * \brief Open a file for reading at the given path. */ diff --git a/src/dusk/config.cpp b/src/dusk/config.cpp index c0edd84591..aed43089c4 100644 --- a/src/dusk/config.cpp +++ b/src/dusk/config.cpp @@ -8,6 +8,8 @@ #include "dusk/settings.h" #include +#include +#include #include #include "dusk/main.h" @@ -24,8 +26,24 @@ aurora::Module DuskConfigLog("dusk::config"); static absl::flat_hash_map RegisteredConfigVars; static bool RegistrationDone = false; -static std::u8string GetConfigJsonPath() { - return (dusk::ConfigPath / ConfigFileName).u8string(); +static std::filesystem::path GetConfigJsonPath() { + return dusk::ConfigPath / ConfigFileName; +} + +static std::filesystem::path GetTempConfigJsonPath(const std::filesystem::path& configJsonPath) { + auto tempPath = configJsonPath; + tempPath.replace_filename(fmt::format(".{}.tmp", configJsonPath.filename().string())); + return tempPath; +} + +static void ReplaceFile(const std::filesystem::path& source, const std::filesystem::path& target) { + std::error_code ec; + std::filesystem::rename(source, target, ec); + if (ec) { + const auto renameError = ec; + std::filesystem::remove(source, ec); + throw std::system_error(renameError); + } } ConfigVarBase::ConfigVarBase(const char* name, const ConfigImplBase* impl) : name(name), registered(false), layer(ConfigVarLayer::Default), impl(impl) { @@ -211,7 +229,8 @@ void dusk::config::LoadFromUserPreferences() { if (configJsonPath.empty()) { return; } - LoadFromFileName(reinterpret_cast(configJsonPath.c_str())); + const auto configPathString = io::fs_path_to_string(configJsonPath); + LoadFromFileName(configPathString.c_str()); } static void LoadFromPath(const char* path) { @@ -254,6 +273,10 @@ void dusk::config::LoadFromFileName(const char* path) { } else { DuskConfigLog.error("Failed to load from config! {}", e.what()); } + } catch (const nlohmann::json::parse_error& e) { + DuskConfigLog.error("Failed to parse config JSON, staying with defaults: {}", e.what()); + } catch (const std::exception& e) { + DuskConfigLog.error("Failed to load from config, staying with defaults: {}", e.what()); } } @@ -262,10 +285,11 @@ void dusk::config::Save() { if (configJsonPath.empty()) { return; } + const auto configPathString = io::fs_path_to_string(configJsonPath); DuskConfigLog.info( "Saving config to '{}'", - reinterpret_cast(configJsonPath.c_str())); + configPathString); json j; @@ -276,7 +300,13 @@ void dusk::config::Save() { } } - io::FileStream::WriteAllText(reinterpret_cast(configJsonPath.c_str()), j.dump(4)); + try { + const auto tempConfigJsonPath = GetTempConfigJsonPath(configJsonPath); + io::FileStream::WriteAllText(tempConfigJsonPath, j.dump(4)); + ReplaceFile(tempConfigJsonPath, configJsonPath); + } catch (const std::exception& e) { + DuskConfigLog.error("Failed to save config to '{}': {}", configPathString, e.what()); + } } void dusk::config::ClearAllActionBindings(int port) { diff --git a/src/dusk/io.cpp b/src/dusk/io.cpp index 4f6ef16a9b..88bd04ae8f 100644 --- a/src/dusk/io.cpp +++ b/src/dusk/io.cpp @@ -1,5 +1,7 @@ +#include #include #include +#include #include "dusk/io.hpp" @@ -30,6 +32,9 @@ static FILE* ThrowIfNotOpen(const FileStream& file) { } [[noreturn]] static void ThrowForError(int code) { + if (code == 0) { + throw std::system_error(std::make_error_code(std::errc::io_error)); + } throw std::system_error(std::make_error_code(static_cast(code))); } @@ -77,6 +82,14 @@ FileStream::~FileStream() { fclose(static_cast(file)); } +void FileStream::Flush() { + FILE* fileHandle = ThrowIfNotOpen(*this); + + if (fflush(fileHandle) != 0) { + ThrowForError(errno); + } +} + FileStream FileStream::OpenRead(const char* utf8Path) { return FileStream(OpenCore(utf8Path, MODE("rb"), _SH_DENYWR)); } @@ -163,10 +176,11 @@ void FileStream::WriteAllText(const char* utf8Path, const std::string_view text) void FileStream::WriteAllText(const std::filesystem::path& path, const std::string_view text) { auto handle = Create(path); handle.Write(text.data(), text.size()); + handle.Flush(); } FILE* FileStream::ToInner() { auto handle = file; file = nullptr; return handle; -} \ No newline at end of file +} diff --git a/src/dusk/ui/graphics_tuner.cpp b/src/dusk/ui/graphics_tuner.cpp index 8cea89e984..f91c300a75 100644 --- a/src/dusk/ui/graphics_tuner.cpp +++ b/src/dusk/ui/graphics_tuner.cpp @@ -95,7 +95,6 @@ void set_value(GraphicsOption option, int value) { getSettings().game.bloomMultiplier.setValue(std::clamp(value, 0, 100) / 100.0f); break; } - config::Save(); } Rml::Element* create_stepped_carousel_root(Rml::Element* parent) { @@ -292,6 +291,7 @@ void GraphicsTuner::show() { } void GraphicsTuner::hide(bool close) { + config::Save(); mRoot->RemoveAttribute("open"); if (close) { mPendingClose = true; diff --git a/src/dusk/ui/settings.cpp b/src/dusk/ui/settings.cpp index d6545f5121..0a3cf26a1f 100644 --- a/src/dusk/ui/settings.cpp +++ b/src/dusk/ui/settings.cpp @@ -1501,4 +1501,9 @@ void SettingsWindow::update() { Window::update(); } +void SettingsWindow::hide(bool close) { + config::Save(); + Window::hide(close); +} + } // namespace dusk::ui diff --git a/src/dusk/ui/settings.hpp b/src/dusk/ui/settings.hpp index 1d071e6575..1c8e9b826b 100644 --- a/src/dusk/ui/settings.hpp +++ b/src/dusk/ui/settings.hpp @@ -8,6 +8,7 @@ public: SettingsWindow(bool prelaunch = false); void update() override; + void hide(bool close) override; protected: bool mPrelaunch; diff --git a/src/dusk/ui/ui.cpp b/src/dusk/ui/ui.cpp index 072bf70b66..af05d0f476 100644 --- a/src/dusk/ui/ui.cpp +++ b/src/dusk/ui/ui.cpp @@ -61,7 +61,6 @@ bool initialize() noexcept { } void shutdown() noexcept { - config::Save(); sDocumentStack.clear(); sPassiveDocuments.clear(); sConnectedGamepads.clear();