﻿cmake_minimum_required(VERSION 3.16)
project(mkw_recompiled)

set(MKW_TARGET_ANDROID FALSE)
if(CMAKE_SYSTEM_NAME STREQUAL "Android" AND CMAKE_ANDROID_ARCH_ABI STREQUAL "arm64-v8a")
    set(MKW_TARGET_ANDROID TRUE)

    # The NDK's toolchain file (android-legacy.toolchain.cmake) defaults
    # CMAKE_FIND_ROOT_PATH_MODE_PACKAGE to ONLY, so any find_package(... PATHS <dir>) whose <dir>
    # sits outside CMAKE_FIND_ROOT_PATH (== the NDK sysroot) is silently ignored - find_package
    # reports "considered the following locations: [empty]" and fails, even though the requested
    # path plainly contains a valid Config.cmake (verified directly). This is a standard, well-documented Android
    # cross-compilation gotcha, unrelated to Dawn or anything in this project; every
    # find_package(...) call this build tree makes (Dawn, and later nod/SDL3's own package
    # providers) fetches prebuilt packages that live outside the sysroot, so BOTH (search both the
    # root path and normal system locations) is the correct project-wide setting here, not a
    # narrowly-scoped NO_CMAKE_FIND_ROOT_PATH on one call site.
    set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH)
endif()

# Switch homebrew, via devkitPro's Switch.cmake toolchain file (sets CMAKE_SYSTEM_NAME to
# "NintendoSwitch" and CMAKE_SYSTEM_PROCESSOR to "aarch64"). Unlike the NDK's toolchain file,
# devkitA64.cmake's find_root_path handling only takes the CMAKE_FIND_ROOT_PATH_MODE_PACKAGE=ONLY
# branch on CMake <3.16 (see dkp-toolchain-common.cmake) - this project requires 3.16+ (see
# cmake_minimum_required above), so it lands in the CMAKE_SYSTEM_PREFIX_PATH branch instead and
# never sets that variable at all. The Android find_package gotcha above does not apply here;
# nothing needs overriding for Switch on that front.
set(MKW_TARGET_SWITCH FALSE)
if(CMAKE_SYSTEM_NAME STREQUAL "NintendoSwitch")
    set(MKW_TARGET_SWITCH TRUE)
    # libnx gives every pthread/std::thread a 128 KiB stack; Tint's WGSL resolver and mesa's GLSL
    # compiler overflow that. __wrap___syscall_thread_create (main.cpp) raises the floor for every
    # thread in the process, including ones created inside Dawn, SDL and libstdc++.
    add_link_options(-Wl,--wrap=__syscall_thread_create)
    # Native versions of a few hot maths routines (runtime/src/hle/native_math.cpp) take the place
    # of the translated ones at link time: calls and the dispatch table reach __wrap_func_ADDR, the
    # translated body stays as __real_func_ADDR. Keep this list equal to that file's entry points.
    set(MKW_NATIVE_MATH_WRAPS 80199D64 8019A91C 80085B80 80005F34)
    foreach(mkw_native_math_address IN LISTS MKW_NATIVE_MATH_WRAPS)
        add_link_options(-Wl,--wrap=func_${mkw_native_math_address})
    endforeach()
    # Pack the functions a 12-kart race actually runs (from on-console profiles, generated by
    # a local script) at the front of .text. 43 MB of translated code otherwise
    # scatters the hot set across dozens of 1 MiB regions and thousands of pages the A57's TLBs
    # and L2 cannot hold; measured 2026-09-25: 28 -> 30 FPS stock, 40 -> 50 FPS overclocked.
    # The file must begin with KEEP (*(.crt0)): hbloader enters the NRO at offset 0.
    option(MKW_SWITCH_SECTION_ORDERING "Order hot functions first in the Switch NRO" ON)
    set(MKW_SWITCH_SECTION_ORDER_FILE "${CMAKE_CURRENT_SOURCE_DIR}/tools/switch_section_order.ld")
    if(MKW_SWITCH_SECTION_ORDERING AND EXISTS "${MKW_SWITCH_SECTION_ORDER_FILE}")
        add_link_options("-Wl,--section-ordering-file=${MKW_SWITCH_SECTION_ORDER_FILE}")
    endif()
endif()

if((NOT (WIN32 AND MINGW)) AND (NOT CMAKE_SYSTEM_NAME STREQUAL "Linux") AND
   (NOT MKW_TARGET_ANDROID) AND (NOT MKW_TARGET_SWITCH))
    message(FATAL_ERROR "WiiCompiled requires Windows (LLVM-MinGW), native Linux, Android arm64-v8a, or Nintendo Switch (devkitA64)")
endif()
if(MKW_TARGET_ANDROID)
    if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR
       NOT CMAKE_SIZEOF_VOID_P EQUAL 8 OR
       NOT CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64|arm64|AARCH64)$")
        message(FATAL_ERROR "WiiCompiled for Android requires 64-bit Clang targeting arm64-v8a")
    endif()
