cmake_minimum_required(VERSION 3.20)
project(wwhd_recomp C CXX)
# Renderers compiled into the executable:
#   BOTH   (macOS default) Metal and Vulkan (MoltenVK) in one app; Graphics > Renderer picks one at start
#   METAL  Metal only (macOS)
#   VULKAN Vulkan only, with the portable SDL3 host (default on other platforms)
set(WWHD_RENDERER "" CACHE STRING "Graphics backends: BOTH, METAL or VULKAN")
if(NOT WWHD_RENDERER)
  if(APPLE)
    set(WWHD_RENDERER BOTH)
  else()
    set(WWHD_RENDERER VULKAN)
  endif()
endif()
string(TOUPPER "${WWHD_RENDERER}" WWHD_RENDERER)
set_property(CACHE WWHD_RENDERER PROPERTY STRINGS BOTH METAL VULKAN)
if(NOT WWHD_RENDERER MATCHES "^(BOTH|METAL|VULKAN)$")
  message(FATAL_ERROR "WWHD_RENDERER must be BOTH, METAL or VULKAN")
endif()
set(WWHD_HAS_METAL OFF)
set(WWHD_HAS_VULKAN OFF)
if(WWHD_RENDERER MATCHES "^(BOTH|METAL)$")
  if(NOT APPLE)
    message(FATAL_ERROR "Metal is available only on macOS; select VULKAN")
  endif()
  set(WWHD_HAS_METAL ON)
  enable_language(OBJCXX)
endif()
if(WWHD_RENDERER MATCHES "^(BOTH|VULKAN)$")
  set(WWHD_HAS_VULKAN ON)
endif()
# the SDL3 host (windows, input, audio) serves Vulkan-only builds; with Metal compiled in, the
# AppKit host (TV/GamePad windows, menus, controls window) serves both renderers
if(WWHD_HAS_VULKAN AND NOT WWHD_HAS_METAL)
  set(WWHD_SDL_HOST ON)
else()
  set(WWHD_SDL_HOST OFF)
endif()
message(STATUS "WWHD renderers: ${WWHD_RENDERER} (Metal ${WWHD_HAS_METAL}, Vulkan ${WWHD_HAS_VULKAN}, SDL host ${WWHD_SDL_HOST})")
if(NOT CMAKE_C_COMPILER_ID MATCHES "Clang")
  message(FATAL_ERROR "The recompiled game requires Clang (musttail support); select clang/clang++")
endif()

# Linux and Android on arm64: plain char is unsigned in that ABI, but signed on x86-64 and Apple
# arm64, where the runtime and the recompiled game code are tested. Keep it signed everywhere (also
# reaches the game code the installer compiles: its flags come from this build).
if(CMAKE_SYSTEM_NAME MATCHES "^(Linux|Android)$" AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64|arm64|ARM64)$")
  add_compile_options(-fsigned-char)
endif()

set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
if(NOT CMAKE_BUILD_TYPE)
  set(CMAKE_BUILD_TYPE RelWithDebInfo)
endif()

# Generated by tools/recomp/recomp.py from game/code/cking.rpx
set(GEN_DIR ${CMAKE_SOURCE_DIR}/build/gen CACHE PATH "recompiler output")
file(GLOB GEN_SOURCES CONFIGURE_DEPENDS ${GEN_DIR}/code_*.c)
if(NOT GEN_SOURCES)
  message(FATAL_ERROR "no generated code in ${GEN_DIR}; run tools/recomp/recomp.py first")
endif()

# WWHD_BUNDLED_DEPS: build glslang, zlib, LZ4 (and SDL3 if not found) from pinned sources with the
# project's own compiler. Release builds use it so the prebuilt runtime and its dependencies all
# come from the toolchain the installer later links with (llvm-mingw, zig).
option(WWHD_BUNDLED_DEPS "build missing dependencies from pinned sources" OFF)
if(WIN32 AND CMAKE_CXX_SIMULATE_ID STREQUAL "MSVC" OR WWHD_BUNDLED_DEPS)
  include(cmake/WindowsDependencies.cmake)
endif()
if(WIN32 AND CMAKE_CXX_SIMULATE_ID STREQUAL "MSVC")
  add_compile_definitions(NOMINMAX _CRT_SECURE_NO_WARNINGS)
endif()

add_library(gamecode STATIC ${GEN_SOURCES} ${GEN_DIR}/table.c ${GEN_DIR}/imports.c)
target_include_directories(gamecode PUBLIC runtime/include ${GEN_DIR})
# exact FP semantics: no contraction into FMA, no fast-math
target_compile_options(gamecode PRIVATE -O3 -ffp-contract=off -fno-strict-aliasing -w)
if(APPLE AND CMAKE_SYSTEM_PROCESSOR MATCHES "arm64|aarch64")
  target_compile_options(gamecode PRIVATE -mcpu=apple-m1)
endif()


