# nxrt: the Windows runtime that hosts the recompiled game (M3 v0). See docs/m3-runtime.md.
# Ported from nxnative (MIT, © seedds; third_party/nxnative-LICENSE). The HLE registry and adapters in gen/out are generated by gen/gen_hle.py.
set(NXRT_HLE
  hle/hle_sdkinit.c hle/hle_offline.c hle/hle_net.c hle/hle_nvdrv.c hle/hle_account.c hle/hle_friends.c hle/hle_time.c hle/hle_table.c hle/hle_nvn.c
  hle/hle_fs.c hle/hle_oe.c hle/hle_sdk16.c hle/hle_hid.c hle/hle_touch.c hle/hle_haptics.c hle/hle_nv.c hle/hle_vi.c
  hle/movie_real.c hle/hle_audio.c hle/audio_silent.c hle/hle_audren.c hle/hle_sysmix.c hle/sysmix_ring.c hle/hle_misc.c hle/hle_codec.c)
if(WIN32)
  list(APPEND NXRT_HLE hle/save_txn.c)
else()
  list(APPEND NXRT_HLE hle/save_txn_linux.c)
endif()
if(WIN32)
  set(NXRT_PLATFORM plat/plat_win32.c host/addrspace.c host/prof.c)
  set(NXRT_SYS_LIBS bcrypt dbghelp)
else()
  set(NXRT_PLATFORM plat/plat_linux.c host/addrspace_linux.c host/prof_linux.c)
  set(NXRT_SYS_LIBS ${CMAKE_DL_LIBS} pthread m)
endif()
# Real movie decoding (hle/movie_real.c): libavcodec (libdav1d inside) linked dynamically when pkg-config finds it; enabled by default, NXRT_MOVIE_DECODE=0 opts out.
option(NXRT_ENABLE_FFMPEG "Build optional FFmpeg movie decoding" ON)
find_package(PkgConfig QUIET)
if(NXRT_ENABLE_FFMPEG AND PkgConfig_FOUND AND NOT WIN32)
  pkg_check_modules(NXRT_FFMPEG QUIET IMPORTED_TARGET libavcodec libavutil)
endif()
add_library(nxrt_core STATIC
  ${NXRT_PLATFORM}
  host/log.c host/crash.c host/hle_os.c host/thread.c host/hle_thread.c host/hle_svc.c host/rt.c host/hle.c host/loader.cpp
  host/main.c host/fiber.c host/guest_jmp.c host/ctl_standins.c host/hle_guestthread.c gfx/nvn_tex.c
  gfx/gfx_select.c gfx/gfx_null.c gfx/nvn_dirty.c gfx/nvn_texio.c gfx/nxs_rt.c
  ${NXRT_HLE}
  gen/out/hle_registry.gen.c gen/out/hle_stubs.gen.c)
target_include_directories(nxrt_core PUBLIC include plat PRIVATE host hle gfx gen/out)
target_compile_definitions(nxrt_core PUBLIC NXRT_BUILDING PRIVATE _USE_MATH_DEFINES)
if(NOT WIN32)
  target_compile_definitions(nxrt_core PUBLIC _GNU_SOURCE)
endif()
set_target_properties(nxrt_core PROPERTIES C_STANDARD 11 C_EXTENSIONS ON)
target_link_libraries(nxrt_core PUBLIC nxw_abi nxload nxw_interp s2tex PRIVATE ${NXRT_SYS_LIBS} coop)   # coop: Link Play (src/coop, docs/coop-step1b.md)
# NXRT_PDB=ON: nxrt.exe with nxrt.pdb (CodeView), so the profiler (host/prof.c) names host functions
option(NXRT_PDB "build nxrt with debug information for the profiler" OFF)
if(NXRT_PDB)
  target_compile_options(nxrt_core PRIVATE -gcodeview)
