From 1ef7f7f13202f1ab0813654d7951feefb9d13f76 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Mon, 8 Dec 2025 10:29:55 -0600 Subject: [PATCH 1/5] initial implementation of `Files` subsystem move methods related to file system operations out of core begin to implement mirrored implementation of DF's "portable mode" behavior with respect to dfhack configuration files --- library/CMakeLists.txt | 22 ++++---- library/Core.cpp | 94 ++++++++++++++++++---------------- library/Files.cpp | 60 ++++++++++++++++++++++ library/LuaApi.cpp | 6 +-- library/include/Core.h | 12 +++-- library/include/Files.h | 21 ++++++++ library/modules/Filesystem.cpp | 12 ++--- 7 files changed, 158 insertions(+), 69 deletions(-) create mode 100644 library/Files.cpp create mode 100644 library/include/Files.h diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index b405469b0e..c3e4edaeed 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -59,6 +59,7 @@ set(MAIN_HEADERS include/DebugManager.h include/Error.h include/Export.h + include/Files.h include/Format.h include/Hooks.h include/LuaTools.h @@ -89,33 +90,34 @@ set(MAIN_HEADERS_WINDOWS ) set(MAIN_SOURCES - Core.cpp ColorText.cpp Commands.cpp CompilerWorkAround.cpp + Core.cpp DataDefs.cpp DataIdentity.cpp + DataStatics.cpp + DataStaticsCtor.cpp Debug.cpp Error.cpp - VTableInterpose.cpp - LuaWrapper.cpp - LuaTypes.cpp - LuaTools.cpp + Files.cpp LuaApi.cpp - DataStatics.cpp - DataStaticsCtor.cpp + LuaTools.cpp + LuaTypes.cpp + LuaWrapper.cpp MemoryPatcher.cpp MiscUtils.cpp - Types.cpp PluginManager.cpp PluginStatics.cpp PlugLoad.cpp Process.cpp - TileTypes.cpp - VersionInfoFactory.cpp RemoteClient.cpp RemoteServer.cpp RemoteTools.cpp + TileTypes.cpp + Types.cpp + VersionInfoFactory.cpp + VTableInterpose.cpp ) file(GLOB_RECURSE TEST_SOURCES diff --git a/library/Core.cpp b/library/Core.cpp index a5cd996667..cffd79bcc2 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -27,6 +27,7 @@ distribution. #include "Internal.h" #include "Error.h" +#include "Files.h" #include "MemAccess.h" #include "DataDefs.h" #include "Debug.h" @@ -117,16 +118,6 @@ namespace DFHack { DBG_DECLARE(core, keybinding, DebugCategory::LINFO); DBG_DECLARE(core, script, DebugCategory::LINFO); - static const std::filesystem::path getConfigPath() - { - return Filesystem::getInstallDir() / "dfhack-config"; - }; - - static const std::filesystem::path getConfigDefaultsPath() - { - return Filesystem::getInstallDir() / "hack" / "data" / "dfhack-config-defaults"; - }; - class MainThread { public: //! MainThread::suspend keeps the main DF thread suspended from Core::Init to @@ -442,8 +433,10 @@ static bool try_autocomplete(color_ostream &con, const std::string &first, std:: bool Core::addScriptPath(std::filesystem::path path, bool search_before) { - std::lock_guard lock(script_path_mutex); - auto &vec = script_paths[search_before ? 0 : 1]; + std::lock_guard lock(script_path_mutex); + + auto& vec = search_before ? script_paths_first : script_paths_last; + if (std::find(vec.begin(), vec.end(), path) != vec.end()) return false; if (!Filesystem::isdir(path)) @@ -452,57 +445,70 @@ bool Core::addScriptPath(std::filesystem::path path, bool search_before) return true; } -bool Core::setModScriptPaths(const std::vector &mod_script_paths) { - std::lock_guard lock(script_path_mutex); - script_paths[2] = mod_script_paths; +bool Core::setModScriptPaths(const Core::filelist &mod_script_paths) { + std::lock_guard lock(script_path_mutex); + + script_paths_mod = mod_script_paths; return true; } -bool Core::removeScriptPath(std::filesystem::path path) +bool Core::removeScriptPath(std::filesystem::path& path) { - std::lock_guard lock(script_path_mutex); + auto pathlists = {&script_paths_first, &script_paths_last, &script_paths_mod}; + + std::lock_guard lock(script_path_mutex); + bool found = false; - for (int i = 0; i < 2; i++) + + for (auto& vec : pathlists) { - auto &vec = script_paths[i]; - while (1) - { - auto it = std::find(vec.begin(), vec.end(), path); - if (it == vec.end()) - break; - vec.erase(it); + auto cnt = std::erase(*vec, path); + if (cnt > 0) found = true; - } } + return found; } -void Core::getScriptPaths(std::vector *dest) +Core::filelist Core::getScriptPaths() { - std::lock_guard lock(script_path_mutex); - dest->clear(); - std::filesystem::path df_pref_path = Filesystem::getBaseDir(); - std::filesystem::path df_install_path = Filesystem::getInstallDir(); - for (auto & path : script_paths[0]) - dest->emplace_back(path); - // should this be df_pref_path? probably - dest->push_back(getConfigPath() / "scripts"); - if (df::global::world && isWorldLoaded()) { + using path = std::filesystem::path; + + std::lock_guard lock(script_path_mutex); + + filelist dest; + dest.clear(); + + for (auto & path : script_paths_first) + dest.emplace_back(path); + + filelist candidates{path{"dfhack-config"} / "scripts"}; + + if (df::global::world && isWorldLoaded()) + { std::string save = World::ReadWorldFolder(); if (save.size()) - dest->emplace_back(df_pref_path / "save" / save / "scripts"); + candidates.push_back(path{"save"} / save / "scripts"); } - dest->emplace_back(df_install_path / "hack" / "scripts"); - for (auto & path : script_paths[2]) - dest->emplace_back(path); - for (auto & path : script_paths[1]) - dest->emplace_back(path); + + for (auto& cand : candidates) + for (auto& path : getPossiblePaths(cand)) + dest.push_back(path); + + dest.emplace_back(getInstallPath() / "hack" / "scripts"); + + for (auto & path : script_paths_mod) + dest.push_back(path); + + for (auto & path : script_paths_last) + dest.push_back(path); + + return dest; } std::filesystem::path Core::findScript(std::string name) { - std::vector paths; - getScriptPaths(&paths); + auto paths = getScriptPaths(); for (auto& path : paths) { std::error_code ec; diff --git a/library/Files.cpp b/library/Files.cpp new file mode 100644 index 0000000000..26a0821a5d --- /dev/null +++ b/library/Files.cpp @@ -0,0 +1,60 @@ +#include +#include + +#include "Modules/DFSDL.h" +#include "Files.h" + +#include "df/init.h" +#include "df/init_media_flags.h" + +namespace DFHack +{ + namespace fs = std::filesystem; + + const fs::path getInstallPath() noexcept + { + return DFSDL::DFSDL_GetBasePath(); + }; + + const fs::path getUserPath() noexcept + { + return DFSDL::DFSDL_GetPrefPath("Bay 12 Games", "Dwarf Fortress"); + }; + + const bool isPortableMode() noexcept + { + return !df::global::init || df::global::init->media.flag.is_set(df::enums::init_media_flags::PORTABLE_MODE); + } + + const fs::path getBasePath(bool portable) noexcept + { + return portable ? getInstallPath() : getUserPath(); + } + + const fs::path getBasePath() noexcept + { + return getBasePath(isPortableMode()); + } + + const fs::path getAltBasePath() noexcept + { + return getBasePath(!isPortableMode()); + } + + const fs::path getConfigPath(bool portable) noexcept + { + return getBasePath(portable) / "dfhack-config"; + }; + + const fs::path getConfigDefaultsPath() noexcept + { + return getInstallPath() / "hack" / "data" / "dfhack-config-defaults"; + } + + const std::vector getPossiblePaths(fs::path file) noexcept + { + bool portable = isPortableMode(); + return {getBasePath(portable) / file, getBasePath(!portable) / file}; + } + +} \ No newline at end of file diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 4875b934c6..0d8f0f51f9 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -4099,7 +4099,8 @@ static int internal_addScriptPath(lua_State *L) static int internal_removeScriptPath(lua_State *L) { - const char *path = luaL_checkstring(L, 1); + const char *pathStr = luaL_checkstring(L, 1); + std::filesystem::path path = pathStr; lua_pushboolean(L, Core::getInstance().removeScriptPath(path)); return 1; } @@ -4108,8 +4109,7 @@ static int internal_getScriptPaths(lua_State *L) { int i = 1; lua_newtable(L); - vector paths; - Core::getInstance().getScriptPaths(&paths); + auto paths = Core::getInstance().getScriptPaths(); for (auto it = paths.begin(); it != paths.end(); ++it) { lua_pushinteger(L, i++); diff --git a/library/include/Core.h b/library/include/Core.h index 1f3cea5837..088747123d 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -176,9 +176,12 @@ namespace DFHack bool addScriptPath(std::filesystem::path path, bool search_before = false); bool setModScriptPaths(const std::vector & mod_script_paths); - bool removeScriptPath(std::filesystem::path path); + bool removeScriptPath(std::filesystem::path& path); std::filesystem::path findScript(std::string name); - void getScriptPaths(std::vector *dest); + + using filelist = std::vector; + + filelist getScriptPaths(); bool getSuppressDuplicateKeyboardEvents() const; void setSuppressDuplicateKeyboardEvents(bool suppress); @@ -308,7 +311,10 @@ namespace DFHack // Hotkey Manager DFHack::HotkeyManager *hotkey_mgr; - std::vector script_paths[3]; + filelist script_paths_first; + filelist script_paths_mod; + filelist script_paths_last; + std::mutex script_path_mutex; int8_t modstate; diff --git a/library/include/Files.h b/library/include/Files.h new file mode 100644 index 0000000000..130b02b2e7 --- /dev/null +++ b/library/include/Files.h @@ -0,0 +1,21 @@ +#include +#include + +namespace DFHack { + + const std::filesystem::path getInstallPath() noexcept; + const std::filesystem::path getUserPath() noexcept; + + const bool isPortableMode() noexcept; + + const std::filesystem::path getBasePath() noexcept; + const std::filesystem::path getAltBasePath() noexcept; + + const std::filesystem::path getBasePath(bool portable) noexcept; + const std::filesystem::path getConfigPath(bool portable = false) noexcept; + + const std::vector getPossiblePaths(std::filesystem::path file) noexcept; + + const std::filesystem::path getConfigDefaultsPath() noexcept; + +} diff --git a/library/modules/Filesystem.cpp b/library/modules/Filesystem.cpp index 6d49c6a3b7..c02326163f 100644 --- a/library/modules/Filesystem.cpp +++ b/library/modules/Filesystem.cpp @@ -51,6 +51,7 @@ SOFTWARE. #include #include +#include "Files.h" #include "modules/DFSDL.h" #include "modules/Filesystem.h" @@ -246,17 +247,10 @@ std::filesystem::path Filesystem::canonicalize(std::filesystem::path p) noexcept std::filesystem::path Filesystem::getInstallDir() noexcept { - return std::filesystem::path{ DFSDL::DFSDL_GetBasePath() }; + return DFHack::getInstallPath(); } std::filesystem::path Filesystem::getBaseDir() noexcept { - auto getsavebase = []() { - // assume portable mode is _on_ if init is missing - if (!df::global::init || df::global::init->media.flag.is_set(df::enums::init_media_flags::PORTABLE_MODE)) - return DFSDL::DFSDL_GetBasePath(); - else - return DFSDL::DFSDL_GetPrefPath("Bay 12 Games", "Dwarf Fortress"); - }; - return std::filesystem::path{ getsavebase() }; + return DFHack::getBasePath(); } From eb45fc92f0a10f4bf5e362a0055b2e51e877ff2b Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Mon, 8 Dec 2025 12:34:44 -0600 Subject: [PATCH 2/5] fixes * fix case dependency on `modules` * `"dfhack"` -> `"dfhack_config"` --- library/Core.cpp | 5 +++-- library/Files.cpp | 26 +++++++++++++++++++++++--- library/include/Files.h | 7 +++++++ 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index cffd79bcc2..a7bb157219 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -528,8 +528,9 @@ std::filesystem::path Core::findScript(std::string name) bool loadScriptPaths(color_ostream &out, bool silent = false) { - std::filesystem::path filename{ getConfigPath() / "script-paths.txt" }; - std::ifstream file(filename); + constexpr auto filename = "script-paths.txt"; + auto file = openConfigFile(filename); + if (!file) { if (!silent) diff --git a/library/Files.cpp b/library/Files.cpp index 26a0821a5d..6cf10e8d22 100644 --- a/library/Files.cpp +++ b/library/Files.cpp @@ -1,7 +1,8 @@ #include +#include #include -#include "Modules/DFSDL.h" +#include "modules/DFSDL.h" #include "Files.h" #include "df/init.h" @@ -51,10 +52,29 @@ namespace DFHack return getInstallPath() / "hack" / "data" / "dfhack-config-defaults"; } - const std::vector getPossiblePaths(fs::path file) noexcept + const std::vector getPossiblePaths(fs::path filename) noexcept { bool portable = isPortableMode(); - return {getBasePath(portable) / file, getBasePath(!portable) / file}; + return {getBasePath(portable) / filename, getBasePath(!portable) / filename}; + } + + std::ifstream openConfigFile(fs::path filename) + { + return openFile("dfhack-config" / filename); + } + + std::ifstream openFile(fs::path filename) + { + std::ifstream file; + fmt::print(stderr, "Trying to open file: {}\n", filename.string()); + for (const auto& path : getPossiblePaths(filename)) + { + file.open(path); + fmt::print(stderr, "Trying {}: {}\n", path.string(), file.is_open()); + if (file) + break; + } + return file; } } \ No newline at end of file diff --git a/library/include/Files.h b/library/include/Files.h index 130b02b2e7..75629b76de 100644 --- a/library/include/Files.h +++ b/library/include/Files.h @@ -1,4 +1,5 @@ #include +#include #include namespace DFHack { @@ -18,4 +19,10 @@ namespace DFHack { const std::filesystem::path getConfigDefaultsPath() noexcept; + std::ifstream openConfigFile(std::filesystem::path filename); + + std::ifstream openFile(std::filesystem::path filename); + + + } From f615ac8867c4184f5e128ca2b8fb38075decbb04 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Mon, 8 Dec 2025 13:08:34 -0600 Subject: [PATCH 3/5] force portable mode during testing --- ci/run-tests.py | 5 +++++ library/Files.cpp | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/ci/run-tests.py b/ci/run-tests.py index 13eeb099c8..15303fe38d 100755 --- a/ci/run-tests.py +++ b/ci/run-tests.py @@ -72,6 +72,11 @@ def change_setting(content, setting, value): #if args.headless: # init_contents = change_setting(init_contents, 'PRINT_MODE', 'TEXT') +# force portable mode for testing so that anything in the user prefs doesn't interfere +portable_txt_path = 'prefs/portable.txt' +with open(portable_txt_path, 'w') as f: + pass # empty file to enable portable mode + init_path = 'dfhack-config/init' if not os.path.isdir('hack/init'): # we're on an old branch that still reads init files from the root dir diff --git a/library/Files.cpp b/library/Files.cpp index 6cf10e8d22..4d771659e6 100644 --- a/library/Files.cpp +++ b/library/Files.cpp @@ -77,4 +77,4 @@ namespace DFHack return file; } -} \ No newline at end of file +} From 37e6893af22bd47eca575eb789faba05d467ca0e Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Mon, 8 Dec 2025 14:02:06 -0600 Subject: [PATCH 4/5] make script loading work correctly --- library/Commands.cpp | 2 +- library/Core.cpp | 4 +++- library/Files.cpp | 34 ++++++++++++++++++++++++---------- library/include/Files.h | 4 +++- 4 files changed, 31 insertions(+), 13 deletions(-) diff --git a/library/Commands.cpp b/library/Commands.cpp index b19ed68bb4..da795e75ca 100644 --- a/library/Commands.cpp +++ b/library/Commands.cpp @@ -434,7 +434,7 @@ namespace DFHack { if (parts.size() == 1) { - core.loadScriptFile(con, std::filesystem::weakly_canonical(std::filesystem::path{parts[0]}), false); + core.loadScriptFile(con, std::filesystem::path{parts[0]}.lexically_normal(), false); return CR_OK; } else diff --git a/library/Core.cpp b/library/Core.cpp index a7bb157219..c8c84d5c18 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -866,13 +866,15 @@ bool Core::loadScriptFile(color_ostream &out, std::filesystem::path fname, bool INFO(script,out) << "Running script: " << fname << std::endl; std::cerr << "Running script: " << fname << std::endl; } - std::ifstream script{ fname.c_str() }; + + auto script = openFile(fname); if ( !script ) { if(!silent) out.printerr("Error loading script: {}\n", fname); return false; } + std::string command; while(script.good()) { std::string temp; diff --git a/library/Files.cpp b/library/Files.cpp index 4d771659e6..d9a50985dc 100644 --- a/library/Files.cpp +++ b/library/Files.cpp @@ -52,7 +52,7 @@ namespace DFHack return getInstallPath() / "hack" / "data" / "dfhack-config-defaults"; } - const std::vector getPossiblePaths(fs::path filename) noexcept + const std::vector getPossiblePaths(const fs::path filename) noexcept { bool portable = isPortableMode(); return {getBasePath(portable) / filename, getBasePath(!portable) / filename}; @@ -63,18 +63,32 @@ namespace DFHack return openFile("dfhack-config" / filename); } - std::ifstream openFile(fs::path filename) + const fs::path findConfigFile(fs::path filename) { - std::ifstream file; - fmt::print(stderr, "Trying to open file: {}\n", filename.string()); + return findFile("dfhack-config" / filename); + } + + std::ifstream openFile(const fs::path filename) + { + auto path = findFile(filename); + if (path.empty()) + return std::ifstream(); + return std::ifstream(path); + } + + const fs::path findFile(const fs::path filename) + { + if (filename.is_absolute()) + return filename; + + fmt::print(stderr, "Trying to locate file: {}\n", filename.string()); + for (const auto& path : getPossiblePaths(filename)) { - file.open(path); - fmt::print(stderr, "Trying {}: {}\n", path.string(), file.is_open()); - if (file) - break; + if (fs::exists(path)) + return path; } - return file; + return fs::path(); } - } + diff --git a/library/include/Files.h b/library/include/Files.h index 75629b76de..316986c287 100644 --- a/library/include/Files.h +++ b/library/include/Files.h @@ -15,12 +15,14 @@ namespace DFHack { const std::filesystem::path getBasePath(bool portable) noexcept; const std::filesystem::path getConfigPath(bool portable = false) noexcept; - const std::vector getPossiblePaths(std::filesystem::path file) noexcept; + const std::vector getPossiblePaths(const std::filesystem::path file) noexcept; const std::filesystem::path getConfigDefaultsPath() noexcept; + const std::filesystem::path findConfigFile(const std::filesystem::path filename); std::ifstream openConfigFile(std::filesystem::path filename); + const std::filesystem::path findFile(const std::filesystem::path filename); std::ifstream openFile(std::filesystem::path filename); From 9a9f521ecbff8cc820c1b7f5845a28d2f0ac7b9e Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Tue, 9 Dec 2025 08:59:18 -0600 Subject: [PATCH 5/5] fix end of line --- library/Files.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/library/Files.cpp b/library/Files.cpp index d9a50985dc..3957fc88c6 100644 --- a/library/Files.cpp +++ b/library/Files.cpp @@ -91,4 +91,3 @@ namespace DFHack return fs::path(); } } -