cmake_minimum_required(VERSION 3.24)
project(HWDER C CXX)

# HWDER: Hyrule Warriors: Definitive Edition static recompilation.
# The game's code (all ExeFS modules) is generated by tools/recomp/gen_main.py into generated/.
# Build with clang-cl (VS 2022 LLVM toolset): needs musttail, __int128 and GNU builtins.
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_C_STANDARD 11)
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded")

set(GEN_DIR "${CMAKE_SOURCE_DIR}/generated" CACHE PATH "User-local recompiled game sources")
set(TP ${CMAKE_SOURCE_DIR}/third_party)
if(MINGW)
    # This private UCRT stack targets Windows10+, not MinGW's Windows7 default.
    add_compile_definitions(_WIN32_WINNT=0x0A00 WINVER=0x0A00 NTDDI_VERSION=0x0A000000)
endif()
option(HWDER_SYNTHETIC_TESTS_ONLY "Build public tests without generated game code" OFF)
option(HWDER_HOST_TESTS_ONLY "Build portable host contract tests only" OFF)
option(HWDER_GAME_ONLY "Build generated game library without host runtime" OFF)
option(HWDER_SETUP_UI_ONLY "Build only the code-free native Windows setup window" OFF)
option(HWDER_MANAGED_PROBE_ONLY "Compile public production units and private-toolchain contracts without game inputs" OFF)
option(HWDER_PUBLIC_RUNTIME_LINK_ONLY "Public synthetic guest fixture: link only, NEVER run or distribute" OFF)
if(HWDER_PUBLIC_RUNTIME_LINK_ONLY AND (NOT MINGW OR NOT EXISTS "${GEN_DIR}/public-link-fixture.json"))
    message(FATAL_ERROR "Public runtime link requires the private GNU ABI and explicit public fixture")
endif()

if(HWDER_SETUP_UI_ONLY)
    if(NOT WIN32)
        message(FATAL_ERROR "The native setup window currently requires Windows")
    endif()
    # Icon, manifest and version information: an exe without them looks like a dropper to antivirus heuristics.
    add_executable(hwder_setup WIN32 tools/setup_ui/bootstrap.cpp tools/setup_ui/bootstrap.rc)
    set_source_files_properties(tools/setup_ui/bootstrap.rc PROPERTIES OBJECT_DEPENDS
        "${CMAKE_SOURCE_DIR}/tools/setup_ui/setup_version.h;${CMAKE_SOURCE_DIR}/tools/setup_ui/bootstrap.manifest;${CMAKE_SOURCE_DIR}/tools/setup_ui/hwder.ico")
    target_compile_definitions(hwder_setup PRIVATE NOMINMAX WIN32_LEAN_AND_MEAN)
    target_link_libraries(hwder_setup PRIVATE comctl32 comdlg32 shell32 ole32 uuid advapi32 user32 gdi32 winhttp)
    if(MINGW)
        # -s: no symbol table or DWARF. No link timestamp: unchanged sources give the same file in
        # every release, so antivirus reputation earned by one Setup carries over (setup_version.h).
        target_link_options(hwder_setup PRIVATE -municode -static -s -Wl,--no-insert-timestamp
                            "-Wl,-Map,${CMAKE_BINARY_DIR}/setup-ui-link.map")
    elseif(MSVC)
        target_link_options(hwder_setup PRIVATE /MANIFEST:NO /Brepro)  # bootstrap.rc embeds the manifest
    endif()
    set_target_properties(hwder_setup PROPERTIES OUTPUT_NAME "HWDER Setup")
    return()
endif()

if(HWDER_MANAGED_PROBE_ONLY)
    if(NOT WIN32 OR NOT MINGW OR NOT CMAKE_C_COMPILER_ID STREQUAL "Clang")
        message(FATAL_ERROR "The managed probe requires the private Windows LLVM-MinGW stack")
    endif()
    include(tools/tests/managed_probe.cmake)
    return()
endif()

