From 97c7eecb272aa8a625aef81d741b6cc8f1e3681c Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Sat, 14 Mar 2026 08:16:18 -0500 Subject: [PATCH 01/23] split dfhack.dll out of dfhooks_dfhack.dll removes disparallelism with linux, and makes dealing with cross-folder hooking mechanics easier --- library/CMakeLists.txt | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index b405469b0e..3982a1a37c 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -318,8 +318,6 @@ endif() # Compilation -add_definitions(-DBUILD_DFHACK_LIB) - if(UNIX) if(CONSOLE_NO_CATCH) add_definitions(-DCONSOLE_NO_CATCH) @@ -373,6 +371,7 @@ if(EXISTS ${dfhack_SOURCE_DIR}/.git/index AND EXISTS ${dfhack_SOURCE_DIR}/.git/m endif() add_library(dfhack SHARED ${PROJECT_SOURCES}) +target_compile_definitions(dfhack PRIVATE BUILD_DFHACK_LIB) target_include_directories(dfhack PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/proto) get_target_property(xlsxio_INCLUDES xlsxio_read_STATIC INTERFACE_INCLUDE_DIRECTORIES) @@ -381,6 +380,7 @@ add_dependencies(dfhack generate_proto_core) add_dependencies(dfhack generate_headers) add_library(dfhack-client SHARED RemoteClient.cpp ColorText.cpp MiscUtils.cpp Error.cpp ${PROJECT_PROTO_SRCS} ${CONSOLE_SOURCES}) +target_compile_definitions(dfhack-client PRIVATE BUILD_DFHACK_LIB) target_include_directories(dfhack-client PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/proto) add_dependencies(dfhack-client dfhack) @@ -391,16 +391,16 @@ add_executable(binpatch binpatch.cpp) target_link_libraries(binpatch dfhack-md5) if(WIN32) - set_target_properties(dfhack PROPERTIES OUTPUT_NAME "dfhooks_dfhack" ) set_target_properties(dfhack PROPERTIES COMPILE_FLAGS "/FI\"Export.h\"" ) set_target_properties(dfhack-client PROPERTIES COMPILE_FLAGS "/FI\"Export.h\"" ) else() set_target_properties(dfhack PROPERTIES COMPILE_FLAGS "-include Export.h" ) set_target_properties(dfhack-client PROPERTIES COMPILE_FLAGS "-include Export.h" ) - add_library(dfhooks_dfhack SHARED Hooks.cpp) - target_link_libraries(dfhooks_dfhack dfhack ${FMTLIB}) endif() +add_library(dfhooks_dfhack SHARED Hooks.cpp) +target_link_libraries(dfhooks_dfhack PUBLIC dfhack ${FMTLIB}) + # effectively disables debug builds... set_target_properties(dfhack PROPERTIES DEBUG_POSTFIX "-debug" ) @@ -450,10 +450,11 @@ if(UNIX) install(PROGRAMS ${dfhack_SOURCE_DIR}/package/linux/dfhack-run DESTINATION .) endif() - install(TARGETS dfhooks_dfhack +endif() + +install(TARGETS dfhooks_dfhack LIBRARY DESTINATION . RUNTIME DESTINATION .) -endif() # install the main lib install(TARGETS dfhack From a3a26bda97160b1809afa213070533cb9ceb943c Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Sat, 14 Mar 2026 11:07:24 -0500 Subject: [PATCH 02/23] update dfhooks to resteam branch --- depends/dfhooks | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/depends/dfhooks b/depends/dfhooks index 4c48e25a2a..2d84a5826c 160000 --- a/depends/dfhooks +++ b/depends/dfhooks @@ -1 +1 @@ -Subproject commit 4c48e25a2a33538bf0c522f69987fd28c1525503 +Subproject commit 2d84a5826c51e99a6ff2c7d4c530680b366044c1 diff --git a/scripts b/scripts index b672e4afc6..56c934eb5d 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit b672e4afc6225d4e5e414beeb13f530b8cf66b39 +Subproject commit 56c934eb5d4c957ca731705783a0a43397a9ba2c From e2bbfc9fe8a987035622503b9d8c476b8c50d164 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Mon, 16 Mar 2026 14:46:29 -0500 Subject: [PATCH 03/23] Further work on Steam compatibility 1. Adopt changes to dfhooks 2. Install dfhooks_dfhack the `hack` folder, and generate and install an appropriate `dfhooks_dfhack.ini` into the DF folder 3. Adjust RPATH (on Linux) so dfhooks_dfhack.dll can find dfhack.dll 4. Use dfhooks preinit to get the hack base path. Use this path when initializing Core 5. in `getHackPath()`, use the collected hack base path instead of hardcoding `./hack`. 6. Fix at one instance where `hack` was hardcoded outside of `getHackPath` (note: there are others this commit does not fix) 7. Update VersionInfoFactory to use a fs `path` instead of a `string` as its argument --- CMakeLists.txt | 13 +++++-------- library/CMakeLists.txt | 12 +++++++++--- library/Core.cpp | 5 +++-- library/Hooks.cpp | 10 +++++++++- library/include/Core.h | 4 +++- 5 files changed, 29 insertions(+), 15 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 93df1f3576..5694d4bd9f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -226,13 +226,10 @@ set(DFHACK_DATA_DESTINATION hack) ## where to install things (after the build is done, classic 'make install' or package structure) # the dfhack libraries will be installed here: -if(UNIX) - # put the lib into DF/hack - set(DFHACK_LIBRARY_DESTINATION ${DFHACK_DATA_DESTINATION}) -else() - # windows is crap, therefore we can't do nice things with it. leave the libs on a nasty pile... - set(DFHACK_LIBRARY_DESTINATION .) -endif() + +# put the lib into DF/hack +# windows will find it because dfhooks will `AddDllDirectory` the hack folder at runtime +set(DFHACK_LIBRARY_DESTINATION ${DFHACK_DATA_DESTINATION}) # external tools will be installed here: set(DFHACK_BINARY_DESTINATION .) @@ -267,7 +264,7 @@ if(UNIX) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -m32 -march=i686") endif() string(REPLACE "-DNDEBUG" "" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") - set(CMAKE_INSTALL_RPATH ${DFHACK_LIBRARY_DESTINATION}) + set(CMAKE_INSTALL_RPATH "$ORIGIN/${DFHACK_LIBRARY_DESTINATION}") elseif(MSVC) # for msvc, tell it to always use 8-byte pointers to member functions to avoid confusion set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /vmg /vmm /MP") diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 3982a1a37c..f91afef2e2 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -131,7 +131,6 @@ endif() set(MAIN_SOURCES_WINDOWS ${CONSOLE_SOURCES} - Hooks.cpp ) if(WIN32) @@ -453,8 +452,9 @@ if(UNIX) endif() install(TARGETS dfhooks_dfhack - LIBRARY DESTINATION . - RUNTIME DESTINATION .) + LIBRARY DESTINATION ${DFHACK_LIBRARY_DESTINATION} + RUNTIME DESTINATION ${DFHACK_LIBRARY_DESTINATION}) + # install the main lib install(TARGETS dfhack @@ -465,6 +465,12 @@ install(TARGETS dfhack-run dfhack-client binpatch LIBRARY DESTINATION ${DFHACK_LIBRARY_DESTINATION} RUNTIME DESTINATION ${DFHACK_LIBRARY_DESTINATION}) +file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/dfhooks_dfhack.ini + CONTENT "${DFHACK_DATA_DESTINATION}/$") + +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/dfhooks_dfhack.ini + DESTINATION .) + endif(BUILD_LIBRARY) # install the offset file diff --git a/library/Core.cpp b/library/Core.cpp index 8979cb3eee..9fe3cf9bc6 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1054,16 +1054,17 @@ void Core::fatal (std::string output, const char * title) std::filesystem::path Core::getHackPath() { - return Filesystem::get_initial_cwd() / "hack"; + return hack_path; } df::viewscreen * Core::getTopViewscreen() { return getInstance().top_viewscreen; } -bool Core::InitMainThread() { +bool Core::InitMainThread(std::filesystem::path path) { // this hook is always called from DF's main (render) thread, so capture this thread id df_render_thread = std::this_thread::get_id(); + hack_path = path; Filesystem::init(); diff --git a/library/Hooks.cpp b/library/Hooks.cpp index 0f957fea06..31d711056b 100644 --- a/library/Hooks.cpp +++ b/library/Hooks.cpp @@ -7,6 +7,14 @@ static bool disabled = false; DFhackCExport const int32_t dfhooks_priority = 100; +static std::filesystem::path basepath{"./hack"}; + +// called by the chainloader before the main thread is initialized and before any other hooks are called. +DFhackCExport void dfhooks_preinit(std::filesystem::path dllpath) +{ + basepath = dllpath.parent_path(); +} + // called from the main thread before the simulation thread is started // and the main event loop is initiated DFhackCExport void dfhooks_init() { @@ -17,7 +25,7 @@ DFhackCExport void dfhooks_init() { } // we need to init DF globals before we can check the commandline - if (!DFHack::Core::getInstance().InitMainThread() || !df::global::game) { + if (!DFHack::Core::getInstance().InitMainThread(basepath) || !df::global::game) { // we don't set disabled to true here so symbol generation can work return; } diff --git a/library/include/Core.h b/library/include/Core.h index 1f3cea5837..9ccf0ca871 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -268,7 +268,7 @@ namespace DFHack struct Private; std::unique_ptr d; - bool InitMainThread(); + bool InitMainThread(std::filesystem::path path); bool InitSimulationThread(); int Update (void); int Shutdown (void); @@ -353,6 +353,8 @@ namespace DFHack uint32_t unpaused_ms; // reset to 0 on map load + std::filesystem::path hack_path; + friend class CoreService; friend class ServerConnection; friend class CoreSuspender; From 8e20fbd4eb3ffe9bbaacc4d7429fcc8ebdc4c40d Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Mon, 16 Mar 2026 15:23:06 -0500 Subject: [PATCH 04/23] canonicalize hack path at init --- library/Hooks.cpp | 2 +- library/include/Hooks.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/library/Hooks.cpp b/library/Hooks.cpp index 31d711056b..3f5ca36c32 100644 --- a/library/Hooks.cpp +++ b/library/Hooks.cpp @@ -25,7 +25,7 @@ DFhackCExport void dfhooks_init() { } // we need to init DF globals before we can check the commandline - if (!DFHack::Core::getInstance().InitMainThread(basepath) || !df::global::game) { + if (!DFHack::Core::getInstance().InitMainThread(std::filesystem::canonical(basepath)) || !df::global::game) { // we don't set disabled to true here so symbol generation can work return; } diff --git a/library/include/Hooks.h b/library/include/Hooks.h index 6945de2ea6..5f49d8daba 100644 --- a/library/include/Hooks.h +++ b/library/include/Hooks.h @@ -26,6 +26,7 @@ distribution. union SDL_Event; +DFhackCExport void dfhooks_preinit(std::filesystem::path dllpath); DFhackCExport void dfhooks_init(); DFhackCExport void dfhooks_shutdown(); DFhackCExport void dfhooks_update(); From a546399f150bb352fb57f39adbb0dcc44f3a6b31 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Mon, 16 Mar 2026 15:54:22 -0500 Subject: [PATCH 05/23] test: use RPATH `$ORIGIN` on Linux --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5694d4bd9f..9be9193210 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -264,7 +264,7 @@ if(UNIX) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -m32 -march=i686") endif() string(REPLACE "-DNDEBUG" "" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") - set(CMAKE_INSTALL_RPATH "$ORIGIN/${DFHACK_LIBRARY_DESTINATION}") + set(CMAKE_INSTALL_RPATH "$ORIGIN") elseif(MSVC) # for msvc, tell it to always use 8-byte pointers to member functions to avoid confusion set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /vmg /vmm /MP") From 02f64aa48c7712f54e03e8e575f671836680c0be Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Mon, 16 Mar 2026 21:34:16 -0500 Subject: [PATCH 06/23] add path autodetection --- library/Hooks.cpp | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/library/Hooks.cpp b/library/Hooks.cpp index 3f5ca36c32..9865ca3276 100644 --- a/library/Hooks.cpp +++ b/library/Hooks.cpp @@ -3,11 +3,36 @@ #include "df/gamest.h" +#ifdef _WIN32 +# define WIN32_LEAN_AND_MEAN +# include +# include +#else +# include +#endif + static bool disabled = false; DFhackCExport const int32_t dfhooks_priority = 100; -static std::filesystem::path basepath{"./hack"}; +static std::filesystem::path getModulePath() +{ +#ifdef _WIN32 + HMODULE module = nullptr; + GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCWSTR)getModulePath, &module); + if (!module) return std::filesystem::path(); // should never happen, but just in case, return an empty path instead of crashing + + wchar_t path[MAX_PATH]; + GetModuleFileNameW(module, path, MAX_PATH); + return std::filesystem::path(path); +#else + DL_info info; + dladdr(getModulePath, &info); + return std::filesystem::path(info.dli_fname); +#endif +} + +static std::filesystem::path basepath{getModulePath()}; // called by the chainloader before the main thread is initialized and before any other hooks are called. DFhackCExport void dfhooks_preinit(std::filesystem::path dllpath) From 0413b712ca06206943e0013a84363b07927b9cff Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Mon, 16 Mar 2026 21:34:38 -0500 Subject: [PATCH 07/23] fix lua default path --- library/Core.cpp | 50 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index 9fe3cf9bc6..d7fc3f0d7b 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1092,6 +1092,7 @@ bool Core::InitMainThread(std::filesystem::path path) { std::cerr << "Build url: " << Version::dfhack_run_url() << std::endl; } std::cerr << "Starting with working directory: " << Filesystem::getcwd() << std::endl; + std::cerr << "Hack path: " << getHackPath() << std::endl; std::cerr << "Binding to SDL.\n"; if (!DFSDL::init(con)) { @@ -1233,9 +1234,9 @@ bool Core::InitSimulationThread() { // the update hook is only called from the simulation thread, so capture this thread id df_simulation_thread = std::this_thread::get_id(); - if(started) + if (started) return true; - if(errorstate) + if (errorstate) return false; // Lock the CoreSuspendMutex until the thread exits or call Core::Shutdown @@ -1277,20 +1278,20 @@ bool Core::InitSimulationThread() std::cout << "Console disabled.\n"; } } - else if(con.init(false)) + else if (con.init(false)) std::cerr << "Console is running.\n"; else std::cerr << "Console has failed to initialize!\n"; -/* - // dump offsets to a file - std::ofstream dump("offsets.log"); - if(!dump.fail()) - { - //dump << vinfo->PrintOffsets(); - dump.close(); - } - */ - // initialize data defs + /* + // dump offsets to a file + std::ofstream dump("offsets.log"); + if(!dump.fail()) + { + //dump << vinfo->PrintOffsets(); + dump.close(); + } + */ + // initialize data defs virtual_identity::Init(this); // create config directory if it doesn't already exist @@ -1307,7 +1308,8 @@ bool Core::InitSimulationThread() else { // ensure all config file directories exist before we start copying files - for (auto &entry : default_config_files) { + for (auto& entry : default_config_files) + { // skip over files if (!entry.second) continue; @@ -1317,19 +1319,22 @@ bool Core::InitSimulationThread() } // copy files from the default tree that don't already exist in the config tree - for (auto &entry : default_config_files) { + for (auto& entry : default_config_files) + { // skip over directories if (entry.second) continue; std::filesystem::path filename = entry.first; - if (!config_files.contains(filename)) { + if (!config_files.contains(filename)) + { std::filesystem::path src_file = getConfigDefaultsPath() / filename; if (!Filesystem::isfile(src_file)) continue; std::filesystem::path dest_file = getConfigPath() / filename; std::ifstream src(src_file, std::ios::binary); std::ofstream dest(dest_file, std::ios::binary); - if (!src.good() || !dest.good()) { + if (!src.good() || !dest.good()) + { con.printerr("Copy failed: '{}'\n", filename); continue; } @@ -1340,6 +1345,17 @@ bool Core::InitSimulationThread() } } + // set lua default path if not already set + if (std::getenv("DFHACK_LUA_PATH") == nullptr) + { + std::filesystem::path lua_path = getHackPath() / "lua" / "?.lua"; +#ifdef WIN32 + _putenv_s("DFHACK_LUA_PATH", lua_path.string().c_str()); +#else + setenv("DFHACK_LUA_PATH", lua_path.string().c_str(), 1); +#endif + } + loadScriptPaths(con); // initialize common lua context From 0b1ea135a6bdecc8ab4f9735b3ddfda8a6b904f7 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Mon, 16 Mar 2026 22:51:37 -0500 Subject: [PATCH 08/23] `script`: try multiple locatons specifically, try in the hack path, in the hack path parent directory, and in the CWD, in that order --- library/Core.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/library/Core.cpp b/library/Core.cpp index d7fc3f0d7b..2deba390ff 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -859,7 +859,22 @@ 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 pathlist = {getHackPath(), getHackPath().parent_path(), std::filesystem::current_path()}; + + std::filesystem::path path; + + for (auto& p : pathlist) + { + auto candidate = fname.is_relative() ? p / fname : fname; + if (std::filesystem::exists(candidate)) + { + path = candidate; + break; + } + } + + std::ifstream script{ path }; if ( !script ) { if(!silent) From 0e25a2fd28d93988c46e619244b635eba1e8cd60 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Mon, 16 Mar 2026 23:04:09 -0500 Subject: [PATCH 09/23] fix typo --- library/Hooks.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/Hooks.cpp b/library/Hooks.cpp index 9865ca3276..d37f9fad08 100644 --- a/library/Hooks.cpp +++ b/library/Hooks.cpp @@ -26,7 +26,7 @@ static std::filesystem::path getModulePath() GetModuleFileNameW(module, path, MAX_PATH); return std::filesystem::path(path); #else - DL_info info; + Dl_info info; dladdr(getModulePath, &info); return std::filesystem::path(info.dli_fname); #endif From 56462222b00ca499d4881b9a03652a7799b8d908 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Mon, 16 Mar 2026 23:10:24 -0500 Subject: [PATCH 10/23] gcc wants an explicit cast here --- library/Hooks.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/Hooks.cpp b/library/Hooks.cpp index d37f9fad08..42e9f859af 100644 --- a/library/Hooks.cpp +++ b/library/Hooks.cpp @@ -27,7 +27,7 @@ static std::filesystem::path getModulePath() return std::filesystem::path(path); #else Dl_info info; - dladdr(getModulePath, &info); + dladdr((const void*)getModulePath, &info); return std::filesystem::path(info.dli_fname); #endif } From 1ec0bc211a9ad372c8149a5114ca0f7aee70ec07 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 26 Mar 2026 11:47:07 -0500 Subject: [PATCH 11/23] Make stonesense loadable in a relocated installation --- library/PlugLoad.cpp | 3 ++- plugins/stonesense | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/library/PlugLoad.cpp b/library/PlugLoad.cpp index 336e7f50e7..fa58d39514 100644 --- a/library/PlugLoad.cpp +++ b/library/PlugLoad.cpp @@ -15,10 +15,11 @@ #ifdef WIN32 #define NOMINMAX #include +#include #define global_search_handle() GetModuleHandle(nullptr) #define get_function_address(plugin, function) GetProcAddress((HMODULE)plugin, function) #define clear_error() -#define load_library(fn) LoadLibraryW(fn.c_str()) +#define load_library(fn) LoadLibraryExW(fn.wstring().c_str(), NULL, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS); #define close_library(handle) (!(FreeLibrary((HMODULE)handle))) #else #include diff --git a/plugins/stonesense b/plugins/stonesense index 4760027eee..581f8ff731 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 4760027eee745d8c35cca843a2fcc46c21be326a +Subproject commit 581f8ff7317fd15723fb31632650746afede7b6c From 2fee311c0c1f2142bb9c07cf1e21a73599b61290 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 26 Mar 2026 13:57:23 -0500 Subject: [PATCH 12/23] incorporate stonesense updates --- plugins/stonesense | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/stonesense b/plugins/stonesense index 581f8ff731..64cc02b123 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 581f8ff7317fd15723fb31632650746afede7b6c +Subproject commit 64cc02b12321abf9b99e41c4e5f931f71bc81a45 From 8efc6726301f084c70e66b3d702945257f04150d Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 26 Mar 2026 14:48:54 -0500 Subject: [PATCH 13/23] sync stonesense --- plugins/stonesense | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/stonesense b/plugins/stonesense index 64cc02b123..b1b676bcf1 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 64cc02b12321abf9b99e41c4e5f931f71bc81a45 +Subproject commit b1b676bcf1dc810e7210aceef89c1626069136f5 From 2d6d34ef1c7b47cd8a2915e700fe0f10c73cc79b Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Fri, 27 Mar 2026 15:26:00 -0500 Subject: [PATCH 14/23] on linux, use proper rpath for plugins Co-Authored-By: Christian Doczkal <20443222+chdoc@users.noreply.github.com> --- plugins/Plugins.cmake | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/Plugins.cmake b/plugins/Plugins.cmake index 82439f69a5..192662bccc 100644 --- a/plugins/Plugins.cmake +++ b/plugins/Plugins.cmake @@ -141,6 +141,10 @@ macro(dfhack_plugin) set_target_properties(${PLUGIN_NAME} PROPERTIES SUFFIX .plug.dll) endif() + if (UNIX) + set_target_properties(${PLUGIN_NAME} PROPERTIES INSTALL_RPATH "$ORIGIN/..") + endif() + install(TARGETS ${PLUGIN_NAME} LIBRARY DESTINATION ${DFHACK_PLUGIN_DESTINATION} RUNTIME DESTINATION ${DFHACK_PLUGIN_DESTINATION}) From 4166d211fb21db8f16c40c66e859269d4640912b Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Wed, 1 Apr 2026 11:36:15 -0500 Subject: [PATCH 15/23] changes to launchdf to handle relocatable install instead of assuming colocation, this version will (when both DF and DFHack are installed in Steam) automatically inject DFHack into DF environments where not both apps are installed in Steam are (hopefully) unaffected --- package/launchdf.cpp | 74 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 67 insertions(+), 7 deletions(-) diff --git a/package/launchdf.cpp b/package/launchdf.cpp index 5a850c6cf9..c7f9b58c89 100644 --- a/package/launchdf.cpp +++ b/package/launchdf.cpp @@ -10,6 +10,8 @@ #include "steam_api.h" #include +#include +#include #define xstr(s) str(s) #define str(s) #s @@ -245,14 +247,19 @@ int main(int argc, char* argv[]) { } bool nowait = false; + bool installmode = false; #ifdef WIN32 std::wstring cmdline(lpCmdLine); if (cmdline.find(L"--nowait") != std::wstring::npos) nowait = true; + if (cmdline.find(L"--install") != std::wstring::npos) + installmode = true; #else for (int idx = 0; idx < argc; ++idx) { if (strcmp(argv[idx], "--nowait") == 0) nowait = true; + if (strcmp(argv[idx], "--install") == 0) + installmode = true; } #endif @@ -294,22 +301,75 @@ int main(int argc, char* argv[]) { char buf[2048] = ""; int b1 = SteamApps()->GetAppInstallDir(DFHACK_STEAM_APPID, (char*)&buf, 2048); - std::string dfhack_install_folder = (b1 != -1) ? std::string(buf) : ""; + std::filesystem::path dfhack_install_folder = (b1 != -1) ? std::string(buf) : ""; int b2 = SteamApps()->GetAppInstallDir(DF_STEAM_APPID, (char*)&buf, 2048); - std::string df_install_folder = (b2 != -1) ? std::string(buf) : ""; + std::filesystem::path df_install_folder = (b2 != -1) ? std::string(buf) : ""; + if (df_install_folder != dfhack_install_folder) + { +#ifdef WIN32 + constexpr auto dfhooks_dll_name = "dfhooks.dll"; + constexpr auto dfhook_dfhack_dll_name = "dfhooks_dfhack.dll"; +#else + constexpr auto dfhooks_dll_name = "libdfhooks.so"; + constexpr auto dfhook_dfhack_dll_name = "libdfhooks_dfhack.so"; +#endif + // DF and DFHack are not co-installed (modern case) + // inject dfhooks.dll and dfhooks_dfhack.ini into DF install folder + std::filesystem::path dfhooks_dll_src = dfhack_install_folder / dfhooks_dll_name; + std::filesystem::path dfhooks_dll_dst = df_install_folder / dfhooks_dll_name; + std::filesystem::path dfhooks_ini_dst = df_install_folder / "dfhooks_dfhack.ini"; + std::filesystem::path dfhooks_dfhack_dll_src = dfhack_install_folder / "hack" / dfhook_dfhack_dll_name; - if (df_install_folder != dfhack_install_folder) { - // DF and DFHack are not installed in the same library + std::error_code ec; + + std::filesystem::copy(dfhooks_dll_src, dfhooks_dll_dst, std::filesystem::copy_options::update_existing, ec); + if (!ec) + { + std::string indirection; + if (std::filesystem::exists(dfhooks_ini_dst)) + { + std::ifstream ini(dfhooks_ini_dst); + std::getline(ini, indirection); + } + + if (indirection != dfhooks_dfhack_dll_src.string()) + { + std::ofstream ini(dfhooks_ini_dst); + ini << dfhooks_dfhack_dll_src.string() << std::endl; + } + } + else + { #ifdef WIN32 - MessageBoxW(NULL, L"DFHack and Dwarf Fortress must be installed in the same Steam library.\nAborting.", NULL, 0); + std::wstring message{ + L"Failed to inject DFHack into Dwarf Fortress\n\n" + L"Details:\n" + std::filesystem::relative(dfhooks_dll_src).wstring() + + L" -> " + std::filesystem::relative(dfhooks_dll_dst).wstring() + + L"\n\nError code: " + std::to_wstring(ec.value()) + + L"\nError message: " + std::filesystem::relative(ec.message()).wstring() + }; + + MessageBoxW(NULL, message.c_str(), NULL, 0); #else - notify("DFHack and Dwarf Fortress must be installed in the same Steam library.\nAborting."); + std::string message{ + "Failed to inject DFHack into Dwarf Fortress\n\n" + "Details:\n" + std::filesystem::relative(dfhooks_dll_src).string() + + " -> " + std::filesystem::relative(dfhooks_dll_dst).string() + + "\n\nError code: " + std::to_string(ec.value()) + + "\nError message: " + std::filesystem::relative(ec.message()).string() + }; + + notify(message.c_str()); #endif - exit(1); + exit(1); + } } + if (installmode) + exit(0); + if (!wrap_launch(launch_via_steam)) exit(1); From e718a021e349db0f55f3b79bd31d990888361aad Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Fri, 3 Apr 2026 12:33:46 -0500 Subject: [PATCH 16/23] add code to detect/clean legacy install on windows, will prompt user on linux, will advise user on how to manually clean also removed install mode - can't seem to make work with steam API rn --- package/launchdf.cpp | 106 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 97 insertions(+), 9 deletions(-) diff --git a/package/launchdf.cpp b/package/launchdf.cpp index c7f9b58c89..03085ed6d5 100644 --- a/package/launchdf.cpp +++ b/package/launchdf.cpp @@ -236,6 +236,80 @@ bool waitForDF(bool nowait) { #endif +constexpr const char* old_filelist[] { + "hack", + "stonesense", +#ifdef WIN32 + "binpatch.exe", + "dfhack-run.exe", + "allegro-5.2.dll", + "allegro_color-5.2.dll", + "allegro_font-5.2.dll", + "allegro_image-5.2.dll", + "allegro_primitives-5.2.dll", + "allegro_ttf-5.2.dll", + "allegro-5.2.dll", + "dfhack-client.dll", + "dfhooks_dfhack.dll", + "lua53.dll", + "protobuf-lite.dll" +#else + "binpatch", + "dfhack-run", + "liballegro-5.2.so", + "liballegro_color-5.2.so", + "liballegro_font-5.2.so", + "liballegro_image-5.2.so", + "liballegro_primitives-5.2.so", + "liballegro_ttf-5.2.so", + "liballegro-5.2.so", + "libdfhack-client.so", + "libdfhooks_dfhack.so", + "liblua53.so", + "libprotobuf-lite.so" +#endif +}; + +bool check_for_old_install(std::filesystem::path df_path) +{ + for (auto file : old_filelist) + { + std::filesystem::path p = df_path / file; + bool exists = std::filesystem::exists(p); +// std::wstring message = L"Checking for legacy files:\n" + p.wstring() + L": " + (exists ? L"found" : L"not found"); +// MessageBoxW(NULL, message.c_str(), L"Checking for legacy files", 0); + if (exists) + return true; + } + return false; +} + +void remove_old_install(std::filesystem::path df_path) +{ + std::string message{ + "Removing legacy files:" + }; + + for (auto file : old_filelist) + { + std::error_code ec; + + std::filesystem::path p = df_path / file; + + if (std::filesystem::is_directory(p)) + std::filesystem::remove_all(p, ec); + else if (std::filesystem::is_regular_file(p)) + std::filesystem::remove(p, ec); + else + continue; + + message += "\n" + p.string() + ": " + (ec ? "failed to remove - " + ec.message() : "removed successfully"); + } +#ifdef WIN32 + MessageBoxW(NULL, std::wstring(message.begin(), message.end()).c_str(), L"Legacy Install Cleanup", 0); +#endif +} + #ifdef WIN32 int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nShowCmd) { #else @@ -247,19 +321,14 @@ int main(int argc, char* argv[]) { } bool nowait = false; - bool installmode = false; #ifdef WIN32 std::wstring cmdline(lpCmdLine); if (cmdline.find(L"--nowait") != std::wstring::npos) nowait = true; - if (cmdline.find(L"--install") != std::wstring::npos) - installmode = true; #else for (int idx = 0; idx < argc; ++idx) { if (strcmp(argv[idx], "--nowait") == 0) nowait = true; - if (strcmp(argv[idx], "--install") == 0) - installmode = true; } #endif @@ -365,10 +434,30 @@ int main(int argc, char* argv[]) { #endif exit(1); } - } + bool dirty = check_for_old_install(df_install_folder); + if (dirty) + { +#ifdef WIN32 + int ok = MessageBoxW(NULL, L"A legacy install of DFHack has been detected in the Dwarf Fortress folder. This likely means that you have installed DFHack with the old Steam client (or manually). This legacy installation will almost certainly interfere with using DFHack. Do you want to remove the old files now? (recommended)", L"Legacy DFHack Install Detected", MB_OKCANCEL); - if (installmode) - exit(0); + if (ok == IDOK) + remove_old_install(df_install_folder); +#else + int response = 0; + std::string filelist; + for (auto file : old_filelist) + if (std::filesystem::exists(df_install_folder / file)) + filelist += (filelist.empty() ? "" : std::string(",")) + file; + + std::string message{ + "A legacy install of DFHack has been detected in the Dwarf Fortress directory.This likely means that you have installed DFHack with the old Steam client (or manually).This installation will almost certainly interfere with using DFHack. \n\n" + "To remove these files, run the following command: rm -r " + df_install_folder.string() + "/{ " + filelist + "}\n\n" + }; + + notify(message.c_str()); +#endif + } + } if (!wrap_launch(launch_via_steam)) exit(1); @@ -389,6 +478,5 @@ int main(int argc, char* argv[]) { usleep(1000000); #endif } - exit(0); } From 5ecdca159200723f9455d10cf62ce93125db0633 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Fri, 3 Apr 2026 13:34:44 -0500 Subject: [PATCH 17/23] changes to hopefully make injection work in wine changed injection/legacy check code to before instead of after wine-specific launch code also made path detection hopefully more robust (and less wet) --- package/launchdf.cpp | 79 ++++++++++++++++++++++++++------------------ 1 file changed, 46 insertions(+), 33 deletions(-) diff --git a/package/launchdf.cpp b/package/launchdf.cpp index 03085ed6d5..a2e3cbaf3e 100644 --- a/package/launchdf.cpp +++ b/package/launchdf.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #define xstr(s) str(s) #define str(s) #s @@ -275,10 +276,7 @@ bool check_for_old_install(std::filesystem::path df_path) for (auto file : old_filelist) { std::filesystem::path p = df_path / file; - bool exists = std::filesystem::exists(p); -// std::wstring message = L"Checking for legacy files:\n" + p.wstring() + L": " + (exists ? L"found" : L"not found"); -// MessageBoxW(NULL, message.c_str(), L"Checking for legacy files", 0); - if (exists) + if (std::filesystem::exists(p)) return true; } return false; @@ -341,42 +339,38 @@ int main(int argc, char* argv[]) { } #ifdef WIN32 - if (is_running_on_wine()) { - // attempt launch via steam client - LPCWSTR err = launch_via_steam_posix(); - - if (err != NULL) - // steam client launch failed, attempt fallback launch - err = launch_direct(); - - if (err != NULL) - { - MessageBoxW(NULL, err, NULL, 0); - exit(1); - } - exit(0); - } + bool wine_detected = is_running_on_wine(); +#else + bool wine_detected = false; #endif - // steam detected and not running in wine + bool df_detected = SteamApps()->BIsAppInstalled(DF_STEAM_APPID); - if (!SteamApps()->BIsAppInstalled(DF_STEAM_APPID)) { + if (!df_detected) { // Steam DF is not installed. Assume DF is installed in same directory as DFHack and do a fallback launch exit(wrap_launch(launch_direct) ? 0 : 1); } - // obtain DF app path - - char buf[2048] = ""; + // obtain DF and DFHack app paths - int b1 = SteamApps()->GetAppInstallDir(DFHACK_STEAM_APPID, (char*)&buf, 2048); - std::filesystem::path dfhack_install_folder = (b1 != -1) ? std::string(buf) : ""; + auto get_app_path_from_steam = [] (AppId_t appid) -> std::optional { + char buf[2048] = ""; + int bytes = SteamApps()->GetAppInstallDir(appid, (char*)&buf, 2048); + if (bytes == -1) + return std::nullopt; + // steam API counts the null terminator in the byte count returned + if (buf[bytes] == '\0') bytes--; + return std::string(buf, bytes); + }; - int b2 = SteamApps()->GetAppInstallDir(DF_STEAM_APPID, (char*)&buf, 2048); - std::filesystem::path df_install_folder = (b2 != -1) ? std::string(buf) : ""; + auto opt_dfhack_install_folder = get_app_path_from_steam(DFHACK_STEAM_APPID); + auto opt_df_install_folder = get_app_path_from_steam(DF_STEAM_APPID); - if (df_install_folder != dfhack_install_folder) + if (opt_dfhack_install_folder && opt_df_install_folder && (*opt_df_install_folder != *opt_dfhack_install_folder)) { + auto& dfhack_install_folder = *opt_dfhack_install_folder; + auto& df_install_folder = *opt_df_install_folder; + #ifdef WIN32 constexpr auto dfhooks_dll_name = "dfhooks.dll"; constexpr auto dfhook_dfhack_dll_name = "dfhooks_dfhack.dll"; @@ -414,8 +408,8 @@ int main(int argc, char* argv[]) { #ifdef WIN32 std::wstring message{ L"Failed to inject DFHack into Dwarf Fortress\n\n" - L"Details:\n" + std::filesystem::relative(dfhooks_dll_src).wstring() + - L" -> " + std::filesystem::relative(dfhooks_dll_dst).wstring() + + L"Details:\n" + dfhooks_dll_src.wstring() + + L" -> " + dfhooks_dll_dst.wstring() + L"\n\nError code: " + std::to_wstring(ec.value()) + L"\nError message: " + std::filesystem::relative(ec.message()).wstring() }; @@ -424,8 +418,8 @@ int main(int argc, char* argv[]) { #else std::string message{ "Failed to inject DFHack into Dwarf Fortress\n\n" - "Details:\n" + std::filesystem::relative(dfhooks_dll_src).string() + - " -> " + std::filesystem::relative(dfhooks_dll_dst).string() + + "Details:\n" + dfhooks_dll_src.string() + + " -> " + dfhooks_dll_dst.string() + "\n\nError code: " + std::to_string(ec.value()) + "\nError message: " + std::filesystem::relative(ec.message()).string() }; @@ -459,6 +453,25 @@ int main(int argc, char* argv[]) { } } +#ifdef WIN32 + if (wine_detected) + { + // attempt launch via steam client + LPCWSTR err = launch_via_steam_posix(); + + if (err != NULL) + // steam client launch failed, attempt fallback launch + err = launch_direct(); + + if (err != NULL) + { + MessageBoxW(NULL, err, NULL, 0); + exit(1); + } + exit(0); + } +#endif + if (!wrap_launch(launch_via_steam)) exit(1); From 7afeddc100614dec6adfab8048c400260f4ccc88 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Fri, 3 Apr 2026 13:36:27 -0500 Subject: [PATCH 18/23] slightly safer return check for `GetAppInstallDir` --- package/launchdf.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/launchdf.cpp b/package/launchdf.cpp index a2e3cbaf3e..96875e3b68 100644 --- a/package/launchdf.cpp +++ b/package/launchdf.cpp @@ -356,7 +356,7 @@ int main(int argc, char* argv[]) { auto get_app_path_from_steam = [] (AppId_t appid) -> std::optional { char buf[2048] = ""; int bytes = SteamApps()->GetAppInstallDir(appid, (char*)&buf, 2048); - if (bytes == -1) + if (bytes <= 0) return std::nullopt; // steam API counts the null terminator in the byte count returned if (buf[bytes] == '\0') bytes--; From 3483d778db665fabb764264375f03ba5763d0fba Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Fri, 3 Apr 2026 13:41:13 -0500 Subject: [PATCH 19/23] move toward release candidate status --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9be9193210..083ea5bc38 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,8 +7,8 @@ cmake_policy(SET CMP0074 NEW) # set up versioning. set(DF_VERSION "53.11") -set(DFHACK_RELEASE "r2") -set(DFHACK_PRERELEASE FALSE) +set(DFHACK_RELEASE "r3rc1") +set(DFHACK_PRERELEASE TRUE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") set(DFHACK_ABI_VERSION 2) From cef0742f571de6bd66d711db56c52a15488cc05c Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Fri, 3 Apr 2026 14:02:15 -0500 Subject: [PATCH 20/23] make gcc happy --- package/launchdf.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/package/launchdf.cpp b/package/launchdf.cpp index 96875e3b68..5ffc939e15 100644 --- a/package/launchdf.cpp +++ b/package/launchdf.cpp @@ -340,8 +340,6 @@ int main(int argc, char* argv[]) { #ifdef WIN32 bool wine_detected = is_running_on_wine(); -#else - bool wine_detected = false; #endif bool df_detected = SteamApps()->BIsAppInstalled(DF_STEAM_APPID); @@ -437,7 +435,6 @@ int main(int argc, char* argv[]) { if (ok == IDOK) remove_old_install(df_install_folder); #else - int response = 0; std::string filelist; for (auto file : old_filelist) if (std::filesystem::exists(df_install_folder / file)) From 6a0f9c89d5a928b930d370aaf5c2fa90fb732aa7 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Sat, 4 Apr 2026 16:59:45 +0000 Subject: [PATCH 21/23] Auto-update submodules scripts: master plugins/stonesense: master depends/dfhooks: main --- depends/dfhooks | 2 +- plugins/stonesense | 2 +- scripts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/depends/dfhooks b/depends/dfhooks index 2d84a5826c..4c48e25a2a 160000 --- a/depends/dfhooks +++ b/depends/dfhooks @@ -1 +1 @@ -Subproject commit 2d84a5826c51e99a6ff2c7d4c530680b366044c1 +Subproject commit 4c48e25a2a33538bf0c522f69987fd28c1525503 diff --git a/plugins/stonesense b/plugins/stonesense index b1b676bcf1..9dfa9d731f 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit b1b676bcf1dc810e7210aceef89c1626069136f5 +Subproject commit 9dfa9d731f6b84b6deaba42364168b7374157e6e diff --git a/scripts b/scripts index 56c934eb5d..af457f9b5c 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 56c934eb5d4c957ca731705783a0a43397a9ba2c +Subproject commit af457f9b5c86fc041a064ef5bdfbcab38f38a50f From e88c8c16d747fa5c413be8f008d43b3e3de79ff4 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Sat, 4 Apr 2026 12:24:36 -0500 Subject: [PATCH 22/23] revert inadvertent dfhooks submodule change in ff1b068 --- depends/dfhooks | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depends/dfhooks b/depends/dfhooks index 4c48e25a2a..2d84a5826c 160000 --- a/depends/dfhooks +++ b/depends/dfhooks @@ -1 +1 @@ -Subproject commit 4c48e25a2a33538bf0c522f69987fd28c1525503 +Subproject commit 2d84a5826c51e99a6ff2c7d4c530680b366044c1 From 731662185e022b8abbbf7d20b3beb4ab3bc01cc6 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Mon, 6 Apr 2026 08:17:51 -0500 Subject: [PATCH 23/23] Update changelog.txt --- docs/changelog.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index bf2ca7d998..8feb15325f 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -59,8 +59,10 @@ Template for new versions: ## New Features ## Fixes +- Steam launcher: Switch to injection strategy, allowing Dwarf Fortress and DFHack to be installed in disparate locations ## Misc Improvements +- Make DFHack relocatable so that it doesn't depend on being fully co-installed with Dwarf Fortress ## Documentation