endif()
# nxnative's own style in the ported files, not bugs
if(NXRT_FFMPEG_FOUND)
  target_compile_definitions(nxrt_core PRIVATE NXRT_HAVE_FFMPEG)
  target_link_libraries(nxrt_core PRIVATE PkgConfig::NXRT_FFMPEG)
  message(STATUS "nxrt: real movie decoding with libavcodec ${NXRT_FFMPEG_libavcodec_VERSION}")
  # offline harness: first N frames of a .webm -> PPM (docs/FASE6-VIDEO.md)
  pkg_check_modules(NXRT_FFMPEG_FMT QUIET IMPORTED_TARGET libavformat)
  if(NXRT_FFMPEG_FMT_FOUND)
    add_executable(nxrt-movie-decode tools/movie_decode_test.c hle/movie_real.c)
    target_include_directories(nxrt-movie-decode PRIVATE hle)
    target_compile_definitions(nxrt-movie-decode PRIVATE NXRT_HAVE_FFMPEG _GNU_SOURCE)
    target_link_libraries(nxrt-movie-decode PRIVATE PkgConfig::NXRT_FFMPEG PkgConfig::NXRT_FFMPEG_FMT pthread)
  endif()
endif()
target_compile_options(nxrt_core PRIVATE $<$<COMPILE_LANGUAGE:C>:-Wno-sign-compare -Wno-missing-field-initializers -Wno-unused-function>)

# Every executable that hosts guest code: high-entropy ASLR off, so the host's own allocations stay out of the guest range (DESIGN D3)
function(nxrt_executable name)
  add_executable(${name} ${ARGN})
  target_link_libraries(${name} PRIVATE nxrt_core)
  target_include_directories(${name} PRIVATE plat host hle gen/out)
  if(WIN32)
    target_link_options(${name} PRIVATE -Xlinker /HIGHENTROPYVA:NO $<$<BOOL:${NXRT_PDB}>:-g>)
  else()
    # exports: only what game.so imports, rt_* and adapter_* (plat/linux/nxrt.dynlist; pokopia-native 56be9d2, #30)
    target_link_options(${name} PRIVATE "-Wl,--dynamic-list=${CMAKE_CURRENT_FUNCTION_LIST_DIR}/plat/linux/nxrt.dynlist")
    set_target_properties(${name} PROPERTIES BUILD_RPATH "$ORIGIN")   # libSDL3.so.0 beside the program
  endif()
  set_target_properties(${name} PROPERTIES C_STANDARD 11 C_EXTENSIONS ON)
endfunction()

nxrt_executable(nxrt tools/nxrt_main.c)
if(WIN32)
  set_target_properties(nxrt PROPERTIES ENABLE_EXPORTS ON)   # nxrt.lib, what the game DLL links against
else()   # Linux: the rt_* / adapter_* the game's shared object binds to at load come from the dynamic list; ENABLE_EXPORTS would add --export-dynamic past it
  add_test(NAME nxrt.exports COMMAND ${CMAKE_COMMAND} -DNM=${CMAKE_NM} -DEXE=$<TARGET_FILE:nxrt> -P ${CMAKE_CURRENT_SOURCE_DIR}/tests/check_exports.cmake)
endif()

