cmake_minimum_required(VERSION 3.20)

# ============================================================================
# YOUR_GAME_NAME - Static Recompilation
#
# Copy this template to your game project root and customize:
#   1. Change the project name below
#   2. Set XBOXRECOMP_DIR to where you cloned xboxrecomp
#   3. Adjust source file lists as you add game-specific code
#   4. Add any game-specific compile definitions or link libraries
# ============================================================================

project(your_game_recomp C)

set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)

# Common definitions
add_compile_definitions(
    WIN32_LEAN_AND_MEAN
    NOMINMAX
)

# Diagnostic build (separate build dir): every direct call of the recompiled game counts itself in
# recomp_call_profile_hit (game_patches.c); F10 in game writes the counts to profile_<n>.txt. Slower.
option(RECOMP_CALL_PROFILE "count every direct call, F10 dumps the counts" OFF)
if(RECOMP_CALL_PROFILE)
    add_compile_definitions(RECOMP_CALL_PROFILE)
endif()

# Developer diagnostics hooked into the generated code (AI probes, shot/projectile logs, aim measurement, aim-smoothing
# test). OFF in the player build: the hooks are not compiled in, so they cannot cost a frame. A diagnostic build uses its
# own build directory with -DRECOMP_DIAG=ON.
option(RECOMP_DIAG "compile the developer diagnostic hooks into the generated code" OFF)
if(RECOMP_DIAG)
    add_compile_definitions(RECOMP_DIAG)
endif()

# getenv/_putenv answered from a per-thread cache in every file (src/rc_env.h, rc_env.c).
if(MSVC)
    add_compile_options("$<$<COMPILE_LANGUAGE:C>:/FI${CMAKE_CURRENT_SOURCE_DIR}/src/rc_env.h>")
endif()

# ── xboxrecomp toolkit ──────────────────────────────────────────
#
# Option A: xboxrecomp as a subdirectory (simplest)
#   Place xboxrecomp/ next to your game project and use a relative path.
#
# Option B: find_package (if you install xboxrecomp system-wide)
#   find_package(xboxrecomp REQUIRED)
#
# Adjust this path to point to your xboxrecomp clone:
set(XBOXRECOMP_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../xboxrecomp-0.16.0" CACHE PATH
    "Path to the xboxrecomp toolkit root directory")

# Add xboxrecomp as a subdirectory -- this builds all 6 runtime libraries
# and provides the 'xboxrecomp' umbrella target to link against.
# Full debug info for the runtime only (small; lets a sampling profiler name
# its static functions). The generated code keeps its flags -- the linker's
# /DEBUG below still gives every sub_XXXXXXXX a public symbol.
set(_saved_c_flags_release "${CMAKE_C_FLAGS_RELEASE}")
if(MSVC)
    set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /Zi")
endif()
add_subdirectory(${XBOXRECOMP_DIR} ${CMAKE_BINARY_DIR}/xboxrecomp)
set(CMAKE_C_FLAGS_RELEASE "${_saved_c_flags_release}")

# ── Recompiled game executable ──────────────────────────────────

# Collect generated recompiled code.
# The recomp pipeline outputs files into src/recomp/gen/.
# With --split 1000, you get gen_0000.c, gen_0001.c, etc.
# CONFIGURE_DEPENDS so re-running the recompiler picks up added/removed files
# without a manual re-configure. A plain GLOB is evaluated once at configure
# time, so a newly generated file (an extra chunk, recomp_stubs_unresolved.c)
# is silently left out of the build and shows up as unresolved externals.
file(GLOB RECOMP_GEN_SOURCES CONFIGURE_DEPENDS "src/recomp/gen/*.c")
# The function chunks (recomp_NNNN.c) take the register file's addresses once per function (RC_REGS_PROLOGUE
# in recomp_types.h) instead of re-deriving the thread-local base on every access. RECOMP_REGS_CACHE=OFF
# builds without that (a function chunk regenerated by the recompiler has no prologue line: re-add it first).
option(RECOMP_REGS_CACHE "generated functions cache the thread-local register addresses" ON)
if(RECOMP_REGS_CACHE)
    foreach(_g ${RECOMP_GEN_SOURCES})
        if(_g MATCHES "recomp_[0-9][0-9][0-9][0-9]\\.c$")
            set_property(SOURCE ${_g} APPEND PROPERTY COMPILE_DEFINITIONS RECOMP_REGS_CACHE=1)
        endif()
    endforeach()
