# SDK boilerplate lives in generated/rexglue.cmake.

cmake_minimum_required(VERSION 3.25)
project(tmnt_turtles_in_time_reshelled VERSION 0.2.0 LANGUAGES C CXX)

set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Where tools/codegen.ps1 and tools/package-linux.sh unpack the pinned SDK.
if(WIN32)
    set(TMNT_SDK_DIR "${CMAKE_CURRENT_SOURCE_DIR}/tools/rexglue/win-amd64")
else()
    set(TMNT_SDK_DIR "${CMAKE_CURRENT_SOURCE_DIR}/tools/rexglue/linux-amd64")
endif()
list(APPEND CMAKE_PREFIX_PATH "${TMNT_SDK_DIR}")

# Release binaries must not carry the build machine's folders (__FILE__ in log calls).
add_compile_options("$<$<CONFIG:Release>:-fmacro-prefix-map=${CMAKE_CURRENT_SOURCE_DIR}=.>")

include(generated/rexglue.cmake)

# TRG Launcher: the third_party/trg-launcher submodule, or a local checkout
# named by TMNT_TRG_LAUNCHER_DIR while the submodule is not checked out.
set(TMNT_TRG_LAUNCHER_DIR "$ENV{TMNT_TRG_LAUNCHER_DIR}" CACHE PATH
    "Local TRG Launcher checkout, used when third_party/trg-launcher is empty")

# Upstream TRG is immutable. Every build compiles a patched copy under the
# binary directory; the submodule checkout must stay at the pinned revision.
set(TMNT_TRG_LAUNCHER_REVISION "b384a28c712c9320eea8f9d50639dec49981b6c1")
set(TMNT_TRG_LAUNCHER_PATCH_DIR "${CMAKE_CURRENT_SOURCE_DIR}/patches/trg-launcher")
set(TMNT_TRG_LAUNCHER_PATCH_STAMP_VERSION 3)
file(GLOB _trg_patch_names CONFIGURE_DEPENDS RELATIVE "${TMNT_TRG_LAUNCHER_PATCH_DIR}"
    "${TMNT_TRG_LAUNCHER_PATCH_DIR}/*.patch")
list(SORT _trg_patch_names)
if(NOT _trg_patch_names)
    message(FATAL_ERROR "TRG Launcher: ${TMNT_TRG_LAUNCHER_PATCH_DIR} holds no patches")
endif()
# The glob dependency only tracks added or removed files; list the patches as
# configure dependencies too so editing one triggers a reconfigure.
foreach(_trg_patch_name IN LISTS _trg_patch_names)
    set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
        "${TMNT_TRG_LAUNCHER_PATCH_DIR}/${_trg_patch_name}")
endforeach()

find_package(Git QUIET)
if(NOT Git_FOUND)
    message(FATAL_ERROR "TRG Launcher: git is required to apply patches/trg-launcher")
endif()

# Copies the source into output_dir and applies the patches there. The stamp
# covers the source state and every patch, so the copy is rebuilt only when one
# of them changes. GIT_CEILING_DIRECTORIES keeps git apply from discovering the
# parent repository when the generated copy has no .git of its own.
function(tmnt_prepare_patched_trg_launcher source_dir output_dir require_pinned)
    set(_source_state "")
    if(EXISTS "${source_dir}/.git")
        execute_process(COMMAND "${GIT_EXECUTABLE}" -C "${source_dir}" rev-parse HEAD
                        OUTPUT_VARIABLE _head OUTPUT_STRIP_TRAILING_WHITESPACE
                        RESULT_VARIABLE _head_result ERROR_QUIET)
        if(NOT _head_result EQUAL 0)
            message(FATAL_ERROR "TRG Launcher: ${source_dir} is not a git checkout; run `git submodule update --init third_party/trg-launcher`")
        endif()
        execute_process(COMMAND "${GIT_EXECUTABLE}" -C "${source_dir}" status --porcelain --untracked-files=normal
                        OUTPUT_VARIABLE _status OUTPUT_STRIP_TRAILING_WHITESPACE)
        execute_process(COMMAND "${GIT_EXECUTABLE}" -C "${source_dir}" diff --binary HEAD
                        OUTPUT_VARIABLE _diff)
        string(SHA256 _source_state "${_head}\n${_status}\n${_diff}")
        if(require_pinned)
            if(NOT _head STREQUAL TMNT_TRG_LAUNCHER_REVISION)
                message(FATAL_ERROR "TRG Launcher: ${source_dir} is at ${_head}, expected the pinned ${TMNT_TRG_LAUNCHER_REVISION}. Run `git submodule update --init third_party/trg-launcher`.")
            endif()
            if(NOT _status STREQUAL "")
                message(FATAL_ERROR "TRG Launcher: ${source_dir} has local changes; run `git -C third_party/trg-launcher checkout .` and configure again.")
            endif()
        endif()
    else()
        file(GLOB_RECURSE _source_files LIST_DIRECTORIES false RELATIVE "${source_dir}" "${source_dir}/*")
        list(SORT _source_files)
        foreach(_file IN LISTS _source_files)
            if(_file MATCHES "(^|/)\\.git(/|$)")
                continue()
            endif()
            file(SHA256 "${source_dir}/${_file}" _file_hash)
            string(APPEND _source_state "${_file}\n${_file_hash}\n")
        endforeach()
        string(SHA256 _source_state "${_source_state}")
    endif()

    set(_fingerprint "${TMNT_TRG_LAUNCHER_PATCH_STAMP_VERSION}\n${source_dir}\n${_source_state}\n")
    foreach(_patch_name IN LISTS _trg_patch_names)
        file(SHA256 "${TMNT_TRG_LAUNCHER_PATCH_DIR}/${_patch_name}" _patch_hash)
        string(APPEND _fingerprint "${_patch_name}\n${_patch_hash}\n")
    endforeach()
    string(SHA256 _fingerprint "${_fingerprint}")

    set(_stamp "${output_dir}/.tmnt-trg-patch-stamp")
    if(EXISTS "${_stamp}" AND EXISTS "${output_dir}/CMakeLists.txt")
        file(READ "${_stamp}" _stamp_value)
        if(_stamp_value STREQUAL _fingerprint)
            return()
        endif()
    endif()

    message(STATUS "TRG Launcher: preparing patched copy in ${output_dir}")
    file(REMOVE_RECURSE "${output_dir}")
    file(MAKE_DIRECTORY "${output_dir}")
    file(COPY "${source_dir}/" DESTINATION "${output_dir}" PATTERN ".git" EXCLUDE)

    get_filename_component(_ceiling "${output_dir}" DIRECTORY)
    foreach(_patch_name IN LISTS _trg_patch_names)
        set(_patch "${TMNT_TRG_LAUNCHER_PATCH_DIR}/${_patch_name}")
        execute_process(COMMAND "${CMAKE_COMMAND}" -E env "GIT_CEILING_DIRECTORIES=${_ceiling}"
                        "${GIT_EXECUTABLE}" -C "${output_dir}" apply --check --ignore-whitespace "${_patch}"
                        RESULT_VARIABLE _can_apply ERROR_VARIABLE _apply_error)
        if(NOT _can_apply EQUAL 0)
            message(FATAL_ERROR "TRG Launcher: ${_patch} does not apply to ${source_dir} (${_apply_error}); the patch and the source revision are out of sync.")
        endif()
        execute_process(COMMAND "${CMAKE_COMMAND}" -E env "GIT_CEILING_DIRECTORIES=${_ceiling}"
                        "${GIT_EXECUTABLE}" -C "${output_dir}" apply --ignore-whitespace "${_patch}"
                        RESULT_VARIABLE _apply_result)
        if(NOT _apply_result EQUAL 0)
            message(FATAL_ERROR "TRG Launcher: failed to apply ${_patch}")
        endif()
    endforeach()
    file(WRITE "${_stamp}" "${_fingerprint}")
endfunction()

set(_trg_source_dir "${CMAKE_CURRENT_SOURCE_DIR}/third_party/trg-launcher")
if(EXISTS "${_trg_source_dir}/CMakeLists.txt")
    set(_trg_require_pinned TRUE)
else()
    set(_trg_source_dir "${TMNT_TRG_LAUNCHER_DIR}")
    set(_trg_require_pinned FALSE)
endif()
if(NOT EXISTS "${_trg_source_dir}/CMakeLists.txt")
    message(FATAL_ERROR "TRG Launcher not found: run `git submodule update --init third_party/trg-launcher`")
endif()

set(_trg_patched_dir "${CMAKE_BINARY_DIR}/trg-launcher-patched")
tmnt_prepare_patched_trg_launcher("${_trg_source_dir}" "${_trg_patched_dir}" ${_trg_require_pinned})

file(READ "${_trg_patched_dir}/src/launcher.cpp" _trg_launcher_source)
string(FIND "${_trg_launcher_source}" "void Launcher::HandleHotkeys() {" _hotkeys_pos)
string(SUBSTRING "${_trg_launcher_source}" ${_hotkeys_pos} -1 _hotkeys_source)
string(FIND "${_hotkeys_source}" "const bool ctrl = ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl);" _ctrl_pos)
string(FIND "${_hotkeys_source}" "if (ctrl && ImGui::IsKeyPressed(ImGuiKey_S, false)) {" _ctrl_s_pos)
string(FIND "${_hotkeys_source}" "if (io.WantTextInput) return;" _text_guard_pos)
if(_hotkeys_pos EQUAL -1 OR _ctrl_pos EQUAL -1 OR _ctrl_s_pos EQUAL -1 OR _text_guard_pos EQUAL -1 OR _ctrl_s_pos GREATER _text_guard_pos)
    message(FATAL_ERROR "TRG Launcher: the staged HandleHotkeys does not handle Ctrl+S from the left/right key state before the text-input guard (${_trg_patched_dir}/src/launcher.cpp)")