# The tests that exercise Win32 specifics stay on Windows; on Linux the runtime is checked by its self-contained tests and by running the game
if(NOT WIN32)
  # tests/test_addrspace.c uses Win32 VirtualAllocEx/CreateProcess APIs, so its
  # reservation-conflict probe is registered only in the Windows block below.
  nxrt_executable(nxrt-tests-vibration tests/test_vibration.c)
  target_link_options(nxrt-tests-vibration PRIVATE -Wl,--wrap=guest_sym)
  add_test(NAME nxrt.vibration COMMAND nxrt-tests-vibration)
  set_tests_properties(nxrt.vibration PROPERTIES TIMEOUT 20)
  nxrt_executable(nxrt-tests-nvn2 tests/test_nvn2.c)
  target_include_directories(nxrt-tests-nvn2 PRIVATE gfx)
  add_test(NAME nxrt.nvn2 COMMAND nxrt-tests-nvn2)
  set_tests_properties(nxrt.nvn2 PROPERTIES TIMEOUT 20)
  add_executable(nxrt-tests-save-linux tests/test_save_linux.c hle/save_txn_linux.c)
  target_include_directories(nxrt-tests-save-linux PRIVATE hle)
  target_compile_definitions(nxrt-tests-save-linux PRIVATE _GNU_SOURCE)
  add_test(NAME nxrt.save_linux COMMAND nxrt-tests-save-linux ${CMAKE_BINARY_DIR}/save-test)
  find_package(Threads REQUIRED)
  add_executable(nxrt-tests-fiber tests/test_fiber.c host/fiber.c)
  target_include_directories(nxrt-tests-fiber PRIVATE include plat)
  target_link_libraries(nxrt-tests-fiber PRIVATE nxw_abi Threads::Threads)
  target_compile_definitions(nxrt-tests-fiber PRIVATE _GNU_SOURCE)
  add_test(NAME nxrt.fiber COMMAND nxrt-tests-fiber)
  add_executable(nxrt-tests-guest-jmp tests/test_guest_jmp.c host/guest_jmp.c host/fiber.c)
  target_include_directories(nxrt-tests-guest-jmp PRIVATE include plat)
  target_link_libraries(nxrt-tests-guest-jmp PRIVATE nxw_abi Threads::Threads)
  target_compile_definitions(nxrt-tests-guest-jmp PRIVATE _GNU_SOURCE)
  add_test(NAME nxrt.guest_jmp COMMAND nxrt-tests-guest-jmp)
  add_executable(nxrt-tests-vk-handle tests/test_vk_handle.c)
  target_include_directories(nxrt-tests-vk-handle PRIVATE vk)
  add_test(NAME nxrt.vk_handle COMMAND nxrt-tests-vk-handle)
  find_package(Vulkan QUIET)
  if(Vulkan_FOUND)
    add_library(nxrt-vulkan-loader-probe SHARED tests/vulkan_loader_probe.c)
    target_link_libraries(nxrt-vulkan-loader-probe PRIVATE Vulkan::Vulkan)
    add_test(NAME nxrt.vulkan_loader_interposition
      COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tests/check_vk_loader_preload.py
        $<TARGET_FILE:nxrt> $<TARGET_FILE:nxrt-vulkan-loader-probe>)
    set_tests_properties(nxrt.vulkan_loader_interposition PROPERTIES TIMEOUT 20)
  endif()
  nxrt_executable(nxrt-tests-sysmix tests/test_sysmix.c)
  add_test(NAME nxrt.sysmix COMMAND nxrt-tests-sysmix)
  set_tests_properties(nxrt.sysmix PROPERTIES TIMEOUT 15 ENVIRONMENT "SDL_AUDIO_DRIVER=dummy;SDL_AUDIODRIVER=dummy;DM_AUDIO=1")
  add_subdirectory(vk)
  return()
endif()

nxrt_executable(nxrt-tests-as tests/test_addrspace.c)
add_test(NAME nxrt.addrspace COMMAND nxrt-tests-as)
nxrt_executable(nxrt-tests-svc tests/test_svc.c tests/guest_test.c)
add_test(NAME nxrt.svc COMMAND nxrt-tests-svc)
nxrt_executable(nxrt-tests-threads tests/test_threads.c tests/guest_test.c)
add_test(NAME nxrt.threads COMMAND nxrt-tests-threads)
nxrt_executable(nxrt-tests-adapters tests/test_adapters.c tests/hle_sig_test.c)
add_test(NAME nxrt.adapters COMMAND nxrt-tests-adapters)
nxrt_executable(nxrt-tests-dump tests/test_dump.c)
add_test(NAME nxrt.dump COMMAND nxrt-tests-dump)
set_tests_properties(nxrt.dump PROPERTIES SKIP_RETURN_CODE 77)
# M3 integration: the game DLL contract (a stand-in DLL importing from the test executable, as the game DLL imports from nxrt.exe), the bootstrap's
# order on the real modules, the null NVN device
nxrt_executable(nxrt-tests-gamedll tests/test_game_dll.c)
set_target_properties(nxrt-tests-gamedll PROPERTIES ENABLE_EXPORTS ON)
add_library(nxrt_test_game SHARED tests/game_dll.c)
target_include_directories(nxrt_test_game PRIVATE include)
target_link_libraries(nxrt_test_game PRIVATE nxw_abi nxrt-tests-gamedll)
add_test(NAME nxrt.gamedll COMMAND nxrt-tests-gamedll)
nxrt_executable(nxrt-tests-boot tests/test_boot_order.c)
add_test(NAME nxrt.boot_order COMMAND nxrt-tests-boot)
set_tests_properties(nxrt.boot_order PROPERTIES SKIP_RETURN_CODE 77)
nxrt_executable(nxrt-tests-nvn tests/test_nvn.c)
add_test(NAME nxrt.nvn COMMAND nxrt-tests-nvn)
# M4: audio pacing (the silent sink on the guest clock) and the NTFS save commit killed at every step (DESIGN D13)
nxrt_executable(nxrt-tests-audio tests/test_audio.c)
add_test(NAME nxrt.audio COMMAND nxrt-tests-audio)
nxrt_executable(nxrt-tests-save tests/test_save.c)
add_test(NAME nxrt.save COMMAND nxrt-tests-save)
# M5 track A: nxnative's NVN recording layer against the null backend (docs/m5-record-shaders.md)
nxrt_executable(nxrt-tests-nvn-rec tests/test_nvn_rec.c tests/guest_test.c)
add_test(NAME nxrt.nvn_rec COMMAND nxrt-tests-nvn-rec)
# M6: the save round trip through nn::fs (docs/m6-input-gameplay.md)
nxrt_executable(nxrt-tests-save-rt tests/test_save_rt.c tests/guest_test.c)
add_test(NAME nxrt.save_rt COMMAND nxrt-tests-save-rt)
# M6 phase B: play.ps1's defaults (nxrt.exe and the work folder) however the script is started (docs/m6-phase-b.md)
add_test(NAME scripts.play_defaults COMMAND powershell -NoProfile -ExecutionPolicy Bypass -File ${CMAKE_SOURCE_DIR}/scripts/test_play.ps1 -Repo ${CMAKE_SOURCE_DIR})
set_tests_properties(scripts.play_defaults PROPERTIES SKIP_RETURN_CODE 77)
# Co-op step 1b: the hooks route to the coop module (off: the offline answers; host / join: a node through the hooks with one made directly), one process each
nxrt_executable(nxrt-tests-coop tests/test_coop.c)
target_link_libraries(nxrt-tests-coop PRIVATE coop)
foreach(m off host join)
  add_test(NAME nxrt.coop_${m} COMMAND nxrt-tests-coop ${m})
  set_tests_properties(nxrt.coop_${m} PROPERTIES TIMEOUT 60)
