Files
BT411/CMakeLists.txt
T
Joe DiPrimaandClaude Fable 5 68ffe556ce make a field crash resolvable: no Release PDB existed, and /O2 had eaten the frame pointers
BTCrashFilter writes `[crash] ... addr=0x... (btl4+0xNNNN)` plus an EBP-chain
walk into the tester's day log, and its own comment claims "we hold the PDB".
We did not. Two independent gaps, both fatal to the point of the thing:

  1. No Release PDB was produced AT ALL -- only build/Debug/btl4.pdb, and the
     shipped exe embedded no PDB path. btl4+0xNNNN from a tester could not be
     turned into a function name by anyone.
  2. Release flags were /O2 /Ob2 /DNDEBUG with no /Oy- anywhere, and /O2 implies
     /Oy on x86. The walker follows EBP, so the stack it printed was unreliable
     even when the faulting address was not.

Net effect: when the Owens crash finally lands we would have received an address
nobody could resolve, and a call chain we could not trust. Cheaper to fix before
tonight's session than to wait for the crash to happen twice.

  /Zi   emit debug info -> a PDB. Does not change codegen.
  /Oy-  keep EBP as a frame pointer so the walk is trustworthy.
  /DEBUG + /OPT:REF + /OPT:ICF -- /DEBUG turns the last two OFF by default,
        which would have quietly bloated the shipped exe with unreferenced
        code. The exe grew 512 bytes, the debug directory entry, and nothing else.

Also emitting /MAP, which turned out to matter: this machine has no cdb, and a
tester's operator may not have one either. The .map is plain text, so an offset
can be resolved with a text editor. tools/symcrash.py does it properly --
nearest-preceding-symbol against the archived map, module-external addresses
(ntdll, kernel32) passed through untouched since they are not ours.

Verified end to end rather than assumed, using the BT_CRASHTEST=1 hook that
exists for exactly this:

  [crash] addr=0xb8260 (btl4+0x8260) access=1 target=0x0
  [crash] stack: btl4+0x8260 btl4+0xfe7ed 0x763ffcc9 ...

  btl4+0x8260   -> _WinMain@16 +0x7e0
  btl4+0xfe7ed  -> __scrt_common_main_seh +0xf8

which is exactly right: BT_CRASHTEST does *(volatile int *)0 = 0 inside WinMain,
called from the CRT entry. Correct stack AND correct names.

The PDB and map are archived per build and never shipped: .gitignore already
covers *.pdb and dist/, and mkdist only picks up .dll next to the exe, so
neither can reach a zip by accident. The session header stamps
build=4.11.<n> (<hash>), so an archived pair matches a tester's log exactly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 20:09:36 -05:00

484 lines
20 KiB
CMake

