diff --git a/CMakeLists.txt b/CMakeLists.txt index 91910cfef..32fd27a41 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -90,11 +90,36 @@ add_subdirectory(lua) add_subdirectory(Multiplayer) add_subdirectory(wine) -# A non-MSVC toolchain rebuilds the Bink import library and points -# binkw32_lib at it (see clang-cl.cmake); MSVC binds the shipped -# library fine, so fall back to that. +# Rebuild the Bink import library rather than link the one the game ships: that one +# is a 2002 long-format ordinal library, and only link.exe still binds it. lld-link +# takes it without complaint but emits an import descriptor with an empty thunk +# table, so every Bink call reaches a stale address and BinkInitialize faults the +# process during the intro. mingw/GNU ar cannot do this at all, see mingw.cmake if(NOT binkw32_lib) - set(binkw32_lib "${CMAKE_SOURCE_DIR}/binkw32.lib") + # Only the name shape differs by toolchain: lib.exe prepends the x86 leading + # underscore to every name in the .def, llvm-lib and llvm-dlltool take the name as + # written. The checked-in file carries the underscore for the llvm tools, so strip + # it back off for lib.exe -- get this backwards either way and the Bink symbols + # come out undefined at link time. + # The test is on the compiler because it is what CMAKE_AR follows: clang-cl reports + # COMPILER_ID Clang and brings llvm-lib, cl.exe reports MSVC and brings lib.exe. Do + # not reach for the MSVC variable or COMPILER_FRONTEND_VARIANT here -- both are set + # for clang-cl too, and either would strip the underscore for the wrong archiver. + set(binkw32Def "${CMAKE_SOURCE_DIR}/binkw32.def") + if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + file(READ "${binkw32Def}" binkw32DefText) + string(REPLACE "\n_" "\n" binkw32DefText "${binkw32DefText}") + set(binkw32Def "${CMAKE_BINARY_DIR}/binkw32.msvc.def") + file(WRITE "${binkw32Def}" "${binkw32DefText}") + endif() + execute_process( + COMMAND "${CMAKE_AR}" /nologo /machine:X86 + "/def:${binkw32Def}" "/out:${CMAKE_BINARY_DIR}/binkw32.lib" + RESULT_VARIABLE binkw32LibResult) + if(NOT binkw32LibResult EQUAL 0) + message(FATAL_ERROR "${CMAKE_AR} failed to build the binkw32 import library") + endif() + set(binkw32_lib "${CMAKE_BINARY_DIR}/binkw32.lib") endif() set(Ja2_Libraries diff --git a/binkw32.def b/binkw32.def index 1ac234d5f..64b66f20f 100644 --- a/binkw32.def +++ b/binkw32.def @@ -1,10 +1,11 @@ -; Import definition for binkw32.dll, used to rebuild the Bink import library -; when building with clang-cl.cmake +; Import definition for binkw32.dll, used to rebuild the Bink import library on +; every toolchain (see CMakeLists.txt and cmake/toolchains/). ; ; Two things forced the shape of this file: -; 1. lld-link cannot bind the ordinal-based long-format import library the -; game ships (binkw32.lib). It leaves every Bink call unresolved, and at -; run time the calls jump into arbitrary code and crash at startup. +; 1. No current linker can bind the ordinal-based long-format import library +; the game ships (binkw32.lib, built 2002). lld-link leaves every Bink call +; unresolved; link.exe writes an import descriptor with an empty thunk +; table. Either way the calls jump into arbitrary code and crash at startup. ; 2. binkw32.dll exports decorated stdcall names that keep their leading ; underscore (_BinkOpen@8). llvm-dlltool strips that underscore from the ; imported name whatever the --no-leading-underscore flag says, so a @@ -13,8 +14,9 @@ ; function binkw32.dll.BinkSetSoundSystem@8"). ; ; Importing by ordinal (NONAME) sidesteps both: there is no name to mismatch, -; and the ordinals are those of the retail binkw32.dll the game ships. MSVC's -; link.exe binds the shipped library fine and keeps using it. +; and the ordinals are those of the retail binkw32.dll the game ships. Names here +; carry the leading underscore llvm-dlltool needs; lib.exe adds its own, so the +; MSVC path strips it back off. LIBRARY binkw32.dll EXPORTS _BinkBufferBlit@12 @1 NONAME diff --git a/cmake/toolchains/clang-cl.cmake b/cmake/toolchains/clang-cl.cmake index f04d1c7f0..e67d584a7 100644 --- a/cmake/toolchains/clang-cl.cmake +++ b/cmake/toolchains/clang-cl.cmake @@ -32,7 +32,7 @@ set(_sdk_lib "${MSVC_SDK}/kits/10/lib/${_sdk_version}") # Find every tool here rather than trusting PATH, so the choice of compiler, # linker and rc is the toolchain file's alone. -foreach(tool clang-cl lld-link llvm-rc llvm-lib llvm-mt llvm-dlltool) +foreach(tool clang-cl lld-link llvm-rc llvm-lib llvm-mt) string(TOUPPER "${tool}" _variable) string(REPLACE "-" "_" _variable "${_variable}") # An absolute path: CMake resolves a bare name against the source directory. @@ -76,16 +76,3 @@ set(CMAKE_RC_FLAGS_INIT "${_rc_flags}") set(CMAKE_EXE_LINKER_FLAGS_INIT "/libpath:${_msvc}/lib/x86 /libpath:${_sdk_lib}/ucrt/x86 /libpath:${_sdk_lib}/um/x86") - -# lld-link cannot bind the Bink import library the game ships, so rebuild a -# bindable one from binkw32.def (which explains why) and hand it to the top-level -# CMakeLists through binkw32_lib. MSVC keeps using the shipped library. -execute_process( - COMMAND "${LLVM_DLLTOOL}" -m i386 --no-leading-underscore - -d "${CMAKE_CURRENT_LIST_DIR}/../../binkw32.def" - -l "${CMAKE_BINARY_DIR}/binkw32.lib" - RESULT_VARIABLE _binkw32DlltoolResult) -if(NOT _binkw32DlltoolResult EQUAL 0) - message(FATAL_ERROR "llvm-dlltool failed to build the binkw32 import library") -endif() -set(binkw32_lib "${CMAKE_BINARY_DIR}/binkw32.lib") diff --git a/cmake/toolchains/mingw.cmake b/cmake/toolchains/mingw.cmake index 0e4285d0a..971699ed4 100644 --- a/cmake/toolchains/mingw.cmake +++ b/cmake/toolchains/mingw.cmake @@ -30,7 +30,7 @@ set(CMAKE_CXX_COMPILER_TARGET ${triple}) # GNU ld cannot bind the Bink import library the game ships either, so rebuild a # bindable one from binkw32.def (which explains why) and hand it to the top-level -# CMakeLists through binkw32_lib. MSVC keeps using the shipped library. +# CMakeLists through binkw32_lib. execute_process( COMMAND "${MINGW_DLLTOOL}" -m i386 -d "${CMAKE_CURRENT_LIST_DIR}/../../binkw32.def"