elseif(MKW_TARGET_SWITCH)
    # devkitPro ships no Clang cross-compiler for Switch - devkitA64 is GCC-only (verified: GCC
    # 16.1.0, no clang/aarch64-none-elf-clang binary exists anywhere under devkitA64/bin). This is
    # a deliberate, accepted exception to the Clang-everywhere policy enforced below for every
    # other target, not an oversight. GCC-vs-Clang
    # divergences (e.g. a Clang-only #pragma in ax_effects.cpp) are fixed as they're found rather
    # than assumed away; do not add a second exception here without checking for more.
    if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR
       NOT CMAKE_SIZEOF_VOID_P EQUAL 8 OR
       NOT CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64|arm64|AARCH64)$")
        message(FATAL_ERROR "WiiCompiled for Switch requires 64-bit GCC (devkitA64) targeting aarch64")
    endif()
else()
    if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR
       NOT CMAKE_SIZEOF_VOID_P EQUAL 8 OR
       NOT CMAKE_SYSTEM_PROCESSOR MATCHES "^(AMD64|amd64|x86_64|X86_64)$")
        message(FATAL_ERROR "WiiCompiled requires 64-bit Clang targeting x86_64")
    endif()
endif()
if(NOT CMAKE_BUILD_TYPE STREQUAL "Release")
    message(FATAL_ERROR "WiiCompiled only supports Release builds")
endif()

# Preprocessor definitions that belong to this project's own code (the runtime,
# the translated shards and the product glue) and to nothing else. They are
# applied directory-scoped, immediately after the aurora add_subdirectory() call,
# so every target created afterwards (mkw_runtime_common, the translated shard
# libraries and the products in cmake/PublicProducts.cmake) inherits them while
# aurora-main and its third-party tree stay unaffected.
set(MKW_PROJECT_COMPILE_DEFINITIONS NOMINMAX)

# Profile-guided optimization for the Switch build. Two-pass:
#   cmake -DMKW_PGO=generate ...  -> instrumented build; play, then the runtime calls
#                                   __gcov_dump() and the .gcda files land under
#                                   sdmc:/switch/WiiCompiled/Cache/gcov (GCOV_PREFIX).
#   copy that tree back over the build dir, then
#   cmake -DMKW_PGO=use ...      -> optimized build using the measured hot paths.
set(MKW_PGO "" CACHE STRING "PGO pass (Switch and Android): empty, generate or use")
# Android (clang): the recording build writes files/WiiCompiled/pgo/game.profraw on the device
# (runtime/src/android_pgo.cpp). Pull it, run `llvm-profdata merge -o android.profdata`, and pass
# the result as MKW_PGO_PROFILE to the "use" build.
set(MKW_PGO_PROFILE "" CACHE FILEPATH "Android PGO: merged .profdata for -DMKW_PGO=use")
if(ANDROID AND MKW_PGO STREQUAL "generate")
    add_compile_options(-fprofile-generate)
    add_link_options(-fprofile-generate)
    list(APPEND MKW_PROJECT_COMPILE_DEFINITIONS MKW_PGO_GENERATE=1)
elseif(ANDROID AND MKW_PGO STREQUAL "use")
    if(NOT EXISTS "${MKW_PGO_PROFILE}")
        message(FATAL_ERROR "MKW_PGO=use on Android needs MKW_PGO_PROFILE=<merged .profdata>")
    endif()
    # A source edited after the recording only loses its own profile instead of failing the build.
    add_compile_options("-fprofile-use=${MKW_PGO_PROFILE}" -Wno-profile-instr-unprofiled
                        -Wno-profile-instr-out-of-date -Wno-backend-plugin)
elseif(MKW_PGO STREQUAL "generate")
    # single, not atomic: the A57 is ARMv8.0 with no LSE, so every atomic counter bump would be an
    # exclusive-monitor retry loop and the training run would crawl. Translated code runs on one
    # thread; the few racy counters elsewhere are what -fprofile-correction absorbs below.
    # -fno-profile-values: devkitA64's prebuilt libgcov is built for -mtp=el0, so its indirect-call
    # profiler reads TLS through TPIDR_EL0, which Horizon resets. The first indirect call during
    # static init wrote through a garbage pointer and the recording build hung on a black screen
    # before main. Arc (branch/block) counts, the part layout and hot/cold splitting need, stay on.
    add_compile_options(-fprofile-generate -fprofile-update=single -fno-profile-values)
    add_link_options(-fprofile-generate -fprofile-update=single -fno-profile-values)
    list(APPEND MKW_PROJECT_COMPILE_DEFINITIONS MKW_PGO_GENERATE=1)
elseif(MKW_PGO STREQUAL "use")
    # -fprofile-correction: counters are written from several threads; missing or
    # inconsistent counts must not fail the build.
    # -fprofile-use also switches on -O3-class loop transforms; keep them off (code growth is a
    # measured loss on this i-cache-bound binary) and let the profile drive layout instead.
    # No -freorder-blocks-and-partition: devkitA64's GCC emits jump-table entries that subtract
    # labels across the hot and .text.unlikely sections, which the assembler rejects ("invalid
    # operands ... for `-'"). -fno-reorder-functions keeps every function in `.text.<name>`
    # (instead of .text.hot./.text.unlikely.) so MKW_SWITCH_SECTION_ORDERING's measured race order
    # still applies to the profiled build.
    # -fno-profile-values matches the generate pass (see above), which records no value counters.
    # -Wno-error=coverage-mismatch: a source edited after the recording build only loses its own
    # profile (GCC warns and falls back) instead of failing the whole build.
    add_compile_options(-fprofile-use -fprofile-correction -Wno-missing-profile -fno-profile-values
                        -Wno-error=coverage-mismatch -fno-reorder-functions
                        -fno-unroll-loops -fno-peel-loops -fno-unswitch-loops -fno-split-loops
                        -fno-tree-loop-distribution -fno-loop-interchange -fno-loop-unroll-and-jam
                        -fno-predictive-commoning -fno-version-loops-for-strides -fno-ipa-cp-clone
                        -fno-tracer -fvect-cost-model=very-cheap)
    add_link_options(-fprofile-use -fprofile-correction)