cmake_minimum_required(VERSION 3.20)
project(bt411 CXX)
set(CMAKE_CXX_STANDARD 14)
# BattleTech 4.11 -- Virtual World Entertainment pod BattleTech (Tesla rel 4.10)
# reconstructed on the shared RP411 Windows engine. Build 32-bit (Win32) with
# the VS2019 BuildTools instance -- see README.md for the exact configure line.
# Run from the content dir: cd content && ../build/Debug/btl4.exe -egg DEV.EGG
# --- external dependency: legacy DirectX SDK (June 2010); override with -DDXSDK=... ---
set(DXSDK "C:/Program Files (x86)/Microsoft DirectX SDK (June 2010)" CACHE PATH "Legacy DirectX SDK root")
#===========================================================================#
# ONE BUILD (unified 2026-07-21; was pod / glass / steam compile splits).
# Everything compiles into the single exe; behavior is selected at RUNTIME
# by env gates -- BT_PLATFORM=glass arms the desktop-cockpit profile
# (PadRIO clicks, bindings.txt keymap, plasma window, miniconsole menu).
# A bare boot (no env, no args) behaves like the old pod build: DEV
# profile, no menu, waits for the console egg -- the cabinet default is
# unchanged. The BT_GLASS macro remains (always defined) as a seam marker.
#
# THE ONE COMPILE GATE: BT_STEAM (default OFF). The Steamworks SDK's
# license means a RELEASE build must exclude Steam entirely (no SDK
# headers, no steam_api.lib, no DLL). When ON: the transport compiles in,
# steam_api.dll is DELAY-LOADED, and BT_STEAM_NET=1 arms it at runtime --
# machines without the DLL still run everything else.
# Dev checkout: configure build/ once with -DBT_STEAM=ON.
#===========================================================================#
option(BT_STEAM "Steam transport + lobby (Steamworks SDK -- EXCLUDE from public releases)" OFF)
message(STATUS "bt411: BT_STEAM=${BT_STEAM}")
#===========================================================================#
# Tester-build expiry tripwire (2026-07-22). Distributed test zips go stale
# and stray: with BT_EXPIRE on (the DEFAULT, so a zip can never accidentally
# ship without it) the exe refuses to start BT_EXPIRE_DAYS after its build
# date, telling the tester to fetch a current build. Dev is unaffected --
# btversion.h re-stamps the build day on every build. Disable for a
# non-expiring build with -DBT_EXPIRE=OFF (mkdist marks such a zip
# '-noexpire'). Quality gate, not DRM -- a clock rollback defeats it.
#===========================================================================#
option(BT_EXPIRE "Expire the exe BT_EXPIRE_DAYS after its build date" ON)
set(BT_EXPIRE_DAYS 14 CACHE STRING "Days a BT_EXPIRE build keeps running")
message(STATUS "bt411: BT_EXPIRE=${BT_EXPIRE} (${BT_EXPIRE_DAYS} days)")
set(BT_DEFS WIN32 _WINDOWS UNICODE _UNICODE NOMINMAX
_CRT_SECURE_NO_DEPRECATE _CRT_SECURE_NO_WARNINGS
_SILENCE_STDEXT_HASH_DEPRECATION_WARNINGS _SILENCE_ALL_CXX17_DEPRECATION_WARNINGS
BT_GLASS)
if(BT_STEAM)
list(APPEND BT_DEFS BT_STEAM)
endif()
set(BT_OPTS /permissive /W0 /wd4996 /EHsc /bigobj /MP)
# FIELD CRASHES MUST BE SYMBOLIZABLE (2026-07-28).
# BTCrashFilter (btl4main.cpp) writes `[crash] ... addr=0x... (btl4+0xNNNN)`
# plus an EBP-chain walk into the tester's day log. Both halves were useless
# in a shipped build: no Release PDB was produced at all, so btl4+0xNNNN could
# not be resolved to a function, and /O2 implies /Oy (frame-pointer omission),
# so the walk itself was unreliable. A tester handed us an address nobody
# could turn into a name -- which is the whole point of the self-report.
# /Zi emit debug info -> a PDB. Does NOT change codegen.
# /Oy- keep EBP as a frame pointer so the stack walk is trustworthy.
# The PDB is ARCHIVED per build (dist/pdb/), never shipped: mkdist only picks
# up .dll next to the exe, so it cannot leak into a zip by accident.
list(APPEND BT_OPTS $<$<CONFIG:Release>:/Zi> $<$<CONFIG:Release>:/Oy->)
#===========================================================================#
# Engine: MUNGA (shared sim/render engine) + MUNGA_L4 (Win32/D3D9 HAL).
# The 2007 RP411 Windows engine, carrying our BT render/loader work
# (bgfload/L4D3D/L4VIDEO: BSL bit-slice decode, LOD/ground/shadow models).
# Include path is DXSDK + shim ONLY -- adding MUNGA dirs shadows <time.h>.
#===========================================================================#
add_library(munga_engine STATIC
"engine/MUNGA/AFFNMTRX.cpp"
"engine/MUNGA/ANGLE.cpp"
"engine/MUNGA/ANIMTOOL.cpp"
"engine/MUNGA/APP.cpp"
"engine/MUNGA/APPMGR.cpp"
"engine/MUNGA/APPMSG.cpp"
"engine/MUNGA/APPTASK.cpp"
"engine/MUNGA/AUDCMP.cpp"
"engine/MUNGA/AUDENT.cpp"
"engine/MUNGA/AUDIO.cpp"
"engine/MUNGA/AUDLOC.cpp"
"engine/MUNGA/AUDLVL.cpp"
"engine/MUNGA/AUDMIDI.cpp"
"engine/MUNGA/AUDREND.cpp"
"engine/MUNGA/AUDSEQ.cpp"
"engine/MUNGA/AUDSRC.cpp"
"engine/MUNGA/AUDTIME.cpp"
"engine/MUNGA/AUDTOOLS.cpp"
"engine/MUNGA/AUDWGT.cpp"
"engine/MUNGA/AUDWTHR.cpp"
"engine/MUNGA/AVERAGE.cpp"
"engine/MUNGA/BNDGBOX.cpp"
"engine/MUNGA/BOXCONE.cpp"
"engine/MUNGA/BOXDISKS.cpp"
"engine/MUNGA/BOXIRAMP.cpp"
"engine/MUNGA/BOXLIST.cpp"
"engine/MUNGA/BOXRAMP.cpp"
"engine/MUNGA/BOXSOLID.cpp"
"engine/MUNGA/BOXSORT.cpp"
"engine/MUNGA/BOXSPHR.cpp"
"engine/MUNGA/BOXTILE.cpp"
"engine/MUNGA/BOXTREE.cpp"
"engine/MUNGA/BOXWEDGE.cpp"
"engine/MUNGA/CAMINST.cpp"
"engine/MUNGA/CAMMGR.cpp"
"engine/MUNGA/CAMMPPR.cpp"
"engine/MUNGA/CAMSHIP.cpp"
"engine/MUNGA/CHAIN.cpp"
"engine/MUNGA/CMPNNT.cpp"
"engine/MUNGA/COLLASST.cpp"
"engine/MUNGA/COLLORGN.cpp"
"engine/MUNGA/COLOR.cpp"
"engine/MUNGA/CONSOLE.cpp"
"engine/MUNGA/CONTROLS.cpp"
"engine/MUNGA/CSTR.cpp"
"engine/MUNGA/CULTURAL.cpp"
"engine/MUNGA/DAMAGE.cpp"
"engine/MUNGA/DIRECTOR.cpp"
"engine/MUNGA/DOOR.cpp"
"engine/MUNGA/DOORFRAM.cpp"
"engine/MUNGA/DROPZONE.cpp"
"engine/MUNGA/ENTITY.cpp"
"engine/MUNGA/ENTITY2.cpp"
"engine/MUNGA/ENTITYID.cpp"
"engine/MUNGA/ENVIRNMT.cpp"
"engine/MUNGA/EVENT.cpp"
"engine/MUNGA/EVTSTAT.cpp"
"engine/MUNGA/EXPLODE.cpp"
"engine/MUNGA/EXPTBL.cpp"
"engine/MUNGA/EXTNTBOX.cpp"
"engine/MUNGA/EYECANDY.cpp"
"engine/MUNGA/FILESTRM.cpp"
"engine/MUNGA/FILESTUB.cpp"
"engine/MUNGA/FILEUTIL.cpp"
"engine/MUNGA/GAUGALRM.cpp"
"engine/MUNGA/GAUGE.cpp"
"engine/MUNGA/GAUGMAP.cpp"
"engine/MUNGA/GAUGREND.cpp"
"engine/MUNGA/GRAPH2D.cpp"
"engine/MUNGA/HASH.cpp"
"engine/MUNGA/HOST.cpp"
"engine/MUNGA/HOSTMGR.cpp"
"engine/MUNGA/ICOM.cpp"
"engine/MUNGA/INTEREST.cpp"
"engine/MUNGA/INTORGN.cpp"
"engine/MUNGA/ITERATOR.cpp"
"engine/MUNGA/JMOVER.cpp"
"engine/MUNGA/JOINT.cpp"
"engine/MUNGA/LAMP.cpp"
"engine/MUNGA/LATTICE.cpp"
"engine/MUNGA/LINE.cpp"
"engine/MUNGA/LINK.cpp"
"engine/MUNGA/LINMTRX.cpp"
"engine/MUNGA/MAPTOOL.cpp"
"engine/MUNGA/MATRIX.cpp"
"engine/MUNGA/MEMBLOCK.cpp"
"engine/MUNGA/MEMREG.cpp"
"engine/MUNGA/MEMSTRM.CPP"
"engine/MUNGA/MISSION.cpp"
"engine/MUNGA/MODE.cpp"
"engine/MUNGA/MODTOOL.cpp"
"engine/MUNGA/MOTION.cpp"
"engine/MUNGA/MOVER.cpp"
"engine/MUNGA/MTRXSTK.cpp"
"engine/MUNGA/NAMELIST.cpp"
"engine/MUNGA/NETWORK.cpp"
"engine/MUNGA/NODE.cpp"
"engine/MUNGA/NORMAL.cpp"
"engine/MUNGA/NOTATION.cpp"
"engine/MUNGA/NTTMGR.cpp"
"engine/MUNGA/OBJSTRM.cpp"
"engine/MUNGA/ORIGIN.cpp"
"engine/MUNGA/PLANE.cpp"
"engine/MUNGA/PLAYER.cpp"
"engine/MUNGA/PLUG.cpp"
"engine/MUNGA/POINT3D.cpp"
"engine/MUNGA/RANDOM.cpp"
"engine/MUNGA/RAY.cpp"
"engine/MUNGA/RECEIVER.cpp"
"engine/MUNGA/RECT2D.cpp"
"engine/MUNGA/REGISTRY.cpp"
"engine/MUNGA/RENDERER.cpp"
"engine/MUNGA/RESOURCE.cpp"
"engine/MUNGA/RETICLE.cpp"
"engine/MUNGA/RNDORGN.cpp"
"engine/MUNGA/ROTATION.cpp"
"engine/MUNGA/SCALAR.cpp"
"engine/MUNGA/SCHAIN.cpp"
"engine/MUNGA/SCNROLE.cpp"
"engine/MUNGA/SEGMENT.cpp"
"engine/MUNGA/SET.cpp"
"engine/MUNGA/SFESKT.cpp"
"engine/MUNGA/SIMULATE.cpp"
"engine/MUNGA/SLOT.cpp"
"engine/MUNGA/SOCKET.cpp"
"engine/MUNGA/SPHERE.cpp"
"engine/MUNGA/SPLINE.cpp"
"engine/MUNGA/SPOOLER.cpp"
"engine/MUNGA/SRTSKT.cpp"
"engine/MUNGA/SUBSYSTM.cpp"
"engine/MUNGA/TABLE.cpp"
"engine/MUNGA/TEAM.cpp"
"engine/MUNGA/TERRAIN.cpp"
"engine/MUNGA/TESTALL.cpp"
"engine/MUNGA/TIME.cpp"
# TIMESTUB.cpp REMOVED (2026-07-14): its GetRTC/GetHiRes were 2007 call-counter
# stubs (`return time++` -- the "clock" advanced per CALL, not per ms) that WON
# the /FORCE duplicate-symbol race over the REAL QueryPerformanceCounter clock
# in L4TIME.cpp (LNK4006 "second definition ignored"). Every Now()-domain
# consumer (dead-reckoning above all) ran on call-count pseudo-time -> the
# replicant stall/jump chop. L4TIME.cpp covers every symbol TIMESTUB defined.
"engine/MUNGA/TOOL.cpp"
"engine/MUNGA/TRACE.cpp"
"engine/MUNGA/TRACSTUB.cpp"
"engine/MUNGA/TREE.cpp"
"engine/MUNGA/UNITVEC.cpp"
"engine/MUNGA/UPDATE.cpp"
"engine/MUNGA/VCHAIN.cpp"
"engine/MUNGA/VDATA.cpp"
"engine/MUNGA/VECTOR3D.cpp"
"engine/MUNGA/VECTOR4D.cpp"
"engine/MUNGA/VERIFY.cpp"
"engine/MUNGA/VIDREND.cpp"
"engine/MUNGA/WATCHER.cpp"
"engine/MUNGA/WRHOUS.cpp"
"engine/MUNGA_L4/DXUtils.cpp"
"engine/MUNGA_L4/L4APP.cpp"
"engine/MUNGA_L4/L4AUDEFX.cpp"
"engine/MUNGA_L4/L4AUDHDW.cpp"
"engine/MUNGA_L4/L4AUDIO.cpp"
"engine/MUNGA_L4/L4AUDLVL.cpp"
"engine/MUNGA_L4/L4AUDRES.cpp"
"engine/MUNGA_L4/L4AUDRND.cpp"
"engine/MUNGA_L4/L4AUDTUL.cpp"
"engine/MUNGA_L4/L4AUDWTR.cpp"
"engine/MUNGA_L4/L4CTLTUL.cpp"
"engine/MUNGA_L4/L4CTRL.cpp"
"engine/MUNGA_L4/L4D3D.cpp"
"engine/MUNGA_L4/L4DINPUT.cpp"
"engine/MUNGA_L4/L4DPLMEM.cpp"
"engine/MUNGA_L4/L4GAUGE.cpp"
"engine/MUNGA_L4/L4GAUIMA.cpp"
"engine/MUNGA_L4/L4GAUTUL.cpp"
"engine/MUNGA_L4/L4GREND.cpp"
"engine/MUNGA_L4/L4HOST.cpp"
"engine/MUNGA_L4/L4ICOM.cpp"
"engine/MUNGA_L4/L4KEYBD.cpp"
"engine/MUNGA_L4/L4LAMP.cpp"
"engine/MUNGA_L4/L4MOUSE.cpp"
"engine/MUNGA_L4/L4MPPR.cpp"
"engine/MUNGA_L4/L4NET.CPP"
"engine/MUNGA_L4/L4PARTICLES.cpp"
"engine/MUNGA_L4/L4PCSPAK.cpp"
"engine/MUNGA_L4/L4PLASMA.cpp"
"engine/MUNGA_L4/L4RIO.cpp"
"engine/MUNGA_L4/L4SERIAL.cpp"
"engine/MUNGA_L4/L4SPLR.cpp"
"engine/MUNGA_L4/L4TIME.cpp"
"engine/MUNGA_L4/L4TRACE.cpp"
"engine/MUNGA_L4/L4TSTALL.cpp"
"engine/MUNGA_L4/L4VB16.cpp"
"engine/MUNGA_L4/L4VB8.cpp"
"engine/MUNGA_L4/L4VIDEO.cpp"
"engine/MUNGA_L4/L4VIDPER.cpp"
"engine/MUNGA_L4/L4VIDRND.cpp"
"engine/MUNGA_L4/L4VIDTUL.cpp"
"engine/MUNGA_L4/L4WRHOUS.cpp"
"engine/MUNGA_L4/WTPresets.cpp"
"engine/MUNGA_L4/temp.cpp"
"engine/MUNGA_L4/bgfload.cpp"
"engine/MUNGA_L4/image.cpp"
)
target_sources(munga_engine PRIVATE
"engine/MUNGA_L4/L4PADRIO.cpp"
"engine/MUNGA_L4/L4PADBINDINGS.cpp"
"engine/MUNGA_L4/L4JOY.cpp"
"engine/MUNGA_L4/L4PADPANEL.cpp"
"engine/MUNGA_L4/L4RIOBANK.cpp"
"engine/MUNGA_L4/L4GLASSWIN.cpp"
"engine/MUNGA_L4/L4PLASMAWIN.cpp"
"engine/MUNGA_L4/L4KEYLIGHT.cpp"
)
# RGB keyboard lamp mirror (Windows Dynamic Lighting). C++/WinRT needs C++17
# AND conformance mode, while the rest of the project builds C++14 /permissive
# -- so this ONE file gets its own flags. Its interface (l4keylight.h) is
# scalars only, so nothing else has to change dialect. (RP412 additionally
# forced default struct packing here because its engine is /Zp1; BT411 sets no
# /Zp, so that part does not apply -- see the file header.)
set_source_files_properties("engine/MUNGA_L4/L4KEYLIGHT.cpp" PROPERTIES
COMPILE_OPTIONS "/std:c++17;/permissive-")
# ... and only where the SDK can actually compile it. SDK 10.0.19041's
# bundled cppwinrt fails inside winrt/base itself (C2039 'wait_for'), so on a
# machine with only an older SDK the TU builds its dormant stub instead --
# same interface, logs once, everything else identical. Dynamic Lighting is
# a Windows 11 22H2 feature anyway, so nothing real is lost on such a machine.
set(_bt_sdk "${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}")
if(NOT _bt_sdk)
set(_bt_sdk "${CMAKE_SYSTEM_VERSION}")
endif()
if(_bt_sdk VERSION_LESS "10.0.22000")
message(STATUS "keylight: Windows SDK ${_bt_sdk} too old for C++/WinRT "
"Dynamic Lighting -- building the dormant stub")
set_property(SOURCE "engine/MUNGA_L4/L4KEYLIGHT.cpp" APPEND PROPERTY
COMPILE_DEFINITIONS BT_KEYLIGHT_STUB)
endif()
target_include_directories(munga_engine BEFORE PRIVATE
"${CMAKE_SOURCE_DIR}/engine/shim"
"${DXSDK}/Include")
target_compile_definitions(munga_engine PRIVATE ${BT_DEFS})
target_compile_options(munga_engine PRIVATE ${BT_OPTS})
#===========================================================================#
# Game logic: reconstructed BT modules + surviving-original BT source.
# Headers come from game/original/BT{,_L4} and the game/fwd shims
# (which forward <NAME.hpp> -> the engine's NAME.h).
#===========================================================================#
add_library(bt410_l4 STATIC
"game/reconstructed/ammobin.cpp"
"game/reconstructed/btdirect.cpp"
"game/reconstructed/btinput.cpp"
"game/reconstructed/btl4app.cpp"
"game/reconstructed/btl4galm.cpp"
"game/reconstructed/btl4gau2.cpp"
"game/reconstructed/btl4gau3.cpp"
"game/reconstructed/btl4gaug.cpp"
"game/reconstructed/btl4grnd.cpp"
"game/reconstructed/btl4mppr.cpp"
"game/reconstructed/btl4mssn.cpp"
"game/reconstructed/btl4pb.cpp"
"game/reconstructed/btl4rdr.cpp"
"game/reconstructed/btl4vid.cpp"
"game/reconstructed/btplayer.cpp"
"game/reconstructed/btvisgnd.cpp"
"game/reconstructed/dmgtable.cpp"
"game/reconstructed/emitter.cpp"
"game/reconstructed/gnrator.cpp"
"game/reconstructed/gyro.cpp"
"game/reconstructed/matchlog.cpp"
"game/reconstructed/heat.cpp"
"game/reconstructed/heatfamily_reslice.cpp"
"game/reconstructed/hud.cpp"
"game/reconstructed/mech.cpp"
"game/reconstructed/mech2.cpp"
"game/reconstructed/mech3.cpp"
"game/reconstructed/mech4.cpp"
"game/reconstructed/mechdmg.cpp"
"game/reconstructed/mechmppr.cpp"
"game/reconstructed/mechsub.cpp"
"game/reconstructed/mechtech.cpp"
"game/reconstructed/mechweap.cpp"
"game/reconstructed/messmgr.cpp"
"game/reconstructed/mislanch.cpp"
"game/reconstructed/missile.cpp"
"game/reconstructed/misthrst.cpp"
"game/reconstructed/myomers.cpp"
"game/reconstructed/powersub.cpp"
"game/reconstructed/projtile.cpp"
"game/reconstructed/projweap.cpp"
"game/reconstructed/searchlight.cpp"
"game/reconstructed/seeker.cpp"
"game/reconstructed/sensor.cpp"
"game/reconstructed/seqctl.cpp"
"game/reconstructed/thermalsight.cpp"
"game/reconstructed/torso.cpp"
"game/reconstructed/btstubs.cpp"
"game/reconstructed/audiopresets.cpp"
"game/reconstructed/dpl2d.cpp"
"game/original/BT/BTMSSN.CPP"
"game/original/BT/BTREG.CPP"
"game/original/BT/BTTEAM.CPP"
"game/original/BT/BTSCNRL.CPP"
"game/original/BT/BTCNSL.CPP"
"game/original/BT/GAUSS.CPP"
"game/original/BT/PPC.CPP"
"game/original/BT_L4/BTL4MODE.CPP"
"game/original/BT_L4/BTL4ARND.CPP"
)
target_include_directories(bt410_l4 BEFORE PRIVATE
"${CMAKE_BINARY_DIR}"
"${CMAKE_SOURCE_DIR}/engine/shim"
"${CMAKE_SOURCE_DIR}/game/reconstructed"
"${CMAKE_SOURCE_DIR}/game/original/BT"
"${CMAKE_SOURCE_DIR}/game/original/BT_L4"
"${CMAKE_SOURCE_DIR}/game/fwd"
"${DXSDK}/Include")
target_compile_definitions(bt410_l4 PRIVATE ${BT_DEFS})
target_compile_options(bt410_l4 PRIVATE ${BT_OPTS})
#===========================================================================#
# Executable: btl4.exe (WinMain launcher + game lib + engine lib + D3D9/audio)
#===========================================================================#
# Version stamp: 4.11.<git commit count> (<short hash>[+]) regenerated every
# build into ${CMAKE_BINARY_DIR}/btversion.h (see tools/btversion.cmake).
add_custom_target(btversion
COMMAND ${CMAKE_COMMAND}
-DOUT=${CMAKE_BINARY_DIR}/btversion.h
-DSRC=${CMAKE_SOURCE_DIR}
-P ${CMAKE_SOURCE_DIR}/tools/btversion.cmake
BYPRODUCTS ${CMAKE_BINARY_DIR}/btversion.h
COMMENT "Stamping btversion.h")
add_dependencies(bt410_l4 btversion) # matchlog.cpp stamps btversion.h into its HDR line
add_executable(btl4 WIN32 "${CMAKE_SOURCE_DIR}/game/btl4main.cpp")
add_dependencies(btl4 btversion)
target_include_directories(btl4 BEFORE PRIVATE
"${CMAKE_BINARY_DIR}"
"${CMAKE_SOURCE_DIR}/engine/shim"
"${CMAKE_SOURCE_DIR}/game/reconstructed"
"${CMAKE_SOURCE_DIR}/game/original/BT"
"${CMAKE_SOURCE_DIR}/game/original/BT_L4"
"${CMAKE_SOURCE_DIR}/game/fwd"
"${DXSDK}/Include")
target_sources(btl4 PRIVATE
"game/glass/btl4fe.cpp"
"game/glass/btl4console.cpp"
)
target_include_directories(btl4 BEFORE PRIVATE "${CMAKE_SOURCE_DIR}/game/glass")
target_compile_definitions(btl4 PRIVATE ${BT_DEFS})
if(BT_EXPIRE)
target_compile_definitions(btl4 PRIVATE BT_EXPIRE=1 BT_EXPIRE_DAYS=${BT_EXPIRE_DAYS})
endif()
target_compile_options(btl4 PRIVATE ${BT_OPTS})
target_link_libraries(btl4 PRIVATE
bt410_l4
munga_engine
"${CMAKE_SOURCE_DIR}/engine/lib/OpenAL32.lib"
"${DXSDK}/Lib/x86/d3d9.lib"
"${DXSDK}/Lib/x86/d3dx9.lib"
"${DXSDK}/Lib/x86/dinput8.lib"
"${DXSDK}/Lib/x86/dxguid.lib"
"${DXSDK}/Lib/x86/DxErr.lib"
winmm dbghelp shell32 user32 gdi32 ole32 ws2_32)
# /FORCE: the 1995 headers define free funcs/globals without inline/extern, so
# identical symbols land in every TU (~124 LNK2005); MULTIPLE keeps the first.
# UNRESOLVED tolerates the dead offline-tool factory in mech3.cpp. See docs.
target_link_options(btl4 PRIVATE /FORCE)
# ...and emit the PDB the crash filter's offsets are resolved against (see the
# /Zi note by BT_OPTS). /DEBUG turns OFF /OPT:REF and /OPT:ICF by default, which
# would silently bloat the shipped exe with unreferenced code, so put both back:
# the goal is a symbol file BESIDE an otherwise-unchanged Release binary.
# /MAP as well as the PDB, deliberately: the .map is PLAIN TEXT, so a
# `btl4+0xNNNN` from a tester's log can be resolved to a function name with a
# text editor and no debugger installed at all (this machine has no cdb).
# tools/symcrash.py does the lookup. Archive both per build.
target_link_options(btl4 PRIVATE
$<$<CONFIG:Release>:/DEBUG>
$<$<CONFIG:Release>:/OPT:REF>
$<$<CONFIG:Release>:/OPT:ICF>
$<$<CONFIG:Release>:/MAP>)
# Steam transport (BT_STEAM=ON only -- the license gate): DELAY-LOADED.
# steam_api.dll is only mapped when the first SteamAPI call runs, and every
# call site gates on BT_STEAM_NET=1 (btl4main step-4b bring-up; L4NET's
# BTSteamNet_* short-circuit on steamActive) -- so even the steam-enabled
# exe runs without the DLL as long as the env gate stays off. A BT_STEAM=OFF
# configure touches NO Steamworks bits: no SDK headers, no lib, no DLL copy
# (and mkdist then leaves play_steam.bat + steam_api.dll out of the zip).
if(BT_STEAM)
set(STEAMWORKS "${CMAKE_SOURCE_DIR}/extern/steamworks_sdk_164")
target_sources(munga_engine PRIVATE "engine/MUNGA_L4/L4STEAMNET.cpp")
target_sources(btl4 PRIVATE "game/glass/btl4lobby.cpp")
target_include_directories(munga_engine PRIVATE "${STEAMWORKS}/public")
target_include_directories(btl4 PRIVATE "${STEAMWORKS}/public")
target_link_libraries(btl4 PRIVATE
"${STEAMWORKS}/redistributable_bin/steam_api.lib"
delayimp)
target_link_options(btl4 PRIVATE "/DELAYLOAD:steam_api.dll")
add_custom_command(TARGET btl4 POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${STEAMWORKS}/redistributable_bin/steam_api.dll"
"$<TARGET_FILE_DIR:btl4>")
endif()
# Copy the OpenAL runtime DLL next to the built exe. (libsndfile is gone: its
# repo DLL was a no-op STUB -- L4AUDRES now loads the soundbank WAVs with an
# in-tree RIFF/PCM reader, no external dependency.)
add_custom_command(TARGET btl4 POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${CMAKE_SOURCE_DIR}/engine/lib/OpenAL32.dll"
"$<TARGET_FILE_DIR:btl4>")