endif()
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${_trg_patched_dir}/src/launcher.cpp")

# Reconfigure when the patches, the upstream checkout or its HEAD change.
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
    "${_trg_source_dir}/CMakeLists.txt")
if(EXISTS "${_trg_source_dir}/.git")
    execute_process(COMMAND "${GIT_EXECUTABLE}" -C "${_trg_source_dir}" rev-parse --git-path HEAD
                    OUTPUT_VARIABLE _trg_head_path OUTPUT_STRIP_TRAILING_WHITESPACE)
    if(_trg_head_path)
        get_filename_component(_trg_head_path "${_trg_head_path}" ABSOLUTE BASE_DIR "${_trg_source_dir}")
        set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${_trg_head_path}")
    endif()
endif()

# The launcher draws inside the runtime's own Dear ImGui frame, so it must use
# the ImGui compiled into rexruntime (one context), never a downloaded copy.
add_library(tmnt_imgui INTERFACE)
target_link_libraries(tmnt_imgui INTERFACE rex::runtime)
set(TRG_LAUNCHER_IMGUI_TARGET tmnt_imgui CACHE STRING "" FORCE)
set(TRG_LAUNCHER_STANDALONE OFF CACHE BOOL "" FORCE)
set(TRG_LAUNCHER_BUILD_DEMO OFF CACHE BOOL "" FORCE)
set(TRG_LAUNCHER_FETCH_DEPS OFF CACHE BOOL "" FORCE)
add_subdirectory("${_trg_patched_dir}" trg-launcher)
# Written for C++17: in C++20 and later u8string() is a different type.
set_target_properties(trg_launcher PROPERTIES CXX_STANDARD 17)
# TRG defines this only for cl.exe; clang++ needs it too for the MSVC CRT.
target_compile_definitions(trg_launcher PRIVATE _CRT_SECURE_NO_WARNINGS)
get_directory_property(TMNT_TRG_VERSION DIRECTORY "${_trg_patched_dir}" DEFINITION PROJECT_VERSION)

# Online play: the online service client, identity, connection test and the gameplay socket with its relay. No game
# runtime needed; the tests build it as it is.
set(TMNT_ONLINE_SOURCES
    src/netplay/lobby_mux.cpp
    src/netplay/online_game_socket.cpp
    src/netplay/relay_frame.cpp
    src/netplay/udp_transport.cpp
    src/online/connection_test.cpp
    src/online/friend_view.cpp
    src/online/identity.cpp
    src/online/instance.cpp
    src/online/lobby_link.cpp
    src/online/local_addresses.cpp
    src/online/match_session.cpp
    src/online/net_addr.cpp
    src/online/online_service.cpp
    src/online/peer_ping.cpp
    src/online/protocol.cpp
    src/online/random.cpp
    src/online/rendezvous.cpp
    src/online/snes_features.cpp
    src/online/stun.cpp
    src/online/telemetry.cpp
    src/online/upnp.cpp
    src/online/websocket.cpp
    src/online/websocket_frame.cpp
    src/updater/json.cpp
    src/updater/sha256.cpp
    src/updater/update_paths.cpp
)
set(TMNT_ONLINE_LIBS)
if(WIN32)
    list(APPEND TMNT_ONLINE_SOURCES src/online/websocket_win.cpp)
    list(APPEND TMNT_ONLINE_LIBS winhttp ws2_32 bcrypt iphlpapi)
else()
    # libssl is loaded at run time (src/online/websocket_posix.cpp), so no OpenSSL is needed to build.
    list(APPEND TMNT_ONLINE_SOURCES src/online/websocket_posix.cpp)
    list(APPEND TMNT_ONLINE_LIBS Threads::Threads ${CMAKE_DL_LIBS})
endif()

# The SPC700 and S-DSP emulator from Game_Music_Emu 0.6.4 (LGPL 2.1 or later), unmodified: third_party/snes_spc.
add_library(tmnt_snes_spc STATIC
    third_party/snes_spc/Snes_Spc.cpp
    third_party/snes_spc/Spc_Cpu.cpp
    third_party/snes_spc/Spc_Dsp.cpp
)
target_include_directories(tmnt_snes_spc PUBLIC third_party/snes_spc)
target_compile_definitions(tmnt_snes_spc PUBLIC BLARGG_LITTLE_ENDIAN=1)
target_compile_options(tmnt_snes_spc PRIVATE -w)

set(TMNT_CLASSIC_SOUNDTRACK_CORE
    src/classic_soundtrack/activation.cpp
    src/classic_soundtrack/importer.cpp
    src/classic_soundtrack/konami_lz.cpp
    src/classic_soundtrack/manifest.cpp
    src/classic_soundtrack/music_streams.cpp
    src/classic_soundtrack/resampler.cpp
    src/classic_soundtrack/rom_zip.cpp
    src/classic_soundtrack/runtime_start.cpp
    src/classic_soundtrack/snes_rom.cpp
    src/classic_soundtrack/sound_parser.cpp
    src/classic_soundtrack/soundset.cpp
    src/classic_soundtrack/soundtrack_stream.cpp
    src/classic_soundtrack/spc_player.cpp
    src/classic_soundtrack/xma_provider.cpp
    src/snes_gameplay/gameplay_data.cpp
    src/snes_gameplay/runtime_start.cpp
)

set(TMNT_SOURCES
    src/main.cpp
    src/achievement_alias.cpp
    src/achievement_menu.cpp
    src/achievement_toast.cpp
    src/achievements_overlay.cpp
    src/active_log.cpp
    src/cheat_menu.cpp
    src/cheats.cpp
    src/classic_soundtrack_overlay.cpp
    src/classic_soundtrack_page.cpp
    src/classic_soundtrack_runtime.cpp
    src/snes_gameplay_runtime.cpp
    src/snes_menu.cpp
    src/crash_report.cpp
    src/diagnostics.cpp
    src/frame_counter.cpp
    src/frame_limiter.cpp
    src/frame_wait.cpp
    src/friend_code_field.cpp
    src/game_files.cpp
    src/graphics_adapters.cpp
    src/guest_functions.cpp
    src/imgui_gamepad.cpp
    src/incident_json.cpp
    src/incident_page.cpp
    src/incident_report.cpp
    src/invite_ui.cpp
    src/input_summary.cpp
    src/launcher.cpp
    src/launcher_achievements.cpp
    src/legacy_settings.cpp
    src/live_fix.cpp
    src/live_stats.cpp
    src/local_stats.cpp
    src/localization/font_patch.cpp
    src/localization/game_text.cpp
    src/localization/game_text_hook.cpp
    src/localization/pt_br_game_text.cpp
    src/localization/pt_br_ui_text.cpp
    src/localization/ui_text.cpp
    src/fire_menu_hook.cpp
    src/fire_movie_patch.cpp
    src/log_filter.cpp
    src/log_path.cpp
    src/log_scrubber.cpp
    src/modal_dialog.cpp
    src/my_reports.cpp
    src/notice_dialog.cpp
    src/online_bridge.cpp
    src/online_page.cpp
    src/online_runtime.cpp
    src/page_icons.cpp
    src/ping_display.cpp
    src/platform.cpp
    src/player_match.cpp
    src/player_match_hook.cpp
    src/player_match_invite_hook.cpp
    src/player_match_net.cpp
    src/player_match_net_hook.cpp
    src/port_version.cpp
    src/report_history.cpp
    src/session_log.cpp
    src/settings.cpp
    src/stfs.cpp
    src/system_info.cpp
    src/title_bgm_level.cpp
    src/update_config.cpp
    src/updater/github_release.cpp
    src/updater/json.cpp
    src/updater/sha256.cpp
    src/updater/third_party/miniz/miniz.c
    src/updater/update_controller.cpp
    src/updater/update_job.cpp
    src/updater/update_paths.cpp
    src/updater/update_platform.cpp
    src/updater/update_ui.cpp
    src/updater/update_zip.cpp
    src/updater/version.cpp
    src/user_data.cpp
    src/window_host.cpp
)
if(WIN32)
    list(APPEND TMNT_SOURCES src/clipboard_win32.cpp)
else()
    list(APPEND TMNT_SOURCES src/clipboard_sdl.cpp)
endif()
list(APPEND TMNT_SOURCES ${TMNT_CLASSIC_SOUNDTRACK_CORE})
foreach(_online_source IN LISTS TMNT_ONLINE_SOURCES)
    if(NOT _online_source IN_LIST TMNT_SOURCES)
        list(APPEND TMNT_SOURCES ${_online_source})
    endif()
endforeach()