elseif(NOT MKW_PGO STREQUAL "")
    message(FATAL_ERROR "MKW_PGO must be empty, generate or use")
endif()

set(CMAKE_CXX_STANDARD 17)

# Declared before the vendored libraries so the third-party block below can
# branch on it and so cmake/NativePrebuilt.cmake is included after the names it
# must provide are known.
set(MKW_NATIVE_PREBUILT_DIR "" CACHE PATH
    "Precompiled aurora/third-party package to link instead of building aurora-main")
# Maintainer-only counterpart: describe the configured aurora build so the
# package can be harvested from it.
set(MKW_NATIVE_PREBUILT_EXPORT_DIR "" CACHE PATH
    "Maintainer-only: write a description of the configured aurora build here")

# The two Crypto++ configuration macros are load-bearing for ABI: they shape the
# structs and inline code the headers generate, so a first-party TU including
# <cryptopp/...> must see exactly what the archive was compiled with. Both the
# from-source target below and the prebuilt import in cmake/NativePrebuilt.cmake
# consume this one definition.
set(MKW_CRYPTOPP_INTERFACE_DEFINITIONS CRYPTOPP_DISABLE_ASM CRYPTOPP_NO_GLOBAL_BYTE)

# Riivolution XML is parsed with pugixml, matching the parser used by Dolphin.
# The four upstream files from v1.15 (ee86beb30e4973f5feffe3ce63bfa4fbadf72f38)
# are vendored so builds remain usable with FETCHCONTENT_FULLY_DISCONNECTED.
add_library(mkw_pugixml STATIC third_party/pugixml/pugixml.cpp)
add_library(mkw::pugixml ALIAS mkw_pugixml)
target_include_directories(mkw_pugixml PUBLIC third_party/pugixml)
target_compile_features(mkw_pugixml PUBLIC cxx_std_17)
set_target_properties(mkw_pugixml PROPERTIES UNITY_BUILD OFF)

# Non-Windows guest-fiber scheduling (runtime/src/fiber_manager.cpp) needs a symmetric
# stackful-coroutine primitive to stand in for Win32 Fibers. libco's co_switch() transfers
# directly to any other created coroutine, matching SwitchToFiber's semantics exactly (unlike
# asymmetric resume/yield coroutine libraries, which would need every call site restructured).
# Vendored from upstream (higan-emu/libco @ e18e09d, 2019-10-16, ISC license; valgrind.h is
# separately BSD-style licensed, see third_party/libco/LICENSE) - all of libco's non-Windows
# CPU-architecture backends are kept, even though libco.c's own preprocessor dispatch
# (__amd64__/__i386__/__arm__/__aarch64__/etc.) only ever selects amd64.c for this project's
# x86_64-only target (see the platform/arch check above). Windows keeps using native Fibers
# untouched, so this target is never built there.
if(NOT WIN32)
    add_library(mkw_libco STATIC third_party/libco/libco.c)
    add_library(mkw::libco ALIAS mkw_libco)
    target_include_directories(mkw_libco PUBLIC third_party/libco)
    set_target_properties(mkw_libco PROPERTIES UNITY_BUILD OFF)
endif()

# Runtime configuration is real TOML, parsed by toml11 rather than a project-
# specific line parser. Keep it header-only and vendored so disconnected release
# builds have exactly the same parser as developer builds.
add_library(mkw_toml11 INTERFACE)
add_library(mkw::toml11 ALIAS mkw_toml11)
target_include_directories(mkw_toml11 SYSTEM INTERFACE third_party/toml11)
target_compile_features(mkw_toml11 INTERFACE cxx_std_17)

