cmake_minimum_required(VERSION 3.16)
project(dirtdash C)

if(NOT CMAKE_C_COMPILER_ID STREQUAL "GNU")
  message(FATAL_ERROR "gcc required (the lifted code uses computed labels and statement expressions)")
endif()
if(NOT CMAKE_BUILD_TYPE)
  set(CMAKE_BUILD_TYPE RelWithDebInfo)
endif()
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_EXTENSIONS ON)
add_compile_options(-fno-strict-aliasing -fwrapv)

# PORTABLE (default ON): pin glibc symbol versions so a binary built here runs on older systems -- the Steam Deck included.
# (Prop Cycle's ../include/glibc_compat.h: one copy for every game.)
option(PORTABLE "Bind glibc symbols to baseline versions (runs on glibc >= 2.34)" ON)
if(PORTABLE AND NOT WIN32 AND CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64")
  add_compile_options(-include ${CMAKE_CURRENT_SOURCE_DIR}/../include/glibc_compat.h)
endif()

include_directories(include gen)

# THE SHARED ENGINE (../engine): the 68K host runtime (lift_cpu.c) today; the renderer, mixer
# and DSP translation when T4-T6 land (HARD RULE 3 in ../CLAUDE.md).
include(${CMAKE_CURRENT_SOURCE_DIR}/../engine/engine.cmake)

# gen/dd_lifted_NN.c + gen/dd_lifted_tab.c: the 68K program, GENERATED by ../tools/namco22/lift.py from decompiled/pcode.txt and SPLIT by the
# shared lift_split.py (as Time Crisis):
#   python3 ../tools/namco22/lift.py decompiled/pcode.txt gen dd && python3 ../tools/namco22/lift_split.py gen/dd_lifted.c 16
# One 23-27 MB file was a single gcc process on one core for minutes (three times per release); 16 parts compile in parallel.
# Big goto-heavy C; -O1 keeps the build short and is plenty for a 24 MHz 68EC020.
file(GLOB DD_LIFTED CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/gen/dd_lifted_*.c)
if(NOT DD_LIFTED)
  if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/gen/dd_lifted.c)
    message(FATAL_ERROR "gen/dd_lifted.c is not split: run  python3 ../tools/namco22/lift_split.py gen/dd_lifted.c 16")
  endif()
  message(FATAL_ERROR "gen/dd_lifted_NN.c missing (the lifted 68K program)")
endif()
set_source_files_properties(${DD_LIFTED} PROPERTIES COMPILE_OPTIONS "-O1;-w")

# The MASTER DSP program (the C71 BIOS + the block the 68K uploads through it), translated to C from
# the ROM files by the shared tool ../tools/gen/c25_translate.py. tools/gen/c25.cov: the addresses
# the interpreter oracle executed (addresses only, no ROM data; committed). The game has no C25
# fetch/decode loop. The master program block: located in the 68K ROM at T4.
find_package(Python3 REQUIRED COMPONENTS Interpreter)
add_custom_command(OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/gen/dd_c25.c
  COMMAND ${Python3_EXECUTABLE} ../tools/gen/c25_translate.py --game dd --roms extracted
          --cov tools/gen/c25.cov --out gen/dd_c25.c --func dd_c25_exec
  DEPENDS ../tools/gen/c25_translate.py tools/gen/c25.cov ../engine/c25/c71_bios.inc
  WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
  COMMENT "Translating the master DSP program to C")
set_source_files_properties(gen/dd_c25.c PROPERTIES COMPILE_OPTIONS "-O2;-w")

# The SOUND MCU program (dt1dataa.8k -- the S22-BIOS ver1.41 and the game's sound program, which also
# reads the cabinet's ports), translated to C from the ROM file by the shared tools/gen/snd_translate.py
# (--game dd). tools/gen/snd.cov: the (address, M/X, length) of every instruction the dd_oracle test
# build executed (addresses only, no ROM data; committed). The game has no M37710 fetch/decode loop.
add_custom_command(OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/gen/dd_snd_driver.c
  COMMAND ${Python3_EXECUTABLE} ../tools/gen/snd_translate.py tools/gen/snd.cov --game dd --roms extracted
          --out gen/dd_snd_driver.c
  DEPENDS ../tools/gen/snd_translate.py ../tools/gen/snd_rom.py ../tools/gen/snd_spin.inc ../tools/gen/snd_dispatch.py ../tools/gen/snd_indirect.py ../tools/gen/snd_exits.py ../tools/gen/m7700_decode.py tools/gen/snd.cov
  WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
  COMMENT "Translating the sound MCU program to C")
set_source_files_properties(gen/dd_snd_driver.c PROPERTIES COMPILE_OPTIONS "-O1;-w")

# the game: the 68K (lifted), the master DSP (translated), the sound MCU (translated) and its C352
# the picture and the window: the shared engine's geometry stage, texture bake, GL rasteriser and Super 22 layers
# (ss22_gl.c), around Dirt Dash' own video RAM (dd_video.c), controls (dd_input.c) and window (dd_host.c)
find_package(PkgConfig REQUIRED)
pkg_check_modules(SDL2 REQUIRED sdl2)
if(WIN32)
  set(ZLIB_USE_STATIC_LIBS ON)                    # MSYS2's zlib comes as a DLL too: link the static one, or the .exe needs zlib1.dll (CMake 3.24+)