endforeach()
foreach(t nxrt.addrspace nxrt.svc nxrt.threads nxrt.adapters nxrt.dump nxrt.gamedll nxrt.boot_order nxrt.nvn nxrt.audio nxrt.save nxrt.nvn_rec nxrt.save_rt
          nxrt.coop_off nxrt.coop_host nxrt.coop_join)
  set_tests_properties(${t} PROPERTIES ENVIRONMENT "NXRT_LOG_DIR=${CMAKE_BINARY_DIR}/nxrt-test-logs")
endforeach()

# The generated files are current with nxnative's sources and the ports (skipped without the nxnative tree). Inside pokopia-native (this tree as
# windows/ beside nxs2.py) nxnative is the installer's own checkout; in the private layout the installer is a sibling folder.
if(EXISTS "${CMAKE_SOURCE_DIR}/../nxs2.py")
  set(_nxrt_nxnative "${CMAKE_SOURCE_DIR}/../nxnative")
else()
  set(_nxrt_nxnative "${CMAKE_SOURCE_DIR}/../installer/nxnative")
endif()
set(NXRT_NXNATIVE "${_nxrt_nxnative}" CACHE PATH "nxnative checkout (for the generator check)")
find_package(Python3 COMPONENTS Interpreter)
if(Python3_FOUND)
  add_test(NAME nxrt.generated COMMAND ${Python3_EXECUTABLE} -I ${CMAKE_CURRENT_SOURCE_DIR}/gen/gen_hle.py
           --nxnative ${NXRT_NXNATIVE} --port ${CMAKE_CURRENT_SOURCE_DIR}/host --port ${CMAKE_CURRENT_SOURCE_DIR}/hle
           --extra ${CMAKE_CURRENT_SOURCE_DIR}/tests/hle_sig_test.c --out ${CMAKE_CURRENT_SOURCE_DIR}/gen/out --check)
  set_tests_properties(nxrt.generated PROPERTIES SKIP_RETURN_CODE 77)
  # the backend dispatcher's table covers the contract headers (gfx/gen_gfx_api.py)
  add_test(NAME nxrt.gfx_api COMMAND ${Python3_EXECUTABLE} -I ${CMAKE_CURRENT_SOURCE_DIR}/gfx/gen_gfx_api.py ${CMAKE_CURRENT_SOURCE_DIR}/gfx --check)
endif()

# M5 track B: the Vulkan backend (docs/m5-vulkan.md); skipped without the Vulkan SDK and SDL3
add_subdirectory(vk)