# Wii ES signatures use the standard binary-field sect233r1 curve. Crypto++
# supplies that curve, SHA-1, ECDSA and an OS-seeded CSPRNG. The complete 8.9.0
# source distribution is vendored. Assembly and algorithm-specific SIMD units
# are disabled to keep the library portable and independent of the runtime's
# x86-64-v3 policy; the static linker retains only the algorithms we reference.
#
# Crypto++ is by far the most expensive vendored library to compile (~150
# translation units), and under the shipped fixed toolchain it is bit-for-bit
# identical for every user, so the prebuilt package harvests its archive and
# this block is skipped entirely in that mode (see cmake/NativePrebuilt.cmake).
if(NOT MKW_NATIVE_PREBUILT_DIR)
    file(GLOB MKW_CRYPTOPP_SOURCES CONFIGURE_DEPENDS
        "${CMAKE_CURRENT_LIST_DIR}/third_party/cryptopp/*.cpp")
    list(FILTER MKW_CRYPTOPP_SOURCES EXCLUDE REGEX
        "/(adhoc|bench[123]|datatest|dlltest|fipsalgt|fipstest|pch|regtest[1234]|simple|test|validat[0-9]+)\\.cpp$")
    list(FILTER MKW_CRYPTOPP_SOURCES EXCLUDE REGEX
        "(_avx|_ppc|_simd|_sse)\\.cpp$")
    add_library(mkw_cryptopp STATIC ${MKW_CRYPTOPP_SOURCES})
    add_library(mkw::cryptopp ALIAS mkw_cryptopp)
    target_include_directories(mkw_cryptopp SYSTEM PUBLIC third_party)
    target_compile_features(mkw_cryptopp PUBLIC cxx_std_17)
    target_compile_definitions(mkw_cryptopp PUBLIC ${MKW_CRYPTOPP_INTERFACE_DEFINITIONS})
    target_compile_options(mkw_cryptopp PRIVATE -w)
    set_target_properties(mkw_cryptopp PROPERTIES UNITY_BUILD OFF)
    if(MKW_TARGET_ANDROID)
        # cpu.cpp unconditionally does `#if defined(__ANDROID__) # include "cpu-features.h"
        # #endif` and calls android_getCpuFamily()/android_getCpuFeatures() to gate hardware-
        # accelerated (NEON/crypto-extension) code paths. That header+source pair used to ship in
        # every NDK at $ANDROID_NDK_ROOT/sources/android/cpufeatures, but modern NDKs (confirmed:
        # r27c, the one this project uses) dropped it - android_getCpuFeatures() was deprecated in
        # favor of getauxval(AT_HWCAP). This project already disables Crypto++'s ASM/SIMD code
        # entirely via CRYPTOPP_DISABLE_ASM (see the comment above this block - "portable and
        # independent of the runtime's x86-64-v3 policy"), so the feature-detection RESULT these
        # calls return is unused either way; only the declarations need to exist for cpu.cpp to
        # compile. Same stub-header trick this file already uses for DXProgrammableCapture.h on
        # Windows (see MKW_DX_STUB_DIR below) - "no accelerated features" (0) is both the simplest
        # and the semantically correct answer given ASM is off project-wide.
        set(MKW_ANDROID_CPU_FEATURES_STUB_DIR "${CMAKE_BINARY_DIR}/android_cpu_features_stub")
        if(NOT EXISTS "${MKW_ANDROID_CPU_FEATURES_STUB_DIR}/cpu-features.h")
            file(MAKE_DIRECTORY "${MKW_ANDROID_CPU_FEATURES_STUB_DIR}")
            file(WRITE "${MKW_ANDROID_CPU_FEATURES_STUB_DIR}/cpu-features.h"
"#pragma once
// Stub for the NDK cpu-features.h this project's vendored Crypto++ still references (removed
// from modern NDKs). CRYPTOPP_DISABLE_ASM means the returned bits are never acted on - see
// runtime/CMakeLists.txt for the full explanation.
#include <stdint.h>
typedef enum { ANDROID_CPU_FAMILY_UNKNOWN = 0, ANDROID_CPU_FAMILY_ARM = 1, ANDROID_CPU_FAMILY_ARM64 = 2 } AndroidCpuFamily;
enum {
    ANDROID_CPU_ARM_FEATURE_ARMv7 = 0, ANDROID_CPU_ARM_FEATURE_NEON = 0,
    ANDROID_CPU_ARM_FEATURE_CRC32 = 0, ANDROID_CPU_ARM_FEATURE_PMULL = 0,
    ANDROID_CPU_ARM_FEATURE_AES = 0, ANDROID_CPU_ARM_FEATURE_SHA1 = 0,
    ANDROID_CPU_ARM_FEATURE_SHA2 = 0, ANDROID_CPU_ARM_FEATURE_SHA3 = 0,
    ANDROID_CPU_ARM_FEATURE_SHA512 = 0, ANDROID_CPU_ARM_FEATURE_SM3 = 0, ANDROID_CPU_ARM_FEATURE_SM4 = 0,
    ANDROID_CPU_ARM64_FEATURE_ASIMD = 0, ANDROID_CPU_ARM64_FEATURE_CRC32 = 0,
    ANDROID_CPU_ARM64_FEATURE_PMULL = 0, ANDROID_CPU_ARM64_FEATURE_AES = 0,
    ANDROID_CPU_ARM64_FEATURE_SHA1 = 0, ANDROID_CPU_ARM64_FEATURE_SHA2 = 0,
    ANDROID_CPU_ARM64_FEATURE_SHA3 = 0, ANDROID_CPU_ARM64_FEATURE_SHA512 = 0,
    ANDROID_CPU_ARM64_FEATURE_SM3 = 0, ANDROID_CPU_ARM64_FEATURE_SM4 = 0
};
#ifdef __cplusplus
extern \"C\" {
#endif
inline AndroidCpuFamily android_getCpuFamily(void) { return ANDROID_CPU_FAMILY_ARM64; }
inline uint64_t android_getCpuFeatures(void) { return 0; }
#ifdef __cplusplus
}
#endif
")
        endif()
        target_include_directories(mkw_cryptopp PRIVATE "${MKW_ANDROID_CPU_FEATURES_STUB_DIR}")
    endif()
endif()

