From a95bc76237eccb59898b2753a628864f62b87478 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Tue, 14 Jul 2026 11:04:15 -0600 Subject: [PATCH] Mods: Remove -lldmingw + pseudo_reloc It turns out this approach can't work on ARM64 Windows due to the relocation model, so we can simplify and just remove it entirely. Separately, I will make a commit tagging all game data as DUSK_GAME_DATA, so things work uniformly across cl/clang-cl and x64/ arm64. --- cmake/ModSDK.cmake | 46 ++++---------- sdk/pseudo_reloc.cpp | 139 ------------------------------------------- 2 files changed, 11 insertions(+), 174 deletions(-) delete mode 100644 sdk/pseudo_reloc.cpp diff --git a/cmake/ModSDK.cmake b/cmake/ModSDK.cmake index 3bf80035e6..0e8d72a0a0 100644 --- a/cmake/ModSDK.cmake +++ b/cmake/ModSDK.cmake @@ -127,52 +127,28 @@ function(add_mod target_name) BUILD_RPATH "$ORIGIN" INSTALL_RPATH "$ORIGIN") elseif (WIN32) - # Function calls resolve through import thunks. Data is toolchain dependent: - # - clang-cl: lld's mingw driver synthesizes imports straight from the game - # executable's export table (no import library needed) and auto-imports data - # references, fixed up at load by the mod SDK's pseudo-relocation runtime - # (pseudo_reloc.cpp). - # - cl (MSVC): link.exe only imports through an import library, so - # DUSK_GAME_EXE must point at one (sdk/windows-.lib or `symgen stub` - # output); only DUSK_GAME_DATA-annotated data is reachable. Un-annotated - # references fail to link. + # Mods link against the game's import library (sdk/windows-.lib, generated by + # setup_windows_exports); cl and clang-cl both consume it in MSVC mode. Function + # calls resolve through import thunks; game data is reachable only through + # __declspec(dllimport), i.e. DUSK_GAME_DATA-annotated declarations. if (TARGET dusklight) - if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") - set(_game_lib "$") - add_dependencies(${target_name} dusklight) - elseif (DUSK_GAME_IMPLIB) - set(_game_lib "${DUSK_GAME_IMPLIB}") - else () + if (NOT DUSK_GAME_IMPLIB) message(FATAL_ERROR "add_mod: DUSK_GAME_IMPLIB is not set (see setup_windows_exports)") endif () + set(_game_lib "${DUSK_GAME_IMPLIB}") elseif (DUSK_GAME_EXE) _mod_resolve_source_path(_game_lib "${DUSK_GAME_EXE}") - if (NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND NOT _game_lib MATCHES "\\.lib$") - message(FATAL_ERROR "add_mod: cl must use an import library for DUSK_GAME_EXE") + if (NOT _game_lib MATCHES "\\.lib$") + message(FATAL_ERROR + "add_mod: DUSK_GAME_EXE must be an import library on Windows " + "(sdk/windows-.lib)") endif () else () - message(FATAL_ERROR "add_mod: DUSK_GAME_EXE is not set (game executable or import library)") + message(FATAL_ERROR "add_mod: DUSK_GAME_EXE is not set (import library)") endif () target_link_libraries(${target_name} PRIVATE "${_game_lib}") set_target_properties(${target_name} PROPERTIES MSVC_RUNTIME_LIBRARY "MultiThreadedDLL") target_compile_definitions(${target_name} PRIVATE _ITERATOR_DEBUG_LEVEL=0) - if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") - target_compile_options(${target_name} PRIVATE "$<$:/clang:-mcmodel=large>") - target_sources(${target_name} PRIVATE "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../sdk/pseudo_reloc.cpp") - # lld mingw mode rewrites /DEFAULTLIB directives to -l style and skips %LIB%, so - # the CRT libraries and search paths are spelled out explicitly. - target_link_options(${target_name} PRIVATE -lldmingw /nodefaultlib /INCREMENTAL:NO) - target_link_libraries(${target_name} PRIVATE - msvcrt.lib msvcprt.lib vcruntime.lib ucrt.lib - oldnames.lib uuid.lib kernel32.lib user32.lib) - set(_lib_dirs "$ENV{LIB}") - if ("${_lib_dirs}" STREQUAL "") - message(FATAL_ERROR "add_mod: %LIB% is empty; configure from a VS dev shell") - endif () - foreach (_libdir IN LISTS _lib_dirs) - target_link_options(${target_name} PRIVATE "/libpath:${_libdir}") - endforeach () - endif () endif () endif () if (ARG_RUNTIME_LIBRARIES AND NOT _has_lib) diff --git a/sdk/pseudo_reloc.cpp b/sdk/pseudo_reloc.cpp deleted file mode 100644 index f86d783f2d..0000000000 --- a/sdk/pseudo_reloc.cpp +++ /dev/null @@ -1,139 +0,0 @@ -// Mod SDK runtime (Windows): applies lld's MinGW-style runtime pseudo-relocations on -// the plain MSVC CRT. This is what lets mods reference game data (`extern` globals) with no -// __declspec(dllimport) annotations: `lld-link -lldmingw` auto-imports the data through IAT -// slots and records fixups, and this module applies them at load time. -// -// The fixup pass runs as an early CRT initializer, and the IAT slots it reads were already -// bound by the OS loader, so even mod static initializers observe patched references. - -#include - -#include -#include -#include - -extern "C" char __RUNTIME_PSEUDO_RELOC_LIST__; -extern "C" char __RUNTIME_PSEUDO_RELOC_LIST_END__; -extern "C" IMAGE_DOS_HEADER __ImageBase; - -namespace { - -struct HdrV2 { - uint32_t magic1; - uint32_t magic2; - uint32_t version; -}; -struct ItemV2 { - uint32_t sym; // RVA of the __imp_ slot (OS-bound IAT entry) - uint32_t target; // RVA of the reference to patch - uint32_t flags; // low 8 bits: bit width of the reference -}; - -bool g_relocsFailed = false; - -void report(const char* fmt, ...) { - char buf[512]; - va_list args; - va_start(args, fmt); - vsnprintf(buf, sizeof(buf), fmt, args); - va_end(args); - fprintf(stderr, "%s\n", buf); - OutputDebugStringA(buf); -} - -bool compute_fixup(const ItemV2& item, char* base, intptr_t* out) { - char* impSlot = base + item.sym; - const intptr_t real = *reinterpret_cast(impSlot); - char* target = base + item.target; - const int bits = static_cast(item.flags & 0xff); - - intptr_t reldata; - switch (bits) { - case 8: - reldata = *reinterpret_cast(target); - break; - case 16: - reldata = *reinterpret_cast(target); - break; - case 32: - reldata = *reinterpret_cast(target); - break; - case 64: - reldata = *reinterpret_cast(target); - break; - default: - report("unsupported %d-bit pseudo-relocation at RVA 0x%x", bits, item.target); - return false; - } - reldata -= reinterpret_cast(impSlot); - reldata += real; - - if (bits < 64) { - const intptr_t maxUnsigned = (intptr_t{1} << bits) - 1; - const intptr_t minSigned = -(intptr_t{1} << (bits - 1)); - if (reldata > maxUnsigned || reldata < minSigned) { - report("%d-bit data fixup at RVA 0x%x is out of range (delta %+lld)", bits, item.target, - static_cast(reldata)); - return false; - } - } - *out = reldata; - return true; -} - -} // namespace - -// lld refuses to emit runtime pseudo-relocs unless a function with exactly this (mingw CRT) -// name exists in the image. It is also our actual entry point. -extern "C" void _pei386_runtime_relocator() { - char* base = reinterpret_cast(&__ImageBase); - char* start = &__RUNTIME_PSEUDO_RELOC_LIST__; - char* end = &__RUNTIME_PSEUDO_RELOC_LIST_END__; - if (end - start < static_cast(sizeof(HdrV2))) { - return; // no data auto-imports in this mod - } - const HdrV2* hdr = reinterpret_cast(start); - if (hdr->magic1 != 0 || hdr->magic2 != 0 || hdr->version != 1) { - report("unexpected pseudo-relocation list format"); - g_relocsFailed = true; - return; - } - const ItemV2* items = reinterpret_cast(hdr + 1); - const ItemV2* itemsEnd = reinterpret_cast(end); - - // Validate everything before writing anything, so a bad fixup can't leave the image - // half-patched. - intptr_t scratch; - for (const ItemV2* it = items; it < itemsEnd; ++it) { - if (!compute_fixup(*it, base, &scratch)) { - g_relocsFailed = true; - return; - } - } - for (const ItemV2* it = items; it < itemsEnd; ++it) { - intptr_t reldata; - compute_fixup(*it, base, &reldata); - char* target = base + it->target; - const size_t len = static_cast(it->flags & 0xff) / 8; - DWORD old = 0; - if (!VirtualProtect(target, len, PAGE_EXECUTE_READWRITE, &old)) { - report("VirtualProtect failed at RVA 0x%x", it->target); - g_relocsFailed = true; - return; - } - std::memcpy(target, &reldata, len); - VirtualProtect(target, len, old, &old); - } -} - -using PVFV = void (*)(); -#pragma section(".CRT$XCB", read) -extern "C" __declspec(allocate(".CRT$XCB")) PVFV dusk_mod_pseudo_reloc_init = - _pei386_runtime_relocator; - -BOOL WINAPI DllMain(HINSTANCE, DWORD reason, LPVOID) { - if (reason == DLL_PROCESS_ATTACH && g_relocsFailed) { - return FALSE; - } - return TRUE; -}