if(HWDER_SYNTHETIC_TESTS_ONLY)
    enable_testing()
    add_executable(save_folder_selection_test runtime/tests/save_folder_selection_test.cpp)
    target_include_directories(save_folder_selection_test PRIVATE runtime/src)
    add_test(NAME save_folder_selection COMMAND save_folder_selection_test "${CMAKE_BINARY_DIR}/save-folder-fixture")
    set_tests_properties(save_folder_selection PROPERTIES TIMEOUT 30)
    add_executable(steam_shortcut_test tools/tests/steam_shortcut_test.cpp)
    add_test(NAME steam_shortcut COMMAND steam_shortcut_test)
    include(runtime/src/gpu/vk/sources.cmake)
    add_test(NAME pipeline_warmup_cache COMMAND pipeline_warmup_cache_test)
    add_test(NAME pipeline_catalog COMMAND pipeline_catalog_test)
    # Added by the independent public synthetic-CI candidate when integrated.
    # Pure-function unit tests (runtime/tests/runtime_unit_test.cpp): no Vulkan device, no game data.
    add_executable(runtime_unit_test runtime/tests/runtime_unit_test.cpp
        runtime/src/gpu/vk/formats.cpp runtime/src/gpu/vk/texture_layout.cpp
        runtime/src/service/hipc.cpp runtime/src/kernel/handles.cpp runtime/src/debug_flags.cpp)
    target_include_directories(runtime_unit_test PRIVATE runtime/src runtime/include ${TP}/vulkan-headers/include ${TP}/spirv-headers/include)
    target_compile_definitions(runtime_unit_test PRIVATE NOMINMAX WIN32_LEAN_AND_MEAN)
    target_compile_options(runtime_unit_test PRIVATE ${HW_CODEC_TEST_FLAGS})
    add_test(NAME runtime_unit COMMAND runtime_unit_test)
    if(EXISTS "${CMAKE_SOURCE_DIR}/tools/tests/recompiler.cmake")
        include(tools/tests/recompiler.cmake)
    endif()
    if(TARGET synthetic_tests)
        add_dependencies(synthetic_tests runtime_unit_test)
    endif()
    return()
endif()
if(HWDER_HOST_TESTS_ONLY)
    include(tools/tests/host.cmake)
    return()
endif()

if(MSVC)
    set(HW_ARCH_FLAGS /arch:AVX2 -mfma -mf16c -maes -mpclmul -msse4.2)
    set(HW_GAME_FLAGS /O2 /Z7)