# TLS for non-Windows guest network HLE (runtime/src/hle/net/network_ssl.cpp) - the Windows path
# uses Schannel (a Windows-only OS API), which has no equivalent on Linux/Android, so this project
# needs its own TLS library there. mbed TLS was chosen over OpenSSL specifically because it cross-
# compiles cleanly for Android with nothing beyond a plain C toolchain (no perl/asm build-script
# dependency the way OpenSSL's build has), matching how this project already prefers toolchain-
# simple vendored libraries (see Crypto++ above, similarly stripped of ASM/SIMD for portability).
# Vendored under third_party/mbedtls the same way as Crypto++/pugixml - a real source checkout,
# not a submodule or FetchContent download, matching this project's existing convention for
# permissively-licensed C/C++ dependencies it needs to guarantee builds identically for every user.
file(GLOB MKW_MBEDTLS_SOURCES CONFIGURE_DEPENDS
    "${CMAKE_CURRENT_LIST_DIR}/third_party/mbedtls/library/*.c")
add_library(mkw_mbedtls STATIC ${MKW_MBEDTLS_SOURCES})
add_library(mkw::mbedtls ALIAS mkw_mbedtls)
target_include_directories(mkw_mbedtls SYSTEM PUBLIC "${CMAKE_CURRENT_LIST_DIR}/third_party/mbedtls/include")
target_include_directories(mkw_mbedtls PRIVATE "${CMAKE_CURRENT_LIST_DIR}/third_party/mbedtls/library")
target_compile_features(mkw_mbedtls PUBLIC c_std_99)
target_compile_options(mkw_mbedtls PRIVATE -w)
set_target_properties(mkw_mbedtls PROPERTIES UNITY_BUILD OFF)

set(MKW_TRANSLATED_COMPILE_JOBS 0 CACHE STRING
    "Cap on concurrently compiling translated shard TUs via a Ninja job pool (0 = uncapped). \
Scheduling only - never affects output bytes, so it is deliberately outside the canonical flag fingerprint.")
set(MKW_CPPWINRT_INCLUDE_DIR "" CACHE PATH "Optional self-contained C++/WinRT include directory")

# Precompiled aurora + third-party package (see launcher/Prepare-NativePrebuilt.ps1).
# Empty - the default - keeps the from-source behaviour every developer build
# uses. When set, aurora-main, its extern/ tree and the vendored Crypto++ are
# not compiled at all; imported targets with the same names are wired up from
# the package instead, so this project's own code compiles and links exactly
# as before.
set(MKW_AURORA_DIR "${CMAKE_CURRENT_LIST_DIR}/../aurora-main")

# The translator's explicit float casts are PPC single-precision rounding points.
# Fast-math may erase them and change guest-visible integer conversions.
set(MKW_TRANSLATED_PPC_FP_OPTIONS -fno-fast-math -ffp-contract=off)

# ----------------------------------------------------------------------
# Third-party: aurora-main (provides SDL3 + GPU backends)
# ----------------------------------------------------------------------
if(MKW_NATIVE_PREBUILT_DIR)
    # Precompiled aurora + third-party archives. The source trees are still
    # present (the runtime includes aurora headers directly), they are simply
    # not compiled here.
    include("${CMAKE_CURRENT_LIST_DIR}/cmake/NativePrebuilt.cmake")
