cmake_minimum_required(VERSION 3.20)
cmake_policy(SET CMP0091 NEW)
# macOS and iOS: the oldest release the game is built for (ignored
# elsewhere). Without it the programs require the system they were built on.
if(CMAKE_SYSTEM_NAME STREQUAL "iOS")
    set(CMAKE_OSX_DEPLOYMENT_TARGET "16.3" CACHE STRING "Minimum iOS version")
else()
    # macOS 14: ONNX Runtime 1.30's dylib, for camera motion (#305). A build
    # directory configured before then keeps 13.3 in its cache, so raise it.
    set(CMAKE_OSX_DEPLOYMENT_TARGET "14.0" CACHE STRING "Minimum macOS version")
    if(CMAKE_OSX_DEPLOYMENT_TARGET VERSION_LESS 14.0)
        set(CMAKE_OSX_DEPLOYMENT_TARGET "14.0" CACHE STRING "Minimum macOS version" FORCE)
    endif()
endif()
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
project(FreeRidersRecompiled LANGUAGES C CXX)
# The icon resource (src/app_icon.rc): Ninja's dependency scanner for .rc
# files runs the C compiler as rc's preprocessor and fails with clang-cl; the
# resource includes nothing, so there are no dependencies to scan.
set(CMAKE_NINJA_CMCLDEPS_RC OFF)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# libc++ (the NDK's, and older LLVM's on macOS) ships std::jthread and
# std::stop_token as experimental. Newer Apple libc++ has them as standard,
# and the iOS Simulator SDK has no experimental library at all: on Apple
# platforms the flag is used only where std::jthread needs it.
set(SFR_EXPERIMENTAL_LIBRARY ${ANDROID})
if(APPLE)
    include(CheckCXXSourceCompiles)
    set(CMAKE_REQUIRED_FLAGS "-std=c++20")
    check_cxx_source_compiles("#include <thread>
int main() { std::jthread t([](std::stop_token) {}); }" SFR_STANDARD_JTHREAD)
    unset(CMAKE_REQUIRED_FLAGS)
    if(NOT SFR_STANDARD_JTHREAD)
        set(SFR_EXPERIMENTAL_LIBRARY ON)
    endif()
endif()
if(SFR_EXPERIMENTAL_LIBRARY)
    add_compile_options($<$<COMPILE_LANGUAGE:CXX>:-fexperimental-library>)
    add_link_options(-fexperimental-library)
endif()
if(APPLE)
    # The pinned fmt uses malloc without including <cstdlib>, which newer
    # libc++ no longer brings in on its own.
    add_compile_options("$<$<COMPILE_LANGUAGE:CXX>:SHELL:-include cstdlib>")
endif()
option(SFR_BUILD_DIAGNOSTIC "Build the game CPU diagnostic (requires generated code)" OFF)
# Guest memory read and written as base + address by generated code, as in
# Unleashed and Marathon Recompiled, instead of through GuestMemory's checks
# (docs/architecture-migration.md, phase 3).
option(SFR_DIRECT_MEMORY "Generated code accesses guest memory directly" ON)
if(SFR_DIRECT_MEMORY)
    add_compile_definitions(SFR_DIRECT_MEMORY=1)
endif()
# The generated code's label checkpoints call the permit from inline assembly
# where it is written, AArch64 Linux and Android (src/diagnostic_hooks.h,
# docs/performance.md). OFF builds the plain call there too, for comparison.
option(SFR_HIDDEN_CHECKPOINT "Label checkpoints hide their permit call from the compiler (AArch64 Linux/Android)" ON)
if(NOT SFR_HIDDEN_CHECKPOINT)
    add_compile_definitions(SFR_HIDDEN_CHECKPOINT=0)
endif()
# Profile-guided optimisation with Clang (docs/performance.md, data/pgo):
#   auto (default)  Android game builds use the committed profile
#                   data/pgo/android-<abi>.profdata.gz when there is one;
#                   every other build is off
#   off, none or empty: no profile
#   generate        an instrumented game that writes default_*.profraw into
#                   its working directory when it exits (on Android the app's
#                   files directory); merge them with llvm-profdata
#   a .profdata or .profdata.gz file: that profile
# Functions the profile does not match are compiled as without it; Clang
# warns per file with the number whose code changed since the profile was
# collected (-Wprofile-instr-out-of-date): collect it again then.
set(SFR_PGO "auto" CACHE STRING "Profile-guided optimisation: auto, off, generate, or a .profdata(.gz) file")
set(SFR_PGO_SOURCE "${SFR_PGO}")
string(TOLOWER "${SFR_PGO_SOURCE}" SFR_PGO_KEYWORD)
if(SFR_PGO_KEYWORD STREQUAL "auto")
    set(SFR_PGO_SOURCE "")
    if(ANDROID AND SFR_BUILD_DIAGNOSTIC)
        string(REPLACE "arm64-v8a" "arm64" SFR_PGO_ABI "${ANDROID_ABI}")
        set(SFR_PGO_DEFAULT "${CMAKE_CURRENT_SOURCE_DIR}/data/pgo/android-${SFR_PGO_ABI}.profdata.gz")
        if(EXISTS "${SFR_PGO_DEFAULT}")
            set(SFR_PGO_SOURCE "${SFR_PGO_DEFAULT}")
        endif()
    endif()
elseif(SFR_PGO_KEYWORD MATCHES "^(off|none)$")
    set(SFR_PGO_SOURCE "")
endif()
if(SFR_PGO_SOURCE)
    if(NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
        message(FATAL_ERROR "SFR_PGO needs Clang with its GNU-style driver (Android, Linux, macOS).")
    endif()
    # A static function's profile name is its file name and its own name,
    # not the full path, so a profile collected from one checkout or build
    # directory matches another.
    set(SFR_PGO_NAMES "SHELL:-mllvm -static-func-full-module-prefix=false")
    if(SFR_PGO_KEYWORD STREQUAL "generate")
        message(STATUS "SFR_PGO: instrumented build (-fprofile-generate)")
        add_compile_options(-fprofile-generate "${SFR_PGO_NAMES}")
        add_link_options(-fprofile-generate)
    else()
        get_filename_component(SFR_PGO_SOURCE "${SFR_PGO_SOURCE}" ABSOLUTE)
        if(NOT EXISTS "${SFR_PGO_SOURCE}")
            message(FATAL_ERROR "SFR_PGO: no profile at ${SFR_PGO_SOURCE}")
        endif()
        # The profile is copied (unpacked) into the build directory under a
        # name with its hash: a changed profile changes every compile command,
        # so Ninja rebuilds what it affects, and editing it reconfigures.
        set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${SFR_PGO_SOURCE}")
        file(SHA256 "${SFR_PGO_SOURCE}" SFR_PGO_HASH)
        string(SUBSTRING "${SFR_PGO_HASH}" 0 16 SFR_PGO_HASH)
        set(SFR_PGO_PROFILE "${CMAKE_CURRENT_BINARY_DIR}/pgo/profile-${SFR_PGO_HASH}.profdata")
        if(NOT EXISTS "${SFR_PGO_PROFILE}")
            file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/pgo")
            if(SFR_PGO_SOURCE MATCHES "\\.gz$")
                find_package(Python3 COMPONENTS Interpreter REQUIRED)
                execute_process(COMMAND "${Python3_EXECUTABLE}" -c
                        "import gzip, shutil, sys\nwith gzip.open(sys.argv[1]) as i, open(sys.argv[2], 'wb') as o: shutil.copyfileobj(i, o)"
                        "${SFR_PGO_SOURCE}" "${SFR_PGO_PROFILE}.part" RESULT_VARIABLE SFR_PGO_UNPACKED)
                if(NOT SFR_PGO_UNPACKED EQUAL 0)
                    message(FATAL_ERROR "SFR_PGO: could not unpack ${SFR_PGO_SOURCE}")
                endif()
                file(RENAME "${SFR_PGO_PROFILE}.part" "${SFR_PGO_PROFILE}")
            else()
                configure_file("${SFR_PGO_SOURCE}" "${SFR_PGO_PROFILE}" COPYONLY)
            endif()
        endif()
        message(STATUS "SFR_PGO: optimising with ${SFR_PGO_SOURCE}")
        # Files and functions the profile has no counts for are normal (a
        # sparse profile leaves out what never ran): only changed functions
        # are reported.
        add_compile_options("-fprofile-use=${SFR_PGO_PROFILE}" "${SFR_PGO_NAMES}"
            -Wno-profile-instr-unprofiled -Wno-profile-instr-missing)
        # Clang warns for each function whose code changed since the profile
        # was collected (hash mismatch) and compiles it without the profile;
        # scripts/pgo_compile.py counts them, and the game's link prints the
        # total (a warning, not an error).
        find_package(Python3 COMPONENTS Interpreter REQUIRED)
        foreach(language C CXX)
            set(CMAKE_${language}_COMPILER_LAUNCHER "${Python3_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/scripts/pgo_compile.py" ${CMAKE_${language}_COMPILER_LAUNCHER})
        endforeach()
    endif()
endif()
if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/tools/XenonRecomp/CMakeLists.txt")
    message(FATAL_ERROR "Run scripts/bootstrap.py to obtain the pinned XenonRecomp tools.")
endif()
add_subdirectory(tools/XenonRecomp)
# Decoding the verified XEX and installing from a disc image or folder.
add_library(sfr_installer STATIC src/xex_image.cpp src/installer.cpp)
target_include_directories(sfr_installer PUBLIC src PRIVATE tools/XenonRecomp/thirdparty/TinySHA1)
target_link_libraries(sfr_installer PRIVATE XenonUtils)
add_executable(sfr_image_dump src/image_dump.cpp)
target_link_libraries(sfr_image_dump PRIVATE sfr_installer)
include(CTest)
add_executable(sfr_shader_pack_format_test tests/shader_pack_format_test.cpp)
target_include_directories(sfr_shader_pack_format_test PRIVATE src)
add_test(NAME shader_pack_format COMMAND sfr_shader_pack_format_test)
# Host platform: Windows kernel objects and threads, or their POSIX
# counterparts (portable_waitables emulates the objects).
if(WIN32)
    set(SFR_NATIVE_THREAD src/native_thread.cpp)
    set(SFR_PORTABLE_WAITABLES)
    set(SFR_ASSET_FILES src/asset_files.cpp)
else()
    set(SFR_NATIVE_THREAD src/native_thread_posix.cpp src/portable_waitables.cpp)
    set(SFR_PORTABLE_WAITABLES src/portable_waitables.cpp)
    set(SFR_ASSET_FILES src/asset_files_posix.cpp)
    # Window, input and sound on Linux and Android.
    if(ANDROID)
        # Android has no system SDL2: SDL 2.32.8 from source (tools/SDL),
        # shared, because its Java activity loads libSDL2.so, then libmain.so.
        if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/tools/SDL/CMakeLists.txt")
            message(FATAL_ERROR "Run scripts/bootstrap.py to obtain the pinned SDL.")
        endif()
        set(SDL_SHARED ON CACHE BOOL "" FORCE)
        set(SDL_STATIC OFF CACHE BOOL "" FORCE)
        set(SDL_TEST OFF CACHE BOOL "" FORCE)
        set(SDL2_DISABLE_SDL2MAIN ON CACHE BOOL "" FORCE)
        add_subdirectory(tools/SDL EXCLUDE_FROM_ALL)
        set(SDL2_VERSION 2.32.8)
    elseif(APPLE)
        # macOS: the same pinned SDL, linked in, so FreeRidersRecompiled.app
        # needs no Homebrew and runs on the macOS set above.
        if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/tools/SDL/CMakeLists.txt")
            message(FATAL_ERROR "Run scripts/bootstrap.py to obtain the pinned SDL.")
        endif()
        enable_language(OBJC)  # SDL's Cocoa code; a language enabled only inside its directory fails to generate
        if(IOS)
            # iOS: the launcher and the game share one process (the game is
            # a library the launcher loads, as on Android), so one shared SDL;
            # SDL2main is UIKit's entry, which calls the launcher's SDL_main.
            enable_language(OBJCXX)
            set(SDL_SHARED ON CACHE BOOL "" FORCE)
            set(SDL_STATIC OFF CACHE BOOL "" FORCE)
            set(SDL2_DISABLE_SDL2MAIN OFF CACHE BOOL "" FORCE)
            add_compile_definitions(SFR_IOS=1)
        else()
            set(SDL_SHARED OFF CACHE BOOL "" FORCE)
            set(SDL_STATIC ON CACHE BOOL "" FORCE)
            set(SDL2_DISABLE_SDL2MAIN ON CACHE BOOL "" FORCE)
        endif()
        set(SDL_TEST OFF CACHE BOOL "" FORCE)
        add_subdirectory(tools/SDL EXCLUDE_FROM_ALL)
        if(NOT TARGET SDL2::SDL2)
            add_library(SDL2::SDL2 ALIAS SDL2-static)
        endif()
        set(SDL2_VERSION 2.32.8)
    else()
        find_package(SDL2 REQUIRED)
    endif()
endif()
add_library(sfr_guest_execution STATIC src/guest_execution.cpp)
add_library(sfr_wait_trace STATIC src/wait_trace.cpp)
target_include_directories(sfr_wait_trace PUBLIC src)
add_executable(sfr_wait_trace_test tests/wait_trace_test.cpp)
target_link_libraries(sfr_wait_trace_test PRIVATE sfr_wait_trace)
add_test(NAME wait_trace COMMAND sfr_wait_trace_test)
add_executable(sfr_host_core_plan_test tests/host_core_plan_test.cpp)
target_include_directories(sfr_host_core_plan_test PRIVATE src)
add_test(NAME host_core_plan COMMAND sfr_host_core_plan_test)
add_executable(sfr_host_thread_stats_test tests/host_thread_stats_test.cpp)
target_include_directories(sfr_host_thread_stats_test PRIVATE src)
if(WIN32)  # its Windows clocks (host_timing.cpp)
    target_link_libraries(sfr_host_thread_stats_test PRIVATE sfr_host_timing)
endif()
add_test(NAME host_thread_stats COMMAND sfr_host_thread_stats_test)
add_library(sfr_native_timer_resolution STATIC src/native_timer_resolution.cpp)
target_include_directories(sfr_native_timer_resolution PUBLIC src)
if(WIN32)
    target_link_libraries(sfr_native_timer_resolution PRIVATE winmm)
endif()
add_executable(sfr_native_timer_resolution_test tests/native_timer_resolution_test.cpp)
target_link_libraries(sfr_native_timer_resolution_test PRIVATE sfr_native_timer_resolution)
add_test(NAME native_timer_resolution COMMAND sfr_native_timer_resolution_test)
find_package(Threads REQUIRED)
target_link_libraries(sfr_guest_execution PUBLIC Threads::Threads)
target_link_libraries(sfr_host_core_plan_test PRIVATE Threads::Threads)  # placement restore test (Linux)
target_include_directories(sfr_guest_execution PUBLIC src)
add_executable(sfr_guest_execution_test tests/guest_execution_test.cpp)
target_link_libraries(sfr_guest_execution_test PRIVATE sfr_guest_execution)
add_test(NAME guest_execution COMMAND sfr_guest_execution_test)
add_executable(sfr_pipeline_cache_file_test tests/pipeline_cache_file_test.cpp src/pipeline_cache_file.cpp)
target_include_directories(sfr_pipeline_cache_file_test PRIVATE src)
add_test(NAME pipeline_cache_file COMMAND sfr_pipeline_cache_file_test)
set_tests_properties(guest_execution PROPERTIES TIMEOUT 15)
add_executable(sfr_job_lifetime_test tests/job_lifetime_test.cpp)
target_link_libraries(sfr_job_lifetime_test PRIVATE sfr_guest_execution)
add_test(NAME job_lifetime COMMAND sfr_job_lifetime_test)
set_tests_properties(job_lifetime PROPERTIES TIMEOUT 15)
add_executable(sfr_stream_reader_gate_test tests/stream_reader_gate_test.cpp)
target_include_directories(sfr_stream_reader_gate_test PRIVATE src)
target_link_libraries(sfr_stream_reader_gate_test PRIVATE Threads::Threads)
add_test(NAME stream_reader_gate COMMAND sfr_stream_reader_gate_test)
set_tests_properties(stream_reader_gate PROPERTIES TIMEOUT 15)
add_executable(sfr_memory_test tests/guest_memory_test.cpp src/guest_memory.cpp)
target_include_directories(sfr_memory_test PRIVATE src)
add_test(NAME guest_memory COMMAND sfr_memory_test)
add_executable(sfr_guest_entry_state_test tests/guest_entry_state_test.cpp src/guest_memory.cpp)
target_link_libraries(sfr_guest_entry_state_test PRIVATE sfr_guest_execution)
target_include_directories(sfr_guest_entry_state_test PRIVATE src)
add_test(NAME guest_entry_state COMMAND sfr_guest_entry_state_test)
add_executable(sfr_guest_registers_test tests/guest_registers_test.cpp src/guest_memory.cpp)
target_include_directories(sfr_guest_registers_test PRIVATE src)
add_test(NAME guest_registers COMMAND sfr_guest_registers_test)
add_executable(sfr_guest_function_table_test tests/guest_function_table_test.cpp)
target_include_directories(sfr_guest_function_table_test PRIVATE src)
add_test(NAME guest_function_table COMMAND sfr_guest_function_table_test)
add_executable(sfr_race_frame_clock_test tests/race_frame_clock_test.cpp)
target_include_directories(sfr_race_frame_clock_test PRIVATE src)
add_test(NAME race_frame_clock COMMAND sfr_race_frame_clock_test)
add_executable(sfr_race_boarding_test tests/race_boarding_test.cpp)
target_include_directories(sfr_race_boarding_test PRIVATE src)
add_test(NAME race_boarding COMMAND sfr_race_boarding_test)
add_executable(sfr_race_scripted_motion_test tests/race_scripted_motion_test.cpp)
target_include_directories(sfr_race_scripted_motion_test PRIVATE src)
add_test(NAME race_scripted_motion COMMAND sfr_race_scripted_motion_test)
add_executable(sfr_race_launch_test tests/race_launch_test.cpp)
target_include_directories(sfr_race_launch_test PRIVATE src)
add_test(NAME race_launch COMMAND sfr_race_launch_test)
add_executable(sfr_race_orientation_hooks_test tests/race_orientation_hooks_test.cpp src/guest_memory.cpp)
target_include_directories(sfr_race_orientation_hooks_test PRIVATE tests/race_hook_stub src)
add_test(NAME race_orientation_hooks COMMAND sfr_race_orientation_hooks_test)
add_executable(sfr_pending_guest_write_test tests/pending_guest_write_test.cpp src/guest_memory.cpp)
target_include_directories(sfr_pending_guest_write_test PRIVATE src)
add_test(NAME pending_guest_write COMMAND sfr_pending_guest_write_test)
add_executable(sfr_debug_monitor_test tests/debug_monitor_test.cpp src/debug_monitor.cpp src/guest_memory.cpp)
target_include_directories(sfr_debug_monitor_test PRIVATE src)
add_test(NAME debug_monitor COMMAND sfr_debug_monitor_test)
add_executable(sfr_nui_device_status_test tests/nui_device_status_test.cpp src/nui_device_status.cpp src/guest_memory.cpp)
target_include_directories(sfr_nui_device_status_test PRIVATE src)
add_test(NAME nui_device_status COMMAND sfr_nui_device_status_test)
add_executable(sfr_nui_skeleton_test tests/nui_skeleton_test.cpp src/nui_skeleton.cpp src/nui_device_status.cpp src/guest_memory.cpp)
target_include_directories(sfr_nui_skeleton_test PRIVATE src)
add_test(NAME nui_skeleton COMMAND sfr_nui_skeleton_test)
add_executable(sfr_kinect_sensor_test tests/kinect_sensor_test.cpp src/kinect_sensor.cpp src/kinect_sensor_none.cpp src/nui_skeleton.cpp src/guest_memory.cpp
    src/kinect_preview.cpp)
target_include_directories(sfr_kinect_sensor_test PRIVATE src)
add_test(NAME kinect_sensor COMMAND sfr_kinect_sensor_test)
add_executable(sfr_motion_input_test tests/motion_input_test.cpp src/motion_input.cpp src/kinect_sensor.cpp src/kinect_sensor_none.cpp
    src/nui_skeleton.cpp src/guest_memory.cpp src/kinect_preview.cpp)
target_include_directories(sfr_motion_input_test PRIVATE src)
add_test(NAME motion_input COMMAND sfr_motion_input_test)
add_executable(sfr_kinect_smoothing_test tests/kinect_smoothing_test.cpp src/kinect_smoothing.cpp src/nui_transform_smooth.cpp)
target_include_directories(sfr_kinect_smoothing_test PRIVATE src)
add_test(NAME kinect_smoothing COMMAND sfr_kinect_smoothing_test)
# The title's NuiTransformSmooth (827701A0) done natively, worked by hand.
add_executable(sfr_nui_transform_smooth_test tests/nui_transform_smooth_test.cpp src/nui_transform_smooth.cpp
    src/nui_transform_smooth_guest.cpp src/guest_memory.cpp)
target_include_directories(sfr_nui_transform_smooth_test PRIVATE src)
add_test(NAME nui_transform_smooth COMMAND sfr_nui_transform_smooth_test)
if(WIN32)
    # The same against the Kinect for Windows SDK 1.8's own NuiTransformSmooth
    # (Kinect10.dll, loaded when there; no sensor is started). Skipped (77)
    # without the SDK, or when it will not smooth without a sensor.
    add_executable(sfr_nui_transform_smooth_sdk_test tests/nui_transform_smooth_sdk_test.cpp src/nui_transform_smooth.cpp)
    target_include_directories(sfr_nui_transform_smooth_sdk_test PRIVATE src)
    add_test(NAME nui_transform_smooth_sdk COMMAND sfr_nui_transform_smooth_sdk_test)
    set_tests_properties(nui_transform_smooth_sdk PROPERTIES SKIP_RETURN_CODE 77)
endif()
# A Kinect v2's camera images and skeleton points in the preview's v1 sizes.
add_executable(sfr_kinect_v2_image_test tests/kinect_v2_image_test.cpp src/kinect_v2_image.cpp)
target_include_directories(sfr_kinect_v2_image_test PRIVATE src)
add_test(NAME kinect_v2_image COMMAND sfr_kinect_v2_image_test)
# Where the Kinect preview window puts its two picture panels.
add_executable(sfr_kinect_preview_layout_test tests/kinect_preview_layout_test.cpp src/kinect_preview_layout.cpp src/kinect_v2_image.cpp)
target_include_directories(sfr_kinect_preview_layout_test PRIVATE src)
add_test(NAME kinect_preview_layout COMMAND sfr_kinect_preview_layout_test)
# A Kinect v2's depth made into a Kinect v1's for the game.
# Its raw recording, and the depth made again from it offline through the same
# view state and synthesis as live (sfr_kinect_depth_resynth).
set(SFR_KINECT_V2_RESYNTH src/kinect_v2_depth.cpp src/kinect_ideal_diagram.cpp src/kinect_v2_image.cpp src/kinect_v2_resynth.cpp src/kinect_record_raw.cpp
    src/kinect_record.cpp)
add_executable(sfr_kinect_v2_depth_test tests/kinect_v2_depth_test.cpp ${SFR_KINECT_V2_RESYNTH})
target_include_directories(sfr_kinect_v2_depth_test PRIVATE src)
add_test(NAME kinect_v2_depth COMMAND sfr_kinect_v2_depth_test)
# The Recompiled view's top-down diagram (the preview window and the launcher).
add_executable(sfr_kinect_ideal_diagram_test tests/kinect_ideal_diagram_test.cpp src/kinect_ideal_diagram.cpp)
target_include_directories(sfr_kinect_ideal_diagram_test PRIVATE src)
add_test(NAME kinect_ideal_diagram COMMAND sfr_kinect_ideal_diagram_test)
# Recording a real Kinect (v1 or v2) and the race's detector answers to CSV,
# and the alone-or-both steadiness summary (docs/kinect-sensor.md).
add_executable(sfr_kinect_record_test tests/kinect_record_test.cpp src/kinect_record.cpp src/kinect_race_record.cpp src/kinect_nui_record.cpp)
target_include_directories(sfr_kinect_record_test PRIVATE src)
add_test(NAME kinect_record COMMAND sfr_kinect_record_test)
add_executable(sfr_kinect_record_summary tests/kinect_record_summary.cpp src/kinect_record.cpp)
target_include_directories(sfr_kinect_record_summary PRIVATE src)
# The depth images given to the game, recorded (SFR_KINECT_RECORD_DEPTH), the
# title's depth view features computed again from them, and a real Kinect
# v1's compared with a Kinect v2's (docs/kinect-sensor.md).
set(SFR_KINECT_DEPTH_TOOLS src/kinect_record_depth.cpp src/kinect_depth_features.cpp src/kinect_depth_compare.cpp
    src/kinect_record.cpp src/kinect_race_record.cpp)
# The skeleton and player index made from a Kinect v1's cameras where the
# Kinect runtime is missing (macOS, #305).
add_executable(sfr_kinect_depth_body_test tests/kinect_depth_body_test.cpp src/kinect_depth_body.cpp src/kinect_preview.cpp)
target_include_directories(sfr_kinect_depth_body_test PRIVATE src)
add_test(NAME kinect_depth_body COMMAND sfr_kinect_depth_body_test)
# Its colour pictures beside them (SFR_KINECT_RECORD_COLOUR), for the Kinect
# on hosts without its runtime (#305).
add_executable(sfr_kinect_colour_record_test tests/kinect_colour_record_test.cpp src/kinect_record_colour.cpp
    src/kinect_record.cpp src/kinect_race_record.cpp)
target_include_directories(sfr_kinect_colour_record_test PRIVATE src)
add_test(NAME kinect_colour_record COMMAND sfr_kinect_colour_record_test)
add_executable(sfr_kinect_depth_test tests/kinect_depth_test.cpp ${SFR_KINECT_DEPTH_TOOLS})
target_include_directories(sfr_kinect_depth_test PRIVATE src)
add_test(NAME kinect_depth COMMAND sfr_kinect_depth_test)
add_executable(sfr_kinect_depth_features tests/kinect_depth_features.cpp ${SFR_KINECT_DEPTH_TOOLS})
target_include_directories(sfr_kinect_depth_features PRIVATE src)
add_executable(sfr_kinect_depth_compare tests/kinect_depth_compare.cpp ${SFR_KINECT_DEPTH_TOOLS})
target_include_directories(sfr_kinect_depth_compare PRIVATE src)
add_executable(sfr_kinect_depth_resynth tests/kinect_depth_resynth.cpp ${SFR_KINECT_V2_RESYNTH} src/kinect_record_depth.cpp)
target_include_directories(sfr_kinect_depth_resynth PRIVATE src)
add_executable(sfr_nui_menu_players_test tests/nui_menu_players_test.cpp src/nui_skeleton.cpp src/guest_memory.cpp)
target_include_directories(sfr_nui_menu_players_test PRIVATE src)
add_test(NAME nui_menu_players COMMAND sfr_nui_menu_players_test)
add_executable(sfr_camera_input_test tests/camera_input_test.cpp)
target_include_directories(sfr_camera_input_test PRIVATE src)
add_test(NAME camera_input COMMAND sfr_camera_input_test)
add_executable(sfr_camera_race_motion_test tests/camera_race_motion_test.cpp)
target_include_directories(sfr_camera_race_motion_test PRIVATE src)
add_test(NAME camera_race_motion COMMAND sfr_camera_race_motion_test)
add_executable(sfr_motion_trace_test tests/motion_trace_test.cpp)
target_include_directories(sfr_motion_trace_test PRIVATE src)
add_test(NAME motion_trace COMMAND sfr_motion_trace_test)
add_executable(sfr_nui_menu_progress_test tests/nui_menu_progress_test.cpp)
target_include_directories(sfr_nui_menu_progress_test PRIVATE src)
add_test(NAME nui_menu_progress COMMAND sfr_nui_menu_progress_test)
add_executable(sfr_nui_speech_test tests/nui_speech_test.cpp src/nui_speech.cpp src/guest_memory.cpp)
target_include_directories(sfr_nui_speech_test PRIVATE src)
add_test(NAME nui_speech COMMAND sfr_nui_speech_test)
add_executable(sfr_nui_race_test tests/nui_race_test.cpp src/nui_race.cpp src/guest_memory.cpp)
target_include_directories(sfr_nui_race_test PRIVATE src)
add_test(NAME nui_race COMMAND sfr_nui_race_test)
add_test(NAME nui_race_legacy_scale COMMAND sfr_nui_race_test 1)
set_tests_properties(nui_race_legacy_scale PROPERTIES ENVIRONMENT "SFR_RACE_LEAN_SCALE=1")
add_test(NAME nui_race_min_scale COMMAND sfr_nui_race_test 0.1)
set_tests_properties(nui_race_min_scale PROPERTIES ENVIRONMENT "SFR_RACE_LEAN_SCALE=0")
add_test(NAME nui_race_max_scale COMMAND sfr_nui_race_test 3.5)
set_tests_properties(nui_race_max_scale PROPERTIES ENVIRONMENT "SFR_RACE_LEAN_SCALE=99")
add_executable(sfr_nui_race_players_test tests/nui_race_players_test.cpp src/nui_race.cpp src/guest_memory.cpp
    src/kinect_record.cpp src/kinect_race_record.cpp src/kinect_nui_record.cpp)
target_include_directories(sfr_nui_race_players_test PRIVATE tests/race_hook_stub src)
add_test(NAME nui_race_players COMMAND sfr_nui_race_players_test)
add_executable(sfr_nui_race_hooks_test tests/nui_race_hooks_test.cpp src/nui_race.cpp src/guest_memory.cpp
    src/kinect_record.cpp src/kinect_race_record.cpp src/kinect_nui_record.cpp)
target_include_directories(sfr_nui_race_hooks_test PRIVATE tests/race_hook_stub src)
add_test(NAME nui_race_hooks COMMAND sfr_nui_race_hooks_test)
add_executable(sfr_shader_inputs_test tests/shader_inputs_test.cpp src/shader_inputs.cpp)
target_include_directories(sfr_shader_inputs_test PRIVATE src)
add_test(NAME shader_inputs COMMAND sfr_shader_inputs_test)
add_executable(sfr_loop_constants_test tests/loop_constants_test.cpp src/loop_constants.cpp)
target_include_directories(sfr_loop_constants_test PRIVATE src)
add_test(NAME loop_constants COMMAND sfr_loop_constants_test)
add_executable(sfr_vertex_palette_test tests/vertex_palette_test.cpp src/vertex_palette.cpp)
target_include_directories(sfr_vertex_palette_test PRIVATE src)
add_test(NAME vertex_palette COMMAND sfr_vertex_palette_test)
add_executable(sfr_shader_constant_use_test tests/shader_constant_use_test.cpp src/shader_constant_use.cpp)
target_include_directories(sfr_shader_constant_use_test PRIVATE src)
add_test(NAME shader_constant_use COMMAND sfr_shader_constant_use_test)
add_executable(sfr_unselected_users_test tests/unselected_users_test.cpp src/unselected_users.cpp src/guest_memory.cpp)
target_include_directories(sfr_unselected_users_test PRIVATE src)
add_test(NAME unselected_users COMMAND sfr_unselected_users_test)
add_executable(sfr_content_files_test tests/content_files_test.cpp src/content_files.cpp src/guest_memory.cpp)
target_include_directories(sfr_content_files_test PRIVATE src)
add_test(NAME content_files COMMAND sfr_content_files_test)
add_executable(sfr_achievements_test tests/achievements_test.cpp src/achievements.cpp)
target_include_directories(sfr_achievements_test PRIVATE src)
target_link_libraries(sfr_achievements_test PRIVATE Threads::Threads)
add_test(NAME achievements COMMAND sfr_achievements_test)
# The achievement notification and page (Issue #288): progress, timing,
# page order and the software drawing they use.
add_executable(sfr_achievement_ui_test tests/achievement_ui_test.cpp src/achievement_overlay.cpp src/achievement_progress.cpp
    src/achievement_toast.cpp src/overlay_canvas.cpp src/screen_overlay.cpp src/achievements.cpp src/image_decode.cpp)
target_include_directories(sfr_achievement_ui_test PRIVATE src)
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/tools/imgui/imstb_truetype.h")
    target_include_directories(sfr_achievement_ui_test PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/tools/imgui")
endif()
target_link_libraries(sfr_achievement_ui_test PRIVATE Threads::Threads)
add_test(NAME achievement_ui COMMAND sfr_achievement_ui_test)
add_executable(sfr_multibyte_unicode_test tests/multibyte_unicode_test.cpp src/multibyte_unicode.cpp src/guest_memory.cpp)
target_include_directories(sfr_multibyte_unicode_test PRIVATE src)
add_test(NAME multibyte_unicode COMMAND sfr_multibyte_unicode_test)
add_executable(sfr_render_state_entries_test tests/render_state_entries_test.cpp)
target_include_directories(sfr_render_state_entries_test PRIVATE src)
add_test(NAME render_state_entries COMMAND sfr_render_state_entries_test)
add_executable(sfr_video_globals_test tests/video_globals_test.cpp src/video_globals.cpp src/guest_memory.cpp)
target_include_directories(sfr_video_globals_test PRIVATE src)
add_test(NAME video_globals COMMAND sfr_video_globals_test)
add_executable(sfr_counted_branch_test tests/counted_branch_test.cpp)
target_include_directories(sfr_counted_branch_test PRIVATE src)
add_test(NAME counted_branch COMMAND sfr_counted_branch_test)
add_executable(sfr_image_protection_test tests/image_protection_test.cpp src/image_protection.cpp src/guest_memory.cpp)
target_include_directories(sfr_image_protection_test PRIVATE src)
add_test(NAME image_protection COMMAND sfr_image_protection_test)
add_executable(sfr_xex_module_test tests/xex_module_test.cpp src/xex_module.cpp src/guest_memory.cpp)
target_include_directories(sfr_xex_module_test PRIVATE src)
add_test(NAME xex_module COMMAND sfr_xex_module_test)
add_executable(sfr_virtual_memory_test tests/virtual_memory_test.cpp src/virtual_memory.cpp src/guest_memory.cpp)
target_include_directories(sfr_virtual_memory_test PRIVATE src)
add_test(NAME virtual_memory COMMAND sfr_virtual_memory_test)
add_executable(sfr_critical_section_test tests/critical_section_test.cpp src/critical_section.cpp src/guest_memory.cpp)
target_include_directories(sfr_critical_section_test PRIVATE src)
add_test(NAME critical_section COMMAND sfr_critical_section_test)
add_executable(sfr_hardware_info_test tests/hardware_info_test.cpp src/hardware_info.cpp src/guest_memory.cpp)
target_include_directories(sfr_hardware_info_test PRIVATE src)
add_test(NAME hardware_info COMMAND sfr_hardware_info_test)
add_executable(sfr_thread_local_storage_test tests/thread_local_storage_test.cpp src/thread_local_storage.cpp src/guest_memory.cpp)
target_include_directories(sfr_thread_local_storage_test PRIVATE src)
add_test(NAME thread_local_storage COMMAND sfr_thread_local_storage_test)
add_executable(sfr_system_time_test tests/system_time_test.cpp src/system_time.cpp src/guest_memory.cpp)
target_include_directories(sfr_system_time_test PRIVATE src)
add_test(NAME system_time COMMAND sfr_system_time_test)
add_executable(sfr_system_config_test tests/system_config_test.cpp src/system_config.cpp src/guest_memory.cpp)
target_include_directories(sfr_system_config_test PRIVATE src)
add_test(NAME system_config COMMAND sfr_system_config_test)
add_executable(sfr_native_language_test tests/native_language_test.cpp src/native_language.cpp src/guest_memory.cpp)
target_include_directories(sfr_native_language_test PRIVATE src)
add_test(NAME native_language COMMAND sfr_native_language_test)
add_executable(sfr_native_country_test tests/native_country_test.cpp src/native_country.cpp src/guest_memory.cpp)
target_include_directories(sfr_native_country_test PRIVATE src)
add_test(NAME native_country COMMAND sfr_native_country_test)
add_executable(sfr_system_country_test tests/system_country_test.cpp src/system_config.cpp src/guest_memory.cpp)
target_include_directories(sfr_system_country_test PRIVATE src)
add_test(NAME system_country COMMAND sfr_system_country_test)
add_executable(sfr_guest_critical_sections_test tests/guest_critical_sections_test.cpp src/guest_critical_sections.cpp src/critical_section.cpp src/guest_memory.cpp)
target_include_directories(sfr_guest_critical_sections_test PRIVATE src)
target_link_libraries(sfr_guest_critical_sections_test PRIVATE sfr_guest_execution)
add_test(NAME guest_critical_sections COMMAND sfr_guest_critical_sections_test)
set_tests_properties(guest_critical_sections PROPERTIES TIMEOUT 20)
add_executable(sfr_voice_language_test tests/voice_language_test.cpp)
target_include_directories(sfr_voice_language_test PRIVATE src)
add_test(NAME voice_language COMMAND sfr_voice_language_test)
add_executable(sfr_game_region_test tests/game_region_test.cpp src/game_region.cpp src/guest_memory.cpp)
target_include_directories(sfr_game_region_test PRIVATE src)
add_test(NAME game_region COMMAND sfr_game_region_test)
add_executable(sfr_guest_clock_test tests/guest_clock_test.cpp src/guest_clock.cpp src/guest_memory.cpp)
target_include_directories(sfr_guest_clock_test PRIVATE src)
add_test(NAME guest_clock COMMAND sfr_guest_clock_test)
add_executable(sfr_timestamp_bundle_test tests/timestamp_bundle_test.cpp src/timestamp_bundle.cpp src/guest_memory.cpp)
target_include_directories(sfr_timestamp_bundle_test PRIVATE src)
add_test(NAME timestamp_bundle COMMAND sfr_timestamp_bundle_test)
add_executable(sfr_vector_memory_test tests/vector_memory_test.cpp src/vector_memory.cpp src/guest_memory.cpp)
target_include_directories(sfr_vector_memory_test PRIVATE src)
add_test(NAME vector_memory COMMAND sfr_vector_memory_test)
add_executable(sfr_optional_import_policy_test tests/optional_import_policy_test.cpp src/optional_import_policy.cpp)
target_include_directories(sfr_optional_import_policy_test PRIVATE src)
add_test(NAME optional_import_policy COMMAND sfr_optional_import_policy_test)
add_executable(sfr_native_modules_test tests/native_modules_test.cpp src/native_modules.cpp src/guest_memory.cpp)
target_include_directories(sfr_native_modules_test PRIVATE src)
add_test(NAME native_modules COMMAND sfr_native_modules_test)
add_executable(sfr_integer_arithmetic_test tests/integer_arithmetic_test.cpp src/integer_arithmetic.cpp src/guest_memory.cpp)
target_include_directories(sfr_integer_arithmetic_test PRIVATE src)
add_test(NAME integer_arithmetic COMMAND sfr_integer_arithmetic_test)
add_executable(sfr_store_halfword_update_test tests/store_halfword_update_test.cpp src/store_halfword_update.cpp src/guest_memory.cpp)
target_include_directories(sfr_store_halfword_update_test PRIVATE src)
add_test(NAME store_halfword_update COMMAND sfr_store_halfword_update_test)
add_executable(sfr_store_float_single_update_test tests/store_float_single_update_test.cpp src/store_float_single_update.cpp src/guest_memory.cpp)
target_include_directories(sfr_store_float_single_update_test PRIVATE src)
add_test(NAME store_float_single_update COMMAND sfr_store_float_single_update_test)
add_executable(sfr_load_halfword_update_test tests/load_halfword_update_test.cpp src/load_halfword_update.cpp src/guest_memory.cpp)
target_include_directories(sfr_load_halfword_update_test PRIVATE src)
add_test(NAME load_halfword_update COMMAND sfr_load_halfword_update_test)
add_executable(sfr_memory_update_forms_test tests/memory_update_forms_test.cpp src/memory_update_forms.cpp src/guest_memory.cpp)
target_include_directories(sfr_memory_update_forms_test PRIVATE src)
add_test(NAME memory_update_forms COMMAND sfr_memory_update_forms_test)
add_executable(sfr_touch_controls_test tests/touch_controls_test.cpp src/touch_controls.cpp)
target_include_directories(sfr_touch_controls_test PRIVATE src)
add_test(NAME touch_controls COMMAND sfr_touch_controls_test)
add_executable(sfr_touch_labels_test tests/touch_labels_test.cpp src/touch_labels.cpp src/touch_controls.cpp src/fps_readout.cpp)
target_include_directories(sfr_touch_labels_test PRIVATE src)
add_test(NAME touch_labels COMMAND sfr_touch_labels_test "${CMAKE_CURRENT_SOURCE_DIR}/src/shaders/touch_labels.hlsli")
# The webcam standing in for the Kinect sensor: Media Foundation on Windows,
# V4L2 on Linux, the NDK's Camera2 on Android (the phone's own cameras),
# AVFoundation on macOS and iOS, nothing elsewhere (the setting then has no
# effect).
set(SFR_CAMERA_LIBRARIES)
if(WIN32)
    set(SFR_CAMERA_CAPTURE src/camera_capture.cpp src/camera_capture_win32.cpp)
elseif(ANDROID)
    set(SFR_CAMERA_CAPTURE src/camera_capture.cpp src/camera_capture_android.cpp)
    set(SFR_CAMERA_LIBRARIES SDL2::SDL2 camera2ndk mediandk)
elseif(APPLE)
    enable_language(OBJCXX)
    set(SFR_CAMERA_CAPTURE src/camera_capture.cpp src/camera_capture_apple.mm)
    set(SFR_CAMERA_LIBRARIES "-framework AVFoundation" "-framework CoreMedia" "-framework CoreVideo"
        "-framework CoreFoundation" "-framework Foundation")
    set_source_files_properties(src/camera_capture_apple.mm PROPERTIES COMPILE_OPTIONS -fobjc-arc)
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
    set(SFR_CAMERA_CAPTURE src/camera_capture.cpp src/camera_capture_v4l2.cpp)
else()
    set(SFR_CAMERA_CAPTURE src/camera_capture.cpp src/camera_capture_none.cpp)
endif()
if(WIN32)
    set(SFR_KINECT_SENSOR src/kinect_sensor_win32.cpp src/kinect_v2_win32.cpp src/kinect_v2_image.cpp src/kinect_v2_depth.cpp src/kinect_ideal_diagram.cpp src/kinect_smoothing.cpp
        src/nui_transform_smooth.cpp src/kinect_record.cpp src/kinect_race_record.cpp src/kinect_nui_record.cpp src/kinect_record_depth.cpp src/kinect_record_colour.cpp src/kinect_record_raw.cpp
        src/kinect_v2_resynth.cpp)
    set(SFR_VOICE src/voice_commands.cpp src/voice_commands_win32.cpp)
else()
    set(SFR_KINECT_SENSOR src/kinect_sensor_none.cpp src/kinect_ideal_diagram.cpp src/nui_transform_smooth.cpp src/kinect_record.cpp src/kinect_race_record.cpp src/kinect_nui_record.cpp src/kinect_record_depth.cpp src/kinect_record_colour.cpp
        src/kinect_record_raw.cpp)
    set(SFR_VOICE src/voice_commands.cpp src/voice_commands_none.cpp)
endif()
# A Kinect v1 on macOS, where the Kinect for Windows runtime does not exist
# (#305): libfreenect over libusb (tools/, scripts/bootstrap.py), both
# compiled here. libfreenect's firmware fetcher and its build are not used:
# the cameras need no firmware. fakenect is the same interface playing a
# recording back (FAKENECT_PATH), for testing without a sensor.
# A Kinect v2 the same way through libfreenect2: its depth decoded on the
# CPU, its colour through VideoToolbox, no OpenGL, OpenCL or libjpeg; its own
# CMake is not used either (config.h and export.h are written here).
set(SFR_FREENECT_AVAILABLE OFF)
if(APPLE AND NOT IOS AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/tools/libfreenect/src/core.c"
   AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/tools/libfreenect2/src/libfreenect2.cpp"
   AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/tools/libusb/libusb/core.c")
    set(SFR_FREENECT_AVAILABLE ON)
    set(SFR_LIBUSB "${CMAKE_CURRENT_SOURCE_DIR}/tools/libusb/libusb")
    add_library(sfr_libusb STATIC ${SFR_LIBUSB}/core.c ${SFR_LIBUSB}/descriptor.c ${SFR_LIBUSB}/hotplug.c
        ${SFR_LIBUSB}/io.c ${SFR_LIBUSB}/strerror.c ${SFR_LIBUSB}/sync.c ${SFR_LIBUSB}/os/darwin_usb.c
        ${SFR_LIBUSB}/os/events_posix.c ${SFR_LIBUSB}/os/threads_posix.c)
    target_include_directories(sfr_libusb PUBLIC ${SFR_LIBUSB} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/tools/libusb/Xcode")
    target_link_libraries(sfr_libusb PUBLIC "-framework IOKit" "-framework CoreFoundation" "-framework Security")
    set_target_properties(sfr_libusb PROPERTIES C_STANDARD 11 COMPILE_OPTIONS -w)
    set(SFR_FREENECT "${CMAKE_CURRENT_SOURCE_DIR}/tools/libfreenect")
    add_library(sfr_freenect STATIC ${SFR_FREENECT}/src/core.c ${SFR_FREENECT}/src/tilt.c ${SFR_FREENECT}/src/cameras.c
        ${SFR_FREENECT}/src/flags.c ${SFR_FREENECT}/src/usb_libusb10.c ${SFR_FREENECT}/src/registration.c
        ${SFR_FREENECT}/src/audio.c ${SFR_FREENECT}/src/loader.c)
    target_include_directories(sfr_freenect PUBLIC ${SFR_FREENECT}/include PRIVATE ${SFR_FREENECT}/src)
    set_target_properties(sfr_freenect PROPERTIES COMPILE_OPTIONS -w)
    set(SFR_FREENECT2 "${CMAKE_CURRENT_SOURCE_DIR}/tools/libfreenect2")
    set(SFR_FREENECT2_GENERATED "${CMAKE_CURRENT_BINARY_DIR}/libfreenect2")
    file(WRITE "${SFR_FREENECT2_GENERATED}/libfreenect2/export.h.new"
        "#pragma once\n#define LIBFREENECT2_EXPORT\n#define LIBFREENECT2_NO_EXPORT\n")
    file(READ "${SFR_FREENECT2}/include/libfreenect2/config.h.in" SFR_FREENECT2_CONFIG)
    string(REPLACE "@PROJECT_VER@" "0.2.1" SFR_FREENECT2_CONFIG "${SFR_FREENECT2_CONFIG}")
    string(REPLACE "@PROJECT_VER_MAJOR@" "0" SFR_FREENECT2_CONFIG "${SFR_FREENECT2_CONFIG}")
    string(REPLACE "@PROJECT_VER_MINOR@" "2" SFR_FREENECT2_CONFIG "${SFR_FREENECT2_CONFIG}")
    string(REPLACE "@TegraJPEG_LIBRARIES@" "" SFR_FREENECT2_CONFIG "${SFR_FREENECT2_CONFIG}")
    foreach(feature VT_SUPPORT THREADING_STDLIB CXX11_SUPPORT)
        string(REPLACE "#cmakedefine LIBFREENECT2_WITH_${feature}" "#define LIBFREENECT2_WITH_${feature}"
            SFR_FREENECT2_CONFIG "${SFR_FREENECT2_CONFIG}")
        string(REPLACE "#cmakedefine LIBFREENECT2_${feature}" "#define LIBFREENECT2_${feature}"
            SFR_FREENECT2_CONFIG "${SFR_FREENECT2_CONFIG}")
    endforeach()
    string(REGEX REPLACE "#cmakedefine [A-Z0-9_]+" "" SFR_FREENECT2_CONFIG "${SFR_FREENECT2_CONFIG}")
    file(WRITE "${SFR_FREENECT2_GENERATED}/libfreenect2/config.h.new" "${SFR_FREENECT2_CONFIG}")
    foreach(header export.h config.h)
        file(COPY_FILE "${SFR_FREENECT2_GENERATED}/libfreenect2/${header}.new"
            "${SFR_FREENECT2_GENERATED}/libfreenect2/${header}" ONLY_IF_DIFFERENT)
    endforeach()
    set(SFR_FREENECT2_SOURCES transfer_pool event_loop usb_control allocator frame_listener_impl packet_pipeline
        rgb_packet_stream_parser rgb_packet_processor depth_packet_stream_parser depth_packet_processor
        cpu_depth_packet_processor resource command_transaction registration logging libfreenect2
        vt_rgb_packet_processor)
    list(TRANSFORM SFR_FREENECT2_SOURCES PREPEND "${SFR_FREENECT2}/src/")
    list(TRANSFORM SFR_FREENECT2_SOURCES APPEND ".cpp")
    add_library(sfr_freenect2 STATIC ${SFR_FREENECT2_SOURCES})
    target_include_directories(sfr_freenect2 PUBLIC ${SFR_FREENECT2}/include ${SFR_FREENECT2_GENERATED}
        PRIVATE ${SFR_FREENECT2}/include/internal)
    target_link_libraries(sfr_freenect2 PUBLIC sfr_libusb "-framework VideoToolbox" "-framework CoreMedia"
        "-framework CoreVideo")
    set_target_properties(sfr_freenect2 PROPERTIES CXX_STANDARD 14 COMPILE_OPTIONS -w)
    # Both kinds come as one: what links the sensor links both.
    target_link_libraries(sfr_freenect PUBLIC sfr_libusb sfr_freenect2)
    add_library(sfr_fakenect STATIC ${SFR_FREENECT}/fakenect/fakenect.c ${SFR_FREENECT}/fakenect/parson.c
        ${SFR_FREENECT}/src/registration.c)
    # libusb's header only: libfreenect's internal header names its types.
    target_include_directories(sfr_fakenect PUBLIC ${SFR_FREENECT}/include PRIVATE ${SFR_FREENECT}/src ${SFR_LIBUSB})
    set_target_properties(sfr_fakenect PROPERTIES COMPILE_OPTIONS -w)
    # Checking a Mac's Kinect, or a recording, without the game.
    add_executable(sfr_freenect_probe tests/freenect_probe.cpp)
    target_link_libraries(sfr_freenect_probe PRIVATE sfr_freenect)
    add_executable(sfr_fakenect_probe tests/freenect_probe.cpp)
    target_link_libraries(sfr_fakenect_probe PRIVATE sfr_fakenect)
    add_executable(sfr_freenect2_probe tests/freenect2_probe.cpp)
    target_link_libraries(sfr_freenect2_probe PRIVATE sfr_freenect2)
    # The Kinect v1 itself, in place of no sensor: what the runtime would
    # make of its cameras is made from them (kinect_depth_body.h), with the
    # camera's pose model (linked below, once it is known whether it is here).
    list(REMOVE_ITEM SFR_KINECT_SENSOR src/kinect_sensor_none.cpp)
    # The v2 then goes to the game as on Windows: through the virtual v1.
    list(APPEND SFR_KINECT_SENSOR src/kinect_sensor_freenect.cpp src/kinect_sensor_freenect2.cpp src/kinect_depth_body.cpp
        src/kinect_v2_depth.cpp src/kinect_v2_image.cpp)
endif()
# The Kinect's voice commands, through the host's speech recognizer.
add_executable(sfr_voice_commands_test tests/voice_commands_test.cpp src/voice_commands.cpp src/voice_commands_none.cpp)
target_include_directories(sfr_voice_commands_test PRIVATE src)
add_test(NAME voice_commands COMMAND sfr_voice_commands_test)
# Checking a host's Kinect without starting the game (no test: it needs one).
add_executable(sfr_kinect_probe tests/kinect_probe.cpp src/kinect_sensor.cpp ${SFR_KINECT_SENSOR})
target_include_directories(sfr_kinect_probe PRIVATE src)
if(WIN32)  # the launcher's camera preview on its own (no test: it needs a camera)
    add_executable(sfr_camera_preview_probe tests/camera_preview_probe.cpp src/camera_preview_window.cpp ${SFR_CAMERA_CAPTURE})
    target_include_directories(sfr_camera_preview_probe PRIVATE src)
endif()
add_executable(sfr_camera_capture_test tests/camera_capture_test.cpp src/camera_capture.cpp)
target_include_directories(sfr_camera_capture_test PRIVATE src)
add_test(NAME camera_capture COMMAND sfr_camera_capture_test)
# Exercise the Android backend on the host with a controllable NDK boundary.
if(NOT ANDROID)
    add_executable(sfr_camera_android_lifecycle_test tests/camera_android_lifecycle_test.cpp
        src/camera_capture.cpp src/camera_capture_android.cpp)
    target_include_directories(sfr_camera_android_lifecycle_test PRIVATE src tests/android_camera_mock)
    target_link_libraries(sfr_camera_android_lifecycle_test PRIVATE Threads::Threads)
    add_test(NAME camera_android_lifecycle COMMAND sfr_camera_android_lifecycle_test)
    set_tests_properties(camera_android_lifecycle PROPERTIES TIMEOUT 15)
endif()
# Checking a host's camera without starting the game (no test: it needs one).
add_executable(sfr_camera_probe tests/camera_probe.cpp ${SFR_CAMERA_CAPTURE})
target_include_directories(sfr_camera_probe PRIVATE src)
target_link_libraries(sfr_camera_probe PRIVATE ${SFR_CAMERA_LIBRARIES})
function(sfr_copy_onnx_dlls target)
    if(TARGET sfr_onnx_dlls)
        add_dependencies(${target} sfr_onnx_dlls)
    endif()
endfunction()
# Finding a body in a camera picture: ONNX Runtime and the pinned MediaPipe
# models (tools/onnx, fetched by scripts/fetch_pose_model.py). Without them
# the camera can still give the title a picture, but not drive the player.
# Android takes the runtime fetched for each ABI (--android), macOS and iOS
# theirs (--apple): iOS links it statically, as its pod is built to be.
if(ANDROID)
    set(SFR_ONNX_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/tools/onnx/onnxruntime-android")
    set(SFR_ONNX_LIBRARY "${SFR_ONNX_ROOT}/lib/${ANDROID_ABI}/libonnxruntime.so")
elseif(IOS)
    set(SFR_ONNX_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/tools/onnx/onnxruntime-ios")
    if(CMAKE_OSX_SYSROOT MATCHES "[Ss]imulator")
        set(SFR_ONNX_LIBRARY "${SFR_ONNX_ROOT}/lib/simulator/libonnxruntime.a")
    else()
        set(SFR_ONNX_LIBRARY "${SFR_ONNX_ROOT}/lib/libonnxruntime.a")
    endif()
elseif(APPLE)
    # A cache entry of its own: a build directory configured before #305
    # keeps the desktop SFR_ONNX_ROOT, which on a Mac points at nothing.
    set(SFR_MACOS_ONNX_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/tools/onnx/onnxruntime-macos" CACHE PATH "macOS ONNX Runtime installation")
    set(SFR_ONNX_ROOT "${SFR_MACOS_ONNX_ROOT}")
    # Its install name, @rpath/libonnxruntime.1.dylib.
    set(SFR_ONNX_LIBRARY "${SFR_ONNX_ROOT}/lib/libonnxruntime.1.dylib")
else()
    set(SFR_ONNX_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/tools/onnx/onnxruntime" CACHE PATH "Host ONNX Runtime installation")
    if(WIN32)
        set(SFR_ONNX_LIBRARY "${SFR_ONNX_ROOT}/lib/onnxruntime.lib")
    else()
        set(SFR_ONNX_LIBRARY "${SFR_ONNX_ROOT}/lib/libonnxruntime.so")
    endif()
endif()
if(EXISTS "${SFR_ONNX_ROOT}/include/onnxruntime_cxx_api.h" AND EXISTS "${SFR_ONNX_LIBRARY}")
    set(SFR_POSE_AVAILABLE ON)
    add_library(sfr_onnxruntime INTERFACE)
    target_include_directories(sfr_onnxruntime INTERFACE "${SFR_ONNX_ROOT}/include")
    target_link_libraries(sfr_onnxruntime INTERFACE "${SFR_ONNX_LIBRARY}")
    if(IOS)
        # What the static library needs of the system (its podspec's list).
        target_link_libraries(sfr_onnxruntime INTERFACE "-framework CoreFoundation" "-framework Foundation"
            "-framework Network" "-framework Security" "-framework SystemConfiguration" "-framework UIKit"
            "-weak_framework CoreML" c++)
    elseif(APPLE)
        # The app keeps the dylib in Contents/Frameworks, beside MoltenVK.
        set(CMAKE_BUILD_RPATH "@executable_path/../Frameworks")
    elseif(NOT WIN32 AND NOT ANDROID)
        # A release keeps libonnxruntime.so.1 beside the program. Through the
        # RPATH property, not a -rpath link option: Ninja read that one's
        # $ORIGIN as a variable and left "$" (#305). Targets made after this
        # get it, the game and the pose probe among them.
        set(CMAKE_BUILD_RPATH "$ORIGIN")
    endif()
    # Windows finds a DLL beside the program before System32, which has an
    # older onnxruntime.dll of its own (Windows ML's): without the fetched
    # one beside it, the program loads that one and fails to start.
    if(WIN32)
        file(GLOB SFR_ONNX_DLLS "${SFR_ONNX_ROOT}/lib/*.dll")
        set(SFR_ONNX_COPIES "")
        foreach(dll ${SFR_ONNX_DLLS})
            get_filename_component(name "${dll}" NAME)
            list(APPEND SFR_ONNX_COPIES "${CMAKE_BINARY_DIR}/${name}")
            add_custom_command(OUTPUT "${CMAKE_BINARY_DIR}/${name}"
                COMMAND ${CMAKE_COMMAND} -E copy_if_different "${dll}" "${CMAKE_BINARY_DIR}/${name}" DEPENDS "${dll}")
        endforeach()
        if(SFR_ONNX_COPIES)
            add_custom_target(sfr_onnx_dlls DEPENDS ${SFR_ONNX_COPIES})
        endif()
    endif()
    set(SFR_POSE_SOURCES src/pose_estimator.cpp src/pose_estimator_onnx.cpp src/pose_mediapipe.cpp src/pose_mediapipe_onnx.cpp)
else()
    set(SFR_POSE_AVAILABLE OFF)
    set(SFR_POSE_SOURCES src/pose_estimator.cpp src/pose_estimator_none.cpp)
endif()
# What links the libfreenect sensor needs the pose model too (the game has it
# already).
function(sfr_link_freenect_sensor target)
    if(SFR_FREENECT_AVAILABLE)
        target_sources(${target} PRIVATE ${SFR_POSE_SOURCES})
        target_link_libraries(${target} PRIVATE sfr_freenect)
        if(SFR_POSE_AVAILABLE)
            target_link_libraries(${target} PRIVATE sfr_onnxruntime)
        endif()
    endif()
endfunction()
sfr_link_freenect_sensor(sfr_kinect_probe)
add_executable(sfr_pose_estimator_test tests/pose_estimator_test.cpp src/pose_estimator.cpp)
target_include_directories(sfr_pose_estimator_test PRIVATE src)
add_test(NAME pose_estimator COMMAND sfr_pose_estimator_test)
add_executable(sfr_pose_skeleton_test tests/pose_skeleton_test.cpp src/pose_skeleton.cpp)
target_include_directories(sfr_pose_skeleton_test PRIVATE src)
add_test(NAME pose_skeleton COMMAND sfr_pose_skeleton_test)
# Steadying the points the model reads afresh from every picture.
add_executable(sfr_pose_smoothing_test tests/pose_smoothing_test.cpp src/pose_smoothing.cpp)
target_include_directories(sfr_pose_smoothing_test PRIVATE src)
add_test(NAME pose_smoothing COMMAND sfr_pose_smoothing_test)
add_executable(sfr_pose_stability_test tests/pose_stability_test.cpp src/pose_stability.cpp)
target_include_directories(sfr_pose_stability_test PRIVATE src)
add_test(NAME pose_stability COMMAND sfr_pose_stability_test)
add_executable(sfr_kinect_stability_test tests/kinect_stability_test.cpp src/kinect_stability.cpp src/pose_stability.cpp)
target_include_directories(sfr_kinect_stability_test PRIVATE src)
add_test(NAME kinect_stability COMMAND sfr_kinect_stability_test)
add_executable(sfr_camera_tuner_test tests/camera_tuner_test.cpp src/camera_tuner.cpp)
target_include_directories(sfr_camera_tuner_test PRIVATE src)
add_test(NAME camera_tuner COMMAND sfr_camera_tuner_test)
add_executable(sfr_pause_gesture_test tests/pause_gesture_test.cpp src/pause_gesture.cpp)
target_include_directories(sfr_pause_gesture_test PRIVATE src)
add_test(NAME pause_gesture COMMAND sfr_pause_gesture_test)
if(SFR_POSE_AVAILABLE)
    # Running the model over a camera picture, or a BMP, without the game.
    add_executable(sfr_pose_probe tests/pose_probe.cpp src/pose_skeleton.cpp ${SFR_POSE_SOURCES} ${SFR_CAMERA_CAPTURE})
    target_include_directories(sfr_pose_probe PRIVATE src)
    target_link_libraries(sfr_pose_probe PRIVATE sfr_onnxruntime ${SFR_CAMERA_LIBRARIES})
    sfr_copy_onnx_dlls(sfr_pose_probe)
    # A Kinect recording made on Windows played through what a Mac makes of
    # the cameras, against the runtime's skeleton and player index.
    add_executable(sfr_kinect_body_replay tests/kinect_body_replay.cpp src/kinect_depth_body.cpp src/kinect_record.cpp
        src/kinect_record_depth.cpp src/kinect_record_colour.cpp src/kinect_v2_image.cpp ${SFR_POSE_SOURCES})
    target_include_directories(sfr_kinect_body_replay PRIVATE src)
    target_link_libraries(sfr_kinect_body_replay PRIVATE sfr_onnxruntime)
    if(APPLE)
        # The pose model is found in the app bundle (pose_estimator_onnx.cpp).
        target_link_libraries(sfr_kinect_body_replay PRIVATE "-framework CoreFoundation")
    endif()
    sfr_copy_onnx_dlls(sfr_kinect_body_replay)
endif()
# CI builds every available Android camera consumer, including pose when its
# optional runtime is present, so launcher-only builds cannot hide missing deps.
if(ANDROID)
    add_custom_target(sfr_android_camera_probes DEPENDS sfr_camera_probe)
    if(TARGET sfr_pose_probe)
        add_dependencies(sfr_android_camera_probes sfr_pose_probe)
    endif()
endif()
# Reading a model out of a binary glTF (a .vrm is one).
add_executable(sfr_image_decode_test tests/image_decode_test.cpp src/image_decode.cpp)
target_include_directories(sfr_image_decode_test PRIVATE src)
add_test(NAME image_decode COMMAND sfr_image_decode_test)

add_executable(sfr_gltf_model_test tests/gltf_model_test.cpp src/gltf_model.cpp src/image_decode.cpp)
target_include_directories(sfr_gltf_model_test PRIVATE src)
add_test(NAME gltf_model COMMAND sfr_gltf_model_test)
# Looking at a model without the game: sfr_vrm_probe <file.vrm>
add_executable(sfr_vrm_probe tests/vrm_probe.cpp src/gltf_model.cpp src/image_decode.cpp)
target_include_directories(sfr_vrm_probe PRIVATE src)
# vcmpbfp, which the upstream translator leaves as a debug trap.
add_executable(sfr_vector_compare_bounds_test tests/vector_compare_bounds_test.cpp src/vector_compare_bounds.cpp)
target_include_directories(sfr_vector_compare_bounds_test PRIVATE src)
add_test(NAME vector_compare_bounds COMMAND sfr_vector_compare_bounds_test)
add_executable(sfr_vector_unpack_test tests/vector_unpack_test.cpp)
target_include_directories(sfr_vector_unpack_test PRIVATE src)
add_test(NAME vector_unpack COMMAND sfr_vector_unpack_test)

add_executable(sfr_avatar_state_test tests/avatar_state_test.cpp src/guest_memory.cpp)
target_include_directories(sfr_avatar_state_test PRIVATE src)
add_test(NAME avatar_state COMMAND sfr_avatar_state_test)
# Which player each host controller is.
add_executable(sfr_pad_assignment_test tests/pad_assignment_test.cpp src/pad_assignment.cpp)
target_include_directories(sfr_pad_assignment_test PRIVATE src)
add_test(NAME pad_assignment COMMAND sfr_pad_assignment_test)
add_executable(sfr_input_bindings_test tests/input_bindings_test.cpp src/input_bindings.cpp)
target_include_directories(sfr_input_bindings_test PRIVATE src)
add_test(NAME input_bindings COMMAND sfr_input_bindings_test)
# Button prompts (Issue #284): each gesture on the player's input, and its pixel art.
add_library(sfr_control_prompts STATIC src/control_prompts.cpp src/input_bindings.cpp)
target_include_directories(sfr_control_prompts PUBLIC src)
add_executable(sfr_control_prompts_test tests/control_prompts_test.cpp)
target_link_libraries(sfr_control_prompts_test PRIVATE sfr_control_prompts)
target_include_directories(sfr_control_prompts_test PRIVATE src)
add_test(NAME control_prompts COMMAND sfr_control_prompts_test)

add_executable(sfr_input_trace_test tests/input_trace_test.cpp)
target_include_directories(sfr_input_trace_test PRIVATE src)
add_test(NAME input_trace COMMAND sfr_input_trace_test)
add_executable(sfr_pad_devices_test tests/pad_devices_test.cpp src/pad_devices.cpp src/sony_gamepad.cpp)
target_include_directories(sfr_pad_devices_test PRIVATE src)
if(WIN32)
    target_link_libraries(sfr_pad_devices_test PRIVATE xinput hid setupapi)
else()
    target_link_libraries(sfr_pad_devices_test PRIVATE SDL2::SDL2)
endif()
add_test(NAME pad_devices COMMAND sfr_pad_devices_test)
add_executable(sfr_native_input_test tests/native_input_test.cpp src/native_input.cpp src/control_prompts.cpp src/input_bindings.cpp src/pad_assignment.cpp src/pad_devices.cpp src/sony_gamepad.cpp src/touch_controls.cpp src/guest_memory.cpp)
target_include_directories(sfr_native_input_test PRIVATE src)
if(WIN32)
    target_link_libraries(sfr_native_input_test PRIVATE xinput hid setupapi)
else()
    target_link_libraries(sfr_native_input_test PRIVATE SDL2::SDL2)
endif()
add_test(NAME native_input COMMAND sfr_native_input_test)
add_executable(sfr_native_audio_test tests/native_audio_test.cpp src/native_audio.cpp)
target_include_directories(sfr_native_audio_test PRIVATE src)
if(WIN32)
    target_link_libraries(sfr_native_audio_test PRIVATE xaudio2 ole32)
else()
    target_link_libraries(sfr_native_audio_test PRIVATE SDL2::SDL2)
endif()
add_test(NAME native_audio COMMAND sfr_native_audio_test)
if(IOS)
    # On iOS the output logs the audio session (Issue #249).
    target_sources(sfr_native_audio_test PRIVATE src/ios_audio_session.mm)
    target_link_libraries(sfr_native_audio_test PRIVATE "-framework Foundation" "-framework AVFoundation" objc)
endif()
add_executable(sfr_mod_loader_test tests/mod_loader_test.cpp src/mod_loader.cpp)
target_include_directories(sfr_mod_loader_test PRIVATE src)
add_test(NAME mod_loader COMMAND sfr_mod_loader_test)
if(NOT WIN32)  # the portal is only asked for by the SDL launcher
    add_executable(sfr_xdg_portal_test tests/xdg_portal_test.cpp src/xdg_portal.cpp)
    target_include_directories(sfr_xdg_portal_test PRIVATE src)
    target_link_libraries(sfr_xdg_portal_test PRIVATE ${CMAKE_DL_LIBS})
    add_test(NAME xdg_portal COMMAND sfr_xdg_portal_test)
endif()
# The online service's address and answers, and the launcher's check against
# the game's own service on this device's loopback.
add_executable(sfr_live_connect_test tests/live_connect_test.cpp src/live_connect.cpp src/live_service.cpp)
target_include_directories(sfr_live_connect_test PRIVATE src)
if(WIN32)
    target_link_libraries(sfr_live_connect_test PRIVATE ws2_32 iphlpapi)
else()
    target_link_libraries(sfr_live_connect_test PRIVATE Threads::Threads)
endif()
add_test(NAME live_connect COMMAND sfr_live_connect_test)
# The game's client gives up on a server that stops answering (Issue #329).
add_executable(sfr_live_client_test tests/live_client_test.cpp src/live_client.cpp src/live_connect.cpp src/live_service.cpp)
target_include_directories(sfr_live_client_test PRIVATE src)
if(WIN32)
    target_link_libraries(sfr_live_client_test PRIVATE ws2_32 iphlpapi)
else()
    target_link_libraries(sfr_live_client_test PRIVATE Threads::Threads)
endif()
add_test(NAME live_client COMMAND sfr_live_client_test)
add_executable(sfr_launcher_settings_test tests/launcher_settings_test.cpp src/launcher_settings.cpp)
target_include_directories(sfr_launcher_settings_test PRIVATE src)
add_test(NAME launcher_settings COMMAND sfr_launcher_settings_test)
add_executable(sfr_aspect_settings_test tests/aspect_settings_test.cpp)
target_include_directories(sfr_aspect_settings_test PRIVATE src)
add_test(NAME aspect_settings COMMAND sfr_aspect_settings_test)
add_executable(sfr_race_hud_owners_test tests/race_hud_owners_test.cpp)
target_include_directories(sfr_race_hud_owners_test PRIVATE src)
add_test(NAME race_hud_owners COMMAND sfr_race_hud_owners_test)
add_executable(sfr_consecutive_failures_test tests/consecutive_failures_test.cpp)
target_include_directories(sfr_consecutive_failures_test PRIVATE src)
add_test(NAME consecutive_failures COMMAND sfr_consecutive_failures_test)
add_executable(sfr_launcher_screen_fit_test tests/launcher_screen_fit_test.cpp)
target_include_directories(sfr_launcher_screen_fit_test PRIVATE src)
add_test(NAME launcher_screen_fit COMMAND sfr_launcher_screen_fit_test)
add_executable(sfr_launcher_fonts_test tests/launcher_fonts_test.cpp)
target_include_directories(sfr_launcher_fonts_test PRIVATE src)
add_test(NAME launcher_fonts COMMAND sfr_launcher_fonts_test)
add_executable(sfr_disc_image_search_test tests/disc_image_search_test.cpp)
target_include_directories(sfr_disc_image_search_test PRIVATE src)
add_test(NAME disc_image_search COMMAND sfr_disc_image_search_test)
add_executable(sfr_text_wrap_test tests/text_wrap_test.cpp src/text_wrap.cpp)
target_include_directories(sfr_text_wrap_test PRIVATE src)
add_test(NAME text_wrap COMMAND sfr_text_wrap_test)
add_executable(sfr_touch_scroll_test tests/touch_scroll_test.cpp src/touch_scroll.cpp)
target_include_directories(sfr_touch_scroll_test PRIVATE src)
add_test(NAME touch_scroll COMMAND sfr_touch_scroll_test)
add_executable(sfr_camera_debug_test tests/camera_debug_test.cpp src/camera_debug.cpp)
target_include_directories(sfr_camera_debug_test PRIVATE src)
add_test(NAME camera_debug COMMAND sfr_camera_debug_test)
add_executable(sfr_pose_mediapipe_test tests/pose_mediapipe_test.cpp src/pose_mediapipe.cpp)
target_include_directories(sfr_pose_mediapipe_test PRIVATE src)
add_test(NAME pose_mediapipe COMMAND sfr_pose_mediapipe_test)
if(WIN32)
    # The Kinect skeleton before and after the steadying, four panels, on a
    # real sensor without the game.
    add_executable(sfr_kinect_compare tests/kinect_compare.cpp src/kinect_preview.cpp src/kinect_stability.cpp
        src/pose_stability.cpp src/camera_debug.cpp src/kinect_sensor.cpp ${SFR_KINECT_SENSOR})
    target_include_directories(sfr_kinect_compare PRIVATE src)
    target_link_libraries(sfr_kinect_compare PRIVATE user32 gdi32 ole32)
    # The webcam adjustment window over a real camera, without the game.
    add_executable(sfr_camera_tuner_probe tests/camera_tuner_probe.cpp src/camera_player.cpp src/title_stats.cpp src/camera_tuner.cpp
        src/camera_tuner_window.cpp src/pose_skeleton.cpp src/pose_smoothing.cpp src/pose_stability.cpp
        ${SFR_CAMERA_CAPTURE} ${SFR_POSE_SOURCES})
    target_include_directories(sfr_camera_tuner_probe PRIVATE src)
    target_link_libraries(sfr_camera_tuner_probe PRIVATE user32 gdi32 comctl32 ${SFR_CAMERA_LIBRARIES})
    if(SFR_POSE_AVAILABLE)
        target_link_libraries(sfr_camera_tuner_probe PRIVATE sfr_onnxruntime)
        sfr_copy_onnx_dlls(sfr_camera_tuner_probe)
    endif()
    add_executable(sfr_camera_debug_probe tests/camera_debug_probe.cpp src/camera_debug.cpp src/camera_debug_window.cpp)
    target_include_directories(sfr_camera_debug_probe PRIVATE src)
    target_link_libraries(sfr_camera_debug_probe PRIVATE user32 gdi32)
endif()
add_executable(sfr_portable_waitables_test tests/portable_waitables_test.cpp src/portable_waitables.cpp)
target_include_directories(sfr_portable_waitables_test PRIVATE src)
target_compile_definitions(sfr_portable_waitables_test PRIVATE SFR_PORTABLE_WAITABLE_TESTING)
add_test(NAME portable_waitables COMMAND sfr_portable_waitables_test)
add_executable(sfr_vulkan_shader_source_test tests/vulkan_shader_source_test.cpp src/vulkan_shader_source.cpp)
target_include_directories(sfr_vulkan_shader_source_test PRIVATE src)
add_test(NAME vulkan_shader_source COMMAND sfr_vulkan_shader_source_test)
add_executable(sfr_spirv_bounded_descriptors_test tests/spirv_bounded_descriptors_test.cpp)
target_include_directories(sfr_spirv_bounded_descriptors_test PRIVATE src)
add_test(NAME spirv_bounded_descriptors COMMAND sfr_spirv_bounded_descriptors_test)
add_executable(sfr_bounded_descriptor_cache_test tests/bounded_descriptor_cache_test.cpp)
target_include_directories(sfr_bounded_descriptor_cache_test PRIVATE src)
add_test(NAME bounded_descriptor_cache COMMAND sfr_bounded_descriptor_cache_test)
add_executable(sfr_spirv_clip_distance_test tests/spirv_clip_distance_test.cpp)
target_include_directories(sfr_spirv_clip_distance_test PRIVATE src)
add_test(NAME spirv_clip_distance COMMAND sfr_spirv_clip_distance_test)
add_executable(sfr_spirv_dispatch_loop_test tests/spirv_dispatch_loop_test.cpp)
target_include_directories(sfr_spirv_dispatch_loop_test PRIVATE src tests)
add_test(NAME spirv_dispatch_loop COMMAND sfr_spirv_dispatch_loop_test)
add_executable(sfr_spirv_version_test tests/spirv_version_test.cpp)
target_include_directories(sfr_spirv_version_test PRIVATE src)
add_test(NAME spirv_version COMMAND sfr_spirv_version_test)
add_executable(sfr_spirv_vertex_inputs_test tests/spirv_vertex_inputs_test.cpp)
target_include_directories(sfr_spirv_vertex_inputs_test PRIVATE src)
add_test(NAME spirv_vertex_inputs COMMAND sfr_spirv_vertex_inputs_test)
add_executable(sfr_launcher_art_test tests/launcher_art_test.cpp src/launcher_art.cpp src/launcher_sound.cpp)
target_include_directories(sfr_launcher_art_test PRIVATE src)
if(WIN32)
    target_link_libraries(sfr_launcher_art_test PRIVATE winmm)
endif()
add_test(NAME launcher_art COMMAND sfr_launcher_art_test)
add_executable(sfr_installer_test tests/installer_test.cpp)
target_link_libraries(sfr_installer_test PRIVATE sfr_installer)
add_test(NAME installer COMMAND sfr_installer_test)
# The launcher (settings and install pages before the game), Dear ImGui:
# Direct3D 11 on Windows, SDL's renderer on Linux and Android.
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/tools/imgui/imgui.cpp")
    set(SFR_IMGUI "${CMAKE_CURRENT_SOURCE_DIR}/tools/imgui")
    # The launcher lists the host cameras and controllers.
    set(SFR_LAUNCHER_SOURCES src/launcher_main.cpp src/launcher_settings.cpp src/mod_loader.cpp src/input_bindings.cpp src/control_prompts.cpp src/pad_devices.cpp src/pad_assignment.cpp src/launcher_sound.cpp src/launcher_art.cpp src/sony_gamepad.cpp
        src/achievements.cpp src/achievement_progress.cpp src/image_decode.cpp
        ${SFR_CAMERA_CAPTURE} src/kinect_sensor.cpp ${SFR_KINECT_SENSOR} ${SFR_VOICE}
        src/kinect_preview.cpp src/kinect_preview_window.cpp src/kinect_preview_layout.cpp src/camera_preview_window.cpp src/camera_debug.cpp src/text_wrap.cpp src/touch_scroll.cpp
        src/live_connect.cpp
        ${SFR_IMGUI}/imgui.cpp ${SFR_IMGUI}/imgui_draw.cpp ${SFR_IMGUI}/imgui_tables.cpp ${SFR_IMGUI}/imgui_widgets.cpp)
    if(WIN32)
        add_executable(sfr_launcher WIN32 ${SFR_LAUNCHER_SOURCES} src/launcher_platform_win32.cpp src/app_icon.rc
            ${SFR_IMGUI}/backends/imgui_impl_win32.cpp ${SFR_IMGUI}/backends/imgui_impl_dx11.cpp)
        target_link_libraries(sfr_launcher PRIVATE sfr_installer winmm imm32 d3d11 dxgi d3dcompiler hid setupapi ole32 shell32 xinput gdi32 advapi32 ws2_32 iphlpapi)
        set_target_properties(sfr_launcher PROPERTIES OUTPUT_NAME FreeRidersRecompiled)
    else()
        set(SFR_LAUNCHER_SOURCES ${SFR_LAUNCHER_SOURCES} src/launcher_platform_sdl.cpp src/xdg_portal.cpp
            ${SFR_IMGUI}/backends/imgui_impl_sdl2.cpp ${SFR_IMGUI}/backends/imgui_impl_sdlrenderer2.cpp)
        if(ANDROID)
            # liblauncher.so, run by LauncherActivity (android/).
            add_library(sfr_launcher SHARED ${SFR_LAUNCHER_SOURCES})
            set_target_properties(sfr_launcher PROPERTIES OUTPUT_NAME launcher)
            target_link_libraries(sfr_launcher PRIVATE android log ${SFR_CAMERA_LIBRARIES})
        elseif(IOS)
            # The app's program: UIKit starts it through SDL2main, and Play
            # loads the game's library into the same process.
            add_executable(sfr_launcher ${SFR_LAUNCHER_SOURCES} src/ios_document_picker.mm src/ios_screen.mm src/ios_audio_session.mm)
            set_target_properties(sfr_launcher PROPERTIES OUTPUT_NAME FreeRidersRecompiled)
            target_link_libraries(sfr_launcher PRIVATE SDL2::SDL2main "-framework UIKit" "-framework Foundation"
                "-framework UniformTypeIdentifiers" "-framework CoreText" "-framework CoreGraphics" "-framework AVFoundation")
            target_link_options(sfr_launcher PRIVATE "-Wl,-rpath,@executable_path/Frameworks")
            # iOS 27 stops an app linked against its SDK at launch unless the app
            # adopts the UIScene life cycle, which SDL 2 does not. The SDK the
            # program says it was linked against decides that, so it says iOS 18,
            # whose apps keep the classic app delegate SDL uses (until SDL 3).
            if(CMAKE_OSX_SYSROOT MATCHES "[Ss]imulator")
                target_link_options(sfr_launcher PRIVATE "-Wl,-platform_version,ios-simulator,${CMAKE_OSX_DEPLOYMENT_TARGET},18.0")
            else()
                target_link_options(sfr_launcher PRIVATE "-Wl,-platform_version,ios,${CMAKE_OSX_DEPLOYMENT_TARGET},18.0")
            endif()
            set_source_files_properties(src/ios_document_picker.mm src/ios_screen.mm PROPERTIES COMPILE_OPTIONS -fobjc-arc)
        else()
            add_executable(sfr_launcher ${SFR_LAUNCHER_SOURCES})
            set_target_properties(sfr_launcher PROPERTIES OUTPUT_NAME FreeRidersRecompiled)
        endif()
        target_compile_definitions(sfr_launcher PRIVATE SFR_UI_SOUND_SDL)
        target_link_libraries(sfr_launcher PRIVATE sfr_installer SDL2::SDL2 ${CMAKE_DL_LIBS})
        if(APPLE)
            target_link_libraries(sfr_launcher PRIVATE ${SFR_CAMERA_LIBRARIES})
            sfr_link_freenect_sensor(sfr_launcher)
        endif()
    endif()
    target_include_directories(sfr_launcher PRIVATE src ${SFR_IMGUI} ${SFR_IMGUI}/backends)
endif()
add_executable(sfr_texture_fetch_test tests/texture_fetch_test.cpp src/texture_fetch.cpp)
target_include_directories(sfr_texture_fetch_test PRIVATE src)
add_test(NAME texture_fetch COMMAND sfr_texture_fetch_test)
add_executable(sfr_vector_integer_test tests/vector_integer_test.cpp)
target_include_directories(sfr_vector_integer_test PRIVATE src)
add_test(NAME vector_integer COMMAND sfr_vector_integer_test)
# o1heap is C: its own library, clear of the diagnostic's C++ options. The
# title's heap and the physical heap (PhysicalMemory) allocate from it.
add_library(sfr_o1heap STATIC src/o1heap/o1heap.c)
add_executable(sfr_memory_statistics_test tests/memory_statistics_test.cpp src/memory_statistics.cpp src/virtual_memory.cpp src/physical_memory.cpp src/guest_memory.cpp)
target_link_libraries(sfr_memory_statistics_test PRIVATE sfr_o1heap)
target_include_directories(sfr_memory_statistics_test PRIVATE src)
add_test(NAME memory_statistics COMMAND sfr_memory_statistics_test)
add_executable(sfr_video_mode_test tests/video_mode_test.cpp src/video_mode.cpp src/guest_memory.cpp)
target_include_directories(sfr_video_mode_test PRIVATE src)
add_test(NAME video_mode COMMAND sfr_video_mode_test)
add_executable(sfr_physical_memory_test tests/physical_memory_test.cpp src/physical_memory.cpp src/guest_memory.cpp)
target_link_libraries(sfr_physical_memory_test PRIVATE sfr_o1heap)
target_include_directories(sfr_physical_memory_test PRIVATE src)
add_test(NAME physical_memory COMMAND sfr_physical_memory_test)
add_executable(sfr_resource_coherency_test tests/resource_coherency_test.cpp src/resource_coherency.cpp src/physical_memory.cpp src/guest_memory.cpp)
target_link_libraries(sfr_resource_coherency_test PRIVATE sfr_o1heap)
target_include_directories(sfr_resource_coherency_test PRIVATE src)
add_test(NAME resource_coherency COMMAND sfr_resource_coherency_test)
add_executable(sfr_ansi_string_test tests/ansi_string_test.cpp src/ansi_string.cpp src/guest_memory.cpp)
target_include_directories(sfr_ansi_string_test PRIVATE src)
add_test(NAME ansi_string COMMAND sfr_ansi_string_test)
if(WIN32 OR UNIX)
    add_library(sfr_native_thread STATIC ${SFR_NATIVE_THREAD})
    target_include_directories(sfr_native_thread PUBLIC src)
    if(WIN32)  # checks Windows kernel/file behaviour directly
        add_executable(sfr_native_thread_test tests/native_thread_test.cpp src/guest_memory.cpp)
        target_link_libraries(sfr_native_thread_test PRIVATE sfr_native_thread)
        add_test(NAME native_thread COMMAND sfr_native_thread_test)
        set_tests_properties(native_thread PROPERTIES TIMEOUT 10)
    elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR ANDROID)
        add_executable(sfr_native_thread_posix_test tests/native_thread_posix_test.cpp src/guest_memory.cpp)
        target_link_libraries(sfr_native_thread_posix_test PRIVATE sfr_native_thread)
        add_test(NAME native_thread_posix COMMAND sfr_native_thread_posix_test)
        set_tests_properties(native_thread_posix PROPERTIES TIMEOUT 10)
    endif()
    # Timer resolution and power throttling for the game process, and a sleep
    # that does not round up to the system tick (docs/handheld-timing.md).
    add_library(sfr_host_timing STATIC src/host_timing.cpp)
    target_include_directories(sfr_host_timing PUBLIC src)
    target_link_libraries(sfr_host_timing PUBLIC sfr_native_timer_resolution)
    add_executable(sfr_host_timing_test tests/host_timing_test.cpp)
    target_link_libraries(sfr_host_timing_test PRIVATE sfr_host_timing)
    add_test(NAME host_timing COMMAND sfr_host_timing_test)
    set_tests_properties(host_timing PROPERTIES TIMEOUT 10 ENVIRONMENT "SFR_HOST_TIMING=1")
    add_test(NAME host_timing_disabled COMMAND sfr_host_timing_test)
    set_tests_properties(host_timing_disabled PROPERTIES TIMEOUT 10 ENVIRONMENT "SFR_HOST_TIMING=0")
    add_library(sfr_guest_threads STATIC src/guest_threads.cpp)
    target_link_libraries(sfr_guest_threads PUBLIC sfr_native_thread sfr_host_timing)
    add_executable(sfr_guest_threads_test tests/guest_threads_test.cpp src/guest_memory.cpp src/system_time.cpp src/thread_local_storage.cpp)
    target_link_libraries(sfr_guest_threads_test PRIVATE sfr_guest_threads)
    add_test(NAME guest_threads COMMAND sfr_guest_threads_test)
    set_tests_properties(guest_threads PROPERTIES ENVIRONMENT "SFR_GUEST_SATURATED_PRIORITY=1")
    add_test(NAME guest_threads_priority_legacy COMMAND sfr_guest_threads_test)
    set_tests_properties(guest_threads_priority_legacy PROPERTIES TIMEOUT 10 ENVIRONMENT "SFR_GUEST_SATURATED_PRIORITY=0")
    add_test(NAME guest_threads_priority_default COMMAND sfr_guest_threads_test)
    set_tests_properties(guest_threads_priority_default PROPERTIES TIMEOUT 10 ENVIRONMENT "SFR_GUEST_SATURATED_PRIORITY=")
    set_tests_properties(guest_threads PROPERTIES TIMEOUT 10)
    add_library(sfr_native_sync_objects STATIC src/native_sync_objects.cpp ${SFR_PORTABLE_WAITABLES} src/guest_sync_objects.cpp)
    target_include_directories(sfr_native_sync_objects PUBLIC src)
    add_executable(sfr_native_sync_objects_test tests/native_sync_objects_test.cpp src/guest_memory.cpp)
    target_link_libraries(sfr_native_sync_objects_test PRIVATE sfr_native_sync_objects)
    add_test(NAME native_sync_objects COMMAND sfr_native_sync_objects_test)
    set_tests_properties(native_sync_objects PROPERTIES TIMEOUT 10)
    add_executable(sfr_native_notification_event_test tests/native_notification_event_test.cpp src/guest_memory.cpp)
    target_link_libraries(sfr_native_notification_event_test PRIVATE sfr_native_sync_objects)
    add_test(NAME native_notification_event COMMAND sfr_native_notification_event_test)
    add_executable(sfr_notification_placement_test tests/notification_placement_test.cpp src/guest_memory.cpp)
    target_include_directories(sfr_notification_placement_test PRIVATE src)
    add_test(NAME notification_placement COMMAND sfr_notification_placement_test)
    add_library(sfr_native_notifications STATIC src/native_notifications.cpp)
    target_link_libraries(sfr_native_notifications PUBLIC sfr_native_sync_objects)
    add_executable(sfr_native_notifications_test tests/native_notifications_test.cpp src/guest_memory.cpp)
    target_link_libraries(sfr_native_notifications_test PRIVATE sfr_native_notifications)
    add_test(NAME native_notifications COMMAND sfr_native_notifications_test)
    set_tests_properties(native_notifications PROPERTIES TIMEOUT 10)
    add_executable(sfr_guest_sync_objects_test tests/guest_sync_objects_test.cpp src/guest_memory.cpp)
    target_link_libraries(sfr_guest_sync_objects_test PRIVATE sfr_native_sync_objects)
    add_test(NAME guest_sync_objects COMMAND sfr_guest_sync_objects_test)
    if(WIN32)  # checks Windows kernel/file behaviour directly
        add_executable(sfr_native_named_sync_test tests/native_named_sync_test.cpp src/guest_memory.cpp)
        target_link_libraries(sfr_native_named_sync_test PRIVATE sfr_native_sync_objects)
        add_test(NAME native_named_sync COMMAND sfr_native_named_sync_test)
        set_tests_properties(native_named_sync PROPERTIES TIMEOUT 10)
    endif()
    add_executable(sfr_guest_named_sync_test tests/guest_named_sync_test.cpp src/guest_memory.cpp)
    target_link_libraries(sfr_guest_named_sync_test PRIVATE sfr_native_sync_objects)
    add_test(NAME guest_named_sync COMMAND sfr_guest_named_sync_test)
    set_tests_properties(guest_named_sync PROPERTIES TIMEOUT 10)
    target_sources(sfr_native_sync_objects PRIVATE src/guest_wait.cpp)
    add_executable(sfr_guest_wait_test tests/guest_wait_test.cpp src/guest_memory.cpp)
    target_link_libraries(sfr_guest_wait_test PRIVATE sfr_native_sync_objects)
    add_test(NAME guest_wait COMMAND sfr_guest_wait_test)
    set_tests_properties(guest_wait PROPERTIES TIMEOUT 10)
    add_library(sfr_asset_files STATIC ${SFR_ASSET_FILES} src/guest_files.cpp src/mod_loader.cpp)
    target_include_directories(sfr_asset_files PUBLIC src)
    if(WIN32)  # checks Windows kernel/file behaviour directly
        add_executable(sfr_asset_files_test tests/asset_files_test.cpp)
        target_link_libraries(sfr_asset_files_test PRIVATE sfr_asset_files)
        target_include_directories(sfr_asset_files_test PRIVATE src)
        add_test(NAME asset_files COMMAND sfr_asset_files_test)
    endif()
    if(WIN32)  # checks Windows kernel/file behaviour directly
        add_executable(sfr_async_asset_files_test tests/async_asset_files_test.cpp)
        target_link_libraries(sfr_async_asset_files_test PRIVATE sfr_asset_files)
        add_test(NAME async_asset_files COMMAND sfr_async_asset_files_test)
        set_tests_properties(async_asset_files PROPERTIES TIMEOUT 20)
    endif()
    if(WIN32)  # checks Windows kernel/file behaviour directly
        add_executable(sfr_native_async_read_test tests/native_async_read_test.cpp)
        target_link_libraries(sfr_native_async_read_test PRIVATE sfr_asset_files)
        add_test(NAME native_async_read COMMAND sfr_native_async_read_test)
        set_tests_properties(native_async_read PROPERTIES TIMEOUT 30)
    endif()
    add_executable(sfr_async_completion_primitives_test tests/async_completion_primitives_test.cpp src/guest_memory.cpp)
    target_link_libraries(sfr_async_completion_primitives_test PRIVATE sfr_guest_execution sfr_native_sync_objects)
    add_test(NAME async_completion_primitives COMMAND sfr_async_completion_primitives_test)
    set_tests_properties(async_completion_primitives PROPERTIES TIMEOUT 20)
    add_library(sfr_guest_async_files STATIC src/guest_async_files.cpp)
    target_link_libraries(sfr_guest_async_files PUBLIC sfr_asset_files sfr_native_sync_objects sfr_guest_execution)
    add_executable(sfr_guest_async_files_test tests/guest_async_files_test.cpp src/guest_memory.cpp)
    target_link_libraries(sfr_guest_async_files_test PRIVATE sfr_guest_async_files)
    add_test(NAME guest_async_files COMMAND sfr_guest_async_files_test)
    set_tests_properties(guest_async_files PROPERTIES TIMEOUT 20)
    add_executable(sfr_guest_files_test tests/guest_files_test.cpp src/guest_memory.cpp)
    target_link_libraries(sfr_guest_files_test PRIVATE sfr_asset_files)
    target_include_directories(sfr_guest_files_test PRIVATE src)
    add_test(NAME guest_files COMMAND sfr_guest_files_test)
    add_executable(sfr_guest_file_read_test tests/guest_file_read_test.cpp src/guest_memory.cpp)
    target_link_libraries(sfr_guest_file_read_test PRIVATE sfr_asset_files)
    add_test(NAME guest_file_read COMMAND sfr_guest_file_read_test)
    if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/tools/Plume/CMakeLists.txt")
        message(FATAL_ERROR "Run scripts/bootstrap.py to obtain the pinned Plume renderer.")
    endif()
    set(PLUME_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
    set(D3D12_AGILITY_SDK_ENABLED OFF CACHE BOOL "" FORCE)
    # Plume draws into an SDL window, except on Android, where it draws into
    # the window's ANativeWindow (RenderWindow is one there). Its window-size
    # query prefers the SDL branch whenever this is on, so with it on for
    # Android it read the ANativeWindow as an SDL_Window: every swap chain was
    # made (uint32)-19 wide, and the blit's viewport and scissor with it.
    if(NOT WIN32 AND NOT ANDROID)
        set(SDL_VULKAN_ENABLED ON CACHE BOOL "" FORCE)
    endif()
    add_subdirectory(tools/Plume)
    if(WIN32)
        # Plume exports D3D12SDKVersion (the building Windows SDK's) and
        # D3D12SDKPath even without the Agility SDK. The version the program
        # names has to be the runtime's it ships in D3D12\ (src/d3d12_agility.cpp,
        # below); renamed, Plume's are not exports the loader reads.
        target_compile_definitions(plume PRIVATE D3D12SDKVersion=sfr_plume_unused_D3D12SDKVersion
                                   D3D12SDKPath=sfr_plume_unused_D3D12SDKPath)
    endif()
    if(IOS)
        # Plume's Apple glue is AppKit's; the Vulkan path through SDL needs
        # none of it, so iOS has a stand-in (src/plume_apple_ios.mm).
        get_target_property(SFR_PLUME_SOURCES plume SOURCES)
        list(TRANSFORM SFR_PLUME_SOURCES REPLACE "^plume_apple\\.mm$" "${CMAKE_CURRENT_SOURCE_DIR}/src/plume_apple_ios.mm")
        set_property(TARGET plume PROPERTY SOURCES ${SFR_PLUME_SOURCES})
        target_link_libraries(plume PUBLIC "-framework CoreFoundation" "-framework Foundation"
            "-framework Metal" "-framework QuartzCore" "-framework UIKit" objc)
    elseif(APPLE)
        # Plume's Apple glue asks IOKit for the GPU vendor and uses Metal and
        # Cocoa objects; its own CMake leaves linking them to the user.
        target_link_libraries(plume PUBLIC "-framework CoreFoundation" "-framework IOKit" "-framework Foundation"
            "-framework Metal" "-framework QuartzCore" "-framework AppKit" objc)
    endif()
    if(NOT WIN32 AND SDL2_VERSION VERSION_LESS 2.26)
        # Plume asks SDL for the drawable size by the name SDL 2.26 gave it;
        # older SDL2 has the same call as SDL_Vulkan_GetDrawableSize.
        target_compile_definitions(plume PRIVATE SDL_GetWindowSizeInPixels=SDL_Vulkan_GetDrawableSize)
    endif()
    # -DSFR_VULKAN_VALIDATION=ON asks Plume for the Khronos validation layer,
    # for diagnosis only: the layer has to be beside the program (or in the
    # APK's jniLibs on Android) and it costs a great deal of speed.
    option(SFR_VULKAN_VALIDATION "Enable the Vulkan validation layer" OFF)
    if(SFR_VULKAN_VALIDATION)
        target_compile_definitions(plume PRIVATE VULKAN_VALIDATION_LAYER_ENABLED)
    endif()
    add_library(sfr_native_graphics STATIC src/native_graphics.cpp src/native_pipeline_cache.cpp src/pipeline_cache_file.cpp)
    target_include_directories(sfr_native_graphics PUBLIC src)
    if(WIN32)
        target_link_libraries(sfr_native_graphics PUBLIC plume PRIVATE d3d12 dxgi Threads::Threads)
    else()
        target_link_libraries(sfr_native_graphics PUBLIC plume SDL2::SDL2 PRIVATE Threads::Threads)
    endif()
    set(SFR_SHADER_CACHE_SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/out/shaders/basic/shader_cache_data.cpp" CACHE FILEPATH "Generated private shader cache")
    if(EXISTS "${SFR_SHADER_CACHE_SOURCE}")
        set(SFR_SHADER_CACHE_DATA "${SFR_SHADER_CACHE_SOURCE}")
    else()
        set(SFR_SHADER_CACHE_DATA src/shader_cache_empty.cpp)
    endif()
    add_library(sfr_native_shaders STATIC src/native_shaders.cpp src/runtime_shader_cache.cpp src/vulkan_shader_source.cpp src/vertex_palette.cpp src/shader_constant_use.cpp src/loop_constants.cpp src/shader_inputs.cpp ${SFR_SHADER_CACHE_DATA})
    target_link_libraries(sfr_native_shaders PUBLIC sfr_native_graphics)
    add_executable(sfr_runtime_shader_pack_test tests/runtime_shader_pack_test.cpp src/guest_memory.cpp)
    target_link_libraries(sfr_runtime_shader_pack_test PRIVATE sfr_native_shaders)
    foreach(pack_case empty zero missing)
        add_test(NAME runtime_shader_pack_${pack_case} COMMAND sfr_runtime_shader_pack_test ${pack_case})
    endforeach()
    add_executable(sfr_native_shaders_test tests/native_shaders_test.cpp src/guest_memory.cpp)
    target_link_libraries(sfr_native_shaders_test PRIVATE sfr_native_shaders)
    add_executable(sfr_shader_cache_rebuild tools/shader_cache_rebuild.cpp src/guest_memory.cpp)
    target_link_libraries(sfr_shader_cache_rebuild PRIVATE sfr_native_shaders)
    add_executable(sfr_native_shader_abi_test tests/native_shader_abi_test.cpp src/guest_memory.cpp)
    target_link_libraries(sfr_native_shader_abi_test PRIVATE sfr_native_shaders)
    add_test(NAME native_shader_abi COMMAND sfr_native_shader_abi_test)
    add_test(NAME native_shaders COMMAND sfr_native_shaders_test)
    set_tests_properties(native_shaders PROPERTIES TIMEOUT 20)
    add_executable(sfr_native_graphics_test tests/native_graphics_test.cpp)
    target_link_libraries(sfr_native_graphics_test PRIVATE sfr_native_graphics)
    add_test(NAME native_graphics COMMAND sfr_native_graphics_test)
    set_tests_properties(native_graphics PROPERTIES TIMEOUT 20)
    add_executable(sfr_native_upload_batch_test tests/native_upload_batch_test.cpp src/native_upload_batch.cpp)
    target_link_libraries(sfr_native_upload_batch_test PRIVATE sfr_native_graphics)
    add_test(NAME native_upload_batch_vulkan COMMAND sfr_native_upload_batch_test vulkan)
    set_tests_properties(native_upload_batch_vulkan PROPERTIES TIMEOUT 20)
    if(WIN32)
        add_test(NAME native_upload_batch_d3d12 COMMAND sfr_native_upload_batch_test d3d12)
        set_tests_properties(native_upload_batch_d3d12 PROPERTIES TIMEOUT 20)
    endif()
    add_library(sfr_native_raster_state STATIC src/native_raster_state.cpp)
    target_link_libraries(sfr_native_raster_state PUBLIC sfr_native_graphics)
    # The backend's own shaders, compiled at build time to DXIL and SPIR-V with
    # XenosRecomp's pinned dxc-bin and embedded as byte arrays. The compiler
    # runs on the build machine (an Android build uses the host's).
    if(CMAKE_HOST_WIN32)
        set(SFR_DXC "${CMAKE_CURRENT_SOURCE_DIR}/tools/XenosRecomp/thirdparty/dxc-bin/bin/x64/dxc.exe")
    elseif(CMAKE_HOST_APPLE)
        # dxc-macos (universal) loads libdxcompiler.dylib from dxc-bin's lib directory.
        set(SFR_DXC ${CMAKE_COMMAND} -E env "DYLD_LIBRARY_PATH=${CMAKE_CURRENT_SOURCE_DIR}/tools/XenosRecomp/thirdparty/dxc-bin/lib/x64"
            "${CMAKE_CURRENT_SOURCE_DIR}/tools/XenosRecomp/thirdparty/dxc-bin/bin/x64/dxc-macos")
    else()
        # dxc-linux loads libdxcompiler.so from dxc-bin's lib directory.
        set(SFR_DXC ${CMAKE_COMMAND} -E env "LD_LIBRARY_PATH=${CMAKE_CURRENT_SOURCE_DIR}/tools/XenosRecomp/thirdparty/dxc-bin/lib/x64"
            "${CMAKE_CURRENT_SOURCE_DIR}/tools/XenosRecomp/thirdparty/dxc-bin/bin/x64/dxc-linux")
    endif()
    if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/tools/XenosRecomp/thirdparty/dxc-bin/bin/x64")
        message(FATAL_ERROR "Run scripts/bootstrap.py to obtain XenosRecomp's dxc-bin.")
    endif()
    set(SFR_EMBEDDED_SHADERS "${CMAKE_CURRENT_BINARY_DIR}/embedded_shaders")
    file(MAKE_DIRECTORY "${SFR_EMBEDDED_SHADERS}")
    set(SFR_EMBEDDED_SHADER_HEADERS)
    # Files the shaders #include: a change rebuilds them all.
    set(SFR_SHADER_INCLUDES "${CMAKE_CURRENT_SOURCE_DIR}/src/shaders/touch_labels.hlsli")
    function(sfr_embed_shader source entry profile name)
        set(header "${SFR_EMBEDDED_SHADERS}/${name}_dxil.h")
        add_custom_command(OUTPUT "${header}"
            COMMAND ${SFR_DXC} -T ${profile} -E ${entry} -HV 2021 -Qstrip_debug -Qstrip_reflect -Fh "${header}" -Vn ${name}_dxil "${CMAKE_CURRENT_SOURCE_DIR}/${source}"
            DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${source}" ${SFR_SHADER_INCLUDES} VERBATIM)
        set(spirv_header "${SFR_EMBEDDED_SHADERS}/${name}_spirv.h")
        set(invert)
        if(profile MATCHES "^vs_")
            set(invert -fvk-invert-y)  # D3D clip space in, Vulkan's out
        endif()
        add_custom_command(OUTPUT "${spirv_header}"
            COMMAND ${SFR_DXC} -T ${profile} -E ${entry} -HV 2021 -spirv -fvk-use-dx-layout ${invert} -Qstrip_debug -Fh "${spirv_header}" -Vn ${name}_spirv "${CMAKE_CURRENT_SOURCE_DIR}/${source}"
            DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${source}" ${SFR_SHADER_INCLUDES} VERBATIM)
        set(SFR_EMBEDDED_SHADER_HEADERS ${SFR_EMBEDDED_SHADER_HEADERS} "${header}" "${spirv_header}" PARENT_SCOPE)
    endfunction()
    sfr_embed_shader(src/shaders/blit.hlsl vertexMain vs_6_0 blit_vs)
    sfr_embed_shader(src/shaders/blit.hlsl pixelMain ps_6_0 blit_ps)
    sfr_embed_shader(src/shaders/model.hlsl vertexMain vs_6_0 model_vs)
    sfr_embed_shader(src/shaders/model.hlsl pixelMain ps_6_0 model_ps)
    sfr_embed_shader(src/shaders/overlay.hlsl vertexMain vs_6_0 overlay_vs)
    sfr_embed_shader(src/shaders/overlay.hlsl pixelMain ps_6_0 overlay_ps)
    sfr_embed_shader(src/shaders/depth_flip.hlsl pointMain gs_6_0 depth_flip_point_gs)
    sfr_embed_shader(src/shaders/depth_flip.hlsl lineMain gs_6_0 depth_flip_line_gs)
    sfr_embed_shader(src/shaders/depth_flip.hlsl triangleMain gs_6_0 depth_flip_triangle_gs)
    add_library(sfr_native_presentation STATIC src/native_presentation.cpp src/title_stats.cpp src/touch_controls.cpp src/fps_readout.cpp src/gltf_model.cpp src/image_decode.cpp src/screen_overlay.cpp src/screen_overlay_renderer.cpp ${SFR_EMBEDDED_SHADER_HEADERS})
    target_include_directories(sfr_native_presentation PRIVATE "${SFR_EMBEDDED_SHADERS}")
    add_executable(sfr_native_presentation_test tests/native_presentation_test.cpp)
    if(WIN32)
        target_link_libraries(sfr_native_presentation PUBLIC sfr_native_raster_state sfr_control_prompts PRIVATE user32)
        target_link_libraries(sfr_native_presentation_test PRIVATE sfr_native_presentation user32)
    else()
        target_link_libraries(sfr_native_presentation PUBLIC sfr_native_raster_state sfr_control_prompts PRIVATE SDL2::SDL2)
        target_link_libraries(sfr_native_presentation_test PRIVATE sfr_native_presentation)
    endif()
    add_test(NAME native_presentation COMMAND sfr_native_presentation_test)
    if(WIN32)  # drives D3D12 directly
    # A vertex and pixel shader of the test's own beside the depth flip
    # geometry shader (Issue #177).
    set(SFR_EMBEDDED_SHADER_HEADERS)
    sfr_embed_shader(tests/shaders/depth_flip_test.hlsl vertexMain vs_6_0 depth_flip_test_vs)
    sfr_embed_shader(tests/shaders/depth_flip_test.hlsl pixelMain ps_6_0 depth_flip_test_ps)
    add_executable(sfr_native_raster_state_test tests/native_raster_state_test.cpp ${SFR_EMBEDDED_SHADER_HEADERS})
    target_include_directories(sfr_native_raster_state_test PRIVATE "${SFR_EMBEDDED_SHADERS}")
    target_link_libraries(sfr_native_raster_state_test PRIVATE sfr_native_presentation d3dcompiler)
    add_test(NAME native_raster_state COMMAND sfr_native_raster_state_test)
    set_tests_properties(native_raster_state PROPERTIES TIMEOUT 20)
    endif()
    add_library(sfr_native_blend_control STATIC src/native_blend_control.cpp)
    target_include_directories(sfr_native_blend_control PUBLIC src)
    target_link_libraries(sfr_native_blend_control PUBLIC plume)
    add_executable(sfr_native_blend_control_test tests/native_blend_control_test.cpp)
    target_link_libraries(sfr_native_blend_control_test PRIVATE sfr_native_blend_control)
    add_test(NAME native_blend_control COMMAND sfr_native_blend_control_test)
    add_executable(sfr_native_depth_flip_test tests/native_depth_flip_test.cpp)
    target_include_directories(sfr_native_depth_flip_test PRIVATE src)
    target_link_libraries(sfr_native_depth_flip_test PRIVATE plume)
    add_test(NAME native_depth_flip COMMAND sfr_native_depth_flip_test)
    add_executable(sfr_native_pipeline_key_test tests/native_pipeline_key_test.cpp)
    target_include_directories(sfr_native_pipeline_key_test PRIVATE src)
    target_link_libraries(sfr_native_pipeline_key_test PRIVATE plume)
    add_test(NAME native_pipeline_key COMMAND sfr_native_pipeline_key_test)
    add_executable(sfr_native_vertex_layout_test tests/native_vertex_layout_test.cpp src/native_vertex_layout.cpp src/native_formats.cpp)
    target_include_directories(sfr_native_vertex_layout_test PRIVATE src)
    target_link_libraries(sfr_native_vertex_layout_test PRIVATE plume)
    add_test(NAME native_vertex_layout COMMAND sfr_native_vertex_layout_test)
    add_executable(sfr_native_constant_upload_test tests/native_constant_upload_test.cpp)
    target_include_directories(sfr_native_constant_upload_test PRIVATE src)
    add_test(NAME native_constant_upload COMMAND sfr_native_constant_upload_test)
    add_library(sfr_guest_graphics STATIC src/guest_graphics.cpp src/guest_graphics_state.cpp src/aspect_settings.cpp src/texture_fetch.cpp src/native_formats.cpp src/native_vertex_layout.cpp src/native_renderer.cpp src/native_upload_batch.cpp src/pipeline_manifest.cpp)
    add_executable(sfr_pipeline_manifest_test tests/pipeline_manifest_test.cpp src/pipeline_manifest.cpp)
    target_include_directories(sfr_pipeline_manifest_test PRIVATE src)
    target_link_libraries(sfr_pipeline_manifest_test PRIVATE plume)
    add_test(NAME pipeline_manifest COMMAND sfr_pipeline_manifest_test ${CMAKE_CURRENT_SOURCE_DIR}/data/pipeline-manifests)
    # Joins recorded pipeline lists (data/pipeline-manifests/README.md).
    add_executable(sfr_pipeline_manifest_merge tools/pipeline_manifest_merge.cpp src/pipeline_manifest.cpp)
    target_include_directories(sfr_pipeline_manifest_merge PRIVATE src)
    target_link_libraries(sfr_pipeline_manifest_merge PRIVATE plume)
    if(MSVC)
        target_compile_options(sfr_guest_graphics PRIVATE /fp:strict)
    elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
        target_compile_options(sfr_guest_graphics PRIVATE -ffp-model=strict)
    else()
        target_compile_options(sfr_guest_graphics PRIVATE -frounding-math -ffp-contract=off)
    endif()
    target_link_libraries(sfr_guest_graphics PUBLIC sfr_native_presentation sfr_native_shaders sfr_native_blend_control)
    add_executable(sfr_native_resolution_test tests/native_resolution_test.cpp src/guest_memory.cpp)
    target_link_libraries(sfr_native_resolution_test PRIVATE sfr_guest_graphics)
    target_compile_definitions(sfr_native_resolution_test PRIVATE SFR_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
    add_test(NAME native_resolution COMMAND sfr_native_resolution_test)
    set_tests_properties(native_resolution PROPERTIES TIMEOUT 120)
    add_executable(sfr_native_pipeline_prewarm_test tests/native_pipeline_prewarm_test.cpp src/guest_memory.cpp)
    target_link_libraries(sfr_native_pipeline_prewarm_test PRIVATE sfr_guest_graphics)
    target_compile_definitions(sfr_native_pipeline_prewarm_test PRIVATE SFR_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
    add_test(NAME native_pipeline_prewarm_vulkan COMMAND sfr_native_pipeline_prewarm_test vulkan)
    set_tests_properties(native_pipeline_prewarm_vulkan PROPERTIES TIMEOUT 120)
    if(WIN32)
        add_test(NAME native_pipeline_prewarm_d3d12 COMMAND sfr_native_pipeline_prewarm_test d3d12)
        set_tests_properties(native_pipeline_prewarm_d3d12 PROPERTIES TIMEOUT 120)
    endif()
    add_executable(sfr_guest_graphics_test tests/guest_graphics_test.cpp src/guest_memory.cpp)
    target_link_libraries(sfr_guest_graphics_test PRIVATE sfr_guest_graphics)
    add_test(NAME guest_graphics COMMAND sfr_guest_graphics_test)
    # Its clears and draws on the title's own surfaces read the framebuffer
    # back: the aliasing that SFR_SURFACE_TARGETS=0 keeps.
    set_tests_properties(guest_graphics PROPERTIES ENVIRONMENT SFR_SURFACE_TARGETS=0)
    add_executable(sfr_native_formats_test tests/native_formats_test.cpp)
    target_link_libraries(sfr_native_formats_test PRIVATE sfr_guest_graphics)
    add_test(NAME native_formats COMMAND sfr_native_formats_test)
    set_tests_properties(native_presentation guest_graphics PROPERTIES TIMEOUT 20)
    add_executable(sfr_guest_render_state_test tests/guest_render_state_test.cpp src/guest_memory.cpp)
    target_link_libraries(sfr_guest_render_state_test PRIVATE sfr_guest_graphics)
    add_test(NAME guest_render_state COMMAND sfr_guest_render_state_test)
    set_tests_properties(guest_render_state PROPERTIES TIMEOUT 20)
    add_executable(sfr_guest_blend_request_test tests/guest_blend_request_test.cpp src/guest_memory.cpp)
    target_link_libraries(sfr_guest_blend_request_test PRIVATE sfr_guest_graphics)
    add_test(NAME guest_blend_request COMMAND sfr_guest_blend_request_test)
    set_tests_properties(guest_blend_request PROPERTIES TIMEOUT 20)
    add_executable(sfr_guest_sampler_filter_test tests/guest_sampler_filter_test.cpp src/guest_memory.cpp)
    target_link_libraries(sfr_guest_sampler_filter_test PRIVATE sfr_guest_graphics)
    add_test(NAME guest_sampler_filter COMMAND sfr_guest_sampler_filter_test)
    set_tests_properties(guest_sampler_filter PROPERTIES TIMEOUT 20)
    add_executable(sfr_guest_texture_binding_test tests/guest_texture_binding_test.cpp src/guest_memory.cpp)
    target_link_libraries(sfr_guest_texture_binding_test PRIVATE sfr_guest_graphics)
    add_test(NAME guest_texture_binding COMMAND sfr_guest_texture_binding_test)
    set_tests_properties(guest_texture_binding PROPERTIES TIMEOUT 20)
endif()
if(SFR_BUILD_DIAGNOSTIC)
    if(WIN32)  # compared with the host's own Winsock negotiation
        add_executable(sfr_native_winsock_test tests/native_winsock_test.cpp src/native_winsock.cpp src/guest_memory.cpp)
        target_include_directories(sfr_native_winsock_test PRIVATE src)
        target_link_libraries(sfr_native_winsock_test PRIVATE ws2_32)
        add_test(NAME native_winsock COMMAND sfr_native_winsock_test)
    endif()
    set(SFR_DIAGNOSTIC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/out/recomp/diagnostic" CACHE PATH "Audited generated diagnostic sources")
    if(NOT EXISTS "${SFR_DIAGNOSTIC_DIR}/report.json")
        message(FATAL_ERROR "Run scripts/generate_diagnostic.py first; raw output is not safe to execute.")
    endif()
    file(GLOB SFR_DIAGNOSTIC_SOURCES CONFIGURE_DEPENDS "${SFR_DIAGNOSTIC_DIR}/ppc_recomp.*.cpp")
    # Label checkpoints only at loop heads (scripts/loop_head_checkpoints.py,
    # docs/performance.md): older generator output checkpoints after every
    # label, and the shipped generated code is not regenerated, so the game
    # compiles copies made in the build directory without the forward ones.
    # Output of the current generator passes through unchanged.
    option(SFR_LOOP_HEAD_CHECKPOINTS "Compile the generated code with label checkpoints only at loop heads" ON)
    set(SFR_DIAGNOSTIC_COMPILED_SOURCES ${SFR_DIAGNOSTIC_SOURCES})
    # Guest registers as C++ locals (scripts/register_locals.py, the effect of
    # XenonRecomp's *_as_local options, done on the finished output with a
    # whole-program check of the calling convention): a comma list of the
    # classes ctr, xer, cr, nonargument, nonvolatile, or none. The copies it
    # writes also get the loop-head checkpoints above. Every class by default
    # (docs/performance.md, 2026-10-26); "none" compiles the context form.
    set(SFR_REGISTER_LOCALS "ctr,xer,cr,nonargument,nonvolatile" CACHE STRING "Guest register classes compiled as C++ locals (ctr,xer,cr,nonargument,nonvolatile or none)")
    if(SFR_REGISTER_LOCALS AND NOT SFR_REGISTER_LOCALS STREQUAL "none")
        find_package(Python3 COMPONENTS Interpreter REQUIRED)
        set(SFR_REGISTER_LOCALS_DIR "${CMAKE_CURRENT_BINARY_DIR}/register-locals")
        set(SFR_DIAGNOSTIC_COMPILED_SOURCES)
        foreach(source IN LISTS SFR_DIAGNOSTIC_SOURCES)
            get_filename_component(name "${source}" NAME)
            list(APPEND SFR_DIAGNOSTIC_COMPILED_SOURCES "${SFR_REGISTER_LOCALS_DIR}/${name}")
        endforeach()
        set(SFR_REGISTER_LOCALS_ARGUMENTS --classes "${SFR_REGISTER_LOCALS}")
        if(SFR_LOOP_HEAD_CHECKPOINTS)
            list(APPEND SFR_REGISTER_LOCALS_ARGUMENTS --loop-heads)
        endif()
        # The image beside the generated directory (prepare_recomp.py's
        # layout) narrows indirect calls to functions whose address escapes.
        get_filename_component(SFR_GENERATED_ROOT "${SFR_DIAGNOSTIC_DIR}" DIRECTORY)
        if(EXISTS "${SFR_GENERATED_ROOT}/image/image.bin")
            list(APPEND SFR_REGISTER_LOCALS_ARGUMENTS --image "${SFR_GENERATED_ROOT}/image/image.bin")
        endif()
        add_custom_command(OUTPUT ${SFR_DIAGNOSTIC_COMPILED_SOURCES}
            COMMAND "${Python3_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/scripts/register_locals.py"
                    ${SFR_REGISTER_LOCALS_ARGUMENTS} --report "${SFR_REGISTER_LOCALS_DIR}.json"
                    "${SFR_DIAGNOSTIC_DIR}" "${SFR_REGISTER_LOCALS_DIR}"
            DEPENDS ${SFR_DIAGNOSTIC_SOURCES} "${CMAKE_CURRENT_SOURCE_DIR}/scripts/register_locals.py"
                    "${CMAKE_CURRENT_SOURCE_DIR}/scripts/loop_head_checkpoints.py"
            COMMENT "Guest registers as locals: ${SFR_REGISTER_LOCALS}"
            VERBATIM)
    elseif(SFR_LOOP_HEAD_CHECKPOINTS)
        find_package(Python3 COMPONENTS Interpreter REQUIRED)
        set(SFR_DIAGNOSTIC_COMPILED_SOURCES)
        foreach(source IN LISTS SFR_DIAGNOSTIC_SOURCES)
            get_filename_component(name "${source}" NAME)
            set(copy "${CMAKE_CURRENT_BINARY_DIR}/loop-head-checkpoints/${name}")
            add_custom_command(OUTPUT "${copy}"
                COMMAND "${Python3_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/scripts/loop_head_checkpoints.py" --file "${source}" "${copy}"
                DEPENDS "${source}" "${CMAKE_CURRENT_SOURCE_DIR}/scripts/loop_head_checkpoints.py"
                VERBATIM)
            list(APPEND SFR_DIAGNOSTIC_COMPILED_SOURCES "${copy}")
        endforeach()
    endif()
    # The title's depth view (82439998, 82439D80) as generated, copied out of
    # those sources into the build directory and run on test images against
    # the offline extractor (kinect_depth_features.h): bit for bit.
    set(SFR_DEPTH_VIEW_GUEST "${CMAKE_CURRENT_BINARY_DIR}/kinect_depth_view_guest.inc")
    add_custom_command(OUTPUT "${SFR_DEPTH_VIEW_GUEST}"
        COMMAND ${CMAKE_COMMAND} "-DDIR=${SFR_DIAGNOSTIC_DIR}" "-DOUT=${SFR_DEPTH_VIEW_GUEST}" "-DFUNCTIONS=82439998,82439D80"
                -P "${CMAKE_CURRENT_SOURCE_DIR}/tests/extract_guest_functions.cmake"
        DEPENDS ${SFR_DIAGNOSTIC_SOURCES} "${CMAKE_CURRENT_SOURCE_DIR}/tests/extract_guest_functions.cmake"
        VERBATIM)
    add_executable(sfr_kinect_depth_view_guest_test tests/kinect_depth_view_guest_test.cpp src/kinect_depth_features.cpp
        "${SFR_DEPTH_VIEW_GUEST}")
    set_source_files_properties("${SFR_DEPTH_VIEW_GUEST}" PROPERTIES HEADER_FILE_ONLY TRUE)
    target_include_directories(sfr_kinect_depth_view_guest_test PRIVATE src "${CMAKE_CURRENT_BINARY_DIR}" "${SFR_DIAGNOSTIC_DIR}"
        tools/XenonRecomp/thirdparty/simde)
    # The generated code as the diagnostic compiles it; the extractor as the tools do.
    if(MSVC)
        set(SFR_DEPTH_VIEW_GUEST_OPTIONS -fno-strict-aliasing -Wno-unused-variable -Wno-unused-value /fp:strict)
    else()
        set(SFR_DEPTH_VIEW_GUEST_OPTIONS -fno-strict-aliasing -Wno-unused-variable -Wno-unused-value -ffp-model=strict)
    endif()
    if(CMAKE_SYSTEM_PROCESSOR MATCHES "AMD64|amd64|x86_64")
        list(APPEND SFR_DEPTH_VIEW_GUEST_OPTIONS -march=sandybridge)
    endif()
    set_source_files_properties(tests/kinect_depth_view_guest_test.cpp PROPERTIES COMPILE_OPTIONS "${SFR_DEPTH_VIEW_GUEST_OPTIONS}")
    add_test(NAME kinect_depth_view_guest COMMAND sfr_kinect_depth_view_guest_test)
    # The same two functions with every register class as locals
    # (scripts/register_locals.py --file), against the same extractor: the
    # conversion keeps real generated code (vector, CR bit and loop code) bit
    # for bit.
    find_package(Python3 COMPONENTS Interpreter REQUIRED)
    set(SFR_DEPTH_VIEW_LOCALS "${CMAKE_CURRENT_BINARY_DIR}/register-locals-test/kinect_depth_view_guest.inc")
    add_custom_command(OUTPUT "${SFR_DEPTH_VIEW_LOCALS}"
        COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_CURRENT_BINARY_DIR}/register-locals-test"
        COMMAND "${Python3_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/scripts/register_locals.py" --file
                --classes ctr,xer,cr,nonargument,nonvolatile "${SFR_DEPTH_VIEW_GUEST}" "${SFR_DEPTH_VIEW_LOCALS}"
        DEPENDS "${SFR_DEPTH_VIEW_GUEST}" "${CMAKE_CURRENT_SOURCE_DIR}/scripts/register_locals.py"
        VERBATIM)
    add_executable(sfr_kinect_depth_view_locals_test tests/kinect_depth_view_guest_test.cpp src/kinect_depth_features.cpp
        "${SFR_DEPTH_VIEW_LOCALS}")
    set_source_files_properties("${SFR_DEPTH_VIEW_LOCALS}" PROPERTIES HEADER_FILE_ONLY TRUE)
    target_include_directories(sfr_kinect_depth_view_locals_test PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/register-locals-test"
        src "${SFR_DIAGNOSTIC_DIR}" tools/XenonRecomp/thirdparty/simde)
    add_test(NAME kinect_depth_view_locals COMMAND sfr_kinect_depth_view_locals_test)
    if(ANDROID)
        # The game is libmain.so; SDL's activity calls its SDL_main
        # (src/android_main.cpp), which sets up and calls the usual main.
        add_library(sfr_cpu_diagnostic SHARED src/android_main.cpp src/graphics_selftest.cpp)
        set_target_properties(sfr_cpu_diagnostic PROPERTIES OUTPUT_NAME main)
        set_source_files_properties(src/diagnostic_main.cpp PROPERTIES COMPILE_DEFINITIONS main=sfr_game_main)
        target_link_libraries(sfr_cpu_diagnostic PRIVATE android log ${SFR_CAMERA_LIBRARIES})
        # The NDK hardens every translation unit by default. The guest code is
        # thirty thousand generated functions with large frames that only ever
        # touch the checked guest memory, so the canaries and the fortified
        # library calls buy nothing here and are paid for in every one of them.
        target_compile_options(sfr_cpu_diagnostic PRIVATE -fno-stack-protector -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0)
        # A shared library lets its exported functions be interposed, so every
        # call between them went through a PLT stub: 19822 of them, 3.9% of the
        # main thread in a Thor race (simpleperf). Nothing replaces them, so
        # bind the library's own calls inside it.
        target_link_options(sfr_cpu_diagnostic PRIVATE -Wl,-Bsymbolic-functions)
    elseif(IOS)
        # FreeRidersGame.framework, which the launcher loads at Play once it
        # has set the environment the runtime reads while it loads.
        add_library(sfr_cpu_diagnostic SHARED src/ios_game_entry.cpp)
        set_target_properties(sfr_cpu_diagnostic PROPERTIES OUTPUT_NAME FreeRidersGame)
        set_source_files_properties(src/diagnostic_main.cpp PROPERTIES COMPILE_DEFINITIONS main=sfr_game_main)
        target_link_options(sfr_cpu_diagnostic PRIVATE "-Wl,-install_name,@rpath/FreeRidersGame.framework/FreeRidersGame")
        # SDL is shared from the app's Frameworks; the launcher's SDL2main owns main.
        target_link_options(sfr_cpu_diagnostic PRIVATE "-Wl,-rpath,@loader_path/..")
        target_compile_definitions(sfr_cpu_diagnostic PRIVATE SDL_MAIN_HANDLED)
        # The audio session in game.log (src/native_audio.cpp, Issue #249).
        target_sources(sfr_cpu_diagnostic PRIVATE src/ios_audio_session.mm)
        target_link_libraries(sfr_cpu_diagnostic PRIVATE "-framework Foundation" "-framework AVFoundation" objc)
    else()
        # SFR_GRAPHICS_SELFTEST=1 runs the graphics self test instead of the game.
        add_executable(sfr_cpu_diagnostic src/graphics_selftest.cpp)
        if(WIN32)
            # The icon (Issue #63), and narrow strings in UTF-8 (src/game.manifest, Issue #71).
            target_sources(sfr_cpu_diagnostic PRIVATE src/app_icon.rc src/game.manifest)
        endif()
    endif()
    target_sources(sfr_cpu_diagnostic PRIVATE src/diagnostic_main.cpp src/guest_memory.cpp src/xex_module.cpp src/virtual_memory.cpp src/critical_section.cpp src/hardware_info.cpp src/thread_local_storage.cpp src/system_time.cpp src/guest_clock.cpp src/timestamp_bundle.cpp src/vector_memory.cpp src/optional_import_policy.cpp src/native_modules.cpp src/integer_arithmetic.cpp src/memory_statistics.cpp
        ${SFR_DIAGNOSTIC_COMPILED_SOURCES} "${SFR_DIAGNOSTIC_DIR}/ppc_func_mapping.cpp" "${SFR_DIAGNOSTIC_DIR}/imports.cpp")
    if(APPLE)  # the generated names as calls, as Mach-O has no aliases
        if(NOT EXISTS "${SFR_DIAGNOSTIC_DIR}/aliases.cpp")
            message(FATAL_ERROR "Run scripts/generate_diagnostic.py again; this output has no aliases.cpp.")
        endif()
        target_sources(sfr_cpu_diagnostic PRIVATE "${SFR_DIAGNOSTIC_DIR}/aliases.cpp")
    endif()
    target_include_directories(sfr_cpu_diagnostic PRIVATE src "${SFR_DIAGNOSTIC_DIR}" tools/XenonRecomp/thirdparty/simde tools/XenonRecomp/thirdparty/TinySHA1)
    target_sources(sfr_cpu_diagnostic PRIVATE src/video_mode.cpp src/native_display.cpp src/physical_memory.cpp src/ansi_string.cpp src/store_halfword_update.cpp src/store_float_single_update.cpp src/load_halfword_update.cpp src/vector_compare_bounds.cpp src/debug_monitor.cpp src/image_protection.cpp src/resource_coherency.cpp src/video_globals.cpp src/native_language.cpp src/system_config.cpp)
    target_sources(sfr_cpu_diagnostic PRIVATE src/game_region.cpp)
    target_sources(sfr_cpu_diagnostic PRIVATE src/guest_register_hooks.cpp)
    target_sources(sfr_cpu_diagnostic PRIVATE src/race_frame_clock_hooks.cpp src/race_task_hooks.cpp src/draw_read_watch.cpp src/race_orientation_hooks.cpp src/race_boarding_hooks.cpp src/race_launch_hooks.cpp src/race_scripted_motion_hooks.cpp)
    target_sources(sfr_cpu_diagnostic PRIVATE src/offline_presence_hooks.cpp)
    # The webcam standing in for the sensor, and the body it finds.
    target_sources(sfr_cpu_diagnostic PRIVATE src/camera_player.cpp src/camera_debug.cpp src/camera_debug_window.cpp src/pose_skeleton.cpp src/pose_smoothing.cpp src/pose_stability.cpp src/kinect_stability.cpp
                   src/window_title.cpp src/camera_tuner.cpp src/camera_tuner_window.cpp
                   ${SFR_CAMERA_CAPTURE} ${SFR_POSE_SOURCES})
    # A real Kinect (SFR_CAMERA=kinect), through the Kinect for Windows runtime.
    # One door for both: motion_input.h.
    target_sources(sfr_cpu_diagnostic PRIVATE src/motion_input.cpp src/motion_input_device.cpp)
    target_sources(sfr_cpu_diagnostic PRIVATE src/kinect_sensor.cpp ${SFR_KINECT_SENSOR} ${SFR_VOICE}
        src/kinect_preview.cpp src/kinect_preview_window.cpp src/kinect_preview_layout.cpp)
    if(SFR_POSE_AVAILABLE)
        target_link_libraries(sfr_cpu_diagnostic PRIVATE sfr_onnxruntime)
        sfr_copy_onnx_dlls(sfr_cpu_diagnostic)
    endif()
    if(APPLE)
        target_link_libraries(sfr_cpu_diagnostic PRIVATE ${SFR_CAMERA_LIBRARIES})
        if(SFR_FREENECT_AVAILABLE)
            target_link_libraries(sfr_cpu_diagnostic PRIVATE sfr_freenect)
        endif()
    endif()
    target_sources(sfr_cpu_diagnostic PRIVATE src/memory_update_forms.cpp)
    target_sources(sfr_cpu_diagnostic PRIVATE src/native_input.cpp src/input_bindings.cpp src/pad_assignment.cpp src/pad_devices.cpp src/sony_gamepad.cpp src/native_audio.cpp src/nui_skeleton.cpp src/nui_speech.cpp src/nui_race.cpp)
    target_sources(sfr_cpu_diagnostic PRIVATE src/guest_printf.cpp)
    if(WIN32)
        target_link_libraries(sfr_cpu_diagnostic PRIVATE xinput hid setupapi xaudio2 ole32 comctl32)
        # Symbol map for host-side profiling (SFR_HOST_PROFILE).
        target_link_options(sfr_cpu_diagnostic PRIVATE "/MAP:$<TARGET_FILE_DIR:sfr_cpu_diagnostic>/sfr_cpu_diagnostic.map")
    endif()
    target_sources(sfr_cpu_diagnostic PRIVATE src/native_country.cpp)
    target_sources(sfr_cpu_diagnostic PRIVATE src/nui_device_status.cpp)
    target_sources(sfr_cpu_diagnostic PRIVATE src/unselected_users.cpp src/local_profile.cpp src/content_files.cpp)
    target_sources(sfr_cpu_diagnostic PRIVATE src/achievements.cpp)
    # The achievement notification and page (Issue #288); text through
    # Dear ImGui's copy of stb_truetype when it is there.
    target_sources(sfr_cpu_diagnostic PRIVATE src/achievement_overlay.cpp src/achievement_progress.cpp src/achievement_toast.cpp src/overlay_canvas.cpp)
    if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/tools/imgui/imstb_truetype.h")
        set_source_files_properties(src/overlay_canvas.cpp PROPERTIES INCLUDE_DIRECTORIES "${CMAKE_CURRENT_SOURCE_DIR}/tools/imgui")
    endif()
    target_sources(sfr_cpu_diagnostic PRIVATE src/multibyte_unicode.cpp)
    target_sources(sfr_cpu_diagnostic PRIVATE src/guest_critical_sections.cpp)
    target_link_libraries(sfr_cpu_diagnostic PRIVATE sfr_guest_execution)
    target_link_libraries(sfr_cpu_diagnostic PRIVATE sfr_wait_trace)
    target_link_libraries(sfr_cpu_diagnostic PRIVATE sfr_native_timer_resolution)
    target_sources(sfr_cpu_diagnostic PRIVATE src/guest_graphics_hooks.cpp src/game_patches.cpp src/nui_hooks.cpp src/nui_race_hooks.cpp src/pause_gesture.cpp)
    # The title's NuiTransformSmooth and the SFR_NUI_ANALYSIS_TRACE observations (docs/kinect-sensor.md).
    target_sources(sfr_cpu_diagnostic PRIVATE src/nui_smooth_hooks.cpp src/nui_transform_smooth_guest.cpp src/nui_analysis_hooks.cpp)
    # The title's heap on o1heap (SFR_HOST_HEAP=1; docs/architecture-migration.md, phase 2).
    target_sources(sfr_cpu_diagnostic PRIVATE src/heap_hooks.cpp src/guest_heap.cpp)
    target_link_libraries(sfr_cpu_diagnostic PRIVATE sfr_o1heap)
    target_sources(sfr_cpu_diagnostic PRIVATE src/native_winsock.cpp)
    # Xbox LIVE on this project's own service (SFR_LIVE_SERVER; server/live_server.py).
    target_sources(sfr_cpu_diagnostic PRIVATE src/live_client.cpp src/live_connect.cpp src/live_imports.cpp src/live_service.cpp src/live_hooks.cpp)
    target_sources(sfr_cpu_diagnostic PRIVATE src/guest_timers.cpp)
    target_link_libraries(sfr_cpu_diagnostic PRIVATE sfr_native_notifications)
    target_link_libraries(sfr_cpu_diagnostic PRIVATE sfr_guest_graphics sfr_asset_files sfr_native_sync_objects sfr_guest_threads)
    target_link_libraries(sfr_cpu_diagnostic PRIVATE sfr_guest_async_files)
    if(WIN32)
        target_link_libraries(sfr_cpu_diagnostic PRIVATE ws2_32 iphlpapi user32 gdi32)
    else()
        target_link_libraries(sfr_cpu_diagnostic PRIVATE SDL2::SDL2)
    endif()
    if(NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang")  # Clang or AppleClang
        message(FATAL_ERROR "The generated diagnostic currently requires Clang.")
    endif()
    target_compile_options(sfr_cpu_diagnostic PRIVATE -fno-strict-aliasing -Wno-unused-variable -Wno-unused-value)
    if(MSVC)
        target_compile_options(sfr_cpu_diagnostic PRIVATE "/FI${CMAKE_CURRENT_SOURCE_DIR}/src/diagnostic_hooks.h" /fp:strict)
    else()
        target_compile_options(sfr_cpu_diagnostic PRIVATE -include "${CMAKE_CURRENT_SOURCE_DIR}/src/diagnostic_hooks.h" -ffp-model=strict)
    endif()
    if(CMAKE_SYSTEM_PROCESSOR MATCHES "AMD64|amd64|x86_64")
        target_compile_options(sfr_cpu_diagnostic PRIVATE -march=sandybridge)
    endif()
endif()
# D3D12 through the Agility SDK's runtime (scripts/fetch_d3d12_agility.py,
# run by bootstrap on Windows): every program that draws with D3D12 exports
# the SDK version it was fetched at and finds D3D12Core.dll in D3D12\ beside
# it. Without the runtime, Windows 10 cannot make Plume's command lists and
# the game falls back to Vulkan (native_graphics.cpp).
if(WIN32)
    set(SFR_D3D12_AGILITY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/tools/d3d12-agility")
    if(EXISTS "${SFR_D3D12_AGILITY_DIR}/D3D12Core.dll" AND EXISTS "${SFR_D3D12_AGILITY_DIR}/version.txt")
        file(STRINGS "${SFR_D3D12_AGILITY_DIR}/version.txt" SFR_D3D12_SDK_VERSION LIMIT_COUNT 1)
        message(STATUS "D3D12 Agility SDK runtime: ${SFR_D3D12_SDK_VERSION}")
        add_library(sfr_d3d12_agility OBJECT src/d3d12_agility.cpp)
        target_compile_definitions(sfr_d3d12_agility PRIVATE SFR_D3D12_SDK_VERSION=${SFR_D3D12_SDK_VERSION})
        # One copy for all of them: they are all built into this directory.
        set(SFR_D3D12_CORE "${CMAKE_BINARY_DIR}/D3D12/D3D12Core.dll")
        add_custom_command(OUTPUT "${SFR_D3D12_CORE}"
            COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/D3D12"
            COMMAND ${CMAKE_COMMAND} -E copy_if_different "${SFR_D3D12_AGILITY_DIR}/D3D12Core.dll" "${SFR_D3D12_CORE}"
            DEPENDS "${SFR_D3D12_AGILITY_DIR}/D3D12Core.dll")
        add_custom_target(sfr_d3d12_runtime DEPENDS "${SFR_D3D12_CORE}")
        foreach(target sfr_cpu_diagnostic sfr_native_graphics_test sfr_native_upload_batch_test sfr_native_shaders_test sfr_native_presentation_test
                       sfr_native_resolution_test sfr_native_pipeline_prewarm_test sfr_shader_cache_rebuild sfr_native_shader_abi_test
                       sfr_native_raster_state_test sfr_native_blend_control_test sfr_guest_graphics_test
                       sfr_guest_render_state_test sfr_guest_blend_request_test sfr_guest_sampler_filter_test
                       sfr_guest_texture_binding_test)
            if(TARGET ${target})
                target_sources(${target} PRIVATE $<TARGET_OBJECTS:sfr_d3d12_agility>)
                add_dependencies(${target} sfr_d3d12_runtime)
            endif()
        endforeach()
    else()
        message(STATUS "D3D12 Agility SDK runtime: not fetched (Windows 10 will draw with Vulkan)")
    endif()
endif()
if(SFR_PGO_PROFILE)
    # The profile counts only the game library: the launcher is built
    # without it (a function both define, such as SDL_main, would otherwise
    # be reported as changed).
    if(TARGET sfr_launcher)
        target_compile_options(sfr_launcher PRIVATE -fno-profile-use)
    endif()
    if(TARGET sfr_cpu_diagnostic)
        add_custom_command(TARGET sfr_cpu_diagnostic POST_BUILD
            COMMAND "${Python3_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/scripts/pgo_compile.py" --summary "${CMAKE_CURRENT_BINARY_DIR}"
            VERBATIM)
    endif()
endif()