# ---- vendored Cemu GPU pieces (MPL-2.0): address library + shader decompiler
set(CEMU_DIR ${CMAKE_SOURCE_DIR}/runtime/third_party/cemu)
file(GLOB_RECURSE CEMU_SOURCES CONFIGURE_DEPENDS ${CEMU_DIR}/*.cpp)
if(NOT WWHD_HAS_METAL)
  # the MSL emitter needs metal-cpp (Objective-C runtime); Vulkan uses the GLSL emitter
  list(FILTER CEMU_SOURCES EXCLUDE REGEX "LatteDecompilerEmitMSL[^/]*\\.cpp$")
endif()
add_library(cemu_latte STATIC ${CEMU_SOURCES})
target_include_directories(cemu_latte PUBLIC ${CEMU_DIR} ${CEMU_DIR}/Cafe ${CMAKE_SOURCE_DIR}/runtime/third_party/metal-cpp
                                             ${CMAKE_SOURCE_DIR}/runtime/third_party/fmt/include)
target_compile_options(cemu_latte PRIVATE "SHELL:-include ${CEMU_DIR}/cemu_shim.h")
if(WWHD_HAS_METAL)
  target_compile_definitions(cemu_latte PUBLIC ENABLE_METAL=1)
endif()
if(WWHD_HAS_VULKAN)
  target_compile_definitions(cemu_latte PUBLIC ENABLE_VULKAN=1)
endif()
target_compile_options(cemu_latte PRIVATE -w -O2)

# ---- Dear ImGui (MIT, runtime/third_party/imgui, pinned release): the in-game settings overlay
set(IMGUI_DIR ${CMAKE_SOURCE_DIR}/runtime/third_party/imgui)
add_library(imgui STATIC ${IMGUI_DIR}/imgui.cpp ${IMGUI_DIR}/imgui_draw.cpp ${IMGUI_DIR}/imgui_tables.cpp ${IMGUI_DIR}/imgui_widgets.cpp)
target_include_directories(imgui PUBLIC ${IMGUI_DIR})
target_compile_definitions(imgui PUBLIC IMGUI_DISABLE_DEMO_WINDOWS IMGUI_DISABLE_OBSOLETE_FUNCTIONS)
target_compile_options(imgui PRIVATE -w -O2)

# ---- build identification (runtime/src/build_info.h): version and commit, rewritten by
# cmake/BuildInfo.cmake on every build when they change. Release builds pass the tag.
set(WWHD_VERSION "" CACHE STRING "release version shown in reports and logs (default: from git describe)")
set(WWHD_BUILD_INFO ${CMAKE_BINARY_DIR}/generated/build_info.cpp)
add_custom_target(wwhd_build_info
  COMMAND ${CMAKE_COMMAND} -DSRC=${CMAKE_SOURCE_DIR} -DOUT=${WWHD_BUILD_INFO} "-DVERSION=${WWHD_VERSION}"
          -P ${CMAKE_SOURCE_DIR}/cmake/BuildInfo.cmake
  BYPRODUCTS ${WWHD_BUILD_INFO}
  COMMENT "Build info (version, commit)" VERBATIM)
# Windows: VERSIONINFO + application manifest (wwhd_windows_resources); release builds strip the shipped
# programs' debug information (WWHD_STRIP_RELEASE: -s for GNU-style linkers in Release/MinSizeRel)
include(cmake/WindowsResources.cmake)
if(WIN32 AND NOT MSVC AND NOT CMAKE_CXX_SIMULATE_ID STREQUAL "MSVC")
  set(WWHD_STRIP_RELEASE "$<$<OR:$<CONFIG:Release>,$<CONFIG:MinSizeRel>>:-s>")
else()
  set(WWHD_STRIP_RELEASE "")
endif()

# ---- the game executable: runtime + native GX2 + the selected renderers
# GX2 sources are compiled into the executable (not a static library) so their
# strong HLE definitions replace the generated weak import stubs.
file(GLOB RUNTIME_SOURCES CONFIGURE_DEPENDS runtime/src/*.cpp runtime/src/*.c runtime/src/hle/*.cpp runtime/src/mods/*.cpp runtime/src/motion/*.cpp
                                            runtime/src/overlay/*.cpp)
list(APPEND RUNTIME_SOURCES runtime/src/platform/perf_hint.cpp)  # Android performance hints (no-op elsewhere)
list(APPEND RUNTIME_SOURCES ${WWHD_BUILD_INFO})
# Cemu graphic packs bundled with the runtime (runtime/data/graphic_packs, CC0): compiled in as text,
# so the app, installer and release need no extra files (mods/graphic_packs_data.h)
file(GLOB_RECURSE GRAPHIC_PACK_FILES CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/runtime/data/graphic_packs/*.txt)
list(SORT GRAPHIC_PACK_FILES)
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${GRAPHIC_PACK_FILES})  # an edited pack file regenerates
set(GP_TEXT "// generated by CMakeLists.txt from runtime/data/graphic_packs\n#include \"mods/graphic_packs_data.h\"\nnamespace graphic_packs {\nconst EmbeddedFile kFiles[] = {\n")
foreach(f ${GRAPHIC_PACK_FILES})
  file(RELATIVE_PATH rel ${CMAKE_SOURCE_DIR}/runtime/data/graphic_packs ${f})
  file(READ ${f} content)
  string(APPEND GP_TEXT "    {\"${rel}\", R\"gp_(${content})gp_\"},\n")
endforeach()
string(APPEND GP_TEXT "    {nullptr, nullptr}};\nconst size_t kFileCount = sizeof kFiles / sizeof kFiles[0] - 1;\n}  // namespace graphic_packs\n")
file(WRITE ${CMAKE_BINARY_DIR}/graphic_packs_data.cpp.new "${GP_TEXT}")
configure_file(${CMAKE_BINARY_DIR}/graphic_packs_data.cpp.new ${CMAKE_BINARY_DIR}/graphic_packs_data.cpp COPYONLY)
list(APPEND RUNTIME_SOURCES ${CMAKE_BINARY_DIR}/graphic_packs_data.cpp)
list(APPEND RUNTIME_SOURCES runtime/src/platform/touch_pad.cpp)  # Android on-screen controls (no-op elsewhere)
if(APPLE)
  list(APPEND RUNTIME_SOURCES runtime/src/platform/https.mm)
  set_source_files_properties(runtime/src/platform/https.mm PROPERTIES COMPILE_FLAGS "-fobjc-arc")
  list(APPEND RUNTIME_SOURCES runtime/src/platform/app_launch.mm)  # the app bundle's start (Game Mode)
  set_source_files_properties(runtime/src/platform/app_launch.mm PROPERTIES COMPILE_FLAGS "-fobjc-arc")
elseif(ANDROID)
  list(APPEND RUNTIME_SOURCES runtime/src/platform/https_android.cpp)
elseif(WIN32)
  list(APPEND RUNTIME_SOURCES runtime/src/platform/https_win.cpp)
elseif(UNIX AND NOT ANDROID)
  # libcurl is loaded at run time (dlopen); only its headers are needed. They are copied on their own:
  # their directory (e.g. /usr/include/x86_64-linux-gnu) also holds the system's glibc headers, which
  # must not shadow the libc headers of the release toolchain (zig).
  find_package(CURL REQUIRED)
  find_path(WWHD_CURL_HEADERS curl/curl.h HINTS ${CURL_INCLUDE_DIRS} REQUIRED)
  file(COPY ${WWHD_CURL_HEADERS}/curl DESTINATION ${CMAKE_BINARY_DIR}/curl-headers)
  set(WWHD_CURL_INCLUDE ${CMAKE_BINARY_DIR}/curl-headers)
  list(APPEND RUNTIME_SOURCES runtime/src/platform/https_curl.cpp)
endif()
file(GLOB GX2_SOURCES CONFIGURE_DEPENDS runtime/src/gx2/*.cpp)
# renderer selection / dispatch (runtime/src/gfx/renderer.cpp) and the GamePad screen modes / TV window
# layout both window hosts share (display_modes.cpp): every configuration
set(GFX_SOURCES runtime/src/gfx/renderer.cpp runtime/src/gfx/display_modes.cpp)
set(MOD_GFX_SOURCES)
if(WWHD_HAS_METAL)
  # Metal renderer and the AppKit host (windows, menus, input, controls window, display modes)
  file(GLOB METAL_SOURCES CONFIGURE_DEPENDS runtime/src/gfx/*.mm)
  file(GLOB MOD_GFX_SOURCES CONFIGURE_DEPENDS runtime/src/mods/*.mm)
  list(APPEND GFX_SOURCES ${METAL_SOURCES})
  set_source_files_properties(${METAL_SOURCES} ${MOD_GFX_SOURCES} PROPERTIES COMPILE_FLAGS "-fobjc-arc")
  # the settings overlay's Metal renderer (with guest HUD blend markers)
  set(IMGUI_METAL ${IMGUI_DIR}/backends/imgui_impl_metal.mm)
  set_source_files_properties(${IMGUI_METAL} PROPERTIES COMPILE_FLAGS "-fobjc-arc -w")
endif()
if(WWHD_HAS_VULKAN)
  find_package(Vulkan 1.3 REQUIRED)
  if(NOT TARGET glslang::glslang)
    find_package(glslang CONFIG REQUIRED)
  endif()
  file(GLOB VULKAN_SOURCES CONFIGURE_DEPENDS runtime/src/gfx/vulkan/*.cpp)
  list(APPEND GFX_SOURCES ${VULKAN_SOURCES})
  if(APPLE)
    list(APPEND GFX_SOURCES runtime/src/platform/native.mm)
    set_source_files_properties(runtime/src/platform/native.mm PROPERTIES COMPILE_FLAGS "-fobjc-arc")
  endif()
endif()
if(WWHD_SDL_HOST)
  if(NOT TARGET SDL3::SDL3)
    find_package(SDL3 CONFIG REQUIRED)
  endif()
  list(APPEND GFX_SOURCES runtime/src/platform/input_sdl.cpp runtime/src/platform/mouse_sdl.cpp runtime/src/platform/display_rate.cpp)
  if(APPLE)
    enable_language(OBJCXX)
    list(APPEND GFX_SOURCES runtime/src/gfx/vulkan/graphics_menu.mm)
    set_source_files_properties(runtime/src/gfx/vulkan/graphics_menu.mm PROPERTIES COMPILE_FLAGS "-fobjc-arc")
  endif()
endif()
set_source_files_properties(${GX2_SOURCES} ${GFX_SOURCES} PROPERTIES COMPILE_OPTIONS "-include;${CEMU_DIR}/cemu_shim.h")
if(ANDROID)
  # Android: SDLActivity loads the game as libmain.so and calls its SDL_main (android/)
  add_library(wwhd SHARED ${RUNTIME_SOURCES} ${GX2_SOURCES} ${GFX_SOURCES} ${MOD_GFX_SOURCES} ${IMGUI_METAL})
  set_target_properties(wwhd PROPERTIES OUTPUT_NAME main)
else()
  add_executable(wwhd ${RUNTIME_SOURCES} ${GX2_SOURCES} ${GFX_SOURCES} ${MOD_GFX_SOURCES} ${IMGUI_METAL})
endif()
target_include_directories(wwhd PRIVATE runtime/include runtime/src runtime/src/gx2)
add_dependencies(wwhd wwhd_build_info)
target_compile_options(wwhd PRIVATE -Wall -Wno-unused-function -Wno-unused-variable -ffp-contract=off)
# profiling: line tables for the runtime (Instruments shows the hot lines; the code is unchanged). Off by
# default: the debug map records this build's absolute object paths in the binary
option(WWHD_LINE_TABLES "Line tables for the runtime (profiling)" OFF)
if(WWHD_LINE_TABLES)
  target_compile_options(wwhd PRIVATE -gline-tables-only)
endif()
if(NOT TARGET ZLIB::ZLIB)
  find_package(ZLIB REQUIRED)
endif()
find_package(Threads REQUIRED)
if(UNIX AND NOT APPLE AND NOT ANDROID)
  target_include_directories(wwhd PRIVATE ${WWHD_CURL_INCLUDE})
  target_link_libraries(wwhd PRIVATE ${CMAKE_DL_LIBS})
endif()
target_link_libraries(wwhd PRIVATE gamecode cemu_latte imgui ZLIB::ZLIB Threads::Threads)
if(ANDROID)
  target_link_libraries(wwhd PRIVATE log android)
endif()
if(WIN32)
  target_compile_definitions(wwhd PRIVATE _USE_MATH_DEFINES)
  wwhd_windows_resources(wwhd "The Wind Waker HD, native PC port" "wwhd.exe" gui)
  # advapi32: the Windows version for the performance report (report_header.cpp, RegGetValueW)
  target_link_libraries(wwhd PRIVATE winmm ws2_32 advapi32 winhttp)  # timeBeginPeriod (main.cpp), Cemuhook client sockets (motion/dsu.cpp)
endif()
if(WWHD_HAS_METAL)
  target_compile_definitions(wwhd PRIVATE WWHD_HAS_METAL=1)
  target_link_libraries(wwhd PRIVATE "-framework Metal" "-framework MetalFX" "-framework QuartzCore" "-framework AppKit" "-framework Foundation" "-framework ImageIO" "-framework UniformTypeIdentifiers" "-framework GameController" "-framework Carbon" "-framework AudioToolbox" "-framework CoreAudio" "-framework VideoToolbox" "-framework CoreMedia" "-framework CoreVideo" compression)
endif()
if(WWHD_HAS_VULKAN)
  # VK_NO_PROTOTYPES: the renderer loads every Vulkan function at run time (gfx/vulkan/loader.h), so the
  # executable starts without a Vulkan loader or with an older one, and can say what is missing
  target_compile_definitions(wwhd PRIVATE WWHD_HAS_VULKAN=1 VK_NO_PROTOTYPES)
  if(WWHD_HAS_METAL)
    # One app, two renderers: the Vulkan loader and glslang are weak imports, so the game still
    # starts (with Metal) on a Mac without them; the renderer checks for them before using Vulkan.
    # SYSTEM: searched after the project's own -I paths, so a Homebrew boost/fmt in the same prefix
    # (/opt/homebrew/include) can't replace the vendored ones (cemu_latte is built with those; a
    # different boost changed LatteDecompilerShader's layout and crashed the first shader translation)
    target_include_directories(wwhd SYSTEM PRIVATE $<TARGET_PROPERTY:Vulkan::Vulkan,INTERFACE_INCLUDE_DIRECTORIES>
                                            $<TARGET_PROPERTY:glslang::glslang,INTERFACE_INCLUDE_DIRECTORIES>)
    # Release builds (WWHD_BUNDLED_DEPS) compile glslang statically into the game and ship the Vulkan
    # loader and MoltenVK next to it; only shared libraries are weak.
    target_link_options(wwhd PRIVATE "LINKER:-weak_library,$<TARGET_FILE:Vulkan::Vulkan>")
    get_target_property(WWHD_GLSLANG_TYPE glslang::glslang TYPE)
    if(WWHD_GLSLANG_TYPE STREQUAL "SHARED_LIBRARY")
      target_compile_definitions(wwhd PRIVATE WWHD_GLSLANG_WEAK=1)
      foreach(lib glslang::glslang glslang::SPIRV glslang::glslang-default-resource-limits)
        target_link_options(wwhd PRIVATE "LINKER:-weak_library,$<TARGET_FILE:${lib}>")
      endforeach()
    else()
      target_link_libraries(wwhd PRIVATE glslang::glslang glslang::SPIRV glslang::glslang-default-resource-limits)
    endif()
  elseif(APPLE)
    # SDL host on a Mac: Homebrew's loader is linked; SDL and the renderer find it in the process
    target_link_libraries(wwhd PRIVATE Vulkan::Vulkan glslang::glslang glslang::SPIRV glslang::glslang-default-resource-limits)
  else()
    # Windows, Linux, Android: SDL loads the Vulkan loader at run time (SDL_Vulkan_LoadLibrary);
    # only its headers are used here
    target_include_directories(wwhd SYSTEM PRIVATE $<TARGET_PROPERTY:Vulkan::Vulkan,INTERFACE_INCLUDE_DIRECTORIES>)
    target_link_libraries(wwhd PRIVATE glslang::glslang glslang::SPIRV glslang::glslang-default-resource-limits)
  endif()
endif()
if(WWHD_SDL_HOST)
  target_compile_definitions(wwhd PRIVATE WWHD_SDL_HOST=1)
  target_link_libraries(wwhd PRIVATE SDL3::SDL3)
  if(APPLE)
    target_link_libraries(wwhd PRIVATE compression "-framework Foundation" "-framework AppKit")
  else()
    if(TARGET lz4_static)
      target_link_libraries(wwhd PRIVATE lz4_static)
    else()
      find_path(LZ4_INCLUDE_DIR lz4.h REQUIRED)
      find_library(LZ4_LIBRARY NAMES lz4 REQUIRED)
      target_include_directories(wwhd PRIVATE ${LZ4_INCLUDE_DIR})
      target_link_libraries(wwhd PRIVATE ${LZ4_LIBRARY} ${CMAKE_DL_LIBS})
    endif()
  endif()
endif()

if(WWHD_HAS_METAL)
add_executable(shadertest runtime/tools/shadertest.cpp runtime/src/gx2/gx2_shader_regs.cpp)
target_include_directories(shadertest PRIVATE runtime/include runtime/src runtime/src/gx2)
target_link_libraries(shadertest PRIVATE cemu_latte)
target_compile_options(shadertest PRIVATE "SHELL:-include ${CEMU_DIR}/cemu_shim.h")

endif()


# recomp mod-manager preferences and catalogue (standalone host stubs)

# External native package fixture and isolated lifecycle/integration tests.
add_library(mod_fixture MODULE runtime/tools/mod_fixture.cpp)
target_include_directories(mod_fixture PRIVATE runtime/include)
add_executable(mod_package_test runtime/tools/mod_package_test.cpp runtime/src/mods/manager.cpp runtime/src/mods/packages.cpp runtime/src/mods/guest_png.cpp runtime/src/mods/mod_archive.cpp runtime/src/mods/content.cpp runtime/src/mods/cemu_pack.cpp runtime/src/guest_addr_identity.c)
target_include_directories(mod_package_test PRIVATE runtime/src runtime/include)
target_compile_options(mod_package_test PRIVATE -UNDEBUG)
target_link_libraries(mod_package_test PRIVATE ZLIB::ZLIB Threads::Threads ${CMAKE_DL_LIBS})
add_executable(guest_png_test runtime/tools/guest_png_test.cpp runtime/src/mods/guest_png.cpp runtime/src/mods/mod_archive.cpp)
target_include_directories(guest_png_test PRIVATE runtime/src)
target_compile_options(guest_png_test PRIVATE -UNDEBUG)
target_link_libraries(guest_png_test PRIVATE ZLIB::ZLIB)
add_test(NAME guest_png COMMAND guest_png_test "${CMAKE_SOURCE_DIR}/examples/guest-mods")

# Synthetic integration tests call the real guest filesystem HLE handlers.
add_executable(mod_content_fs_test runtime/tools/mod_content_fs_test.cpp runtime/src/hle/fs.cpp runtime/src/mods/content.cpp runtime/src/mods/mod_archive.cpp runtime/src/game_languages.cpp runtime/src/guest_addr_identity.c runtime/src/mods/mod_loader.cpp runtime/src/mods/content_merge.cpp runtime/src/mods/botw_formats.cpp runtime/src/mods/aamp.cpp
    runtime/src/mods/bcml_merge.cpp runtime/src/mods/bcml_tables.cpp runtime/src/mods/byml.cpp runtime/src/mods/msbt.cpp)
target_include_directories(mod_content_fs_test PRIVATE runtime/src runtime/include)
target_compile_options(mod_content_fs_test PRIVATE -UNDEBUG)
target_link_libraries(mod_content_fs_test PRIVATE ZLIB::ZLIB Threads::Threads)

# language sources: synthetic European/Japanese packs read through the guest file system (game_languages.h)
add_executable(language_source_test runtime/tools/language_source_test.cpp runtime/src/hle/fs.cpp runtime/src/mods/content.cpp runtime/src/mods/mod_archive.cpp runtime/src/game_languages.cpp runtime/src/guest_addr_identity.c runtime/src/mods/mod_loader.cpp runtime/src/mods/content_merge.cpp runtime/src/mods/botw_formats.cpp runtime/src/mods/aamp.cpp
    runtime/src/mods/bcml_merge.cpp runtime/src/mods/bcml_tables.cpp runtime/src/mods/byml.cpp runtime/src/mods/msbt.cpp)
target_include_directories(language_source_test PRIVATE runtime/src runtime/include)
target_compile_options(language_source_test PRIVATE -UNDEBUG)
target_link_libraries(language_source_test PRIVATE ZLIB::ZLIB Threads::Threads)

# controls mapping unit tests (runtime/src/input_map.cpp)
# performance report header: driver version decoding, OS names, overrides list (report_header.h)
add_executable(report_header_test runtime/tools/report_header_test.cpp runtime/src/report_header.cpp ${WWHD_BUILD_INFO})
target_include_directories(report_header_test PRIVATE runtime/src)
target_compile_options(report_header_test PRIVATE -UNDEBUG)
add_dependencies(report_header_test wwhd_build_info)
if(WIN32)
  target_link_libraries(report_header_test PRIVATE advapi32)
endif()

foreach(test register_blocks scheduler_tick full_state_header)
  add_executable(${test}_test runtime/tools/${test}_test.cpp)
  target_include_directories(${test}_test PRIVATE runtime/src)
  target_compile_options(${test}_test PRIVATE -UNDEBUG)
endforeach()
target_link_libraries(scheduler_tick_test PRIVATE Threads::Threads)

# GX2 sampler LOD bias decode (runtime/src/gfx/sampler_lod_bias.h)
add_executable(sampler_lod_bias_test runtime/tools/sampler_lod_bias_test.cpp)
target_include_directories(sampler_lod_bias_test PRIVATE runtime/src)
target_link_libraries(sampler_lod_bias_test PRIVATE cemu_latte)
target_compile_options(sampler_lod_bias_test PRIVATE -UNDEBUG "SHELL:-include \"${CEMU_DIR}/cemu_shim.h\"")
add_test(NAME sampler_lod_bias COMMAND sampler_lod_bias_test)

add_executable(portable_state_test runtime/tools/portable_state_test.cpp runtime/src/portable_state.cpp)
target_include_directories(portable_state_test PRIVATE runtime/src)
target_compile_options(portable_state_test PRIVATE -UNDEBUG)
# register class tables of the GX2 register writes (runtime/src/gx2/register_class.h)
add_executable(register_class_test runtime/tools/register_class_test.cpp)
target_include_directories(register_class_test PRIVATE runtime/include runtime/src runtime/src/gx2 ${CEMU_DIR} ${CEMU_DIR}/Cafe ${CMAKE_SOURCE_DIR}/runtime/third_party/fmt/include)
target_compile_options(register_class_test PRIVATE "SHELL:-include ${CEMU_DIR}/cemu_shim.h" -UNDEBUG)
target_link_libraries(register_class_test PRIVATE Threads::Threads)
add_test(NAME register_class COMMAND register_class_test)
# dynamic resolution's decisions (runtime/src/dynres_core.h)
add_executable(dynres_test runtime/tools/dynres_test.cpp)
target_include_directories(dynres_test PRIVATE runtime/src)
target_compile_options(dynres_test PRIVATE -UNDEBUG)
add_test(NAME dynres COMMAND dynres_test)
add_executable(capture_schedule_test runtime/tools/capture_schedule_test.cpp)
target_include_directories(capture_schedule_test PRIVATE runtime/src)
target_compile_options(capture_schedule_test PRIVATE -UNDEBUG)
add_test(NAME capture_schedule COMMAND capture_schedule_test)

add_executable(headless_compose_test runtime/tools/headless_compose_test.cpp)
target_include_directories(headless_compose_test PRIVATE runtime/src)
target_compile_options(headless_compose_test PRIVATE -UNDEBUG)

add_executable(input_map_test runtime/tools/input_map_test.cpp runtime/src/input_map.cpp runtime/src/mouse_look.cpp)
target_include_directories(input_map_test PRIVATE runtime/src)
# what the game gets back after the settings overlay or the text prompt (overlay/release_gate.h, upstream #153)
add_executable(release_gate_test runtime/tools/release_gate_test.cpp)
target_include_directories(release_gate_test PRIVATE runtime/src)
target_compile_options(release_gate_test PRIVATE -UNDEBUG)
# screenshots: PNG writer, file names, binding, requests and the encoding thread (runtime/src/screenshot.cpp)
add_executable(screenshot_test runtime/tools/screenshot_test.cpp runtime/src/screenshot.cpp runtime/src/input_map.cpp)
target_include_directories(screenshot_test PRIVATE runtime/src runtime/include)
target_compile_options(screenshot_test PRIVATE -UNDEBUG)
target_link_libraries(screenshot_test PRIVATE ZLIB::ZLIB Threads::Threads)
# the text prompt's reader of the game's name font (runtime/src/overlay/game_font.cpp)
add_executable(rtl_text_test runtime/tools/rtl_text_test.cpp runtime/src/rtl_text.cpp)
target_include_directories(rtl_text_test PRIVATE runtime/src)
target_compile_options(rtl_text_test PRIVATE -UNDEBUG)
add_executable(game_font_test runtime/tools/game_font_test.cpp runtime/src/overlay/game_font.cpp runtime/src/game_languages.cpp)
target_include_directories(game_font_test PRIVATE runtime/src runtime/include)
target_compile_options(game_font_test PRIVATE -UNDEBUG)
target_link_libraries(game_font_test PRIVATE Threads::Threads)
# the game's languages from its language packs (runtime/src/game_languages.cpp)
add_executable(game_languages_test runtime/tools/game_languages_test.cpp runtime/src/game_languages.cpp)
target_include_directories(game_languages_test PRIVATE runtime/src runtime/include)
target_compile_options(game_languages_test PRIVATE -UNDEBUG)
target_link_libraries(game_languages_test PRIVATE Threads::Threads)
# content mods (Settings > Mods): Yaz0, SARC, AAMP and size-table formats, merging, BCML merge data, mod folders
add_executable(content_merge_test runtime/tools/content_merge_test.cpp runtime/src/mods/botw_formats.cpp
    runtime/src/mods/content_merge.cpp runtime/src/mods/mod_loader.cpp runtime/src/mods/aamp.cpp runtime/src/mods/bcml_merge.cpp runtime/src/mods/bcml_tables.cpp
    runtime/src/mods/byml.cpp runtime/src/mods/msbt.cpp runtime/src/guest_addr_identity.c)
target_include_directories(content_merge_test PRIVATE runtime/src runtime/include)
target_compile_options(content_merge_test PRIVATE -UNDEBUG)
target_link_libraries(content_merge_test PRIVATE ZLIB::ZLIB Threads::Threads)
# code mods (Settings > Mods): preset values with Cemu's arithmetic, Cemu's presets, pack folders and their
# fingerprints, as tools/botw_code_mods.py has them (runtime/src/mods/code_patch_rules.cpp)
add_executable(code_patch_rules_test runtime/tools/code_patch_rules_test.cpp runtime/src/mods/code_patch_rules.cpp)
target_include_directories(code_patch_rules_test PRIVATE runtime/src runtime/include)
target_compile_options(code_patch_rules_test PRIVATE -UNDEBUG)
# ... and the runtime that switches them (runtime/src/mods/code_patches.cpp), on a hand-written manifest
if(NOT WIN32)
  add_executable(code_patches_test runtime/tools/code_patches_test.cpp runtime/src/mods/code_patches.cpp
      runtime/src/mods/code_patch_rules.cpp)
  target_include_directories(code_patches_test PRIVATE runtime/src runtime/include)
  target_compile_options(code_patches_test PRIVATE -UNDEBUG)
  target_link_libraries(code_patches_test PRIVATE Threads::Threads)
endif()
# the installed release (USA or Europe) and the region the game is told (runtime/src/game_release.cpp)
add_executable(game_release_test runtime/tools/game_release_test.cpp runtime/src/game_release.cpp)
target_include_directories(game_release_test PRIVATE runtime/src runtime/include)
target_compile_options(game_release_test PRIVATE -UNDEBUG)
# frame interpolation: blend fractions, display cap and paced scheduling (runtime/src/interp_pacing.h)
add_executable(interp_pacing_test runtime/tools/interp_pacing_test.cpp)
target_compile_options(interp_pacing_test PRIVATE -UNDEBUG)
add_executable(countdown_test runtime/tools/countdown_test.cpp runtime/src/countdown.cpp runtime/src/guest_addr_identity.c)
target_include_directories(countdown_test PRIVATE runtime/src runtime/include)
target_compile_options(countdown_test PRIVATE -UNDEBUG)
add_test(NAME countdown COMMAND countdown_test)
if(UNIX)
  add_executable(interp_cloth_test runtime/tools/interp_cloth_test.cpp runtime/src/guest_addr_identity.c)
  target_include_directories(interp_cloth_test PRIVATE runtime/include runtime/src)
  target_compile_options(interp_cloth_test PRIVATE -UNDEBUG -ffunction-sections -fdata-sections)
  if(APPLE)
    target_link_options(interp_cloth_test PRIVATE -Wl,-dead_strip)
  else()
    target_link_options(interp_cloth_test PRIVATE -Wl,--gc-sections)
  endif()
  add_test(NAME interp_cloth COMMAND interp_cloth_test)
endif()
# quit prompt decisions: when closing the TV window or Quit asks first (runtime/src/quit_prompt.h, issue #65)
add_executable(quit_prompt_test runtime/tools/quit_prompt_test.cpp)
target_compile_options(quit_prompt_test PRIVATE -UNDEBUG)
# texture change detection: page write tracking of guest memory (runtime/src/write_watch.cpp)
add_executable(write_watch_test runtime/tools/write_watch_test.cpp runtime/src/write_watch.cpp)
target_include_directories(write_watch_test PRIVATE runtime/src)
target_compile_options(write_watch_test PRIVATE -UNDEBUG)
target_link_libraries(write_watch_test PRIVATE Threads::Threads)
# recompiled spin waits see other threads' stores (PPC_LOOP in runtime/include/ppc.h, issue #62):
# built with the game code's flags
if(NOT WIN32)
  add_executable(spin_wait_test runtime/tools/spin_wait_test.c)
  target_include_directories(spin_wait_test PRIVATE runtime/include)
  target_compile_options(spin_wait_test PRIVATE -O3 -ffp-contract=off -fno-strict-aliasing -w)
  if(APPLE AND CMAKE_SYSTEM_PROCESSOR MATCHES "arm64|aarch64")
    target_compile_options(spin_wait_test PRIVATE -mcpu=apple-m1)
  endif()
  target_link_libraries(spin_wait_test PRIVATE Threads::Threads)
  add_test(NAME spin_wait COMMAND spin_wait_test)
endif()
# host audio: TV mix, or TV + GamePad in Off-TV Play, from the game's faders (runtime/src/audio_output_mode.h)
add_executable(audio_output_test runtime/tools/audio_output_test.cpp)
target_include_directories(audio_output_test PRIVATE runtime/src)
target_compile_options(audio_output_test PRIVATE -UNDEBUG)
# Vulkan guest buffer cache: range keys, invalidation by write stamps and hints (runtime/src/gfx/vulkan/buffer_cache_core.h)
add_executable(buffer_cache_test runtime/tools/buffer_cache_test.cpp runtime/src/write_watch.cpp)
target_include_directories(buffer_cache_test PRIVATE runtime/src)
target_compile_options(buffer_cache_test PRIVATE -UNDEBUG)
target_link_libraries(buffer_cache_test PRIVATE Threads::Threads)
# Vulkan snapshot reuse caches never read mapped upload memory (runtime/src/gfx/vulkan/uniform_snapshot.h,
# vertex_snapshot_history.h)
add_executable(snapshot_cache_test runtime/tools/snapshot_cache_test.cpp)
target_include_directories(snapshot_cache_test PRIVATE runtime/src)
target_compile_options(snapshot_cache_test PRIVATE -UNDEBUG)
# gyro: orientation integration, axis mapping, Cemuhook (DSU) packets, mouse as a gyro, settings
# (runtime/src/motion/)
set(MOTION_SOURCES runtime/src/motion/motion.cpp runtime/src/motion/fusion.cpp runtime/src/motion/dsu.cpp)
add_executable(motion_test runtime/tools/motion_test.cpp ${MOTION_SOURCES})
target_include_directories(motion_test PRIVATE runtime/src)
target_compile_options(motion_test PRIVATE -UNDEBUG)
target_link_libraries(motion_test PRIVATE Threads::Threads)
if(WIN32)
  target_link_libraries(motion_test PRIVATE ws2_32)
endif()
# controller packets: actual VPAD/KPAD writers, without a game or renderer
add_executable(pad_delivery_test runtime/tools/pad_delivery_test.cpp runtime/src/hle/padscore.cpp runtime/src/hle/system_stubs.cpp runtime/src/rumble.cpp runtime/src/guest_addr_identity.c)
target_include_directories(pad_delivery_test PRIVATE runtime/src runtime/include)
target_compile_options(pad_delivery_test PRIVATE -UNDEBUG)
target_link_libraries(pad_delivery_test PRIVATE Threads::Threads)

# controller rumble: the game's motor requests and the host motor level (runtime/src/rumble.cpp)
add_executable(rumble_test runtime/tools/rumble_test.cpp runtime/src/rumble.cpp)
target_include_directories(rumble_test PRIVATE runtime/src runtime/include)
target_compile_options(rumble_test PRIVATE -UNDEBUG)
target_link_libraries(rumble_test PRIVATE Threads::Threads)

if(WWHD_SDL_HOST)
  add_executable(overlay_trace_budget_test runtime/tools/overlay_trace_budget_test.cpp)
  target_include_directories(overlay_trace_budget_test PRIVATE ${IMGUI_DIR})
  target_compile_options(overlay_trace_budget_test PRIVATE -UNDEBUG)
  target_link_libraries(overlay_trace_budget_test PRIVATE Threads::Threads)
  add_test(NAME overlay_trace_budget COMMAND overlay_trace_budget_test)
  set_tests_properties(overlay_trace_budget PROPERTIES ENVIRONMENT "WWHD_OVERLAY_TRACE=1" TIMEOUT 30)
  add_executable(overlay_scroll_test runtime/tools/overlay_scroll_test.cpp)
  target_include_directories(overlay_scroll_test PRIVATE runtime/src)
  target_compile_options(overlay_scroll_test PRIVATE -UNDEBUG)
  target_link_libraries(overlay_scroll_test PRIVATE imgui SDL3::SDL3)
  add_test(NAME overlay_scroll COMMAND overlay_scroll_test)
  add_test(NAME overlay_trace COMMAND overlay_scroll_test)
  set_tests_properties(overlay_trace PROPERTIES ENVIRONMENT "WWHD_NO_AUDIO=1;WWHD_OVERLAY_TRACE=1;WWHD_OVERLAY_MOUSE_HANDOFF=1")
  add_test(NAME overlay_trace_session COMMAND overlay_scroll_test --trace-session)
  set_tests_properties(overlay_trace_session PROPERTIES ENVIRONMENT "WWHD_NO_AUDIO=1;WWHD_OVERLAY_TRACE=1;WWHD_OVERLAY_MOUSE_HANDOFF=1")
  add_test(NAME overlay_trace_paths COMMAND overlay_scroll_test --trace-paths)
  set_tests_properties(overlay_trace_paths PROPERTIES ENVIRONMENT "WWHD_NO_AUDIO=1;WWHD_OVERLAY_TRACE=1;WWHD_OVERLAY_MOUSE_HANDOFF=1")
  add_test(NAME overlay_trace_positive COMMAND overlay_scroll_test)
  set_tests_properties(overlay_trace_positive PROPERTIES ENVIRONMENT "WWHD_NO_AUDIO=1;WWHD_OVERLAY_TRACE=1;WWHD_OVERLAY_MOUSE_HANDOFF=0")
  add_executable(input_sdl_test runtime/tools/input_sdl_test.cpp runtime/src/input_map.cpp runtime/src/rumble.cpp runtime/src/platform/input_sdl.cpp runtime/src/platform/mouse_sdl.cpp ${MOTION_SOURCES})
  target_include_directories(input_sdl_test PRIVATE ${IMGUI_DIR} runtime/src runtime/include ${CEMU_DIR} ${CEMU_DIR}/Cafe ${CMAKE_SOURCE_DIR}/runtime/third_party/fmt/include)
  target_compile_definitions(input_sdl_test PRIVATE WWHD_HAS_VULKAN=1 WWHD_SDL_HOST=1)
  target_link_libraries(input_sdl_test PRIVATE SDL3::SDL3 Threads::Threads)
  if(WIN32)
    target_link_libraries(input_sdl_test PRIVATE ws2_32)
  endif()
  add_executable(platform_test runtime/tools/platform_test.cpp)
  target_include_directories(platform_test PRIVATE runtime/src)
  target_link_libraries(platform_test PRIVATE Threads::Threads)
  add_executable(sleep_test runtime/tools/sleep_test.cpp)
  target_include_directories(sleep_test PRIVATE runtime/src)
  target_compile_definitions(sleep_test PRIVATE WWHD_SDL_HOST=1)
  target_compile_options(sleep_test PRIVATE -UNDEBUG)
  target_link_libraries(sleep_test PRIVATE SDL3::SDL3 Threads::Threads)
  if(WIN32)
    target_link_libraries(sleep_test PRIVATE winmm)
  endif()
endif()
# These test executables use assertions as checks, including fixture setup.
target_compile_options(input_map_test PRIVATE -UNDEBUG)
if(WWHD_SDL_HOST)
  target_compile_options(input_sdl_test PRIVATE -UNDEBUG)
  target_compile_options(platform_test PRIVATE -UNDEBUG)
endif()

if(WWHD_SDL_HOST)
  add_executable(mouse_sdl_test runtime/tools/mouse_sdl_test.cpp runtime/src/platform/mouse_sdl.cpp)  # motion.h stubbed
  target_include_directories(mouse_sdl_test PRIVATE runtime/src runtime/include ${CEMU_DIR} ${CEMU_DIR}/Cafe ${CMAKE_SOURCE_DIR}/runtime/third_party/fmt/include)
  target_compile_options(mouse_sdl_test PRIVATE -UNDEBUG)
  target_link_libraries(mouse_sdl_test PRIVATE SDL3::Headers)
endif()

# disc image / Cemu archive extractor used by the installer (tools/wudextract; no Python or
# pycryptodome needed) and its tests; zstd (for .wua) from pinned source (cmake/Zstd.cmake).
# The BotW installer (tools/installer/botw_setup.py) builds without it, so nothing is downloaded.
option(WWHD_EXTRACTOR "build wwhd-extract and its tests (needs zstd: system or downloaded)" ON)
if(WWHD_EXTRACTOR)
include(cmake/Zstd.cmake)
add_library(wudcrypto STATIC tools/wudextract/crypto.cpp)
target_include_directories(wudcrypto PUBLIC tools/wudextract)
add_executable(wwhd-extract tools/wudextract/wwhd_extract.cpp tools/wudextract/zarchive.cpp)
target_link_libraries(wwhd-extract PRIVATE wudcrypto wwhd_zstd)
if(WIN32)
  target_link_libraries(wwhd-extract PRIVATE shell32)
  wwhd_windows_resources(wwhd-extract "Wind Waker HD game extractor (disc images, Cemu archives)" "wwhd-extract.exe" console)
  if(WWHD_STRIP_RELEASE)
    target_link_options(wwhd-extract PRIVATE ${WWHD_STRIP_RELEASE})
  endif()
endif()
add_executable(extract_crypto_test tools/wudextract/crypto_test.cpp)
target_link_libraries(extract_crypto_test PRIVATE wudcrypto)
add_executable(extract_test tools/wudextract/extract_test.cpp)
target_link_libraries(extract_test PRIVATE wudcrypto)
add_executable(wua_test tools/wudextract/wua_test.cpp)
target_link_libraries(wua_test PRIVATE wudcrypto wwhd_zstd)
endif()

enable_testing()
add_executable(presentation_queue_test runtime/tools/presentation_queue_test.cpp)
target_include_directories(presentation_queue_test PRIVATE runtime/src)
target_compile_options(presentation_queue_test PRIVATE -UNDEBUG)
add_test(NAME presentation_queue COMMAND presentation_queue_test)
add_executable(present_worker_test runtime/tools/present_worker_test.cpp)
target_include_directories(present_worker_test PRIVATE runtime/src)
target_compile_options(present_worker_test PRIVATE -UNDEBUG)
target_link_libraries(present_worker_test PRIVATE Threads::Threads)
add_test(NAME present_worker COMMAND present_worker_test)
add_executable(gamepad_touch_test runtime/tools/gamepad_touch_test.cpp)
target_include_directories(gamepad_touch_test PRIVATE runtime/src)
target_compile_options(gamepad_touch_test PRIVATE -UNDEBUG)
add_test(NAME gamepad_touch COMMAND gamepad_touch_test)
add_executable(display_fallback_test runtime/tools/display_fallback_test.cpp runtime/src/gfx/display_modes.cpp)
target_include_directories(display_fallback_test PRIVATE runtime/src runtime/include runtime/third_party/fmt/include)
target_compile_options(display_fallback_test PRIVATE -UNDEBUG)
add_test(NAME display_fallback COMMAND display_fallback_test)
if(WIN32 AND WWHD_SDL_HOST)
  get_target_property(SDL3_LIBRARY_TYPE SDL3::SDL3 TYPE)
  if(SDL3_LIBRARY_TYPE STREQUAL "SHARED_LIBRARY")
    foreach(SDL3_APP wwhd input_sdl_test overlay_scroll_test sleep_test)
      add_custom_command(TARGET ${SDL3_APP} POST_BUILD
        COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:SDL3::SDL3> $<TARGET_FILE_DIR:${SDL3_APP}>
        VERBATIM)
    endforeach()
  endif()
endif()
add_executable(mod_cemu_test runtime/tools/mod_cemu_test.cpp runtime/src/mods/cemu_pack.cpp runtime/src/guest_addr_identity.c)
target_include_directories(mod_cemu_test PRIVATE runtime/src runtime/include)
target_compile_options(mod_cemu_test PRIVATE -UNDEBUG)
add_test(NAME mod_cemu COMMAND mod_cemu_test)
add_test(NAME portable_state COMMAND portable_state_test)
# Verify the shipped EUR address tables without game code, plus the weak USA fallback.
find_package(Python3 COMPONENTS Interpreter QUIET)
if(Python3_Interpreter_FOUND)
  add_test(NAME build_coverage COMMAND "${Python3_EXECUTABLE}" -m unittest discover
    -s "${CMAKE_SOURCE_DIR}/tools/recomp" -p "test*.py")
  # Cemu graphic packs' code patches: expressions, presets, the assembler, packs and their sites (no game needed)
  add_test(NAME cemu_patches COMMAND "${Python3_EXECUTABLE}" "${CMAKE_SOURCE_DIR}/tools/test_cemu_patches.py")
  add_custom_command(OUTPUT "${CMAKE_BINARY_DIR}/guest_map_fixture.c"
    COMMAND "${Python3_EXECUTABLE}" "${CMAKE_SOURCE_DIR}/tools/recomp/write_map_fixture.py"
      "${CMAKE_BINARY_DIR}/guest_map_fixture.c"
    DEPENDS tools/recomp/write_map_fixture.py tools/recomp/builds.py tools/recomp/builds/eu.json tools/recomp/recomp.py)
  add_executable(guest_addr_test runtime/tools/guest_addr_test.cpp "${CMAKE_BINARY_DIR}/guest_map_fixture.c")
  target_include_directories(guest_addr_test PRIVATE runtime/include)
  target_compile_options(guest_addr_test PRIVATE -UNDEBUG)
  add_test(NAME guest_addr COMMAND guest_addr_test)
  add_executable(guest_addr_identity_test runtime/tools/guest_addr_test.cpp runtime/src/guest_addr_identity.c)
  target_include_directories(guest_addr_identity_test PRIVATE runtime/include)
  target_compile_definitions(guest_addr_identity_test PRIVATE TEST_IDENTITY)
  target_compile_options(guest_addr_identity_test PRIVATE -UNDEBUG)
  add_test(NAME guest_addr_identity COMMAND guest_addr_identity_test)
endif()

add_test(NAME register_blocks COMMAND register_blocks_test)
add_test(NAME scheduler_tick COMMAND scheduler_tick_test)
add_test(NAME full_state_header COMMAND full_state_header_test)
add_test(NAME mod_content_fs_off COMMAND mod_content_fs_test off)
add_test(NAME mod_content_fs_on COMMAND mod_content_fs_test on)
add_test(NAME mod_game_source COMMAND mod_package_test --game-source)
add_test(NAME mod_guest_startup COMMAND mod_package_test --guest-startup)
add_test(NAME mod_guest_metadata COMMAND mod_package_test --guest-metadata)
add_test(NAME mod_guest_prepare COMMAND mod_package_test --guest-prepare)
add_executable(mod_game_source_ui_test runtime/tools/mod_game_source_ui_test.cpp)
target_include_directories(mod_game_source_ui_test PRIVATE runtime/src)
target_compile_options(mod_game_source_ui_test PRIVATE -UNDEBUG)
target_link_libraries(mod_game_source_ui_test PRIVATE imgui)
add_test(NAME mod_game_source_ui COMMAND mod_game_source_ui_test)
add_executable(mod_cemu_dialog_test runtime/tools/mod_cemu_dialog_test.cpp)
target_include_directories(mod_cemu_dialog_test PRIVATE runtime/src)
target_compile_options(mod_cemu_dialog_test PRIVATE -UNDEBUG)
target_link_libraries(mod_cemu_dialog_test PRIVATE imgui)
add_test(NAME mod_cemu_dialog COMMAND mod_cemu_dialog_test)
add_test(NAME mod_cemu_conflicts COMMAND mod_package_test --cemu-conflicts)
add_test(NAME mod_cemu_backend COMMAND mod_package_test --cemu-backend)
add_test(NAME mod_cemu_startup COMMAND mod_package_test --cemu-startup)
add_test(NAME mod_content_startup COMMAND mod_package_test --content-startup)
add_test(NAME mod_packages COMMAND mod_package_test ${CMAKE_BINARY_DIR}/package-tests $<TARGET_FILE:mod_fixture>)
add_executable(perf_metrics_test runtime/tools/perf_metrics_test.cpp runtime/src/perf_metrics.cpp)
target_include_directories(perf_metrics_test PRIVATE runtime/src)
target_link_libraries(perf_metrics_test PRIVATE Threads::Threads)
target_compile_options(perf_metrics_test PRIVATE -UNDEBUG)
add_test(NAME perf_metrics COMMAND perf_metrics_test)
add_test(NAME report_header COMMAND report_header_test)
add_test(NAME headless_compose COMMAND headless_compose_test)
add_test(NAME input_mapping COMMAND input_map_test)
add_test(NAME overlay_release_gate COMMAND release_gate_test)
add_test(NAME screenshot COMMAND screenshot_test)
add_test(NAME game_font COMMAND game_font_test)
add_test(NAME rtl_text COMMAND rtl_text_test)
add_test(NAME game_languages COMMAND game_languages_test ${CMAKE_BINARY_DIR}/game_languages_test_work)
add_test(NAME game_release COMMAND game_release_test ${CMAKE_BINARY_DIR}/game_release_test_work)
add_test(NAME content_merge COMMAND content_merge_test ${CMAKE_BINARY_DIR}/content_merge_test_work)
add_test(NAME code_patch_rules COMMAND code_patch_rules_test ${CMAKE_BINARY_DIR}/code_patch_rules_test_work)
if(NOT WIN32)
  add_test(NAME code_patches COMMAND code_patches_test ${CMAKE_BINARY_DIR}/code_patches_test_work)
endif()
add_test(NAME language_sources COMMAND language_source_test)
add_test(NAME interp_pacing COMMAND interp_pacing_test)
add_test(NAME quit_prompt COMMAND quit_prompt_test)
add_test(NAME write_watch COMMAND write_watch_test)
add_test(NAME buffer_cache COMMAND buffer_cache_test)
add_test(NAME snapshot_caches COMMAND snapshot_cache_test)
add_test(NAME pad_delivery COMMAND pad_delivery_test)
add_test(NAME rumble COMMAND rumble_test)
add_test(NAME motion COMMAND motion_test)
add_test(NAME audio_output COMMAND audio_output_test)
if(WWHD_EXTRACTOR)
add_test(NAME extract_crypto COMMAND extract_crypto_test)
add_test(NAME extract_synthetic COMMAND extract_test $<TARGET_FILE:wwhd-extract> ${CMAKE_BINARY_DIR}/extract_test_work)
add_test(NAME extract_wua COMMAND wua_test $<TARGET_FILE:wwhd-extract> ${CMAKE_BINARY_DIR}/wua_test_work)
endif()
add_executable(exception_report_test runtime/tools/exception_report_test.cpp)
target_include_directories(exception_report_test PRIVATE runtime/src)
target_compile_options(exception_report_test PRIVATE -UNDEBUG)
target_link_libraries(exception_report_test PRIVATE Threads::Threads)
add_test(NAME exception_report COMMAND exception_report_test)
if(NOT ANDROID)
  add_test(NAME runtime_error_log COMMAND ${CMAKE_COMMAND} -DWWHD=$<TARGET_FILE:wwhd>
           -DWORK=${CMAKE_BINARY_DIR}/runtime_error_test_work -P ${CMAKE_SOURCE_DIR}/runtime/tools/runtime_error_test.cmake)
  # crash logs name the module of a host crash address (main.cpp, crash_addr.cpp)
  add_test(NAME crash_log_module COMMAND ${CMAKE_COMMAND} -DWWHD=$<TARGET_FILE:wwhd>
           -DWORK=${CMAKE_BINARY_DIR}/crash_log_test_work -P ${CMAKE_SOURCE_DIR}/runtime/tools/crash_log_test.cmake)
endif()
if(WWHD_SDL_HOST)
  add_test(NAME sdl_input COMMAND input_sdl_test)
  set_tests_properties(sdl_input PROPERTIES ENVIRONMENT "WWHD_CONTROLS=${CMAKE_BINARY_DIR}/test-controls.json")
  add_test(NAME host_platform COMMAND platform_test)
  add_test(NAME sdl_mouse COMMAND mouse_sdl_test)
  add_test(NAME host_sleep COMMAND sleep_test)
endif()

add_executable(bc_reference_test runtime/tools/bc_reference_test.cpp)
target_include_directories(bc_reference_test PRIVATE runtime/src)
target_compile_options(bc_reference_test PRIVATE -UNDEBUG)
add_test(NAME bc_reference COMMAND bc_reference_test)

if(ANDROID)
  include(FetchContent)
  # libadrenotools and its pinned linkernsbypass submodule are BSD-2-Clause.
  # The loader uses gipa directly; no volk dependency.
  FetchContent_Declare(wwhd_adrenotools
    GIT_REPOSITORY https://github.com/bylaws/libadrenotools.git
    GIT_TAG 8fae8ce254dfc1344527e05301e43f37dea2df80
    GIT_SUBMODULES lib/linkernsbypass)
  function(wwhd_add_adrenotools)
    set(BUILD_SHARED_LIBS OFF)
    FetchContent_MakeAvailable(wwhd_adrenotools)
  endfunction()
  # libadrenotools supports arm64 only (Adreno devices); x86_64 builds (emulator CI) go without it.
  if(CMAKE_ANDROID_ARCH_ABI STREQUAL "arm64-v8a")
    wwhd_add_adrenotools()
    target_link_libraries(wwhd PRIVATE adrenotools)
    target_compile_definitions(wwhd PRIVATE WWHD_ADRENOTOOLS=1)
    add_dependencies(wwhd main_hook hook_impl file_redirect_hook gsl_alloc_hook)
  endif()
endif()

add_executable(driver_store_test runtime/tools/driver_store_test.cpp runtime/src/gfx/vulkan/driver_store.cpp runtime/src/mods/mod_archive.cpp)
target_include_directories(driver_store_test PRIVATE runtime/src runtime/third_party/fmt/include)
target_link_libraries(driver_store_test PRIVATE ZLIB::ZLIB)
target_compile_options(driver_store_test PRIVATE -UNDEBUG)
find_package(Python3 COMPONENTS Interpreter QUIET)
if(Python3_Interpreter_FOUND AND NOT ANDROID)
  add_test(NAME driver_store COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/runtime/tools/driver_store_test.py $<TARGET_FILE:driver_store_test>)
endif()

add_executable(perf_average_test runtime/tools/perf_average_test.cpp)
target_include_directories(perf_average_test PRIVATE runtime/src)
target_compile_options(perf_average_test PRIVATE -UNDEBUG)
add_test(NAME perf_average COMMAND perf_average_test)
add_executable(move_speed_test runtime/tools/move_speed_test.cpp)
target_include_directories(move_speed_test PRIVATE runtime/src)
target_compile_options(move_speed_test PRIVATE -UNDEBUG)
add_test(NAME move_speed COMMAND move_speed_test)
add_executable(crash_addr_test runtime/tools/crash_addr_test.cpp runtime/src/crash_addr.cpp)
target_include_directories(crash_addr_test PRIVATE runtime/src)
target_compile_options(crash_addr_test PRIVATE -UNDEBUG)
target_link_libraries(crash_addr_test PRIVATE ${CMAKE_DL_LIBS})
add_test(NAME crash_addr COMMAND crash_addr_test)

add_executable(crash_redact_test runtime/tools/crash_redact_test.cpp)
target_include_directories(crash_redact_test PRIVATE runtime/src)
target_compile_options(crash_redact_test PRIVATE -UNDEBUG)
add_test(NAME crash_redact COMMAND crash_redact_test)

# Cheats must preserve sword ownership: it controls Medli/Makar story placement (#85).
if(Python3_Interpreter_FOUND)
  add_test(NAME repair_medli COMMAND "${Python3_EXECUTABLE}" "${CMAKE_SOURCE_DIR}/tools/savegame/repair_medli_test.py")
  add_test(NAME medli_scenario_evidence COMMAND "${Python3_EXECUTABLE}" "${CMAKE_SOURCE_DIR}/runtime/tools/medli_scenario_test.py")
endif()
# Guest mod loader metadata validation; synthetic tables, no game files.
add_executable(guest_validation_test runtime/tools/guest_validation_test.cpp)
target_include_directories(guest_validation_test PRIVATE runtime/include runtime/src)
target_compile_options(guest_validation_test PRIVATE -UNDEBUG)
add_test(NAME guest_validation COMMAND guest_validation_test)

add_executable(guest_hooks_test runtime/tools/guest_hooks_test.cpp runtime/tools/guest_hooks_body.c runtime/src/mods/guest_png.cpp runtime/src/mods/mod_archive.cpp)
# The entry musttail regression must reach the optimized backend even in Debug builds.
set_source_files_properties(runtime/tools/guest_hooks_body.c PROPERTIES COMPILE_OPTIONS "-O3")
target_include_directories(guest_hooks_test PRIVATE runtime/include runtime/src)
target_compile_options(guest_hooks_test PRIVATE -UNDEBUG)
target_link_libraries(guest_hooks_test PRIVATE ZLIB::ZLIB ${CMAKE_DL_LIBS})
add_test(NAME guest_hooks COMMAND guest_hooks_test)

add_executable(process_test runtime/tools/process_test.cpp)
target_include_directories(process_test PRIVATE runtime/src)
target_compile_options(process_test PRIVATE -UNDEBUG)
add_test(NAME process COMMAND process_test)

add_executable(guest_build_test runtime/tools/guest_build_test.cpp)
target_include_directories(guest_build_test PRIVATE runtime/src)
target_compile_options(guest_build_test PRIVATE -UNDEBUG)
add_test(NAME guest_build COMMAND guest_build_test)

add_executable(guest_heap_test runtime/tools/guest_heap_test.cpp)
target_include_directories(guest_heap_test PRIVATE runtime/src)
target_compile_options(guest_heap_test PRIVATE -UNDEBUG)
target_link_libraries(guest_heap_test PRIVATE Threads::Threads)
add_test(NAME guest_heap COMMAND guest_heap_test)

add_executable(guest_files_test runtime/tools/guest_files_test.cpp)
target_include_directories(guest_files_test PRIVATE runtime/src)
target_compile_options(guest_files_test PRIVATE -UNDEBUG)
target_link_libraries(guest_files_test PRIVATE Threads::Threads)
add_test(NAME guest_files COMMAND guest_files_test ${CMAKE_BINARY_DIR}/guest-files-work)

add_executable(guest_state_test runtime/tools/guest_state_test.cpp)
target_include_directories(guest_state_test PRIVATE runtime/src runtime/include)
target_compile_options(guest_state_test PRIVATE -UNDEBUG)
target_link_libraries(guest_state_test PRIVATE Threads::Threads)
add_test(NAME guest_state COMMAND guest_state_test)
add_executable(catalogue_schema_test runtime/tools/catalogue_schema_test.cpp)
target_include_directories(catalogue_schema_test PRIVATE runtime/src)
target_compile_options(catalogue_schema_test PRIVATE -UNDEBUG)
add_test(NAME catalogue_schema COMMAND catalogue_schema_test)
add_executable(catalogue_io_test runtime/tools/catalogue_io_test.cpp)
target_include_directories(catalogue_io_test PRIVATE runtime/src)
target_compile_options(catalogue_io_test PRIVATE -UNDEBUG)
add_test(NAME catalogue_io COMMAND catalogue_io_test)
add_executable(catalogue_setup_test runtime/tools/catalogue_setup_test.cpp)
target_include_directories(catalogue_setup_test PRIVATE runtime/src)
target_compile_options(catalogue_setup_test PRIVATE -UNDEBUG)
add_test(NAME catalogue_setup COMMAND catalogue_setup_test)
add_executable(catalogue_client_test runtime/tools/catalogue_client_test.cpp runtime/src/mods/mod_archive.cpp)
target_include_directories(catalogue_client_test PRIVATE runtime/src)
target_compile_options(catalogue_client_test PRIVATE -UNDEBUG)
target_link_libraries(catalogue_client_test PRIVATE ZLIB::ZLIB)
add_test(NAME catalogue_client COMMAND catalogue_client_test)

add_executable(code_mods_test runtime/tools/code_mods_test.cpp runtime/src/mods/code_mods.cpp)
target_include_directories(code_mods_test PRIVATE runtime/include runtime/src)
target_compile_options(code_mods_test PRIVATE -UNDEBUG)
target_link_libraries(code_mods_test PRIVATE Threads::Threads)
if(Python3_Interpreter_FOUND)
  add_test(NAME code_mods_service COMMAND code_mods_test "${Python3_EXECUTABLE}")
endif()

add_executable(aspect_panes_test runtime/tools/aspect_panes_test.cpp)
target_include_directories(aspect_panes_test PRIVATE runtime/src)
add_test(NAME aspect_panes COMMAND aspect_panes_test)

if(WWHD_HAS_VULKAN)
  add_executable(comparison_format_test runtime/tools/comparison_format_test.cpp runtime/src/gfx/vulkan/formats.cpp)
  target_include_directories(comparison_format_test PRIVATE runtime/src runtime/third_party/fmt/include)
  target_include_directories(comparison_format_test SYSTEM PRIVATE $<TARGET_PROPERTY:Vulkan::Vulkan,INTERFACE_INCLUDE_DIRECTORIES>)
  target_compile_options(comparison_format_test PRIVATE -UNDEBUG)
  add_test(NAME comparison_format COMMAND comparison_format_test)
  add_executable(barrier_state_test runtime/tools/barrier_state_test.cpp)
  target_include_directories(barrier_state_test PRIVATE runtime/src)
  target_include_directories(barrier_state_test SYSTEM PRIVATE $<TARGET_PROPERTY:Vulkan::Vulkan,INTERFACE_INCLUDE_DIRECTORIES>)
  target_compile_options(barrier_state_test PRIVATE -UNDEBUG)
  add_test(NAME barrier_state COMMAND barrier_state_test)
endif()

if(APPLE OR WIN32 OR (UNIX AND NOT ANDROID))
  if(APPLE)
    set(HTTPS_SOURCE runtime/src/platform/https.mm)
  elseif(WIN32)
    set(HTTPS_SOURCE runtime/src/platform/https_win.cpp)
  else()
    set(HTTPS_SOURCE runtime/src/platform/https_curl.cpp)
  endif()
  add_executable(https_test runtime/tools/https_test.cpp ${HTTPS_SOURCE})
  target_include_directories(https_test PRIVATE runtime/src ${WWHD_CURL_INCLUDE})
  target_compile_options(https_test PRIVATE -UNDEBUG)
  if(APPLE)
    target_link_libraries(https_test PRIVATE "-framework Foundation")
  elseif(WIN32)
    target_link_libraries(https_test PRIVATE winhttp)
  else()
    target_link_libraries(https_test PRIVATE ${CMAKE_DL_LIBS})
  endif()
  add_test(NAME https_transport COMMAND https_test)
endif()

if(NOT ANDROID)
  find_package(Java COMPONENTS Development QUIET)
  if(Java_FOUND)
    add_test(NAME https_java_transport COMMAND ${CMAKE_COMMAND}
      "-DJAVAC=${Java_JAVAC_EXECUTABLE}" "-DJAVA=${Java_JAVA_EXECUTABLE}"
      "-DWORK=${CMAKE_BINARY_DIR}/https_java_test" "-DSOURCE=${CMAKE_SOURCE_DIR}"
      -P ${CMAKE_SOURCE_DIR}/runtime/tools/https_java_test.cmake)
  endif()
endif()

if(Python3_Interpreter_FOUND AND NOT ANDROID)
  add_test(NAME catalogue_pilots COMMAND ${Python3_EXECUTABLE}
    ${CMAKE_SOURCE_DIR}/tools/catalogue/test_pilots.py $<TARGET_FILE:mod_package_test>)
endif()

add_executable(guest_settings_test runtime/tools/guest_settings_test.cpp)
target_include_directories(guest_settings_test PRIVATE runtime/src)
target_compile_options(guest_settings_test PRIVATE -UNDEBUG)
add_test(NAME guest_settings COMMAND guest_settings_test)

add_executable(setup_test_plan_test runtime/tools/setup_test_plan_test.cpp)
target_include_directories(setup_test_plan_test PRIVATE runtime/src)
target_compile_options(setup_test_plan_test PRIVATE -UNDEBUG)
add_test(NAME setup_test_plan COMMAND setup_test_plan_test)

add_executable(guest_hud_test runtime/tools/guest_hud_test.cpp)
target_include_directories(guest_hud_test PRIVATE runtime/src)
target_compile_options(guest_hud_test PRIVATE -UNDEBUG)
target_link_libraries(guest_hud_test PRIVATE Threads::Threads)
add_test(NAME guest_hud COMMAND guest_hud_test)

add_executable(guest_hud_renderer_test runtime/tools/guest_hud_renderer_test.cpp runtime/src/overlay/guest_hud.cpp)
target_include_directories(guest_hud_renderer_test PRIVATE runtime/src)
target_compile_options(guest_hud_renderer_test PRIVATE -UNDEBUG)
target_link_libraries(guest_hud_renderer_test PRIVATE imgui Threads::Threads)
add_test(NAME guest_hud_renderer COMMAND guest_hud_renderer_test)

if(Python3_Interpreter_FOUND)
  add_test(NAME guest_original_art COMMAND "${Python3_EXECUTABLE}" "${CMAKE_SOURCE_DIR}/examples/guest-mods/generate_art.py" --check)
endif()

add_executable(fast_forward_test runtime/tools/fast_forward_test.cpp)
target_include_directories(fast_forward_test PRIVATE runtime/src)
target_compile_options(fast_forward_test PRIVATE -UNDEBUG)
add_test(NAME fast_forward COMMAND fast_forward_test)
add_executable(fast_forward_runtime_test runtime/tools/fast_forward_runtime_test.cpp runtime/src/mods/fast_forward.cpp runtime/src/game_clock.cpp runtime/src/guest_addr_identity.c)
target_include_directories(fast_forward_runtime_test PRIVATE runtime/src runtime/include)
target_compile_options(fast_forward_runtime_test PRIVATE -UNDEBUG)
add_test(NAME fast_forward_runtime COMMAND fast_forward_runtime_test)
add_test(NAME fast_forward_startup_valid COMMAND ${CMAKE_COMMAND} -E env WWHD_MOD_FAST_FORWARD=1 WWHD_MOD_FF_RATE=3 WWHD_MOD_FF_BUTTON=0x20 WWHD_MOD_FF_MUTE=0 $<TARGET_FILE:fast_forward_runtime_test> --startup 1 3 0x20 0)
add_test(NAME fast_forward_startup_invalid COMMAND ${CMAKE_COMMAND} -E env WWHD_MOD_FAST_FORWARD=0 WWHD_MOD_FF_RATE=nan WWHD_MOD_FF_BUTTON=0x8000 WWHD_MOD_FF_MUTE=1 $<TARGET_FILE:fast_forward_runtime_test> --startup 0 2 0x40 1)
add_test(NAME fast_forward_startup_bounds COMMAND ${CMAKE_COMMAND} -E env WWHD_MOD_FAST_FORWARD=0 WWHD_MOD_FF_RATE=5 WWHD_MOD_FF_BUTTON=0xffffffffffffffff WWHD_MOD_FF_MUTE=1 $<TARGET_FILE:fast_forward_runtime_test> --startup 0 2 0x40 1)
add_executable(guest_audio_test runtime/tools/guest_audio_test.cpp)
target_include_directories(guest_audio_test PRIVATE runtime/src)
target_compile_options(guest_audio_test PRIVATE -UNDEBUG)
target_link_libraries(guest_audio_test PRIVATE Threads::Threads)
add_test(NAME guest_audio COMMAND guest_audio_test)
add_executable(guest_audio_output_test runtime/tools/guest_audio_output_test.cpp runtime/src/audio_out.cpp runtime/src/game_clock.cpp)
target_include_directories(guest_audio_output_test PRIVATE runtime/src runtime/include)
target_compile_options(guest_audio_output_test PRIVATE -UNDEBUG)
target_link_libraries(guest_audio_output_test PRIVATE Threads::Threads)
if(WWHD_SDL_HOST)
  target_compile_definitions(guest_audio_output_test PRIVATE WWHD_SDL_HOST=1)
  target_link_libraries(guest_audio_output_test PRIVATE SDL3::SDL3)
else()
  target_link_libraries(guest_audio_output_test PRIVATE "-framework AudioToolbox" "-framework CoreAudio")
endif()
add_test(NAME guest_audio_output_gain COMMAND guest_audio_output_test ${CMAKE_CURRENT_BINARY_DIR}/synthetic-audio-gain.wav gain)
add_test(NAME guest_audio_output_mute COMMAND guest_audio_output_test ${CMAKE_CURRENT_BINARY_DIR}/synthetic-audio-mute.wav mute)

# Synthetic setup build -> persisted host preference -> guest package enable gate.
if(Python3_Interpreter_FOUND AND NOT ANDROID)
  add_test(NAME setup_code_mods COMMAND ${CMAKE_COMMAND} -E env
    WWHD_NO_AUDIO=1 WWHD_SETUP_MOD_PACKAGE_TEST=$<TARGET_FILE:mod_package_test>
    "${Python3_EXECUTABLE}" "${CMAKE_SOURCE_DIR}/tools/installer/test_setup.py"
    CodeModsBuild.test_setup_install_publishes_choice_settings_and_variant)
endif()

# Synthetic surround checks run without opening any host audio device or using game assets.
foreach(test audio_producer surround_output)
  add_executable(${test}_test runtime/tools/${test}_test.cpp runtime/src/audio_out.cpp runtime/src/game_clock.cpp)
  target_include_directories(${test}_test PRIVATE runtime/src runtime/include)
  target_compile_options(${test}_test PRIVATE -UNDEBUG -ffunction-sections -fdata-sections)
  target_link_libraries(${test}_test PRIVATE Threads::Threads)
  if(WWHD_SDL_HOST)
    target_compile_definitions(${test}_test PRIVATE WWHD_SDL_HOST=1)
    target_link_libraries(${test}_test PRIVATE SDL3::SDL3)
  else()
    target_link_libraries(${test}_test PRIVATE "-framework AudioToolbox" "-framework CoreAudio")
  endif()
  if(APPLE)
    target_link_options(${test}_test PRIVATE "LINKER:-dead_strip")
  else()
    target_link_options(${test}_test PRIVATE "LINKER:--gc-sections")
  endif()
endforeach()
foreach(mode stereo stereo-fast stereo-mute gain fast mute zero tone stereo-tone tone-fast tone-fast-mute)
  add_test(NAME audio_producer_${mode} COMMAND audio_producer_test ${CMAKE_CURRENT_BINARY_DIR}/synthetic-${mode}.wav ${mode})
endforeach()
foreach(stage 0 1)
  add_test(NAME surround_ax_stage_${stage} COMMAND surround_output_test ${CMAKE_CURRENT_BINARY_DIR}/synthetic-ax-${stage}.wav ${stage})
endforeach()
add_executable(audio_backend_test runtime/tools/audio_backend_test.cpp runtime/src/game_clock.cpp)
target_include_directories(audio_backend_test PRIVATE runtime/src runtime/include)
target_compile_options(audio_backend_test PRIVATE -UNDEBUG)
target_link_libraries(audio_backend_test PRIVATE Threads::Threads)
if(WWHD_SDL_HOST)
  target_compile_definitions(audio_backend_test PRIVATE WWHD_SDL_HOST=1)
  target_link_libraries(audio_backend_test PRIVATE SDL3::SDL3)
  add_test(NAME audio_backend_opened_stereo COMMAND audio_backend_test opened-stereo)
else()
  target_link_libraries(audio_backend_test PRIVATE "-framework AudioToolbox" "-framework CoreAudio")
  add_test(NAME audio_backend_layout_failure COMMAND audio_backend_test layout-failure)
endif()
foreach(mode surround default-stereo override-stereo stereo-device start-failure)
  add_test(NAME audio_backend_${mode} COMMAND audio_backend_test ${mode})
endforeach()

add_executable(mod_setup_run_test runtime/tools/mod_setup_run_test.cpp)
target_include_directories(mod_setup_run_test PRIVATE runtime/src)
target_compile_options(mod_setup_run_test PRIVATE -UNDEBUG)
add_test(NAME mod_setup_run COMMAND mod_setup_run_test)


if(NOT ANDROID)
  find_package(SDL3 CONFIG QUIET)
  if(SDL3_FOUND)
    add_executable(mod_setup_ui_test runtime/tools/mod_setup_ui_test.cpp runtime/third_party/imgui/backends/imgui_impl_sdlrenderer3.cpp)
    target_include_directories(mod_setup_ui_test PRIVATE runtime/src)
    target_compile_options(mod_setup_ui_test PRIVATE -UNDEBUG)
    target_link_libraries(mod_setup_ui_test PRIVATE imgui SDL3::SDL3)
    add_test(NAME mod_setup_ui COMMAND mod_setup_ui_test)
  endif()
endif()

if(NOT ANDROID AND SDL3_FOUND AND Python3_Interpreter_FOUND)
  add_executable(mod_oneclick_flow_test runtime/tools/mod_oneclick_flow_test.cpp
    runtime/third_party/imgui/backends/imgui_impl_sdlrenderer3.cpp runtime/src/mods/code_mods.cpp
    runtime/src/mods/manager.cpp runtime/src/mods/packages.cpp runtime/src/mods/guest_png.cpp
    runtime/src/mods/mod_archive.cpp runtime/src/mods/content.cpp runtime/src/mods/cemu_pack.cpp runtime/src/guest_addr_identity.c)
  target_include_directories(mod_oneclick_flow_test PRIVATE runtime/src runtime/include)
  target_compile_options(mod_oneclick_flow_test PRIVATE -UNDEBUG)
  target_link_libraries(mod_oneclick_flow_test PRIVATE imgui SDL3::SDL3 ZLIB::ZLIB Threads::Threads ${CMAKE_DL_LIBS})
  add_test(NAME mod_oneclick_flow COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/runtime/tools/test_mod_oneclick_flow.py $<TARGET_FILE:mod_oneclick_flow_test>)
endif()

add_test(NAME mod_setup_acceptance COMMAND mod_package_test --setup-acceptance)

if(WIN32 AND SDL3_FOUND)
  get_target_property(MOD_TEST_SDL_TYPE SDL3::SDL3 TYPE)
  if(MOD_TEST_SDL_TYPE STREQUAL "SHARED_LIBRARY")
    foreach(MOD_TEST_APP mod_setup_ui_test mod_oneclick_flow_test)
      if(TARGET ${MOD_TEST_APP})
        add_custom_command(TARGET ${MOD_TEST_APP} POST_BUILD
          COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:SDL3::SDL3> $<TARGET_FILE_DIR:${MOD_TEST_APP}> VERBATIM)
      endif()
    endforeach()
  endif()
endif()

# Espresso NI single-precision underflow, including the camera refinement regression.
add_executable(float_mode_test runtime/tools/float_mode_test.cpp runtime/src/espresso_fp.c)
target_include_directories(float_mode_test PRIVATE runtime/include)
target_compile_options(float_mode_test PRIVATE -UNDEBUG -ffp-contract=off)
add_test(NAME float_mode COMMAND float_mode_test)

# Exercise signed ADPCM offsets through the production HLE and decoder.
add_executable(ax_resume_offset_test runtime/tools/ax_resume_offset_test.cpp)
target_include_directories(ax_resume_offset_test PRIVATE runtime/src runtime/include)
target_compile_options(ax_resume_offset_test PRIVATE -UNDEBUG)
target_link_libraries(ax_resume_offset_test PRIVATE Threads::Threads)
add_test(NAME ax_resume_offset COMMAND ax_resume_offset_test)

# Tests extracting real functions from a user's generated v208 code are opt-in.
# No generated game source or data is checked in. The fixtures use POSIX mmap.
option(BOTW_GUEST_TESTS "Build offline v208 guest-function regressions" OFF)
if(BOTW_GUEST_TESTS AND UNIX)
  find_package(Python3 REQUIRED COMPONENTS Interpreter)
  set(BOTW_JPEG_FIXTURE ${CMAKE_CURRENT_BINARY_DIR}/botw_jpeg_fixture.c)
  add_custom_command(OUTPUT ${BOTW_JPEG_FIXTURE}
    COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tools/jpeg_fixture.py ${GEN_DIR} ${BOTW_JPEG_FIXTURE}
    DEPENDS tools/jpeg_fixture.py ${GEN_SOURCES} VERBATIM)
  add_executable(botw_jpeg_test runtime/tools/botw_jpeg_test.cpp runtime/src/botw_jump.c
    runtime/src/espresso_fp.c ${BOTW_JPEG_FIXTURE})
  target_include_directories(botw_jpeg_test PRIVATE runtime/include)
  target_compile_options(botw_jpeg_test PRIVATE -UNDEBUG -ffp-contract=off)
  set(BOTW_JPEG_TEST_DATA "" CACHE FILEPATH "Locally extracted v208 .rodata for JPEG regression")
  if(BOTW_JPEG_TEST_DATA)
    add_test(NAME botw_jpeg COMMAND botw_jpeg_test ${BOTW_JPEG_TEST_DATA})
  endif()
endif()

add_executable(fps_limiter_test runtime/tools/fps_limiter_test.cpp)
target_compile_options(fps_limiter_test PRIVATE -UNDEBUG)
add_test(NAME fps_limiter COMMAND fps_limiter_test)
if(BOTW_GUEST_TESTS AND UNIX)
  foreach(kind timing loading fps)
    set(fixture ${CMAKE_CURRENT_BINARY_DIR}/botw_${kind}_fixture.c)
    add_custom_command(OUTPUT ${fixture}
      COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tools/${kind}_fixture.py ${GEN_DIR} ${fixture}
      DEPENDS tools/${kind}_fixture.py tools/botw_fps_patches.py ${GEN_SOURCES} VERBATIM)
    set(extra)
    if(NOT kind STREQUAL "loading")
      list(APPEND extra runtime/src/botw_timing.cpp runtime/src/espresso_fp.c)
    endif()
    if(kind STREQUAL "fps")
      list(APPEND extra runtime/src/botw_cutscene.cpp)
    endif()
    add_executable(botw_${kind}_test runtime/tools/botw_${kind}_test.cpp ${extra} ${fixture})
    target_include_directories(botw_${kind}_test PRIVATE runtime/include)
    target_compile_options(botw_${kind}_test PRIVATE -UNDEBUG -ffp-contract=off)
    add_test(NAME botw_${kind} COMMAND botw_${kind}_test)
  endforeach()
endif()

add_executable(botw_motion_test runtime/tools/botw_motion_test.cpp ${MOTION_SOURCES})
target_include_directories(botw_motion_test PRIVATE runtime/src)
target_compile_options(botw_motion_test PRIVATE -UNDEBUG)
target_link_libraries(botw_motion_test PRIVATE Threads::Threads)
if(WIN32)
  target_link_libraries(botw_motion_test PRIVATE ws2_32)
endif()
foreach(fps 30 60 90 120)
  add_test(NAME botw_motion_${fps} COMMAND botw_motion_test ${fps})
endforeach()