else()
    if(NOT EXISTS ${MKW_AURORA_DIR}/CMakeLists.txt)
        message(FATAL_ERROR "Requested aurora-main but ${MKW_AURORA_DIR} is missing")
    endif()
    # Switch: aurora-main (SDL3 windowing + Dawn/WebGPU) has no Switch backend at all and cannot
    # get one - Dawn only targets Vulkan/D3D/Metal/GL, none of which exist on stock Switch
    # homebrew (graphics go through devkitPro's own deko3d instead). This add_subdirectory() is
    # still attempted for Switch rather than skipped, deliberately: Phase 1's own goal is to prove
    # this project's CMake logic gets as far as it can and fails exactly at the SDL3/Dawn boundary,
    # not to paper over that boundary early. Expect this configure (or the following build) to
    # fail somewhere in aurora-main/Dawn for MKW_TARGET_SWITCH - that failure is Phase 1's real
    # deliverable, and Phase 3 replaces aurora_core's GPU/window layer wholesale for this target
    # rather than trying to make Dawn itself work here.
    set(DAWN_ENABLE_D3D11 OFF CACHE BOOL "" FORCE)
    if(WIN32)
        set(DAWN_ENABLE_D3D12 ON CACHE BOOL "" FORCE)
        set(TINT_BUILD_HLSL_WRITER ON CACHE BOOL "" FORCE)
        set(DAWN_USE_WINDOWS_UI OFF CACHE BOOL "" FORCE)
    else()
        # Non-Windows (Linux/Android): mirrors aurora-main's own
        # _aurora_dawn_set_platform_backends() choice for this platform - Vulkan only, no
        # D3D/HLSL. Kept in sync here because this project's own CMake FORCEs these cache
        # variables before aurora-main's add_subdirectory() runs, which pre-empts aurora's
        # auto-detection (CACHE ... INTERNAL "" without FORCE never overrides an existing value).
        set(DAWN_ENABLE_D3D12 OFF CACHE BOOL "" FORCE)
        set(TINT_BUILD_HLSL_WRITER OFF CACHE BOOL "" FORCE)
    endif()
    set(DAWN_ENABLE_VULKAN ON CACHE BOOL "" FORCE)
    if(MKW_TARGET_ANDROID)
        # Android is the one target where Vulkan is not guaranteed present/functional on every
        # device (older or budget SoCs, or a buggy vendor driver) - unlike desktop Linux, where
        # this project treats Vulkan as the only backend. Hence the graphics
        # decision (Vulkan primary, OpenGL ES fallback): aurora's own platform-backend function defaults OpenGLES OFF for Android
        # (it falls into the generic "else" branch there), so it must be forced ON here the same
        # way the other DAWN_ENABLE_* flags already are.
        set(DAWN_ENABLE_OPENGLES ON CACHE BOOL "" FORCE)

        # AuroraDawnProvider.cmake's "auto" resolution only recognizes prebuilt Dawn PACKAGES for
        # windows/linux-x86_64/linux-aarch64/macos - not Android - so it falls through to "vendor"
        # (build Dawn from source via FetchContent), whose pinned URL
        # (github.com/google/dawn/archive/refs/tags/${AURORA_DAWN_VERSION}.tar.gz) 404s: that git
        # tag has been pruned from the real google/dawn repo (confirmed directly). The dawn-build release at the SAME
        # AURORA_DAWN_VERSION this project already pins everywhere else DOES publish
        # dawn-android-aarch64.tar.gz (confirmed via the GitHub releases API) - forcing "package"
        # here uses that prebuilt directly and skips the broken vendor path entirely. KartPad (a
        # sibling static-recompilation port of this project for Apple platforms) does the exact
        # same thing for its own targets: AURORA_DAWN_PROVIDER=package at this same pinned
        # version, never vendor.
        set(AURORA_DAWN_PROVIDER "package" CACHE STRING "" FORCE)

        # The dawn-android-aarch64 release asset's exported lib/cmake/Dawn/DawnTargets.cmake bakes
        # an ABSOLUTE, non-relocatable path to liblog.so from the machine that built the package
        # (something like /usr/local/lib/android/sdk/ndk/<their-ndk-version>/.../liblog.so) into
        # INTERFACE_LINK_LIBRARIES, instead of a portable "log"/"-llog" reference - a packaging bug
        # in that specific release (verified directly: the path doesn't exist on this machine and
        # points at a different NDK version than the one actually configured here), not anything under this project's control to fix at
        # the source. Pre-fetch the SAME package AuroraDawnProvider.cmake's "package" branch will
        # ask for below (identical FetchContent NAME/URL, so its own later
        # FetchContent_Declare/MakeAvailable calls become a documented no-op reuse of what's
        # already populated here) and rewrite the one bad path to point at THIS machine's real NDK
        # liblog.so instead, derived from CMAKE_TOOLCHAIN_FILE rather than assumed.
        # AURORA_DAWN_VERSION itself isn't defined yet in this scope - it's only set once
        # aurora-main's own CMakeLists.txt runs, which is the add_subdirectory() call below this
        # whole block. Read the same pin directly out of that file instead of duplicating the
        # literal version string here (which would silently drift out of sync if aurora-main's own
        # pin is ever bumped).
        file(STRINGS "${MKW_AURORA_DIR}/CMakeLists.txt" MKW_AURORA_DAWN_VERSION_LINE
            REGEX "set\\(AURORA_DAWN_VERSION ")
        string(REGEX MATCH "\"([^\"]+)\"" MKW_AURORA_DAWN_VERSION_MATCH "${MKW_AURORA_DAWN_VERSION_LINE}")
        if(NOT CMAKE_MATCH_1)
            message(FATAL_ERROR "Could not read AURORA_DAWN_VERSION from ${MKW_AURORA_DIR}/CMakeLists.txt")
        endif()
        set(AURORA_DAWN_VERSION "${CMAKE_MATCH_1}")

        get_filename_component(MKW_ANDROID_NDK_ROOT "${CMAKE_TOOLCHAIN_FILE}" DIRECTORY)
        get_filename_component(MKW_ANDROID_NDK_ROOT "${MKW_ANDROID_NDK_ROOT}" DIRECTORY)
        get_filename_component(MKW_ANDROID_NDK_ROOT "${MKW_ANDROID_NDK_ROOT}" DIRECTORY)
        set(MKW_ANDROID_LOG_LIB
            "${MKW_ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/aarch64-linux-android/28/liblog.so")
        if(NOT EXISTS "${MKW_ANDROID_LOG_LIB}")
            message(FATAL_ERROR "Could not derive this machine's liblog.so from CMAKE_TOOLCHAIN_FILE "
                "(looked for ${MKW_ANDROID_LOG_LIB}) - the Dawn Android package patch below needs it.")
        endif()

        include(FetchContent)
        FetchContent_Declare(dawn_prebuilt
            URL "https://github.com/encounter/dawn-build/releases/download/${AURORA_DAWN_VERSION}/dawn-android-aarch64.tar.gz"
            DOWNLOAD_EXTRACT_TIMESTAMP TRUE
        )
        FetchContent_MakeAvailable(dawn_prebuilt)
        set(MKW_DAWN_TARGETS_FILE "${dawn_prebuilt_SOURCE_DIR}/lib/cmake/Dawn/DawnTargets.cmake")
        if(EXISTS "${MKW_DAWN_TARGETS_FILE}")
            file(READ "${MKW_DAWN_TARGETS_FILE}" MKW_DAWN_TARGETS_CONTENT)
            string(REGEX REPLACE
                "/[^;\"]*/ndk/[^;\"]*/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/aarch64-linux-android/28/liblog\\.so"
                "${MKW_ANDROID_LOG_LIB}"
                MKW_DAWN_TARGETS_CONTENT_PATCHED "${MKW_DAWN_TARGETS_CONTENT}")
            if(NOT MKW_DAWN_TARGETS_CONTENT_PATCHED STREQUAL MKW_DAWN_TARGETS_CONTENT)
                file(WRITE "${MKW_DAWN_TARGETS_FILE}" "${MKW_DAWN_TARGETS_CONTENT_PATCHED}")
                message(STATUS "Patched Dawn's Android package: replaced its non-portable liblog.so path with this machine's real one")
            endif()
        endif()

        # Custom Snapdragon Vulkan drivers (Turnip, newer Qualcomm builds) through libadrenotools,
        # BSD-2-Clause, the loader emulators use. Only useful with a Dawn that carries the
        # SetVulkanLibraryOverride patch (point FETCHCONTENT_SOURCE_DIR_DAWN_PREBUILT at one); with
        # the stock package the runtime detects its absence and uses the system driver.
        option(MKW_ANDROID_CUSTOM_GPU_DRIVERS "Load user-installed Vulkan drivers via libadrenotools" ON)
        if(MKW_ANDROID_CUSTOM_GPU_DRIVERS)
            FetchContent_Declare(adrenotools
                GIT_REPOSITORY https://github.com/bylaws/libadrenotools.git
                GIT_TAG 8fae8ce254dfc1344527e05301e43f37dea2df80
                GIT_SUBMODULES_RECURSE TRUE
            )
            FetchContent_MakeAvailable(adrenotools)
        endif()
    else()
        set(DAWN_ENABLE_OPENGLES OFF CACHE BOOL "" FORCE)
    endif()
    set(DAWN_BUILD_SAMPLES OFF CACHE BOOL "" FORCE)
    set(DAWN_BUILD_TESTS OFF CACHE BOOL "" FORCE)

    # Provide a tiny stub for DXProgrammableCapture when the SDK/PIX headers are
    # missing (common on MinGW). Dawn only includes the header; no symbols are
    # referenced when PIX isn't present.
    set(MKW_DX_STUB_DIR "${CMAKE_BINARY_DIR}/aurora_dx_stubs")
    if(NOT EXISTS "${MKW_DX_STUB_DIR}/DXProgrammableCapture.h")
        file(MAKE_DIRECTORY ${MKW_DX_STUB_DIR})
        file(WRITE "${MKW_DX_STUB_DIR}/DXProgrammableCapture.h"
"#pragma once\n// Stubbed PIX capture header for Dawn; no functionality when PIX is absent.\n")
    endif()
    # Deliberately NOT injected project-wide. Only a from-source Dawn build ever
    # includes DXProgrammableCapture.h, and this tree consumes Dawn as a prebuilt
    # package (AURORA_DAWN_PROVIDER=package), so the old
    # include_directories()/add_compile_options(-I...) pair only added a dead
    # -I to every one of the ~500 third-party and runtime compile lines. The
    # aurora_* targets that do want it get it from the
    # target_include_directories() call further down; a from-source Dawn build
    # should add it to the Dawn targets the same way.

    # Keep SDL3 lean when aurora is enabled.
    set(SDL_SHARED OFF CACHE BOOL "" FORCE)
    set(SDL_STATIC ON CACHE BOOL "" FORCE)
    set(SDL_TEST OFF CACHE BOOL "" FORCE)

    # MinGW/WSL can choke on SDL precompiled headers; disable PCH for aurora/SDL.
    set(CMAKE_DISABLE_PRECOMPILE_HEADERS ON)
    # Isolate aurora build products to a subdirectory under the main build dir.
    add_subdirectory(${MKW_AURORA_DIR} ${CMAKE_BINARY_DIR}/aurora-build EXCLUDE_FROM_ALL)
    set(CMAKE_DISABLE_PRECOMPILE_HEADERS OFF)

    # Keep upstream Aurora split: several sources intentionally use file-local
    # names that collide under this repo's aggressive unity build settings.
    set(AURORA_TARGETS aurora_core aurora_gx aurora_pad aurora_si aurora_mtx aurora_vi)
    foreach(t ${AURORA_TARGETS})
        if(TARGET ${t})
            set_target_properties(${t} PROPERTIES UNITY_BUILD OFF)
            target_compile_options(${t} PRIVATE -O3 -ffast-math -w -pipe)
            target_include_directories(${t} PRIVATE ${MKW_DX_STUB_DIR})
            # Aurora's own sources include Windows headers and call std::min/max;
            # they relied on the old project-wide NOMINMAX that no longer leaks
            # into this subtree, so the define is applied per target here.
            target_compile_definitions(${t} PRIVATE NOMINMAX)
        endif()
    endforeach()

    # Frame interpolation filters guest NaN/Inf matrices out of a std::sort
    # comparator, which makes its std::isfinite guards load-bearing for memory
    # safety, not just match quality. -ffinite-math-only (part of -ffast-math
    # above) folds those guards to constants and poisons NaN float arguments
    # via nofpclass, so this one file opts back out of the finite-math
    # assumption; the file #errors if built without this. Source-scoped
    # COMPILE_OPTIONS append after the target options, so this overrides the
    # target's -ffast-math for exactly this TU.
    if(TARGET aurora_gx)
        set_source_files_properties("${MKW_AURORA_DIR}/lib/gx/frame_interpolation.cpp"
            TARGET_DIRECTORY aurora_gx
            PROPERTIES COMPILE_OPTIONS "-fno-finite-math-only")
    endif()

    if(MKW_NATIVE_PREBUILT_EXPORT_DIR)
        include("${CMAKE_CURRENT_LIST_DIR}/cmake/NativePrebuiltExport.cmake")
    endif()