# The sidebar's page icons (assets/launcher/icons/*.svg) as a table of byte arrays, page_icon_data.cpp.
# They are embedded, so the launcher never has to ship or find separate icon files.
file(GLOB TMNT_PAGE_ICONS CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/assets/launcher/icons/*.svg")
list(SORT TMNT_PAGE_ICONS)
set(TMNT_PAGE_ICON_LIST "${CMAKE_CURRENT_BINARY_DIR}/generated/page_icons.txt")
file(CONFIGURE OUTPUT "${TMNT_PAGE_ICON_LIST}" CONTENT "${TMNT_PAGE_ICONS}")
set(TMNT_PAGE_ICON_SOURCE "${CMAKE_CURRENT_BINARY_DIR}/generated/page_icon_data.cpp")
add_custom_command(OUTPUT "${TMNT_PAGE_ICON_SOURCE}"
    COMMAND ${CMAKE_COMMAND} -DINPUT_LIST=${TMNT_PAGE_ICON_LIST} -DOUTPUT=${TMNT_PAGE_ICON_SOURCE}
            -DSYMBOL=kPageIconFiles -P "${CMAKE_CURRENT_SOURCE_DIR}/src/embed_files.cmake"
    DEPENDS "${TMNT_PAGE_ICON_LIST}" ${TMNT_PAGE_ICONS} "${CMAKE_CURRENT_SOURCE_DIR}/src/embed_files.cmake"
    COMMENT "Embedding the launcher page icons"
    VERBATIM)
list(APPEND TMNT_SOURCES "${TMNT_PAGE_ICON_SOURCE}")

set_source_files_properties(src/updater/third_party/miniz/miniz.c PROPERTIES COMPILE_OPTIONS "-w")
# nanosvg is a single-header third-party implementation; keep its warnings out of the project's own output.
set_source_files_properties(src/page_icons.cpp PROPERTIES COMPILE_OPTIONS "-Wno-unused-function;-Wno-unused-variable")
if(WIN32)
    # Resource 1 is the first icon in the executable: Explorer, and SDL's window class, use it.
    enable_language(RC)
    set(TMNT_APP_RC "${CMAKE_CURRENT_SOURCE_DIR}/packaging/windows/tmnt_turtles_in_time_reshelled.rc")
    set_source_files_properties("${TMNT_APP_RC}" PROPERTIES
        OBJECT_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/packaging/icons/app-icon.ico")
    list(APPEND TMNT_SOURCES "${TMNT_APP_RC}")
    add_executable(tmnt_turtles_in_time_reshelled WIN32 ${TMNT_SOURCES})
    set(TMNT_APP_MANIFEST "${CMAKE_CURRENT_SOURCE_DIR}/packaging/windows/tmnt_turtles_in_time_reshelled.manifest")
    target_link_options(tmnt_turtles_in_time_reshelled PRIVATE "LINKER:/MANIFESTINPUT:${TMNT_APP_MANIFEST}")
    set_property(TARGET tmnt_turtles_in_time_reshelled APPEND PROPERTY LINK_DEPENDS "${TMNT_APP_MANIFEST}")
else()
    add_executable(tmnt_turtles_in_time_reshelled ${TMNT_SOURCES})
endif()

rexglue_setup_target(tmnt_turtles_in_time_reshelled GPU_PLUGINS xenos)
target_link_libraries(tmnt_turtles_in_time_reshelled PRIVATE trg::launcher tmnt_snes_spc ${TMNT_ONLINE_LIBS})
if(WIN32)
    target_link_libraries(tmnt_turtles_in_time_reshelled PRIVATE shcore synchronization dxgi shell32)
    # The SDK stages its DLLs only when the program links, so a rebuilt runtime or GPU plugin has to
    # make it link again; tools/build.ps1 also checks the staged copies.
    foreach(_sdk_dll rexruntime rexruntimed rexruntimerd rexgpu-xenos rexgpu-xenosd rexgpu-xenosrd)
        if(EXISTS "${TMNT_SDK_DIR}/bin/${_sdk_dll}.dll")
            set_property(TARGET tmnt_turtles_in_time_reshelled APPEND PROPERTY LINK_DEPENDS
                "${TMNT_SDK_DIR}/bin/${_sdk_dll}.dll")
        endif()
    endforeach()
else()
    # The SDK stages the runtime beside the program only on Windows and macOS; the RPATH is $ORIGIN.
    add_custom_command(TARGET tmnt_turtles_in_time_reshelled POST_BUILD
        COMMAND ${CMAKE_COMMAND} -E copy_if_different
            "$<TARGET_FILE:rex::runtime>"
            "$<TARGET_FILE_DIR:tmnt_turtles_in_time_reshelled>"
        VERBATIM
    )
    # The post-build copies (this one and the SDK's GPU plugin copy) only run when the program links, so a
    # rebuilt runtime or GPU plugin has to make it link again; tools/package-linux.sh also checks the copies.
    foreach(_sdk_lib rexruntime rexruntimed rexruntimerd rexgpu-xenos rexgpu-xenosd rexgpu-xenosrd)
        if(EXISTS "${TMNT_SDK_DIR}/lib/lib${_sdk_lib}.so")
            set_property(TARGET tmnt_turtles_in_time_reshelled APPEND PROPERTY LINK_DEPENDS
                "${TMNT_SDK_DIR}/lib/lib${_sdk_lib}.so")
        endif()
    endforeach()
endif()
# Every build checks that tools/rexglue holds the runtime and GPU plugin built from this branch's patches, and that
# the copies next to the program are those; tools/build.ps1 and tools/build.sh rebuild them when they are not.
if(WIN32)
    # Windows PowerShell has a fixed path, unlike a Store pwsh; the module path a parent pwsh sets breaks it.
    find_program(TMNT_WINDOWS_POWERSHELL NAMES powershell REQUIRED)
    set(TMNT_REXGLUE_CHECK "${CMAKE_COMMAND}" -E env --unset=PSModulePath
        "${TMNT_WINDOWS_POWERSHELL}" -NoProfile -ExecutionPolicy Bypass -File "${CMAKE_CURRENT_SOURCE_DIR}/tools/build-patched-rexruntime.ps1" -Check -Config $<CONFIG>)
    set(TMNT_REXGLUE_STAGED -Staged)
else()
    find_program(TMNT_BASH bash REQUIRED)
    set(TMNT_REXGLUE_CHECK "${TMNT_BASH}" "${CMAKE_CURRENT_SOURCE_DIR}/tools/build-patched-rexruntime.sh"
        --check "${TMNT_SDK_DIR}" --config $<CONFIG>)
    set(TMNT_REXGLUE_STAGED --staged)
endif()
get_property(_multi_config GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if(NOT _multi_config AND NOT CMAKE_BUILD_TYPE MATCHES "^(Debug|RelWithDebInfo|Release)$")
    message(FATAL_ERROR "Set CMAKE_BUILD_TYPE to Debug, RelWithDebInfo or Release (the presets and tools/build.ps1 "
        "do): the ReXGlue runtime and GPU plugin are checked per configuration")
endif()
add_custom_target(tmnt_rexglue_check COMMAND ${TMNT_REXGLUE_CHECK} VERBATIM)
add_dependencies(tmnt_turtles_in_time_reshelled tmnt_rexglue_check)
add_custom_target(tmnt_rexglue_staging ALL
    COMMAND ${CMAKE_COMMAND} -E copy_if_different "$<TARGET_FILE:rex::runtime>" "$<TARGET_FILE:rex::gpu-xenos>"
        "$<TARGET_FILE_DIR:tmnt_turtles_in_time_reshelled>"
    COMMAND ${TMNT_REXGLUE_CHECK} ${TMNT_REXGLUE_STAGED} "$<TARGET_FILE_DIR:tmnt_turtles_in_time_reshelled>"
    VERBATIM
)
add_dependencies(tmnt_rexglue_staging tmnt_turtles_in_time_reshelled)
# ThinLTO in Release and RelWithDebInfo (the default of tools/build.ps1); the runtime and GPU plugin are
# prebuilt. Debug compiles at -O0, which marks every function optnone, so LTO would optimize nothing there.
function(tmnt_enable_thinlto target)
    set(_lto "$<CONFIG:Release,RelWithDebInfo>")
    if(WIN32 AND CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "GNU")
        # lld-link runs the LTO backend at -O2 unless told otherwise; Release compiles at -O3.
        set(_link -flto=thin "$<$<CONFIG:Release>:-Wl,/opt:lldlto=3>"
            "-Wl,/lldltocache:${CMAKE_BINARY_DIR}/lto-cache")
    elseif(UNIX AND NOT APPLE AND CMAKE_CXX_COMPILER_ID MATCHES "Clang")
        set(_link -flto=thin -fuse-ld=lld "-Wl,--thinlto-cache-dir=${CMAKE_BINARY_DIR}/lto-cache")
    else()
        return()
    endif()
    target_compile_options(${target} PRIVATE "$<$<AND:${_lto},$<COMPILE_LANGUAGE:CXX>>:-flto=thin>")
    get_target_property(_type ${target} TYPE)
    if(NOT _type STREQUAL "OBJECT_LIBRARY")
        target_link_options(${target} PRIVATE "$<${_lto}:${_link}>")
    endif()
endfunction()
tmnt_enable_thinlto(tmnt_turtles_in_time_reshelled)
if(TARGET tmnt_turtles_in_time_reshelled_recomp)
    tmnt_enable_thinlto(tmnt_turtles_in_time_reshelled_recomp)
endif()
if(TARGET tmnt_turtles_in_time_reshelled_TMNT_r)
    target_sources(tmnt_turtles_in_time_reshelled_TMNT_r PRIVATE src/live_hooks_tmnt_r.cpp
        src/title_bgm_level_tmnt_r.cpp src/skip_intro_tmnt_r.cpp src/localization/subtitle_default_tmnt_r.cpp
        src/cheats_tmnt_r.cpp src/snes_gameplay_tmnt_r.cpp)
    target_include_directories(tmnt_turtles_in_time_reshelled_TMNT_r PRIVATE src)
    tmnt_enable_thinlto(tmnt_turtles_in_time_reshelled_TMNT_r)
endif()

# Release packages ship the MSYS2 CLANG64 build of these sources (tools/build-updater.ps1).
if(WIN32)
    add_executable(trg-updater WIN32
        src/updater/helper_main.cpp
        src/updater/install_transaction.cpp
        src/updater/sha256.cpp
        src/updater/update_job.cpp
        src/updater/update_paths.cpp
        src/updater/update_platform.cpp
    )
    target_include_directories(trg-updater PRIVATE src)
    target_compile_definitions(trg-updater PRIVATE TMNT_UPDATER_HELPER_BUILD=1)
    target_link_libraries(trg-updater PRIVATE shell32)
    target_link_options(trg-updater PRIVATE -static)
    set_property(TARGET trg-updater PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
    set_target_properties(trg-updater PROPERTIES OUTPUT_NAME "trg-updater")
endif()
set(TMNT_VERSION_DEFINITIONS
    TMNT_VERSION_MAJOR=${PROJECT_VERSION_MAJOR}
    TMNT_VERSION_MINOR=${PROJECT_VERSION_MINOR}
    TMNT_VERSION_PATCH=${PROJECT_VERSION_PATCH})
target_compile_definitions(tmnt_turtles_in_time_reshelled PRIVATE
    ${TMNT_VERSION_DEFINITIONS}
    TMNT_TRG_VERSION="${TMNT_TRG_VERSION}"
    TMNT_BUILD_CONFIG="$<CONFIG>")

if(WIN32)
    # packaging/windows/tmnt_turtles_in_time_reshelled.rc only declares an ICON; it needs
    # none of this target's compile definitions. RC_COMPILER (cmake_llvm_rc) hands every -D
    # to both the clang preprocessor and rc.exe, and a quoted string define's escaped quotes
    # (e.g. TMNT_BUILD_CONFIG="RelWithDebInfo") survive into the resource compile as a
    # literal backslash, breaking it. rexglue_setup_target() above also adds
    # REXGLUE_BUILD_CONFIG this way. Scope every directly-set definition to C++ so the RC
    # source gets none of them.
    get_target_property(_tmnt_app_compile_defs tmnt_turtles_in_time_reshelled COMPILE_DEFINITIONS)
    if(_tmnt_app_compile_defs)
        set_property(TARGET tmnt_turtles_in_time_reshelled PROPERTY COMPILE_DEFINITIONS "")
        target_compile_definitions(tmnt_turtles_in_time_reshelled PRIVATE
            $<$<COMPILE_LANGUAGE:CXX>:${_tmnt_app_compile_defs}>)
    endif()
    unset(_tmnt_app_compile_defs)
endif()

# The runtime reads its cvars from <exe dir>/tmnt_turtles_in_time_reshelled.toml. A POST_BUILD step only runs when the
# program relinks, so the file is a build output of its own: editing the source alone stages it again.
set(TMNT_STAGED_TOML "${CMAKE_CURRENT_BINARY_DIR}/tmnt_turtles_in_time_reshelled.toml")
add_custom_command(
    OUTPUT "${TMNT_STAGED_TOML}"
    COMMAND ${CMAKE_COMMAND} -E copy_if_different
        "${CMAKE_CURRENT_SOURCE_DIR}/config/tmnt_turtles_in_time_reshelled.toml"
        "${TMNT_STAGED_TOML}"
    DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/config/tmnt_turtles_in_time_reshelled.toml"
    VERBATIM
)
add_custom_target(tmnt_turtles_in_time_reshelled_toml ALL DEPENDS "${TMNT_STAGED_TOML}")
add_dependencies(tmnt_turtles_in_time_reshelled tmnt_turtles_in_time_reshelled_toml)

set(TMNT_STAGED_HEADER_ART "${CMAKE_CURRENT_BINARY_DIR}/assets/launcher/header-art.png")
add_custom_command(
    OUTPUT "${TMNT_STAGED_HEADER_ART}"
    COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_CURRENT_BINARY_DIR}/assets/launcher"
    COMMAND ${CMAKE_COMMAND} -E copy_if_different
        "${CMAKE_CURRENT_SOURCE_DIR}/assets/launcher/header-art.png"
        "${TMNT_STAGED_HEADER_ART}"
    DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/assets/launcher/header-art.png"
    VERBATIM
)
set(TMNT_STAGED_SPONSOR_HEART "${CMAKE_CURRENT_BINARY_DIR}/assets/launcher/sponsor-heart.png")
add_custom_command(
    OUTPUT "${TMNT_STAGED_SPONSOR_HEART}"
    COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_CURRENT_BINARY_DIR}/assets/launcher"
    COMMAND ${CMAKE_COMMAND} -E copy_if_different
        "${CMAKE_CURRENT_SOURCE_DIR}/assets/launcher/sponsor-heart.png"
        "${TMNT_STAGED_SPONSOR_HEART}"
    DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/assets/launcher/sponsor-heart.png"
    VERBATIM
)
add_custom_target(tmnt_launcher_header_art ALL DEPENDS "${TMNT_STAGED_HEADER_ART}" "${TMNT_STAGED_SPONSOR_HEART}")
add_dependencies(tmnt_turtles_in_time_reshelled tmnt_launcher_header_art)

find_package(Threads REQUIRED)

# Installer tests, not part of the default build: cmake --build <dir> --target tmnt_installer_tests
add_executable(tmnt_installer_tests EXCLUDE_FROM_ALL
    tests/installer_tests.cpp
    src/game_files.cpp
    src/stfs.cpp
)
target_include_directories(tmnt_installer_tests PRIVATE src)
target_link_libraries(tmnt_installer_tests PRIVATE Threads::Threads)


# Frame counter tests, not part of the default build: cmake --build <dir> --target tmnt_frame_counter_tests
add_executable(tmnt_frame_counter_tests EXCLUDE_FROM_ALL tests/frame_counter_tests.cpp)
target_include_directories(tmnt_frame_counter_tests PRIVATE src)
target_compile_definitions(tmnt_frame_counter_tests PRIVATE TMNT_SRC_DIR="${CMAKE_CURRENT_SOURCE_DIR}/src")

# Guest frame limiter tests, not part of the default build:
# cmake --build <dir> --target tmnt_frame_limiter_tests
add_executable(tmnt_frame_limiter_tests EXCLUDE_FROM_ALL tests/frame_limiter_tests.cpp src/frame_wait.cpp)
target_include_directories(tmnt_frame_limiter_tests PRIVATE src)
target_link_libraries(tmnt_frame_limiter_tests PRIVATE Threads::Threads)
target_compile_definitions(tmnt_frame_limiter_tests PRIVATE TMNT_SRC_DIR="${CMAKE_CURRENT_SOURCE_DIR}/src")





# GPU patch tests (register ranges, host thread affinity, scissor compare), not part of the default build:
# cmake --build <dir> --target tmnt_gpu_perf_patches_tests
add_executable(tmnt_gpu_perf_patches_tests EXCLUDE_FROM_ALL tests/gpu_perf_patches_tests.cpp)
target_include_directories(tmnt_gpu_perf_patches_tests PRIVATE src)
target_include_directories(tmnt_gpu_perf_patches_tests SYSTEM PRIVATE "${TMNT_SDK_DIR}/include")
target_compile_definitions(tmnt_gpu_perf_patches_tests PRIVATE TMNT_SRC_DIR="${CMAKE_CURRENT_SOURCE_DIR}/src")



# Keyboard remapping tests, not part of the default build: cmake --build <dir> --target tmnt_keyboard_tests
add_executable(tmnt_keyboard_tests EXCLUDE_FROM_ALL tests/keyboard_tests.cpp)
target_include_directories(tmnt_keyboard_tests PRIVATE src)
target_include_directories(tmnt_keyboard_tests SYSTEM PRIVATE "${TMNT_SDK_DIR}/include" "${_trg_patched_dir}/include")
target_compile_definitions(tmnt_keyboard_tests PRIVATE TMNT_SRC_DIR="${CMAKE_CURRENT_SOURCE_DIR}/src")


# Player names tests, not part of the default build: cmake --build <dir> --target tmnt_player_names_tests
add_executable(tmnt_player_names_tests EXCLUDE_FROM_ALL tests/player_names_tests.cpp)
target_include_directories(tmnt_player_names_tests PRIVATE src)
target_compile_definitions(tmnt_player_names_tests PRIVATE TMNT_SRC_DIR="${CMAKE_CURRENT_SOURCE_DIR}/src")

# Gameplay cheat tests, not part of the default build: cmake --build <dir> --target tmnt_cheats_tests
add_executable(tmnt_cheats_tests EXCLUDE_FROM_ALL tests/cheats_tests.cpp)
target_include_directories(tmnt_cheats_tests PRIVATE src "${_trg_patched_dir}/include")
target_link_libraries(tmnt_cheats_tests PRIVATE trg::launcher)
target_compile_definitions(tmnt_cheats_tests PRIVATE TMNT_ROOT_DIR="${CMAKE_CURRENT_SOURCE_DIR}")

# Language tests, not part of the default build: cmake --build <dir> --target tmnt_language_tests
add_executable(tmnt_language_tests EXCLUDE_FROM_ALL tests/language_tests.cpp)
target_include_directories(tmnt_language_tests PRIVATE src)
target_compile_definitions(tmnt_language_tests PRIVATE TMNT_SRC_DIR="${CMAKE_CURRENT_SOURCE_DIR}/src")

# Classic Soundtrack importer tests and developer CLI, not part of the default build:
# cmake --build <dir> --target tmnt_classic_soundtrack_tests tmnt_classic_soundtrack_playback_tests
#     tmnt_classic_soundtrack_launcher_tests tmnt_classic_soundtrack_tool
set(TMNT_CLASSIC_SOUNDTRACK_SOURCES ${TMNT_CLASSIC_SOUNDTRACK_CORE} src/updater/sha256.cpp src/updater/update_paths.cpp
    src/updater/third_party/miniz/miniz.c)
add_executable(tmnt_classic_soundtrack_tests EXCLUDE_FROM_ALL tests/classic_soundtrack_tests.cpp ${TMNT_CLASSIC_SOUNDTRACK_SOURCES})
target_include_directories(tmnt_classic_soundtrack_tests PRIVATE src)
target_link_libraries(tmnt_classic_soundtrack_tests PRIVATE tmnt_snes_spc)
add_executable(tmnt_classic_soundtrack_playback_tests EXCLUDE_FROM_ALL tests/classic_soundtrack_playback_tests.cpp src/classic_soundtrack/playback_check.cpp ${TMNT_CLASSIC_SOUNDTRACK_SOURCES})
target_include_directories(tmnt_classic_soundtrack_playback_tests PRIVATE src)
target_compile_definitions(tmnt_classic_soundtrack_playback_tests PRIVATE TMNT_ROOT_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
target_link_libraries(tmnt_classic_soundtrack_playback_tests PRIVATE tmnt_snes_spc)
target_compile_definitions(tmnt_classic_soundtrack_tests PRIVATE TMNT_ROOT_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
add_executable(tmnt_classic_soundtrack_launcher_tests EXCLUDE_FROM_ALL tests/classic_soundtrack_launcher_tests.cpp
    src/classic_soundtrack_page.cpp ${TMNT_CLASSIC_SOUNDTRACK_SOURCES})
target_include_directories(tmnt_classic_soundtrack_launcher_tests PRIVATE src)
target_compile_definitions(tmnt_classic_soundtrack_launcher_tests PRIVATE TMNT_ROOT_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
target_link_libraries(tmnt_classic_soundtrack_launcher_tests PRIVATE tmnt_snes_spc trg::launcher)
add_executable(tmnt_classic_soundtrack_tool EXCLUDE_FROM_ALL tools/classic_soundtrack_tool.cpp src/classic_soundtrack/playback_check.cpp ${TMNT_CLASSIC_SOUNDTRACK_SOURCES})
target_include_directories(tmnt_classic_soundtrack_tool PRIVATE src)
target_link_libraries(tmnt_classic_soundtrack_tool PRIVATE tmnt_snes_spc)

# SNES Gameplay tests, not part of the default build: cmake --build <dir> --target tmnt_snes_gameplay_tests
add_executable(tmnt_snes_gameplay_tests EXCLUDE_FROM_ALL tests/snes_gameplay_tests.cpp
    ${TMNT_CLASSIC_SOUNDTRACK_SOURCES})
target_include_directories(tmnt_snes_gameplay_tests PRIVATE src)
target_compile_definitions(tmnt_snes_gameplay_tests PRIVATE TMNT_ROOT_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
target_link_libraries(tmnt_snes_gameplay_tests PRIVATE tmnt_snes_spc)

# SNES menu tests, not part of the default build: cmake --build <dir> --target tmnt_snes_menu_tests
add_executable(tmnt_snes_menu_tests EXCLUDE_FROM_ALL tests/snes_menu_tests.cpp ${TMNT_CLASSIC_SOUNDTRACK_SOURCES})
target_include_directories(tmnt_snes_menu_tests PRIVATE src)
target_compile_definitions(tmnt_snes_menu_tests PRIVATE TMNT_ROOT_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
target_link_libraries(tmnt_snes_menu_tests PRIVATE tmnt_snes_spc)

# Multisampling setting tests, not part of the default build: cmake --build <dir> --target tmnt_multisampling_tests
add_executable(tmnt_multisampling_tests EXCLUDE_FROM_ALL tests/multisampling_tests.cpp)
target_include_directories(tmnt_multisampling_tests PRIVATE src)
target_compile_definitions(tmnt_multisampling_tests PRIVATE TMNT_SRC_DIR="${CMAKE_CURRENT_SOURCE_DIR}/src")

# GPU plugin startup check tests, not part of the default build: cmake --build <dir> --target tmnt_gpu_plugin_check_tests
add_executable(tmnt_gpu_plugin_check_tests EXCLUDE_FROM_ALL tests/gpu_plugin_check_tests.cpp)
target_include_directories(tmnt_gpu_plugin_check_tests PRIVATE src)

# Aspect Ratio tests, not part of the default build: cmake --build <dir> --target tmnt_aspect_ratio_tests
add_executable(tmnt_aspect_ratio_tests EXCLUDE_FROM_ALL tests/aspect_ratio_tests.cpp)
target_include_directories(tmnt_aspect_ratio_tests PRIVATE src)
target_compile_definitions(tmnt_aspect_ratio_tests PRIVATE TMNT_SRC_DIR="${CMAKE_CURRENT_SOURCE_DIR}/src")

# Removed-feature checks, not part of the default build:
# cmake --build <dir> --target tmnt_internal_render_removed_tests
add_executable(tmnt_internal_render_removed_tests EXCLUDE_FROM_ALL tests/internal_render_removed_tests.cpp)
target_include_directories(tmnt_internal_render_removed_tests PRIVATE src)
target_compile_definitions(tmnt_internal_render_removed_tests PRIVATE TMNT_ROOT_DIR="${CMAKE_CURRENT_SOURCE_DIR}")

# Lock-free guest clock tests, not part of the default build:
# cmake --build <dir> --target tmnt_guest_clock_tests
add_executable(tmnt_guest_clock_tests EXCLUDE_FROM_ALL tests/guest_clock_tests.cpp)
target_include_directories(tmnt_guest_clock_tests PRIVATE src)
target_compile_definitions(tmnt_guest_clock_tests PRIVATE TMNT_SRC_DIR="${CMAKE_CURRENT_SOURCE_DIR}/src")
target_link_libraries(tmnt_guest_clock_tests PRIVATE Threads::Threads)

# D3D12 transfer short-circuit tests, not part of the default build:
# cmake --build <dir> --target tmnt_d3d12_transfer_short_circuit_tests
add_executable(tmnt_d3d12_transfer_short_circuit_tests EXCLUDE_FROM_ALL tests/d3d12_transfer_short_circuit_tests.cpp)
target_include_directories(tmnt_d3d12_transfer_short_circuit_tests PRIVATE src)
target_compile_definitions(tmnt_d3d12_transfer_short_circuit_tests PRIVATE TMNT_SRC_DIR="${CMAKE_CURRENT_SOURCE_DIR}/src")

# D3D12 sampler memoization tests, not part of the default build:
# cmake --build <dir> --target tmnt_d3d12_sampler_memo_tests
add_executable(tmnt_d3d12_sampler_memo_tests EXCLUDE_FROM_ALL tests/d3d12_sampler_memo_tests.cpp)
target_include_directories(tmnt_d3d12_sampler_memo_tests PRIVATE src)
target_compile_definitions(tmnt_d3d12_sampler_memo_tests PRIVATE TMNT_SRC_DIR="${CMAKE_CURRENT_SOURCE_DIR}/src")

# Shared memory fast path tests, not part of the default build:
# cmake --build <dir> --target tmnt_shared_memory_fast_path_tests
add_executable(tmnt_shared_memory_fast_path_tests EXCLUDE_FROM_ALL tests/shared_memory_fast_path_tests.cpp)
target_include_directories(tmnt_shared_memory_fast_path_tests PRIVATE src)
target_compile_definitions(tmnt_shared_memory_fast_path_tests PRIVATE TMNT_SRC_DIR="${CMAKE_CURRENT_SOURCE_DIR}/src")
target_link_libraries(tmnt_shared_memory_fast_path_tests PRIVATE Threads::Threads)

# GPU draw state memoization tests, not part of the default build:
# cmake --build <dir> --target tmnt_gpu_draw_state_memo_tests
add_executable(tmnt_gpu_draw_state_memo_tests EXCLUDE_FROM_ALL tests/gpu_draw_state_memo_tests.cpp)
target_include_directories(tmnt_gpu_draw_state_memo_tests PRIVATE src)
target_compile_definitions(tmnt_gpu_draw_state_memo_tests PRIVATE TMNT_SRC_DIR="${CMAKE_CURRENT_SOURCE_DIR}/src")

# D3D12 asynchronous submission tests, not part of the default build:
# cmake --build <dir> --target tmnt_d3d12_async_submission_tests
add_executable(tmnt_d3d12_async_submission_tests EXCLUDE_FROM_ALL tests/d3d12_async_submission_tests.cpp)
target_include_directories(tmnt_d3d12_async_submission_tests PRIVATE src)
target_compile_definitions(tmnt_d3d12_async_submission_tests PRIVATE TMNT_SRC_DIR="${CMAKE_CURRENT_SOURCE_DIR}/src")
target_link_libraries(tmnt_d3d12_async_submission_tests PRIVATE Threads::Threads)

# Graphics card setting tests, not part of the default build: cmake --build <dir> --target tmnt_graphics_adapter_tests
add_executable(tmnt_graphics_adapter_tests EXCLUDE_FROM_ALL tests/graphics_adapter_tests.cpp src/graphics_adapters.cpp)
target_include_directories(tmnt_graphics_adapter_tests PRIVATE src)
target_link_libraries(tmnt_graphics_adapter_tests PRIVATE trg::launcher)
if(WIN32)
    target_link_libraries(tmnt_graphics_adapter_tests PRIVATE dxgi d3d12)
else()
    target_link_libraries(tmnt_graphics_adapter_tests PRIVATE ${CMAKE_DL_LIBS})
    target_include_directories(tmnt_graphics_adapter_tests SYSTEM PRIVATE "${TMNT_SDK_DIR}/include")
    add_library(tmnt_fake_vulkan SHARED EXCLUDE_FROM_ALL tests/fake_vulkan_driver.cpp)
    target_include_directories(tmnt_fake_vulkan SYSTEM PRIVATE "${TMNT_SDK_DIR}/include")
    add_dependencies(tmnt_graphics_adapter_tests tmnt_fake_vulkan)
    target_compile_definitions(tmnt_graphics_adapter_tests PRIVATE TMNT_FAKE_VULKAN_LIBRARY="$<TARGET_FILE:tmnt_fake_vulkan>")
endif()
target_compile_definitions(tmnt_graphics_adapter_tests PRIVATE TMNT_SRC_DIR="${CMAKE_CURRENT_SOURCE_DIR}/src")

# Linux file picker backends (portal, zenity, kdialog) of the patched TRG Launcher, not part of the default build:
# cmake --build <dir> --target tmnt_file_dialog_tests
add_executable(tmnt_file_dialog_tests EXCLUDE_FROM_ALL tests/file_dialog_tests.cpp "${_trg_patched_dir}/src/file_dialog.cpp")
target_include_directories(tmnt_file_dialog_tests PRIVATE "${_trg_patched_dir}/include")

# Local profile owner tests, not part of the default build: cmake --build <dir> --target tmnt_local_profile_owner_tests
add_executable(tmnt_local_profile_owner_tests EXCLUDE_FROM_ALL tests/local_profile_owner_tests.cpp)
target_include_directories(tmnt_local_profile_owner_tests PRIVATE src)
target_compile_definitions(tmnt_local_profile_owner_tests PRIVATE TMNT_SRC_DIR="${CMAKE_CURRENT_SOURCE_DIR}/src")

# Relaunch tests, not part of the default build: cmake --build <dir> --target tmnt_relaunch_tests
add_executable(tmnt_relaunch_tests EXCLUDE_FROM_ALL tests/relaunch_tests.cpp)
target_include_directories(tmnt_relaunch_tests PRIVATE src)
target_compile_definitions(tmnt_relaunch_tests PRIVATE TMNT_SRC_DIR="${CMAKE_CURRENT_SOURCE_DIR}/src")

# Cursor last-input and window title tests, not part of the default build:
# cmake --build <dir> --target tmnt_window_host_tests
add_executable(tmnt_window_host_tests EXCLUDE_FROM_ALL tests/window_host_tests.cpp)
target_include_directories(tmnt_window_host_tests PRIVATE src)
target_include_directories(tmnt_window_host_tests SYSTEM PRIVATE "${TMNT_SDK_DIR}/include")
target_compile_definitions(tmnt_window_host_tests PRIVATE TMNT_SRC_DIR="${CMAKE_CURRENT_SOURCE_DIR}/src")

# Log folder path tests, not part of the default build:
# cmake --build <dir> --target tmnt_log_path_tests
add_executable(tmnt_log_path_tests EXCLUDE_FROM_ALL tests/log_path_tests.cpp src/log_path.cpp)
target_include_directories(tmnt_log_path_tests PRIVATE src)

# Per-monitor DPI helper tests, not part of the default build:
# cmake --build <dir> --target tmnt_dpi_tests
add_executable(tmnt_dpi_tests EXCLUDE_FROM_ALL tests/dpi_tests.cpp)
target_include_directories(tmnt_dpi_tests PRIVATE src)

# Launcher window placement tests, not part of the default build:
# cmake --build <dir> --target tmnt_window_placement_tests
add_executable(tmnt_window_placement_tests EXCLUDE_FROM_ALL tests/window_placement_tests.cpp)
target_include_directories(tmnt_window_placement_tests PRIVATE src)

# Launcher quit behaviour tests (Escape must not quit), not part of the default build:
# cmake --build <dir> --target tmnt_launcher_quit_tests
add_executable(tmnt_launcher_quit_tests EXCLUDE_FROM_ALL tests/launcher_quit_tests.cpp)
target_include_directories(tmnt_launcher_quit_tests PRIVATE src)
target_compile_definitions(tmnt_launcher_quit_tests PRIVATE TMNT_SRC_DIR="${CMAKE_CURRENT_SOURCE_DIR}/src")

# Launcher controller reading (XInput slot scan on Windows, no SDL instance of the program's own), not part of the
# default build: cmake --build <dir> --target tmnt_launcher_gamepad_tests
add_executable(tmnt_launcher_gamepad_tests EXCLUDE_FROM_ALL tests/launcher_gamepad_tests.cpp)
target_include_directories(tmnt_launcher_gamepad_tests PRIVATE src)
target_compile_definitions(tmnt_launcher_gamepad_tests PRIVATE TMNT_SRC_DIR="${CMAKE_CURRENT_SOURCE_DIR}/src")

# cmake --build <dir> --target tmnt_launcher_assets_tests
add_executable(tmnt_launcher_assets_tests EXCLUDE_FROM_ALL tests/launcher_assets_tests.cpp
    src/page_icons.cpp "${TMNT_PAGE_ICON_SOURCE}")
target_include_directories(tmnt_launcher_assets_tests PRIVATE src)
target_compile_definitions(tmnt_launcher_assets_tests PRIVATE TMNT_SRC_DIR="${CMAKE_CURRENT_SOURCE_DIR}/src")

# Launcher icon raster size and physical-pixel snapping tests, not part of the default build:
# cmake --build <dir> --target tmnt_launcher_dpi_tests
add_executable(tmnt_launcher_dpi_tests EXCLUDE_FROM_ALL tests/launcher_dpi_tests.cpp src/page_icons.cpp
    "${TMNT_PAGE_ICON_SOURCE}")
target_include_directories(tmnt_launcher_dpi_tests PRIVATE src "${_trg_patched_dir}/include")
target_compile_options(tmnt_launcher_dpi_tests PRIVATE -Wall -Wextra)

# Embedded launcher Ctrl+S path, not part of the default build:
# cmake --build <dir> --target tmnt_launcher_hotkey_tests
add_executable(tmnt_launcher_hotkey_tests EXCLUDE_FROM_ALL tests/launcher_hotkey_tests.cpp)
target_include_directories(tmnt_launcher_hotkey_tests PRIVATE src)
target_compile_definitions(tmnt_launcher_hotkey_tests PRIVATE
    TMNT_SRC_DIR="${CMAKE_CURRENT_SOURCE_DIR}/src"
    TMNT_TRG_PATCHED_DIR="${_trg_patched_dir}")
target_compile_options(tmnt_launcher_hotkey_tests PRIVATE -Wall -Wextra)

# Window-close lifecycle policy tests, not part of the default build:
# cmake --build <dir> --target tmnt_lifecycle_tests
add_executable(tmnt_lifecycle_tests EXCLUDE_FROM_ALL tests/lifecycle_tests.cpp)
target_include_directories(tmnt_lifecycle_tests PRIVATE src)
target_compile_definitions(tmnt_lifecycle_tests PRIVATE TMNT_SRC_DIR="${CMAKE_CURRENT_SOURCE_DIR}/src")

# Port version tests, not part of the default build: cmake --build <dir> --target tmnt_port_version_tests
add_executable(tmnt_port_version_tests EXCLUDE_FROM_ALL tests/port_version_tests.cpp src/port_version.cpp)
target_include_directories(tmnt_port_version_tests PRIVATE src)
target_compile_definitions(tmnt_port_version_tests PRIVATE
    ${TMNT_VERSION_DEFINITIONS}
    TMNT_EXPECTED_PORT_VERSION="${PROJECT_VERSION}")

# Windows/Linux feature matrix, read from tools/parity-manifest.txt, not part of the default build:
# cmake --build <dir> --target tmnt_platform_parity_tests
add_executable(tmnt_platform_parity_tests EXCLUDE_FROM_ALL tests/platform_parity_tests.cpp)
target_include_directories(tmnt_platform_parity_tests PRIVATE src)
target_compile_definitions(tmnt_platform_parity_tests PRIVATE TMNT_ROOT_DIR="${CMAKE_CURRENT_SOURCE_DIR}")

# Startup diagnostic formatting tests, not part of the default build:
# cmake --build <dir> --target tmnt_diagnostics_tests
add_executable(tmnt_diagnostics_tests EXCLUDE_FROM_ALL
    tests/diagnostics_tests.cpp
    src/diagnostics.cpp
)
target_include_directories(tmnt_diagnostics_tests PRIVATE src)

# Single-log crash report tests, not part of the default build:
# cmake --build <dir> --target tmnt_crash_report_tests
add_executable(tmnt_crash_report_tests EXCLUDE_FROM_ALL
    tests/crash_report_tests.cpp
    src/crash_report.cpp
)
target_include_directories(tmnt_crash_report_tests PRIVATE src)
target_compile_definitions(tmnt_crash_report_tests PRIVATE TMNT_BUILD_CONFIG="RelWithDebInfo")
if(WIN32)
    target_link_libraries(tmnt_crash_report_tests PRIVATE shell32)
else()
    target_link_libraries(tmnt_crash_report_tests PRIVATE ${CMAKE_DL_LIBS})
endif()

# Active log file tests, not part of the default build:
# cmake --build <dir> --target tmnt_active_log_tests
add_executable(tmnt_active_log_tests EXCLUDE_FROM_ALL
    tests/active_log_tests.cpp
    src/active_log.cpp
    src/log_filter.cpp
)
target_include_directories(tmnt_active_log_tests PRIVATE src)
target_include_directories(tmnt_active_log_tests SYSTEM PRIVATE "${TMNT_SDK_DIR}/include")
target_link_libraries(tmnt_active_log_tests PRIVATE rex::runtime)
add_custom_command(TARGET tmnt_active_log_tests POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy_if_different
        "$<TARGET_FILE:rex::runtime>"
        "$<TARGET_FILE_DIR:tmnt_active_log_tests>"
    VERBATIM
)
if(NOT WIN32)
    set_target_properties(tmnt_active_log_tests PROPERTIES BUILD_RPATH "$ORIGIN")
endif()

# Detailed logging setting and Minimal log filter tests, not part of the default build:
# cmake --build <dir> --target tmnt_log_filter_tests
add_executable(tmnt_log_filter_tests EXCLUDE_FROM_ALL
    tests/log_filter_tests.cpp
    src/active_log.cpp
    src/crash_report.cpp
    src/log_filter.cpp
    src/session_log.cpp
)
target_include_directories(tmnt_log_filter_tests PRIVATE src)
target_include_directories(tmnt_log_filter_tests SYSTEM PRIVATE "${TMNT_SDK_DIR}/include")
target_compile_definitions(tmnt_log_filter_tests PRIVATE
    TMNT_SRC_DIR="${CMAKE_CURRENT_SOURCE_DIR}/src"
    TMNT_BUILD_CONFIG="RelWithDebInfo")
target_link_libraries(tmnt_log_filter_tests PRIVATE rex::runtime)
if(WIN32)
    target_link_libraries(tmnt_log_filter_tests PRIVATE shell32)
else()
    target_link_libraries(tmnt_log_filter_tests PRIVATE ${CMAKE_DL_LIBS})
endif()
add_custom_command(TARGET tmnt_log_filter_tests POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy_if_different
        "$<TARGET_FILE:rex::runtime>"
        "$<TARGET_FILE_DIR:tmnt_log_filter_tests>"
    VERBATIM
)
if(NOT WIN32)
    set_target_properties(tmnt_log_filter_tests PROPERTIES BUILD_RPATH "$ORIGIN")
endif()


# Achievements menu gate tests (B close consumes input, scroll, hold state),
# not part of the default build: cmake --build <dir> --target tmnt_achievement_menu_tests
add_executable(tmnt_achievement_menu_tests EXCLUDE_FROM_ALL tests/achievement_menu_tests.cpp)
target_include_directories(tmnt_achievement_menu_tests PRIVATE src)

# Skip intro and host key tests, not part of the default build: tools/test.ps1 also runs them standalone.
add_executable(tmnt_skip_intro_tests EXCLUDE_FROM_ALL tests/skip_intro_tests.cpp)
target_include_directories(tmnt_skip_intro_tests PRIVATE src)
target_compile_definitions(tmnt_skip_intro_tests PRIVATE TMNT_ROOT_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
add_executable(tmnt_host_key_tests EXCLUDE_FROM_ALL tests/host_key_tests.cpp)
target_include_directories(tmnt_host_key_tests PRIVATE src)
target_include_directories(tmnt_host_key_tests SYSTEM PRIVATE "${TMNT_SDK_DIR}/include" "${_trg_patched_dir}/include")
target_compile_definitions(tmnt_host_key_tests PRIVATE TMNT_ROOT_DIR="${CMAKE_CURRENT_SOURCE_DIR}")

# Detailed logging classification and saved choice, not part of the default build:
# cmake --build <dir> --target tmnt_log_policy_tests
add_executable(tmnt_log_policy_tests EXCLUDE_FROM_ALL tests/log_policy_tests.cpp)
target_include_directories(tmnt_log_policy_tests PRIVATE src)
target_link_libraries(tmnt_log_policy_tests PRIVATE trg::launcher)
target_compile_definitions(tmnt_log_policy_tests PRIVATE TMNT_ROOT_DIR="${CMAKE_CURRENT_SOURCE_DIR}")

# Title BGM level tests (the level heard before the profile loads equals the one it brings), also run by tools/test.ps1,
# not part of the default build: cmake --build <dir> --target tmnt_title_bgm_level_tests
add_executable(tmnt_title_bgm_level_tests EXCLUDE_FROM_ALL tests/title_bgm_level_tests.cpp)
target_include_directories(tmnt_title_bgm_level_tests PRIVATE src)
target_compile_definitions(tmnt_title_bgm_level_tests PRIVATE TMNT_ROOT_DIR="${CMAKE_CURRENT_SOURCE_DIR}")

# Offline leaderboard tests (local stats file, guest results buffers, enumerators),
# not part of the default build: cmake --build <dir> --target tmnt_live_tests
add_executable(tmnt_live_tests EXCLUDE_FROM_ALL
    tests/live_tests.cpp
    src/live_stats.cpp
    src/local_stats.cpp
)
target_include_directories(tmnt_live_tests PRIVATE src)
target_compile_definitions(tmnt_live_tests PRIVATE TMNT_SRC_DIR="${CMAKE_CURRENT_SOURCE_DIR}/src")
if(NOT WIN32)
    target_link_libraries(tmnt_live_tests PRIVATE Threads::Threads)
endif()

# User data tests (portable root and migration), not part of the default build:
# cmake --build <dir> --target tmnt_user_data_tests
add_executable(tmnt_user_data_tests EXCLUDE_FROM_ALL
    tests/user_data_tests.cpp
    src/user_data.cpp
    src/local_stats.cpp
)
target_include_directories(tmnt_user_data_tests PRIVATE src)
target_include_directories(tmnt_user_data_tests SYSTEM PRIVATE "${TMNT_SDK_DIR}/include" "${_trg_patched_dir}/include")

# Updater tests (versions, release JSON, SHA-256, ZIP safety, staging and rollback), not part of the default build:
# cmake --build <dir> --target tmnt_update_tests
add_executable(tmnt_update_tests EXCLUDE_FROM_ALL
    tests/update_tests.cpp
    src/updater/github_release.cpp
    src/updater/install_transaction.cpp
    src/updater/json.cpp
    src/updater/sha256.cpp
    src/updater/update_job.cpp
    src/updater/update_paths.cpp
    src/updater/update_platform.cpp
    src/updater/update_zip.cpp
    src/updater/version.cpp
    src/updater/third_party/miniz/miniz.c
)
target_include_directories(tmnt_update_tests PRIVATE src)
if(WIN32)
    target_link_libraries(tmnt_update_tests PRIVATE shell32)
endif()
target_compile_definitions(tmnt_update_tests PRIVATE TMNT_SRC_DIR="${CMAKE_CURRENT_SOURCE_DIR}/src" TMNT_UPDATER_HELPER_BUILD=1)

# The real trg-updater.exe run from a temporary folder, not part of the default build:
# cmake --build <dir> --target tmnt_update_helper_tests, then run it, optionally with the path of another trg-updater.exe
if(WIN32)
    add_executable(tmnt_update_helper_tests EXCLUDE_FROM_ALL tests/update_helper_tests.cpp)
    # The test is also the program the helper restarts, which the helper starts without a console.
    set_target_properties(tmnt_update_helper_tests PROPERTIES WIN32_EXECUTABLE ON)
    target_link_options(tmnt_update_helper_tests PRIVATE "-Wl,/entry:mainCRTStartup")
    target_link_libraries(tmnt_update_helper_tests PRIVATE shell32)
    target_compile_definitions(tmnt_update_helper_tests PRIVATE TMNT_UPDATER_EXE="$<TARGET_FILE:trg-updater>")
    add_dependencies(tmnt_update_helper_tests trg-updater)
endif()

# The tests that compile game code are built like the game.
foreach(_test installer keyboard frame_counter multisampling guest_clock d3d12_transfer_short_circuit d3d12_sampler_memo d3d12_async_submission graphics_adapter window_host window_placement launcher_quit lifecycle user_data update)
    tmnt_enable_thinlto(tmnt_${_test}_tests)
endforeach()

# Allocator stress test, not part of the default build: cmake --build <dir> --target tmnt_physical_heap_stress
add_executable(tmnt_physical_heap_stress EXCLUDE_FROM_ALL tests/physical_heap_stress.cpp)
target_link_libraries(tmnt_physical_heap_stress PRIVATE rex::runtime)
add_custom_command(TARGET tmnt_physical_heap_stress POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy_if_different
        "$<TARGET_FILE:rex::runtime>"
        "$<TARGET_FILE_DIR:tmnt_physical_heap_stress>"
    VERBATIM
)
if(NOT WIN32)
    set_target_properties(tmnt_physical_heap_stress PROPERTIES BUILD_RPATH "$ORIGIN")
endif()

# Recompiled module teardown bookkeeping test (no runtime), not part of the default build:
# cmake --build <dir> --target tmnt_module_teardown_tests
add_executable(tmnt_module_teardown_tests EXCLUDE_FROM_ALL tests/module_teardown_tests.cpp)
target_compile_definitions(tmnt_module_teardown_tests PRIVATE
    TMNT_PATCH_DIR="${CMAKE_CURRENT_SOURCE_DIR}/patches/rexglue-sdk")

# Audio client teardown test (no runtime), not part of the default build:
# cmake --build <dir> --target tmnt_audio_client_teardown_tests
add_executable(tmnt_audio_client_teardown_tests EXCLUDE_FROM_ALL tests/audio_client_teardown_tests.cpp)
target_link_libraries(tmnt_audio_client_teardown_tests PRIVATE Threads::Threads)
target_compile_definitions(tmnt_audio_client_teardown_tests PRIVATE
    TMNT_PATCH_DIR="${CMAKE_CURRENT_SOURCE_DIR}/patches/rexglue-sdk")

# Guest heap release rules test, not part of the default build: cmake --build <dir> --target tmnt_heap_release_tests
add_executable(tmnt_heap_release_tests EXCLUDE_FROM_ALL tests/heap_release_tests.cpp)
target_link_libraries(tmnt_heap_release_tests PRIVATE rex::runtime)
add_custom_command(TARGET tmnt_heap_release_tests POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy_if_different
        "$<TARGET_FILE:rex::runtime>"
        "$<TARGET_FILE_DIR:tmnt_heap_release_tests>"
    VERBATIM
)
if(NOT WIN32)
    set_target_properties(tmnt_heap_release_tests PROPERTIES BUILD_RPATH "$ORIGIN")
endif()

# Exit Game shutdown order test, not part of the default build: cmake --build <dir> --target tmnt_shutdown_order_tests
add_executable(tmnt_shutdown_order_tests EXCLUDE_FROM_ALL tests/shutdown_order_tests.cpp)
target_link_libraries(tmnt_shutdown_order_tests PRIVATE rex::runtime)
add_custom_command(TARGET tmnt_shutdown_order_tests POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy_if_different
        "$<TARGET_FILE:rex::runtime>"
        "$<TARGET_FILE_DIR:tmnt_shutdown_order_tests>"
    VERBATIM
)
if(NOT WIN32)
    set_target_properties(tmnt_shutdown_order_tests PROPERTIES BUILD_RPATH "$ORIGIN")
endif()

# Guest thread shutdown test, not part of the default build: cmake --build <dir> --target tmnt_title_thread_shutdown_tests
add_executable(tmnt_title_thread_shutdown_tests EXCLUDE_FROM_ALL tests/title_thread_shutdown_tests.cpp)
target_link_libraries(tmnt_title_thread_shutdown_tests PRIVATE rex::runtime)
add_custom_command(TARGET tmnt_title_thread_shutdown_tests POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy_if_different
        "$<TARGET_FILE:rex::runtime>"
        "$<TARGET_FILE_DIR:tmnt_title_thread_shutdown_tests>"
    VERBATIM
)
if(NOT WIN32)
    set_target_properties(tmnt_title_thread_shutdown_tests PROPERTIES BUILD_RPATH "$ORIGIN")
endif()

# RenderDoc detection test (Linux), not part of the default build: cmake --build <dir> --target tmnt_renderdoc_detect_tests
if(UNIX AND NOT APPLE)
    # The fake librenderdoc.so goes into renderdoc/, out of reach of the runtime's $ORIGIN RUNPATH.
    add_library(tmnt_fake_renderdoc SHARED EXCLUDE_FROM_ALL tests/fake_renderdoc.cpp)
    set_target_properties(tmnt_fake_renderdoc PROPERTIES OUTPUT_NAME renderdoc
        LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/renderdoc")
    add_executable(tmnt_renderdoc_detect_tests EXCLUDE_FROM_ALL tests/renderdoc_detect_tests.cpp)
    target_link_libraries(tmnt_renderdoc_detect_tests PRIVATE rex::runtime rex::renderdoc ${CMAKE_DL_LIBS})
    target_compile_options(tmnt_renderdoc_detect_tests PRIVATE -Wall -Wextra)
    add_dependencies(tmnt_renderdoc_detect_tests tmnt_fake_renderdoc)
    add_custom_command(TARGET tmnt_renderdoc_detect_tests POST_BUILD
        COMMAND ${CMAKE_COMMAND} -E copy_if_different
            "$<TARGET_FILE:rex::runtime>"
            "$<TARGET_FILE_DIR:tmnt_renderdoc_detect_tests>"
        VERBATIM
    )
    set_target_properties(tmnt_renderdoc_detect_tests PROPERTIES BUILD_RPATH "$ORIGIN")
endif()

# Online play: protocol, identity and instance isolation, the service state machine over a scripted channel, the
# connection test, per-peer ping, telemetry, wire parity with the online service, and the settings wiring. Headless; nothing opens a window. The
# integration test skips unless TMNT_LOBBY_TEST_URL names a lobby server (node server/tmnt-lobby/local/server.js).
foreach(_test protocol identity service rendezvous connection integration peer_ping telemetry match_session party_report snes)
    set(_name tmnt_online_${_test}_tests)
    add_executable(${_name} EXCLUDE_FROM_ALL tests/online_${_test}_tests.cpp ${TMNT_ONLINE_SOURCES})
    target_include_directories(${_name} PRIVATE src tests)
    target_compile_definitions(${_name} PRIVATE TMNT_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
    target_compile_options(${_name} PRIVATE -Wall -Wextra)
    target_link_libraries(${_name} PRIVATE ${TMNT_ONLINE_LIBS})
endforeach()

add_executable(tmnt_wire_parity_tests EXCLUDE_FROM_ALL tests/wire_parity_tests.cpp ${TMNT_ONLINE_SOURCES})
target_include_directories(tmnt_wire_parity_tests PRIVATE src tests)
target_compile_definitions(tmnt_wire_parity_tests PRIVATE TMNT_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
target_compile_options(tmnt_wire_parity_tests PRIVATE -Wall -Wextra)
target_link_libraries(tmnt_wire_parity_tests PRIVATE ${TMNT_ONLINE_LIBS})

if(NOT WIN32)
    add_executable(tmnt_online_websocket_tls_tests EXCLUDE_FROM_ALL tests/online_websocket_tls_tests.cpp ${TMNT_ONLINE_SOURCES})
    target_include_directories(tmnt_online_websocket_tls_tests PRIVATE src tests)
    target_compile_definitions(tmnt_online_websocket_tls_tests PRIVATE TMNT_SRC_DIR="${CMAKE_CURRENT_SOURCE_DIR}/src")
    target_compile_options(tmnt_online_websocket_tls_tests PRIVATE -Wall -Wextra)
    target_link_libraries(tmnt_online_websocket_tls_tests PRIVATE ${TMNT_ONLINE_LIBS})
endif()

add_executable(tmnt_online_setting_tests EXCLUDE_FROM_ALL tests/online_setting_tests.cpp src/legacy_settings.cpp)
target_include_directories(tmnt_online_setting_tests PRIVATE src "${TMNT_SDK_DIR}/include")
target_link_libraries(tmnt_online_setting_tests PRIVATE trg::launcher)
target_compile_definitions(tmnt_online_setting_tests PRIVATE TMNT_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")

add_executable(tmnt_netplay_online_game_socket_tests EXCLUDE_FROM_ALL tests/netplay_online_game_socket_tests.cpp
    src/netplay/lobby_mux.cpp src/netplay/online_game_socket.cpp src/netplay/relay_frame.cpp src/netplay/udp_transport.cpp)
target_include_directories(tmnt_netplay_online_game_socket_tests PRIVATE src tests)
target_compile_options(tmnt_netplay_online_game_socket_tests PRIVATE -Wall -Wextra)
if(WIN32)
    target_link_libraries(tmnt_netplay_online_game_socket_tests PRIVATE ws2_32)
endif()

add_executable(tmnt_player_match_net_tests EXCLUDE_FROM_ALL tests/player_match_net_tests.cpp src/player_match.cpp
    src/player_match_net.cpp src/netplay/lobby_mux.cpp src/netplay/udp_transport.cpp src/online/protocol.cpp
    src/updater/json.cpp)
target_include_directories(tmnt_player_match_net_tests PRIVATE src)
target_compile_definitions(tmnt_player_match_net_tests PRIVATE TMNT_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
target_compile_options(tmnt_player_match_net_tests PRIVATE -Wall -Wextra)
if(WIN32)
    target_link_libraries(tmnt_player_match_net_tests PRIVATE ws2_32)
endif()

add_executable(tmnt_fire_movie_patch_tests EXCLUDE_FROM_ALL tests/fire_movie_patch_tests.cpp)
target_include_directories(tmnt_fire_movie_patch_tests PRIVATE src)
target_compile_definitions(tmnt_fire_movie_patch_tests PRIVATE TMNT_ROOT_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
target_compile_options(tmnt_fire_movie_patch_tests PRIVATE -Wall -Wextra)

add_executable(tmnt_localization_tests EXCLUDE_FROM_ALL tests/localization_tests.cpp)
target_include_directories(tmnt_localization_tests PRIVATE src)
target_compile_definitions(tmnt_localization_tests PRIVATE TMNT_ROOT_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
target_compile_options(tmnt_localization_tests PRIVATE -Wall -Wextra)

add_executable(tmnt_incident_tests EXCLUDE_FROM_ALL tests/incident_tests.cpp
    src/incident_json.cpp src/incident_report.cpp src/log_scrubber.cpp src/report_history.cpp
    src/online/identity.cpp src/online/random.cpp src/online/websocket.cpp
    src/localization/game_text.cpp src/localization/pt_br_game_text.cpp
    src/localization/ui_text.cpp src/localization/pt_br_ui_text.cpp
    src/port_version.cpp src/updater/json.cpp src/updater/sha256.cpp src/updater/update_paths.cpp)
target_include_directories(tmnt_incident_tests PRIVATE src)
target_compile_definitions(tmnt_incident_tests PRIVATE TMNT_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}"
    ${TMNT_VERSION_DEFINITIONS})
target_compile_options(tmnt_incident_tests PRIVATE -Wall -Wextra)
if(WIN32)
    target_sources(tmnt_incident_tests PRIVATE src/online/websocket_win.cpp)
    target_link_libraries(tmnt_incident_tests PRIVATE winhttp ws2_32 bcrypt)
else()
    target_sources(tmnt_incident_tests PRIVATE src/online/websocket_posix.cpp)
    target_link_libraries(tmnt_incident_tests PRIVATE Threads::Threads ${CMAKE_DL_LIBS})
endif()

add_executable(tmnt_search_ticket_tests EXCLUDE_FROM_ALL tests/search_ticket_tests.cpp)
target_include_directories(tmnt_search_ticket_tests PRIVATE src)
target_compile_definitions(tmnt_search_ticket_tests PRIVATE TMNT_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
target_compile_options(tmnt_search_ticket_tests PRIVATE -Wall -Wextra)

add_executable(tmnt_friend_field_tests EXCLUDE_FROM_ALL tests/friend_field_tests.cpp src/friend_code_field.cpp
    src/online/protocol.cpp src/updater/json.cpp)
target_include_directories(tmnt_friend_field_tests PRIVATE src tests)
target_link_libraries(tmnt_friend_field_tests PRIVATE trg::launcher)

if(WIN32)
    add_executable(tmnt_clipboard_tests EXCLUDE_FROM_ALL tests/clipboard_tests.cpp src/clipboard_win32.cpp
        src/online/protocol.cpp src/updater/json.cpp)
    target_include_directories(tmnt_clipboard_tests PRIVATE src tests)
    target_sources(tmnt_friend_field_tests PRIVATE src/clipboard_win32.cpp)
endif()

# Every test program in one target, not part of the default build: cmake --build <dir> --target tmnt_runtime_tests.
# tools/test.ps1 -CppTests <dir> builds it and runs each program.
get_property(_tmnt_all_targets DIRECTORY PROPERTY BUILDSYSTEM_TARGETS)
add_custom_target(tmnt_runtime_tests)
foreach(_tmnt_target IN LISTS _tmnt_all_targets)
    if(_tmnt_target MATCHES "^tmnt_.+_tests$")
        add_dependencies(tmnt_runtime_tests ${_tmnt_target})
    endif()
endforeach()