endif()
find_package(ZLIB REQUIRED)                       # the first-run ROM unpacker (engine/romzip.c)
set(OpenGL_GL_PREFERENCE GLVND)
find_package(OpenGL REQUIRED)
include_directories(${SDL2_INCLUDE_DIRS})
# Windows (MinGW-w64, ../build-windows.sh): the few POSIX calls mapped in ../include/win_compat.h, no console window, and
# everything linked in -- SDL2 statically too -- so DirtDash.exe needs no DLL beside it. OpenGL is NOT linked: the shared
# engine's gl_dyn.c resolves it at run time, so the bundled Mesa can stand in when Windows has no real driver.
if(WIN32)
  add_compile_options(-include ${CMAKE_CURRENT_SOURCE_DIR}/../include/win_compat.h)
  add_link_options(-mwindows -static-libgcc -static)
  set(SDL2_LIBRARIES ${SDL2_STATIC_LDFLAGS} ws2_32)   # engine/ss22_out.c: the MAME network-output socket (Winsock2)
  set(DD_GL_LIB "")
  set(DD_GL_SRC ${NAMCO22_ENGINE_DIR}/gl_dyn.c)
else()
  set(DD_GL_LIB OpenGL::GL)
  set(DD_GL_SRC "")
endif()

set(DD_CORE_SRC src/dd_game.c
    ${NAMCO22_ENGINE_LIFT_SRC} ${NAMCO22_ENGINE_C25_SRC} ${NAMCO22_ENGINE_SND_SRC}
    ${NAMCO22_ENGINE_GL_SRC} ${NAMCO22_ENGINE_SS22_SRC} ${NAMCO22_ENGINE_SS22_HOST_SRC} ${NAMCO22_ENGINE_UI_SRC} ${NAMCO22_ENGINE_ROMZIP_SRC} ${DD_GL_SRC}
    ${DD_LIFTED})

# THE GAME (needs the translated programs, made from the coverage the oracle build records: tools/gen/c25.cov, tools/gen/snd.cov)
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/tools/gen/c25.cov AND EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/tools/gen/snd.cov)
set(DD_CPU_SRC ${DD_CORE_SRC} gen/dd_c25.c gen/dd_snd_driver.c)
add_executable(dd ${DD_CPU_SRC})
target_compile_options(dd PRIVATE -Wall)
target_link_libraries(dd ${SDL2_LIBRARIES} ${DD_GL_LIB} ZLIB::ZLIB m)
endif()

# Development builds (the instruction trace and the translation gate): only when their sources are present -- the public tree
# has just the game.
if(EXISTS ${NAMCO22_TOOLS_DIR}/sndoracle/sound_oracle.c)
# BRING-UP / TRANSLATION-GATE BUILDS (dev only, never shipped -- HARD RULE 1): the same host with the interpreter oracle for the master
# DSP (C71_COV=file records the addresses it executes) and the original sound program fetched and decoded (SND_COV=file records the
# instructions it executes) -- the coverage the translators read. Before tools/gen/c25.cov exists (DD_NO_C25) the oracle IS the master;
# afterwards the translation (gen/dd_c25.c) is linked too and DD_C25=lockstep runs it beside a shadow master on the oracle
# (tools/c25_gate.sh).
set(DD_C25_SRC gen/dd_c25.c)
set(DD_C25_DEFS "")
add_executable(dd_oracle ${DD_CORE_SRC} ${DD_C25_SRC} ${NAMCO22_C25_ORACLE_SRC}
    ${NAMCO22_TOOLS_DIR}/sndoracle/m37710.c ${NAMCO22_TOOLS_DIR}/sndoracle/sound_oracle.c)
target_compile_options(dd_oracle PRIVATE -Wall)
target_compile_definitions(dd_oracle PRIVATE DD_ORACLE SS22_ORACLE C25_DEV_HOOKS ${DD_C25_DEFS})
target_link_libraries(dd_oracle ${SDL2_LIBRARIES} OpenGL::GL ZLIB::ZLIB m)

# instruction-trace build: every instruction logs PC + D0-D7/A0-A7 (RR_TRACE_FILE); the T3 proof against MAME's register trace
add_executable(dd_trace ${DD_CORE_SRC} ${DD_C25_SRC} ${NAMCO22_C25_ORACLE_SRC}
    ${NAMCO22_TOOLS_DIR}/sndoracle/m37710.c ${NAMCO22_TOOLS_DIR}/sndoracle/sound_oracle.c)
target_compile_options(dd_trace PRIVATE -Wall)
target_compile_definitions(dd_trace PRIVATE RR_TRACE DD_ORACLE SS22_ORACLE C25_DEV_HOOKS ${DD_C25_DEFS})
target_link_libraries(dd_trace ${SDL2_LIBRARIES} OpenGL::GL ZLIB::ZLIB m)
endif()