endif()

# Core game sources
set(GAME_SOURCES
    src/main.c
    src/recomp_manual.c
    src/game_patches.c
    src/recomp_settings.c
    # Add game-specific sources here, e.g.:
    # src/asset_loader.c
    # src/custom_renderer.c
)

# The recomp pipeline also generates a dispatch header and function declarations.
# These are included by the generated .c files:
#   src/recomp/gen/recomp_funcs.h    - extern declarations for all recompiled functions
#   src/recomp/recomp_dispatch.h     - binary search dispatch table
#   src/recomp/recomp_types.h        - register model, ICALL macros, MEM helpers

add_executable(${PROJECT_NAME} WIN32
    ${GAME_SOURCES}
    ${RECOMP_GEN_SOURCES}
)

# Link against the xboxrecomp umbrella target (all 6 runtime libraries)
target_link_libraries(${PROJECT_NAME} PRIVATE xboxrecomp)

# Include paths for generated recomp headers
target_include_directories(${PROJECT_NAME} PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/src
    ${CMAKE_CURRENT_SOURCE_DIR}/src/recomp
    ${XBOXRECOMP_DIR}/include
)

# Windows SDK libraries needed at link time
target_link_libraries(${PROJECT_NAME} PRIVATE
    d3d11
    dxgi
    dxguid
    $<IF:$<BOOL:${MINGW}>,xinput1_4,xinput>   # see src/input/CMakeLists.txt
    winmm
    # Lets the fault handler resolve the host RIP back to the generated
    # function's symbol, i.e. to the guest function that faulted.
    dbghelp
)

# MSVC-specific: large address aware, increase section limit for big generated files
if(MSVC)
    target_link_options(${PROJECT_NAME} PRIVATE
        /LARGEADDRESSAWARE
        # A PDB for profiling and fault reports; keep Release's optimisations.
        $<$<CONFIG:Release>:/DEBUG> $<$<CONFIG:Release>:/OPT:REF>
        $<$<CONFIG:Release>:/OPT:ICF>
    )
    # The generated code files can be very large. Increase object file section limit.
    target_compile_options(${PROJECT_NAME} PRIVATE /bigobj)
    # Every guest memory access (MEM32 & co) is volatile. Under MSVC's default
    # /volatile:ms each one is also an acquire/release barrier for the
    # compiler, which reloads g_xbox_mem_offset and the TLS register file
    # around every access. /volatile:iso keeps the accesses and their order
    # (all guest memory is volatile) but lets the rest be scheduled freely.
    # RECOMP_VOLATILE_MS=ON goes back (for a before/after comparison).
    if(NOT RECOMP_VOLATILE_MS)
        target_compile_options(${PROJECT_NAME} PRIVATE /volatile:iso)
    endif()
    # Profile-guided build of the game's own code (the generated guest functions): -DRECOMP_PGO=LTCG whole-program
    # only, PGI = instrumented (play it, it writes *.pgc beside the exe), PGO = optimised with the recorded profile.
    # Use a separate build directory; the normal build/ is not touched.
    if(RECOMP_PGO)
        target_compile_options(${PROJECT_NAME} PRIVATE /GL)
        if(RECOMP_PGO STREQUAL "PGI")
            target_compile_definitions(${PROJECT_NAME} PRIVATE RECOMP_PGI=1)
            target_link_options(${PROJECT_NAME} PRIVATE /LTCG:PGINSTRUMENT)
        elseif(RECOMP_PGO STREQUAL "PGO")
            target_link_options(${PROJECT_NAME} PRIVATE /LTCG:PGOPTIMIZE)
        else()
            target_link_options(${PROJECT_NAME} PRIVATE /LTCG)
        endif()
    endif()
endif()

# ── Game-specific additions ─────────────────────────────────────
#
# Add any game-specific libraries, loaders, or custom renderers below.
# Examples:
#
# # Custom texture loader for your game's format
# target_sources(${PROJECT_NAME} PRIVATE src/texture_loader.c)
#
# # If your game uses RenderWare, you may need a RW bridge layer
# target_sources(${PROJECT_NAME} PRIVATE src/rw_bridge.c)
#
# # Additional compile definitions for debugging
# target_compile_definitions(${PROJECT_NAME} PRIVATE
#     ENABLE_ICALL_TRACE=1
#     VERBOSE_KERNEL_LOG=1
# )
