mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-07-23 21:46:37 -04:00
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.
This commit is contained in:
+11
-35
@@ -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-<arch>.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-<arch>.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 "$<TARGET_FILE:dusklight>")
|
||||
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-<arch>.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 "$<$<COMPILE_LANGUAGE:C,CXX>:/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)
|
||||
|
||||
@@ -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 <windows.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
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<intptr_t*>(impSlot);
|
||||
char* target = base + item.target;
|
||||
const int bits = static_cast<int>(item.flags & 0xff);
|
||||
|
||||
intptr_t reldata;
|
||||
switch (bits) {
|
||||
case 8:
|
||||
reldata = *reinterpret_cast<int8_t*>(target);
|
||||
break;
|
||||
case 16:
|
||||
reldata = *reinterpret_cast<int16_t*>(target);
|
||||
break;
|
||||
case 32:
|
||||
reldata = *reinterpret_cast<int32_t*>(target);
|
||||
break;
|
||||
case 64:
|
||||
reldata = *reinterpret_cast<int64_t*>(target);
|
||||
break;
|
||||
default:
|
||||
report("unsupported %d-bit pseudo-relocation at RVA 0x%x", bits, item.target);
|
||||
return false;
|
||||
}
|
||||
reldata -= reinterpret_cast<intptr_t>(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<long long>(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<char*>(&__ImageBase);
|
||||
char* start = &__RUNTIME_PSEUDO_RELOC_LIST__;
|
||||
char* end = &__RUNTIME_PSEUDO_RELOC_LIST_END__;
|
||||
if (end - start < static_cast<ptrdiff_t>(sizeof(HdrV2))) {
|
||||
return; // no data auto-imports in this mod
|
||||
}
|
||||
const HdrV2* hdr = reinterpret_cast<const HdrV2*>(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<const ItemV2*>(hdr + 1);
|
||||
const ItemV2* itemsEnd = reinterpret_cast<const ItemV2*>(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<size_t>(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;
|
||||
}
|
||||
Reference in New Issue
Block a user