elseif(WIN32 AND MINGW AND CMAKE_C_COMPILER_ID STREQUAL "Clang")
    # Complete private GNU ABI: never mix these objects with MSVC caches.
    set(HW_ARCH_FLAGS -mavx2 -mfma -mf16c -maes -mpclmul -msse4.2 -mcx16)
    set(HW_GAME_FLAGS -O2 -g)
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|amd64|AMD64)$")
    if(NOT CMAKE_C_COMPILER_ID MATCHES "Clang" OR NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang")
        message(FATAL_ERROR "Generated game code requires Clang (musttail and GNU builtins)")
    endif()
    set(HW_ARCH_FLAGS -mavx2 -mfma -mf16c -maes -mpclmul -msse4.2)
    set(HW_GAME_FLAGS -O2 -g)
else()
    message(FATAL_ERROR "Supported toolchains: Windows clang-cl; experimental Linux x86-64 Clang")
endif()
include("${GEN_DIR}/sources.cmake")


# ---------------------------------------------------------------- recompiled game code
add_library(hwder_game STATIC ${RECOMP_SOURCES} ${GEN_DIR}/functable.c ${GEN_DIR}/module_info.c)
target_include_directories(hwder_game PUBLIC runtime/include ${GEN_DIR})
target_compile_options(hwder_game PRIVATE ${HW_ARCH_FLAGS} ${HW_GAME_FLAGS} -Wno-everything -fno-strict-aliasing)
if(HWDER_GAME_ONLY)
    return()
endif()

# ---------------------------------------------------------------- Linux release: prebuilt static libraries
# The player-ready Linux release links SDL3, libavcodec/libavutil (H.264 only) and libcrypto from the archives that
# tools/linux/build_deps.sh made on glibc 2.35, so hwder needs only glibc and the Vulkan loader on the system.
set(HWDER_LINUX_DEPS "" CACHE PATH "Linux release: prebuilt static SDL3/FFmpeg/libcrypto (tools/linux/build_deps.sh)")
if(HWDER_LINUX_DEPS)
    if(WIN32)
        message(FATAL_ERROR "HWDER_LINUX_DEPS is for Linux builds")
    endif()
    find_package(SDL3 CONFIG REQUIRED PATHS ${HWDER_LINUX_DEPS} NO_DEFAULT_PATH)
    foreach(lib crypto avcodec avutil)
        if(NOT EXISTS ${HWDER_LINUX_DEPS}/lib/lib${lib}.a)
            message(FATAL_ERROR "Missing ${HWDER_LINUX_DEPS}/lib/lib${lib}.a")
        endif()
        add_library(hwder_dep_${lib} STATIC IMPORTED)
        set_target_properties(hwder_dep_${lib} PROPERTIES IMPORTED_LOCATION ${HWDER_LINUX_DEPS}/lib/lib${lib}.a
            INTERFACE_INCLUDE_DIRECTORIES ${HWDER_LINUX_DEPS}/include)
    endforeach()
endif()

# ---------------------------------------------------------------- SDL3: host gamepads only
# Static, with only what controllers need (joystick/gamepad, haptic, sensors, HIDAPI drivers for
# DualSense / DualShock 4 / Switch Pro). No window, renderer or audio: HWDER has its own.
if(NOT HWDER_LINUX_DEPS)
set(SDL_STATIC ON CACHE BOOL "" FORCE)
set(SDL_SHARED OFF CACHE BOOL "" FORCE)
set(SDL_TEST_LIBRARY OFF CACHE BOOL "" FORCE)
set(SDL_TESTS OFF CACHE BOOL "" FORCE)
set(SDL_EXAMPLES OFF CACHE BOOL "" FORCE)
set(SDL_INSTALL OFF CACHE BOOL "" FORCE)
set(SDL_LIBC ON CACHE BOOL "" FORCE)
foreach(sub AUDIO VIDEO GPU RENDER CAMERA POWER DIALOG)
    set(SDL_${sub} OFF CACHE BOOL "" FORCE)
endforeach()
if(NOT WIN32)
    foreach(sub AUDIO VIDEO DIALOG)
        set(SDL_${sub} ON CACHE BOOL "" FORCE)
    endforeach()
endif()
foreach(sub JOYSTICK HAPTIC HIDAPI SENSOR)
    set(SDL_${sub} ON CACHE BOOL "" FORCE)
endforeach()
add_subdirectory(${TP}/SDL ${CMAKE_BINARY_DIR}/SDL EXCLUDE_FROM_ALL)
endif()

# ---------------------------------------------------------------- runtime: HOS kernel, services, GPU
# Explicit lists: the checkout path may contain [ ] which CMake globs treat as patterns.
set(RUNTIME_SOURCES
    runtime/src/host/platform.cpp
    runtime/src/host/thread.cpp
    runtime/src/host/timing.cpp
    runtime/src/host/file.cpp
    runtime/src/host/sampler.cpp
    runtime/src/core.cpp
    runtime/src/debug_flags.cpp
    runtime/src/threads.cpp
    runtime/src/config.cpp
    runtime/src/version.cpp
    runtime/src/perf_stats.cpp
    runtime/src/kernel/kernel.cpp
    runtime/src/kernel/handles.cpp
    runtime/src/kernel/memory.cpp
    runtime/src/kernel/svc.cpp
    runtime/src/service/ipc.cpp
    runtime/src/service/hipc.cpp
    runtime/src/service/sm.cpp
    runtime/src/service/applet.cpp
    runtime/src/service/misc.cpp
)
foreach(sub hid fs audio gpu gpu/vk shader video game interp)
    include(${CMAKE_CURRENT_SOURCE_DIR}/runtime/src/${sub}/sources.cmake)
endforeach()
# Built without HW_ARCH_FLAGS: it must run (and refuse a CPU without AVX2) before any AVX2 code does.
add_library(hwder_cpu_check OBJECT runtime/src/cpu_check.cpp)
if(NOT WIN32)
    list(APPEND RUNTIME_SOURCES runtime/src/host/sdl_main.cpp)
endif()
add_executable(hwder ${RUNTIME_SOURCES} $<TARGET_OBJECTS:hwder_cpu_check>)
if(HWDER_PUBLIC_RUNTIME_LINK_ONLY)
    # Cannot satisfy first_run's hwder.exe lookup or its ready certificate.
    set_target_properties(hwder PROPERTIES OUTPUT_NAME "HWDER-public-link-NOT-A-GAME")
endif()

# Version stamp: regenerated on every build (always out of date), rewritten only when it changes.
set(HW_VERSION_DIR ${CMAKE_BINARY_DIR}/version)
add_custom_target(hwder_version
    COMMAND ${CMAKE_COMMAND} -DSRC=${CMAKE_SOURCE_DIR} -DOUT=${HW_VERSION_DIR}/hwder_version.h
            -P ${CMAKE_SOURCE_DIR}/tools/version.cmake
    BYPRODUCTS ${HW_VERSION_DIR}/hwder_version.h
    COMMENT "Stamping version"
    VERBATIM)
add_dependencies(hwder hwder_version)
target_include_directories(hwder PRIVATE ${HW_VERSION_DIR})
target_link_libraries(hwder PRIVATE hwder_game SDL3::SDL3-static)
if(WIN32)
    target_link_libraries(hwder PRIVATE dbghelp winmm synchronization psapi bcrypt)
endif()
if(MINGW)
    target_link_libraries(hwder PRIVATE mfplat mfuuid wmcodecdspuuid ole32 shell32 uuid dwmapi)
elseif(HWDER_LINUX_DEPS)
    find_package(Threads REQUIRED)
    target_link_libraries(hwder PRIVATE Threads::Threads hwder_dep_avcodec hwder_dep_avutil hwder_dep_crypto m ${CMAKE_DL_LIBS})
    # -rdynamic exports symbols for named backtraces; build_deps.sh builds these libraries with hidden visibility so
    # Vulkan layers loaded into the game (with their own libcrypto, SDL or FFmpeg) never bind to ours.
    # The release must keep the texture write watch (kernel 6.7 PAGEMAP_SCAN): fail the build, never drop it silently.
    target_compile_definitions(hwder PRIVATE HWDER_REQUIRE_WRITE_WATCH)
elseif(NOT WIN32)
    find_package(Threads REQUIRED)
    find_package(OpenSSL 3.0 REQUIRED COMPONENTS Crypto)
    find_package(PkgConfig REQUIRED)
    pkg_check_modules(HWDER_AV REQUIRED IMPORTED_TARGET libavcodec libavutil)
    target_link_libraries(hwder PRIVATE Threads::Threads OpenSSL::Crypto PkgConfig::HWDER_AV ${CMAKE_DL_LIBS})
endif()
target_include_directories(hwder PRIVATE runtime/src ${TP}/vulkan-headers/include ${TP}/spirv-headers/include ${TP}/imgui ${TP}/imgui/backends)
target_compile_definitions(hwder PRIVATE NOMINMAX WIN32_LEAN_AND_MEAN IMGUI_IMPL_VULKAN_NO_PROTOTYPES IMGUI_IMPL_VULKAN_USE_LOADER IMGUI_DISABLE_OBSOLETE_FUNCTIONS IMGUI_USER_CONFIG="gpu/hwder_imconfig.h")
if(MSVC)
    target_compile_options(hwder PRIVATE ${HW_ARCH_FLAGS} /Z7 /O2 /EHsc -Wno-everything)
else()
    target_compile_options(hwder PRIVATE ${HW_ARCH_FLAGS} -g -O2 -fno-strict-aliasing -Wno-everything)
endif()
if(MSVC)
    target_link_options(hwder PRIVATE /DEBUG /STACK:16777216 /DYNAMICBASE:NO /FIXED:NO)
else()
    if(WIN32)
        target_link_options(hwder PRIVATE -static -Wl,--stack,16777216 -Wl,--disable-dynamicbase)
    else()
        target_link_options(hwder PRIVATE -rdynamic -Wl,-z,stack-size=16777216)
    endif()
endif()