endif()

# Everything from here down is this project's own code, so this is where the
# project definitions enter the directory scope. A directory-scoped definition
# seeds the COMPILE_DEFINITIONS of targets as they are created, which means:
#   * aurora-main and its third-party tree were added above -> unaffected;
#   * mkw_runtime_common, the translated shard libraries and the products in
#     cmake/PublicProducts.cmake are all created below (that file is include()d,
#     not add_subdirectory()d, so it shares this directory scope) -> they pick
#     the definitions up exactly as before.
if(MKW_PROJECT_COMPILE_DEFINITIONS)
    add_compile_definitions(${MKW_PROJECT_COMPILE_DEFINITIONS})
endif()

# Runtime sources. CONFIGURE_DEPENDS costs one glob re-check per build (measured
# at ~55 ms across the 100+ globs this build already re-verifies for SDL), and it
# buys correctness: without it, a newly added src/*.cpp that is not referenced by
# a registration file is silently never compiled and never errors. The stale-glob
# failure mode is worth far more than the milliseconds.
file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS "src/*.cpp")
# TEMP-SNDAUDIT: desktop test builds can log the sound thread's heap calls
# (src/hle/audio/sound_core_audit.cpp). Off by default; the wraps need that file.
option(MKW_SOUND_AUDIT "Log heap calls made by the sound host thread (desktop tests)" OFF)
if(MKW_SOUND_AUDIT AND NOT MKW_TARGET_SWITCH)
    foreach(mkw_audit_address 80229814 80229B84 80226C04 80226C78)
        add_link_options(-Wl,--wrap=func_${mkw_audit_address})
    endforeach()
    set_source_files_properties("${CMAKE_CURRENT_LIST_DIR}/src/hle/audio/sound_core_audit.cpp" PROPERTIES
        COMPILE_DEFINITIONS MKW_SOUND_AUDIT
        SKIP_UNITY_BUILD_INCLUSION ON
        SKIP_PRECOMPILE_HEADERS ON)
