# broken_bond - ReXGlue Recompiled Project
#
# SDK boilerplate lives in generated/rexglue.cmake.

cmake_minimum_required(VERSION 3.25)
project(broken_bond LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(ANDROID)
    # The game is a shared library (libmain.so) that SDL's Java activity loads.
    set(CMAKE_POSITION_INDEPENDENT_CODE ON)
endif()

# The isolated host build has no dependency on generated guest code or the SDK.
# It is also available through tools/native-renderer/isolated as a standalone build.
option(BROKENBOND_BUILD_NATIVE_BACKEND "Build the isolated native backend validation harness" OFF)
if(BROKENBOND_BUILD_NATIVE_BACKEND)
    add_subdirectory(tools/native-renderer/isolated)
endif()

include(generated/rexglue.cmake)

# Sources
set(BROKENBOND_SOURCES
    src/main.cpp
    src/input_replay.cpp
    src/graphics_device.cpp
    # The PC features of Naruto The Broken Bond PC.
    src/pc/options_menu.cpp
    src/pc/crash_handler.cpp
    src/pc/fps_counter.cpp
    src/pc/intro_skip.cpp
    src/pc/pad_input.cpp
    src/pc/player_assignment.cpp
    src/pc/input_layout.cpp
    src/pc/unlock_all.cpp
    src/pc/weather.cpp
    src/pc/auto_jutsu.cpp
    src/pc/title_screen.cpp
    src/pc/ultrawide.cpp
    src/pc/mouse_camera.cpp
    src/pc/free_camera.cpp
    src/pc/game_speed.cpp
    src/pc/hokage_fighters.cpp
    src/pc/dlc_packages.cpp
)

# The shipping native build uses the Rise renderer adapted to Broken Bond,
# with Direct3D 12 and Vulkan APIs. The OFF configuration is the separate
# Xenos investigation host and does not build a launcher.
# See docs/renderers.md.
option(BROKENBOND_NATIVE_RENDERER "Build the native game and launcher" OFF)
option(BROKENBOND_NATIVE_D3D12 "Build the native Direct3D 12 backend" ON)
option(BROKENBOND_NATIVE_VULKAN "Build the native Vulkan backend and SPIR-V shaders" OFF)
option(BROKENBOND_NATIVE_TESTS "Build the native backend host tests" OFF)
if(BROKENBOND_NATIVE_RENDERER)
    if(NOT BROKENBOND_NATIVE_D3D12 AND NOT BROKENBOND_NATIVE_VULKAN)
        message(FATAL_ERROR "Enable at least one native backend")
    endif()
    list(APPEND BROKENBOND_SOURCES
         # The version of the port, for the window title.
         src/port_version.cpp
         # The host: configuration, guest kernel exports and the frame lifecycle.
         src/gpu/host/host.cpp src/gpu/host/kernel_video.cpp src/gpu/host/kernel_threading.cpp
         src/gpu/host/kernel_modules.cpp src/gpu/host/journal.cpp src/gpu/host/stop.cpp src/gpu/host/engines.cpp
         src/gpu/host/graphics.cpp src/gpu/host/frame_completion.cpp src/gpu/host/guest_hooks.cpp src/gpu/host/exports.cpp
         src/gpu/host/title_loading.cpp
         # The backend: the D3D12 engine, its pipeline set, its dispatcher and the title-thread witness.
         src/gpu/backend/engine.cpp src/gpu/backend/failure_channel.cpp
         src/gpu/backend/pipeline_keys.cpp src/gpu/backend/pipeline_set.cpp src/gpu/backend/dispatcher.cpp
         src/gpu/common/byte_pool.cpp
         src/gpu/backend/title_witness.cpp
         # The D3D boundary of the native renderer, one unit per responsibility.
         src/gpu/d3d/hooks.cpp src/gpu/d3d/device.cpp src/gpu/d3d/resources.cpp
         src/gpu/d3d/pass.cpp src/gpu/d3d/draw.cpp src/gpu/d3d/state.cpp
         src/gpu/d3d/present.cpp src/gpu/d3d/frame.cpp src/gpu/d3d/close.cpp src/gpu/d3d/query.cpp
         src/gpu/shaders/shaders.cpp src/gpu/shaders/package.cpp src/gpu/shaders/pipelines.cpp
         src/gpu/textures/textures.cpp src/gpu/textures/write_watch.cpp src/gpu/textures/formats.cpp
         src/gpu/textures/layout.cpp
         src/gpu/buffers/buffers.cpp src/gpu/buffers/vertex_layout.cpp
         src/gpu/diagnostics/frame_state.cpp src/gpu/diagnostics/census.cpp
         src/gpu/diagnostics/frame_trace.cpp src/gpu/diagnostics/start_evidence.cpp
         src/gpu/diagnostics/frame_capture.cpp src/gpu/diagnostics/measure.cpp
         src/gpu/diagnostics/present_proof.cpp
         src/gpu/diagnostics/crash_dump.cpp src/gpu/diagnostics/sampler.cpp
         src/gpu/diagnostics/heap_watch.cpp src/gpu/diagnostics/perf_log.cpp
         # The logic tick at a free frame rate and the transforms between the ticks (state interpolation).
         src/gpu/interpolation/tick.cpp src/gpu/interpolation/transforms.cpp
         # The graphics options, under the SDK names.
         src/gpu/settings.cpp
         # The PC overlay: recorded on the UI thread, drawn over the present.
         src/gpu/overlay.cpp
         # The shader cache embedded with #embed, generated by the build.
         src/gpu/shaders/shader_cache_data.cpp
         # Opt-in studies: the CRT's srand/rand and the world conditions
         # (REX_RAND_TRACE, both renderer modes); the audio frame cadence
         # (REX_AUDIO_TRACE, both renderer modes); the XMA stream sample accounting
         # (REX_XMA_STREAM_TRACE, both renderer modes); file reads and XMA voices
         # (REX_EXPORT_TRACE, native mode).
         src/studies/rand_trace.cpp src/studies/audio_trace.cpp src/studies/xma_stream_trace.cpp
         src/studies/export_trace.cpp
         src/studies/kernel_io_exports.cpp src/studies/kernel_xma_exports.cpp)
endif()

# Windows-only parts of the PC features and diagnostics, and their stand-ins
# on the other platforms (Android): XInput -> SDL gamepads, the minidump crash
# handler -> a signal handler, the Windows diagnostics -> inert, and the
# Win32 API subset over POSIX and SDL (src/platform).
if(NOT WIN32)
    list(REMOVE_ITEM BROKENBOND_SOURCES src/pc/pad_input.cpp src/pc/crash_handler.cpp
         src/gpu/diagnostics/crash_dump.cpp src/gpu/diagnostics/sampler.cpp src/gpu/diagnostics/heap_watch.cpp)
    list(APPEND BROKENBOND_SOURCES src/pc/pad_input_sdl.cpp src/pc/crash_handler_posix.cpp
         src/platform/win32_posix.cpp)
    if(BROKENBOND_NATIVE_RENDERER)
        list(APPEND BROKENBOND_SOURCES src/gpu/diagnostics/diagnostics_posix.cpp)
    endif()
endif()

if(WIN32)
    # The executable's icon (the launcher's), for the taskbar and Explorer.
    enable_language(RC)
    configure_file(src/broken_bond.rc.in "${CMAKE_CURRENT_BINARY_DIR}/broken_bond.rc" @ONLY)
    set_source_files_properties("${CMAKE_CURRENT_BINARY_DIR}/broken_bond.rc" PROPERTIES
        OBJECT_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/assets/icon/naruto.ico")
    set(BROKENBOND_RESOURCES "${CMAKE_CURRENT_BINARY_DIR}/broken_bond.rc")
    # The hidden title screen extra, only in local builds that have it.
    if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/assets/secret/secret.rc")
        list(APPEND BROKENBOND_RESOURCES "${CMAKE_CURRENT_SOURCE_DIR}/assets/secret/secret.rc")
    endif()
    # The Hokage portraits of the Hidden Characters & Skins menu (game art, local builds only).
    if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/assets/portraits/portraits.rc")
        list(APPEND BROKENBOND_RESOURCES "${CMAKE_CURRENT_SOURCE_DIR}/assets/portraits/portraits.rc")
    endif()
    add_executable(broken_bond WIN32 ${BROKENBOND_SOURCES} ${BROKENBOND_RESOURCES})
elseif(ANDROID)
    # The resources of the Windows .rc files (title pictures, menu font), as a
    # table of embedded files for FindResource/LoadResource (src/platform).
    include(${CMAKE_CURRENT_SOURCE_DIR}/tools/android/resources.cmake)
    brokenbond_resource_table("${CMAKE_CURRENT_BINARY_DIR}/resources_posix.cpp"
        "${CMAKE_CURRENT_SOURCE_DIR}/assets/icon/naruto.rc"
        "${CMAKE_CURRENT_SOURCE_DIR}/assets/secret/secret.rc"
        "${CMAKE_CURRENT_SOURCE_DIR}/assets/portraits/portraits.rc")
    add_library(broken_bond SHARED ${BROKENBOND_SOURCES} "${CMAKE_CURRENT_BINARY_DIR}/resources_posix.cpp")
    # SDLActivity loads libmain.so and calls its SDL_main.
    set_target_properties(broken_bond PROPERTIES OUTPUT_NAME main)
    # The app's on-screen controller (an SDL virtual gamepad, called through JNI),
    # and the choice of the Vulkan driver (the system's or a bundled Turnip).
    target_sources(broken_bond PRIVATE src/android/touch_controller.cpp src/android/gpu_driver.cpp
                   src/android/sampling_profiler.cpp)
    target_link_libraries(broken_bond PRIVATE android log)
    # libadrenotools loads a Vulkan driver shipped with the app (Turnip) instead
    # of the system's; without it the game always uses the system driver.
    set(BROKENBOND_ADRENOTOOLS_SOURCE "" CACHE PATH "libadrenotools checkout (bundled Vulkan drivers on Android)")
    if(BROKENBOND_ADRENOTOOLS_SOURCE)
        if(NOT CMAKE_ANDROID_ARCH_ABI)
            set(CMAKE_ANDROID_ARCH_ABI "${ANDROID_ABI}")
        endif()
        add_subdirectory("${BROKENBOND_ADRENOTOOLS_SOURCE}" "${CMAKE_CURRENT_BINARY_DIR}/adrenotools" EXCLUDE_FROM_ALL)
        target_link_libraries(broken_bond PRIVATE adrenotools)
        target_compile_definitions(broken_bond PRIVATE BROKENBOND_ADRENOTOOLS=1)
        # Its hooks are loaded from the app's native library folder.
        set(BROKENBOND_ADRENOTOOLS_HOOKS hook_impl main_hook file_redirect_hook gsl_alloc_hook)
        message(STATUS "Android: libadrenotools from ${BROKENBOND_ADRENOTOOLS_SOURCE} (bundled Vulkan drivers)")
    else()
        set(BROKENBOND_ADRENOTOOLS_HOOKS "")
        message(STATUS "Android: no libadrenotools; the game uses the system Vulkan driver")
    endif()
else()
    add_executable(broken_bond ${BROKENBOND_SOURCES})
endif()
if(NOT WIN32)
    # MSVC's keywords and intrinsics (_byteswap_ulong, __declspec, ...) that the
    # port's sources use without an include on Windows.
    target_compile_options(broken_bond PRIVATE
        "SHELL:-include ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/msvc_compat.h")
    target_include_directories(broken_bond PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src")
endif()
if(TARGET imgui)
    # The SDK built in the tree (REXSDK_DIR, the Android build): the imgui
    # headers come from its source tree, as the installed SDK has them in
    # include/. Only the headers: ImGui itself is in librexruntime, and a
    # second copy here would have its own context.
    target_include_directories(broken_bond PRIVATE $<TARGET_PROPERTY:imgui,INTERFACE_INCLUDE_DIRECTORIES>)
endif()

if(BROKENBOND_NATIVE_RENDERER)
    rexglue_setup_target(broken_bond)
else()
    # Retain the separate diagnostic host for renderer investigations.
    rexglue_setup_target(broken_bond GPU_PLUGINS xenos)
endif()
if(WIN32)
    target_link_libraries(broken_bond PRIVATE d3d12 dxgi d3dcompiler dbghelp psapi winmm)
    set_target_properties(broken_bond PROPERTIES OUTPUT_NAME "Naruto The Broken Bond PC")
endif()

if(BROKENBOND_NATIVE_RENDERER)
    if(NOT WIN32 AND NOT ANDROID)
        message(FATAL_ERROR "The native renderer requires Windows or Android")
    endif()
    if(ANDROID AND (BROKENBOND_NATIVE_D3D12 OR NOT BROKENBOND_NATIVE_VULKAN))
        message(FATAL_ERROR "Android builds use the Vulkan backend only: "
                            "-DBROKENBOND_NATIVE_D3D12=OFF -DBROKENBOND_NATIVE_VULKAN=ON")
    endif()
    include(${CMAKE_CURRENT_SOURCE_DIR}/tools/native-renderer/plume-engine.cmake)
    target_include_directories(broken_bond PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src")
    if(BROKENBOND_NATIVE_D3D12)
        target_sources(broken_bond PRIVATE src/gpu/backend/d3d12/backend.cpp
                       src/gpu/backend/d3d12/overlay_renderer.cpp)
        target_link_libraries(broken_bond PRIVATE native_renderer_plume)
    else()
        target_compile_definitions(broken_bond PRIVATE BROKENBOND_NATIVE_D3D12_DISABLED=1)
    endif()
    set(BROKENBOND_NATIVE_VULKAN_SOURCES src/gpu/backend/vulkan/backend.cpp
        src/gpu/backend/vulkan/completion.cpp src/gpu/backend/vulkan/shader_contract.cpp)
    set(native_shader_options)
    if(BROKENBOND_NATIVE_VULKAN)
        target_sources(broken_bond PRIVATE ${BROKENBOND_NATIVE_VULKAN_SOURCES})
        target_link_libraries(broken_bond PRIVATE native_renderer_plume_vulkan)
        target_compile_definitions(broken_bond PRIVATE BROKENBOND_NATIVE_VULKAN=1)
        set(native_shader_options SPIRV PASSES src/gpu/backend)
    endif()
    target_compile_definitions(broken_bond PRIVATE BROKENBOND_NATIVE_RENDERER_BOOTSTRAP=1)
    # The version shown by the game and the launcher when no broken_bond.version
    # file sits next to them (the packages write that file).
    set(BROKENBOND_VERSION "dev" CACHE STRING "Port version")
    target_compile_definitions(broken_bond PRIVATE BROKENBOND_VERSION="${BROKENBOND_VERSION}")
    # The shader cache generated from the local game files (game/xeshader.sdb).
    include(${CMAKE_CURRENT_SOURCE_DIR}/tools/native-renderer/shader-cache.cmake)
    brokenbond_shader_cache(broken_bond src/gpu/shaders/shader_cache_data.cpp ${native_shader_options})
    if(BROKENBOND_NATIVE_TESTS)
        if(NOT BROKENBOND_NATIVE_VULKAN)
            message(FATAL_ERROR "BROKENBOND_NATIVE_TESTS requires BROKENBOND_NATIVE_VULKAN")
        endif()
        enable_testing()
        include(tools/native-renderer/backend-tests.cmake)
    endif()
endif()

if(WIN32 AND CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
    target_link_options(broken_bond PRIVATE -Xlinker /DEBUG)
endif()

if(ANDROID)
    # cmake --build <dir> --target android_jnilibs: the app's native libraries,
    # stripped, into the Gradle project (the unstripped ones stay in the build
    # folder for crash symbolication).
    set(BROKENBOND_ANDROID_JNILIBS "${CMAKE_CURRENT_SOURCE_DIR}/android/app/src/main/jniLibs/${ANDROID_ABI}"
        CACHE PATH "jniLibs folder of the Android app for this ABI")
    set(android_libcxx "${CMAKE_SYSROOT}/usr/lib/${CMAKE_LIBRARY_ARCHITECTURE}/libc++_shared.so")
    if(NOT EXISTS "${android_libcxx}")
        file(GLOB android_libcxx "${CMAKE_SYSROOT}/usr/lib/*-linux-android*/libc++_shared.so")
        list(FILTER android_libcxx INCLUDE REGEX "aarch64")
    endif()
    set(android_jnilibs_commands COMMAND "${CMAKE_COMMAND}" -E make_directory "${BROKENBOND_ANDROID_JNILIBS}")
    set(android_jnilibs_files "$<TARGET_FILE:broken_bond>" "$<TARGET_FILE:rexruntime>" "$<TARGET_FILE:SDL3-shared>"
        "${android_libcxx}")
    set(android_jnilibs_names libmain.so librexruntime.so libSDL3.so libc++_shared.so)
    foreach(hook IN LISTS BROKENBOND_ADRENOTOOLS_HOOKS)
        list(APPEND android_jnilibs_files "$<TARGET_FILE:${hook}>")
        list(APPEND android_jnilibs_names "lib${hook}.so")
    endforeach()
    foreach(lib IN LISTS android_jnilibs_files)
        list(APPEND android_jnilibs_commands COMMAND "${CMAKE_COMMAND}" -E copy "${lib}" "${BROKENBOND_ANDROID_JNILIBS}")
    endforeach()
    foreach(name IN LISTS android_jnilibs_names)
        list(APPEND android_jnilibs_commands COMMAND "${CMAKE_STRIP}" --strip-unneeded "${BROKENBOND_ANDROID_JNILIBS}/${name}")
    endforeach()
    # The function symbols of the game and the runtime for the sampling
    # profiler of the performance log (src/android/sampling_profiler.cpp), from
    # the unstripped libraries, into the app's assets.
    set(BROKENBOND_ANDROID_SYMBOLS "${CMAKE_CURRENT_SOURCE_DIR}/android/app/src/main/assets/symbols"
        CACHE PATH "Symbol files of the Android app's native libraries")
    set(android_nm "${CMAKE_NM}")
    if(NOT android_nm)
        get_filename_component(android_toolchain_bin "${CMAKE_CXX_COMPILER}" DIRECTORY)
        find_program(android_nm NAMES llvm-nm HINTS "${android_toolchain_bin}" REQUIRED)
    endif()
    list(APPEND android_jnilibs_commands COMMAND "${CMAKE_COMMAND}" -E make_directory "${BROKENBOND_ANDROID_SYMBOLS}")
    foreach(pair "$<TARGET_FILE:broken_bond>|libmain.so" "$<TARGET_FILE:rexruntime>|librexruntime.so")
        string(REPLACE "|" ";" pair "${pair}")
        list(GET pair 0 library)
        list(GET pair 1 name)
        list(APPEND android_jnilibs_commands COMMAND "${CMAKE_COMMAND}" -DNM=${android_nm} -DLIBRARY=${library}
             -DOUTPUT=${BROKENBOND_ANDROID_SYMBOLS}/${name}.syms
             -P "${CMAKE_CURRENT_SOURCE_DIR}/tools/android/symbols.cmake")
    endforeach()
    add_custom_target(android_jnilibs ${android_jnilibs_commands}
        DEPENDS broken_bond ${BROKENBOND_ADRENOTOOLS_HOOKS}
        COMMENT "Native libraries of the Android app -> ${BROKENBOND_ANDROID_JNILIBS}"
        VERBATIM)
endif()

option(BROKENBOND_BUILD_LAUNCHER "Build the Windows launcher" ${WIN32})
if(BROKENBOND_BUILD_LAUNCHER AND BROKENBOND_NATIVE_RENDERER)
    add_subdirectory(launcher)
endif()
