Files
source/CMakeLists.txt
T
8b80466a7d 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 <noreply@anthropic.com>
2026-07-28 07:13:52 -03:00

225 lines
8.2 KiB
CMake

cmake_minimum_required(VERSION 3.20)
# /Z7 embedded debug info so objects are self-contained and sccache-able.
# CMP0141 is a 3.25 policy; our floor is 3.20, so opt in explicitly where known.
if(POLICY CMP0141)
cmake_policy(SET CMP0141 NEW)
endif()
project(ja2)
option(USE_SCCACHE "Cache compiled objects with sccache if installed" ON)
if(USE_SCCACHE)
find_program(SCCACHE_PROGRAM sccache)
if(SCCACHE_PROGRAM)
message(STATUS "Using sccache: ${SCCACHE_PROGRAM}")
set(CMAKE_C_COMPILER_LAUNCHER "${SCCACHE_PROGRAM}")
set(CMAKE_CXX_COMPILER_LAUNCHER "${SCCACHE_PROGRAM}")
endif()
endif()
include(cmake/CopyUserPresetTemplate.cmake)
CopyUserPresetTemplate()
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
option(LTO_OPTION "Enable link-time optimization if supported by compiler" OFF)
include(CheckIPOSupported)
check_ipo_supported(RESULT LinkTimeOptimization OUTPUT IpoError LANGUAGES C CXX)
if(LinkTimeOptimization AND LTO_OPTION)
message(STATUS "Configuring WITH link-time optimization")
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
else()
message(STATUS "Configuring WITHOUT link-time optimization ${IpoError}")
endif()
option(ADDRESS_SANITIZER OFF)
if(ADDRESS_SANITIZER)
message(STATUS "AddressSanitizer ENABLED for non-Release builds")
add_compile_options($<IF:$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>,-fsanitize=address,>)
endif()
# whether we are using MSBuild as a generator
set(usingMsBuild $<STREQUAL:${CMAKE_VS_PLATFORM_NAME},Win32>)
# lua51.lib and lua51.vc9.lib have been built with /MTx, so we must as well
# TODO: build our own Lua 5.1.2 from source so we can use whichever
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
# make Debug builds /Z7 instead of /Zi so they can be sccache'd
set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT
"$<$<CONFIG:Debug,RelWithDebInfo>:Embedded>")
add_compile_definitions(CINTERFACE XML_STATIC VFS_STATIC VFS_WITH_SLF VFS_WITH_7ZIP _CRT_SECURE_NO_DEPRECATE)
include_directories(
"${CMAKE_SOURCE_DIR}/Ja2"
"${CMAKE_SOURCE_DIR}/ext/VFS/include"
"${CMAKE_SOURCE_DIR}/ext/libsmacker"
"${CMAKE_SOURCE_DIR}/Utils"
"${CMAKE_SOURCE_DIR}/TileEngine"
"${CMAKE_SOURCE_DIR}/TacticalAI"
"${CMAKE_SOURCE_DIR}/ModularizedTacticalAI/include"
"${CMAKE_SOURCE_DIR}/Tactical"
"${CMAKE_SOURCE_DIR}/Strategic"
"${CMAKE_SOURCE_DIR}/sgp"
"${CMAKE_SOURCE_DIR}/Ja2/Res"
"${CMAKE_SOURCE_DIR}/lua"
"${CMAKE_SOURCE_DIR}/Laptop"
"${CMAKE_SOURCE_DIR}/Multiplayer"
"${CMAKE_SOURCE_DIR}/Editor"
"${CMAKE_SOURCE_DIR}/i18n/include"
)
# external libraries
add_subdirectory("ext/libpng")
add_subdirectory("ext/libsmacker")
add_subdirectory("ext/zlib")
add_subdirectory("ext/VFS")
target_link_libraries(bfVFS PRIVATE 7z)
# from here on down our own code only: vendored code above keeps default flags
include(cmake/Warnings.cmake)
# ja2export utility
add_subdirectory("export/src")
# static libraries whose translation units don't rely on Application preprocessor definitions.
add_subdirectory(lua)
add_subdirectory(Multiplayer)
add_subdirectory(wine)
# 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)
# 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
"${binkw32_lib}"
"${CMAKE_SOURCE_DIR}/libexpatMT.lib"
"${CMAKE_SOURCE_DIR}/lua51.lib"
"${CMAKE_SOURCE_DIR}/lua51.vc9.lib"
"dbghelp.lib"
"winmm.lib"
"ws2_32.lib"
bfVFS
Lua
Multiplayer
smacker
wine
)
# static libraries whose translation units rely on Application preprocessor definitions.
set(Ja2_Libs
Editor
Ja2
Laptop
ModularizedTacticalAI
sgp
Strategic
Tactical
TacticalAI
TileEngine
Utils
)
foreach(lib IN LISTS Ja2_Libs)
add_subdirectory(${lib})
endforeach()
# language library relies on Application preprocessor definition; compiled once per app
# with all 8 languages built in and selected at runtime (docs/plans/language-design.md).
add_subdirectory(i18n)
# simple function to validate the Application choice
include(cmake/ValidateOptions.cmake)
set(ValidApplications JA2 JA2MAPEDITOR JA2UB JA2UBMAPEDITOR)
ValidateOptions("${ValidApplications}" "Applications" "${Applications}" "ApplicationTargets")
# preprocessor definitions for Debug build, per the legacy MSBuild
set(debugFlags $<IF:$<CONFIG:Debug>,JA2BETAVERSION;JA2TESTVERSION;DEBUG_ATTACKBUSY,>)
foreach(app IN LISTS ApplicationTargets)
set(isEditor $<STREQUAL:${app},JA2MAPEDITOR>)
set(isUb $<STREQUAL:${app},JA2UB>)
set(isUbEditor $<STREQUAL:${app},JA2UBMAPEDITOR>)
set(compilationFlags
$<IF:${isEditor},JA2EDITOR;JA2BETAVERSION,>
$<IF:${isUb},JA2UB;JA2UBMAPS,>
$<IF:${isUbEditor},JA2UB;JA2UBMAPS;JA2EDITOR;JA2BETAVERSION,>
)
foreach(lib IN LISTS Ja2_Libs)
# library for an application, e.g. JA2UB_sgp
set(game_library ${app}_${lib})
add_library(${game_library})
target_sources(${game_library} PRIVATE ${${lib}Src})
target_compile_definitions(${game_library} PRIVATE ${compilationFlags} ${debugFlags})
endforeach()
# executable for the application, e.g. JA2.exe
set(exe ${app})
add_executable(${exe} WIN32
sgp/sgp.cpp
Ja2/Res/ja2.rc
)
# RakNetLibStatic.lib was built against an older CRT and still calls
# _vsnprintf, which the UCRT only exports through this compatibility library.
target_link_libraries(${exe} PRIVATE ${Ja2_Libraries} legacy_stdio_definitions.lib)
# The prebuilt lua51.lib and smackw32 import library carry no safe exception
# handler table, so the image cannot claim one whatever links it.
target_link_options(${exe} PRIVATE /SAFESEH:NO)
target_compile_definitions(${exe} PRIVATE ${compilationFlags} ${debugFlags})
# language library for the application, e.g. JA2MAPEDITOR_i18n — one per app, all 8
# languages compiled in, selected at runtime by BindLanguageStrings
set(language_library ${exe}_i18n)
add_library(${language_library})
target_sources(${language_library} PRIVATE ${i18nSrc})
target_compile_definitions(${language_library} PRIVATE ${compilationFlags} ${debugFlags})
target_compile_options(${language_library} PRIVATE /w14062)
target_link_libraries(${exe} PRIVATE ${language_library})
# go through all game libraries again and link them to the app executable
foreach(lib IN LISTS Ja2_Libs)
target_link_libraries(${exe} PRIVATE ${app}_${lib})
endforeach()
# for Utils only
target_link_libraries(${app}_Utils PRIVATE smacker)
# for SGP only
target_link_libraries(${app}_sgp PRIVATE "ddraw.lib" "${PROJECT_SOURCE_DIR}/fmodvc.lib")
target_link_libraries(${app}_sgp PRIVATE libpng)
target_compile_definitions(${app}_sgp PRIVATE ${compilationFlags} ${debugFlags} NO_ZLIB_COMPRESSION)
endforeach()