endif()
set(MKW_BASE_PRODUCT_SOURCE "${CMAKE_CURRENT_LIST_DIR}/src/product/base_product.cpp")
set(MKW_RETRO_REWIND_PRODUCT_SOURCE "${CMAKE_CURRENT_LIST_DIR}/src/product/retro_rewind_product.cpp")
# The host ISA guard is the one translation unit that must not receive the
# x86-64-v3 flags, so it gets its own object library instead of riding along in
# mkw_runtime_common. See cmake/PublicProducts.cmake and the file's own header.
set(MKW_CPU_BASELINE_SOURCE "${CMAKE_CURRENT_LIST_DIR}/src/host_cpu_baseline.cpp")
# Android-only combined-library provider (see cmake/PublicProducts.cmake's MKW_TARGET_ANDROID
# branch) - like the two product sources above, it defines RuntimeProduct::Active() and must
# stay out of mkw_runtime_common's shared object graph or every product duplicate-symbols it.
set(MKW_COMBINED_PRODUCT_SOURCE "${CMAKE_CURRENT_LIST_DIR}/src/product/combined_product.cpp")
list(REMOVE_ITEM SOURCES ${MKW_BASE_PRODUCT_SOURCE} ${MKW_RETRO_REWIND_PRODUCT_SOURCE}
     ${MKW_CPU_BASELINE_SOURCE} ${MKW_COMBINED_PRODUCT_SOURCE})

# The translator emits the complete, content-addressed source graph. Consuming
# this one manifest keeps configure independent of the 28k generated function
# files and of optional Retro Rewind artifacts such as code.map.
set(MKW_TRANSLATED_SHARD_MANIFEST
    "${CMAKE_CURRENT_LIST_DIR}/../generated/build_shards/shards.cmake"
    CACHE FILEPATH "Translator-owned aggregate shard manifest")
if(NOT EXISTS "${MKW_TRANSLATED_SHARD_MANIFEST}")
    message(FATAL_ERROR
        "Missing translator-owned shard manifest: ${MKW_TRANSLATED_SHARD_MANIFEST}. "
        "Run Translator.Cli emit-build-shards first; see translator/README.md.")
endif()
include("${MKW_TRANSLATED_SHARD_MANIFEST}")
set(MKW_HAVE_RETRO_REWIND ${MKW_HAVE_RETRO_REWIND_SHARDS})
message(STATUS
    "Translator graph: ${MKW_SHARED_BASE_FUNCTION_COUNT}/${MKW_BASE_FUNCTION_COUNT} base functions shared; "
    "${MKW_PROFILE_SENSITIVE_CALLER_COUNT} profile-sensitive callers; "
    "${MKW_RETRO_REWIND_FUNCTION_COUNT} Retro Rewind functions")

set(MKW_RUNTIME_SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}")
include("${CMAKE_CURRENT_LIST_DIR}/cmake/PublicProducts.cmake")
