From 8b80466a7dffe93746765ab5920db90841f404c3 Mon Sep 17 00:00:00 2001 From: "Marco Antonio J. Costa" Date: Mon, 27 Jul 2026 18:54:08 -0300 Subject: [PATCH] rebuild the bink import library on every toolchain, not just cross ones binkw32.lib as shipped is a long-format ordinal import library built in 2002, and link.exe is the only linker left that binds it. lld-link takes it without complaint and writes an import descriptor with an empty thunk table, so every Bink call reaches whatever the stale IAT slot holds. The first one is BinkSoundUseDirectSound, reached from BinkInitialize during EnterIntroScreen, which is unconditional -- Intro.cpp constructs its VideoPlayer with VT_SMK|VT_BINK in every application, so a Smacker-only gamedir still runs the Bink init path. The process dies before the splash with an access violation executing an unmapped address. clang-cl.cmake and mingw.cmake already rebuilt the library from binkw32.def for exactly this reason, but they are toolchain files, so the rebuild only happened when someone cross-compiled. Which linker is in use is not a cross-compilation question: a preset that points CMAKE_CXX_COMPILER at clang-cl -- how you use it from Visual Studio -- loads no toolchain file and linked the broken library instead. The test was a proxy for "is this lld-link", correct until it wasn't. So the rebuild moves to the top-level CMakeLists and runs unconditionally, and clang-cl.cmake loses its copy: it already sets CMAKE_AR to llvm-lib, which is the same binary the new code invokes with the same arguments. mingw.cmake keeps its own, because GNU ar cannot build an import library at all and dlltool takes different flags. Only the name shape differs between archivers. 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 keeps the underscore for the llvm tools and the MSVC path strips it back off. Co-Authored-By: Claude Opus 5 --- CMakeLists.txt | 33 +++++++++++++++++++++++++++++---- binkw32.def | 16 +++++++++------- cmake/toolchains/clang-cl.cmake | 15 +-------------- cmake/toolchains/mingw.cmake | 2 +- 4 files changed, 40 insertions(+), 26 deletions(-) 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"