cmake_minimum_required(VERSION 3.20)
# macOS: the `wwhd` executable (Metal). Android: `libwwhd.so` (Vulkan), loaded by the app in android/.
if(APPLE)
  project(wwhd_recomp C CXX OBJCXX)
else()
  project(wwhd_recomp C CXX ASM)
endif()

# no paths of the build machine in the binaries (__FILE__ in checks, debug info, the ops.c bitcode):
# file names are relative to the repository
add_compile_options("-ffile-prefix-map=${CMAKE_SOURCE_DIR}/=" "-ffile-prefix-map=${CMAKE_BINARY_DIR}/=build/")
# 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 (original project, 2a54901).
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()

# The game code: either recompiled on the PC (tools/recomp/recomp.py -> build/gen, linked in), or,
# for an APK without game code, recompiled on the device and loaded at startup (runtime/src/recomp)
option(WWHD_DEVICE_RECOMP "Android: no build/gen; the app recompiles the game from the user's disc image" OFF)
if(NOT WWHD_DEVICE_RECOMP)
  # 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()

  # testing the LLVM recompiler (runtime/src/recomp, runtime/tools/recomp_llvm.cpp): its object files
  # replace the C functions; the function table and import stubs still come from GEN_DIR
  set(GEN_OBJECTS "" CACHE PATH "directory of code_*.o from recomp_llvm (instead of GEN_DIR/code_*.c)")
  if(GEN_OBJECTS)
    file(GLOB GEN_SOURCES CONFIGURE_DEPENDS ${GEN_OBJECTS}/code_*.o)
    set_source_files_properties(${GEN_SOURCES} PROPERTIES EXTERNAL_OBJECT TRUE)
  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)
    target_compile_options(gamecode PRIVATE -mcpu=apple-m1)
  endif()
endif()

# ---- vendored Cemu GPU pieces (MPL-2.0): address library + shader decompiler (MSL for Metal, GLSL for Vulkan)
set(CEMU_DIR ${CMAKE_SOURCE_DIR}/runtime/third_party/cemu)
file(GLOB_RECURSE CEMU_SOURCES CONFIGURE_DEPENDS ${CEMU_DIR}/*.cpp)
if(NOT APPLE)
  list(FILTER CEMU_SOURCES EXCLUDE REGEX "LatteDecompilerEmitMSL")
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/fmt/include)
target_compile_options(cemu_latte PRIVATE "SHELL:-include ${CEMU_DIR}/cemu_shim.h")
if(APPLE)
  target_include_directories(cemu_latte PUBLIC ${CMAKE_SOURCE_DIR}/runtime/third_party/metal-cpp)
  target_compile_definitions(cemu_latte PUBLIC ENABLE_METAL=1)
else()
  target_compile_definitions(cemu_latte PUBLIC ENABLE_VULKAN=1)
endif()
target_compile_options(cemu_latte PRIVATE -w -O2)

# ---- vendored zstd (BSD): its decoder only, for Cemu .wua archives (runtime/src/disc/wua.cpp)
set(ZSTD_DIR ${CMAKE_SOURCE_DIR}/runtime/third_party/zstd/lib)
file(GLOB ZSTD_SOURCES CONFIGURE_DEPENDS ${ZSTD_DIR}/common/*.c ${ZSTD_DIR}/decompress/*.c)
add_library(zstd_dec STATIC ${ZSTD_SOURCES})
target_include_directories(zstd_dec PUBLIC ${ZSTD_DIR})
target_compile_definitions(zstd_dec PRIVATE ZSTD_DISABLE_ASM ZSTD_LEGACY_SUPPORT=0)
target_compile_options(zstd_dec PRIVATE -w -O2)

# ---- runtime sources shared by both platforms
# GX2 and HLE sources are compiled into the final binary (not a static library) so their strong
# 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/disc/*.cpp)
list(FILTER RUNTIME_SOURCES EXCLUDE REGEX "audio_out_(coreaudio|aaudio)\\.cpp$")
# the macOS controls window's mapping (macOS key codes); Android maps input in runtime/src/android
if(NOT APPLE)
  list(FILTER RUNTIME_SOURCES EXCLUDE REGEX "/input_map\\.cpp$")
endif()
file(GLOB GX2_SOURCES CONFIGURE_DEPENDS runtime/src/gx2/*.cpp)
set(SHARED_GFX_SOURCES runtime/src/gfx/shader_headstart.cpp)

if(APPLE)
  # ---- macOS: the game executable, native GX2 on Metal
  file(GLOB GFX_SOURCES CONFIGURE_DEPENDS runtime/src/gfx/*.mm)
  set_source_files_properties(${GX2_SOURCES} ${GFX_SOURCES} ${SHARED_GFX_SOURCES} PROPERTIES COMPILE_OPTIONS "-include;${CEMU_DIR}/cemu_shim.h")
  set_source_files_properties(${GFX_SOURCES} PROPERTIES COMPILE_FLAGS "-fobjc-arc")
  # gameplay mods drawing with Metal (runtime/src/mods/*.mm)
  file(GLOB MOD_GFX_SOURCES CONFIGURE_DEPENDS runtime/src/mods/*.mm)
  set_source_files_properties(${MOD_GFX_SOURCES} PROPERTIES COMPILE_FLAGS "-fobjc-arc")

  add_executable(wwhd ${RUNTIME_SOURCES} runtime/src/audio_out_coreaudio.cpp ${GX2_SOURCES} ${GFX_SOURCES} ${SHARED_GFX_SOURCES}
                      ${MOD_GFX_SOURCES})
  target_include_directories(wwhd PRIVATE runtime/include runtime/src runtime/src/gx2)
  target_compile_options(wwhd PRIVATE -Wall -Wno-unused-function -Wno-unused-variable -ffp-contract=off)
  find_package(ZLIB REQUIRED)
  target_link_libraries(wwhd PRIVATE gamecode cemu_latte ZLIB::ZLIB
    "-framework Metal" "-framework QuartzCore" "-framework AppKit" "-framework Foundation" "-framework ImageIO" "-framework UniformTypeIdentifiers" "-framework GameController" "-framework Carbon" "-framework AudioToolbox" "-framework CoreAudio" compression)

  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")

  # controls mapping unit tests (runtime/src/input_map.cpp)
  add_executable(input_map_test runtime/tools/input_map_test.cpp runtime/src/input_map.cpp)
  target_include_directories(input_map_test PRIVATE runtime/src)

elseif(ANDROID)
  # ---- Android: libwwhd.so, native GX2 on Vulkan; the Java activity in android/ loads it
  include(FetchContent)
  # GLSL -> SPIR-V for the decompiled shaders
  set(ENABLE_OPT OFF CACHE BOOL "" FORCE)
  set(ENABLE_GLSLANG_BINARIES OFF CACHE BOOL "" FORCE)
  set(ENABLE_HLSL OFF CACHE BOOL "" FORCE)
  set(ENABLE_SPVREMAPPER OFF CACHE BOOL "" FORCE)
  set(ENABLE_CTEST OFF CACHE BOOL "" FORCE)
  set(GLSLANG_TESTS OFF CACHE BOOL "" FORCE)
  set(GLSLANG_ENABLE_INSTALL OFF CACHE BOOL "" FORCE)
  set(BUILD_EXTERNAL OFF CACHE BOOL "" FORCE)
  FetchContent_Declare(glslang GIT_REPOSITORY https://github.com/KhronosGroup/glslang.git GIT_TAG 15.4.0 GIT_SHALLOW TRUE)
  # Vulkan Memory Allocator (header only; its CMake project isn't needed)
  FetchContent_Declare(vma GIT_REPOSITORY https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git GIT_TAG v3.3.0
                       GIT_SHALLOW TRUE SOURCE_SUBDIR none)
  # volk: Vulkan entry points loaded at run time, from the system's libvulkan or (libadrenotools)
  # one that loads a GPU driver the user installed; tag matching the NDK's Vulkan headers
  FetchContent_Declare(volk GIT_REPOSITORY https://github.com/zeux/volk.git GIT_TAG vulkan-sdk-1.3.275.0
                       GIT_SHALLOW TRUE SOURCE_SUBDIR none)
  FetchContent_Declare(adrenotools GIT_REPOSITORY https://github.com/bylaws/libadrenotools.git
                       GIT_TAG 8fae8ce254dfc1344527e05301e43f37dea2df80 GIT_SUBMODULES_RECURSE TRUE)
  if(NOT CMAKE_ANDROID_ARCH_ABI)
    set(CMAKE_ANDROID_ARCH_ABI ${ANDROID_ABI})  # libadrenotools checks it
  endif()
  FetchContent_MakeAvailable(glslang vma volk adrenotools)

  file(GLOB VK_SOURCES CONFIGURE_DEPENDS runtime/src/vk/*.cpp)
  file(GLOB ANDROID_SOURCES CONFIGURE_DEPENDS runtime/src/android/*.cpp)
  set_source_files_properties(${GX2_SOURCES} ${VK_SOURCES} ${ANDROID_SOURCES} ${SHARED_GFX_SOURCES} PROPERTIES COMPILE_OPTIONS "-include;${CEMU_DIR}/cemu_shim.h")

  add_library(wwhd SHARED ${RUNTIME_SOURCES} runtime/src/audio_out_aaudio.cpp ${GX2_SOURCES} ${VK_SOURCES} ${ANDROID_SOURCES}
                          ${SHARED_GFX_SOURCES} ${volk_SOURCE_DIR}/volk.c)
  target_include_directories(wwhd PRIVATE runtime/include runtime/src runtime/src/gx2 ${vma_SOURCE_DIR}/include ${glslang_SOURCE_DIR}
                                          ${volk_SOURCE_DIR})
  # no Vulkan prototypes: every vk* call goes through volk's function pointers
  target_compile_definitions(wwhd PRIVATE VK_NO_PROTOTYPES VK_USE_PLATFORM_ANDROID_KHR VMA_STATIC_VULKAN_FUNCTIONS=0
                                      VMA_DYNAMIC_VULKAN_FUNCTIONS=1 VMA_VULKAN_VERSION=1001000)  # Vulkan 1.1 entry points only
  target_compile_options(wwhd PRIVATE -Wall -Wno-unused-function -Wno-unused-variable -Wno-unused-private-field
                                      -Wno-nullability-completeness -Wno-mismatched-tags -ffp-contract=off)
  # 16 KiB page devices (Android 15+); -Bsymbolic: calls between the recompiled functions bind
  # directly instead of through the PLT (their names stay exported for backtraces)
  target_link_options(wwhd PRIVATE "-Wl,-z,max-page-size=16384" "-Wl,-Bsymbolic")
  target_link_libraries(wwhd PRIVATE cemu_latte glslang glslang-default-resource-limits SPIRV
                                     adrenotools android nativewindow log aaudio z dl)
  # libadrenotools' hooks, loaded into the driver's linker namespace (packaged next to libwwhd.so)
  add_dependencies(wwhd main_hook hook_impl)
  foreach(t main_hook hook_impl)
    target_link_options(${t} PRIVATE "-Wl,-z,max-page-size=16384")  # 16 KiB page devices, as libwwhd.so
  endforeach()
  if(NOT WWHD_DEVICE_RECOMP)
    target_link_libraries(wwhd PRIVATE gamecode)
  endif()

  # ---- the licenses of everything in the APK, shown in the app (About and licenses)
  set(LIC_PARTS
    "This project|MPL-2.0|${CMAKE_SOURCE_DIR}/LICENSE"
    "Cemu (GPU address library and shader decompiler, in runtime/third_party/cemu)|MPL-2.0 (same license text as this project)|"
    "{fmt}|MIT|${CMAKE_SOURCE_DIR}/runtime/third_party/fmt/LICENSE"
    "Zstandard (zstd, reads .wua archives)|BSD-3-Clause|${CMAKE_SOURCE_DIR}/runtime/third_party/zstd/LICENSE"
    "glslang|BSD-3-Clause and others|${glslang_SOURCE_DIR}/LICENSE.txt"
    "Vulkan Memory Allocator|MIT|${vma_SOURCE_DIR}/LICENSE.txt"
    "volk|MIT|${volk_SOURCE_DIR}/LICENSE.md"
    "libadrenotools|BSD-2-Clause|${adrenotools_SOURCE_DIR}/LICENSE"
    "linkernsbypass (part of libadrenotools)|BSD-2-Clause|${adrenotools_SOURCE_DIR}/lib/linkernsbypass/LICENSE")
  set(WWHD_LLVM_DIR ${CMAKE_SOURCE_DIR}/build/llvm/install CACHE PATH "LLVM built for Android (tools/android/build-llvm.sh)")
  if(EXISTS ${WWHD_LLVM_DIR}/lib/cmake/llvm/LLVMConfig.cmake)
    find_package(LLVM REQUIRED CONFIG PATHS ${WWHD_LLVM_DIR}/lib/cmake/llvm NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH)
    message(STATUS "on-device recompiler: LLVM ${LLVM_PACKAGE_VERSION} from ${WWHD_LLVM_DIR}")
    set(WWHD_LLVM_COMPONENTS core bitreader passes aarch64codegen aarch64desc aarch64info target mc)
    if(WWHD_DEVICE_RECOMP)
      list(APPEND WWHD_LLVM_COMPONENTS orcjit jitlink orcshared orctargetprocess executionengine)
    endif()
    llvm_map_components_to_libnames(WWHD_LLVM_LIBS ${WWHD_LLVM_COMPONENTS})
    target_sources(wwhd PRIVATE runtime/src/recomp/program.cpp runtime/src/recomp/decode.cpp runtime/src/recomp/emit.cpp
                                runtime/src/recomp/locals.cpp runtime/src/recomp/compile.cpp ${CMAKE_BINARY_DIR}/recomp_data.S)
    set_source_files_properties(runtime/src/recomp/emit.cpp runtime/src/recomp/locals.cpp runtime/src/recomp/compile.cpp PROPERTIES
                                INCLUDE_DIRECTORIES "${LLVM_INCLUDE_DIRS}")
    target_compile_definitions(wwhd PRIVATE WWHD_RECOMP=1 WWHD_RELEASE_MAPS=1)  # release.cpp: the EUR and JPN address maps
    target_link_libraries(wwhd PRIVATE ${WWHD_LLVM_LIBS})
    list(APPEND LIC_PARTS "LLVM (the on-device recompiler) and libc++|Apache-2.0 with LLVM Exceptions|${WWHD_LLVM_DIR}/LICENSE.TXT")
    # ops.c as LLVM bitcode (the instruction semantics the recompiler inlines), embedded with the hook lists
    set(OPS_BC ${CMAKE_BINARY_DIR}/ops.bc)
    add_custom_command(OUTPUT ${OPS_BC}
      # relative paths: the bitcode records the source file name as given (embedded in the APK)
      COMMAND ${CMAKE_C_COMPILER} --target=aarch64-linux-android30 -O2 -ffp-contract=off -fno-strict-aliasing -emit-llvm
              -c runtime/src/recomp/ops.c -Iruntime/include -o ${OPS_BC}
      WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
      DEPENDS runtime/src/recomp/ops.c runtime/include/ppc.h VERBATIM)
    set(HOOKS_DIR ${CMAKE_SOURCE_DIR}/tools/recomp)
    file(WRITE ${CMAKE_BINARY_DIR}/recomp_data.S
"    .section .rodata
    .balign 16
    .global wwhd_ops_bc, wwhd_ops_bc_end, wwhd_hooks, wwhd_hooks_end, wwhd_release_eur, wwhd_release_eur_end
    .global wwhd_release_jpn, wwhd_release_jpn_end
wwhd_release_eur: .incbin \"${HOOKS_DIR}/release_eur.txt\"
wwhd_release_eur_end:
wwhd_release_jpn: .incbin \"${HOOKS_DIR}/release_jpn.txt\"
wwhd_release_jpn_end:
wwhd_ops_bc: .incbin \"${OPS_BC}\"
wwhd_ops_bc_end:
wwhd_hooks:
    .incbin \"${HOOKS_DIR}/hooks.txt\"
    .byte 10
    .incbin \"${HOOKS_DIR}/hooks_climb.txt\"
    .byte 10
    .incbin \"${HOOKS_DIR}/hooks_mods.txt\"
    .byte 10
    .incbin \"${HOOKS_DIR}/hooks_aspect.txt\"
    .byte 10
    .incbin \"${HOOKS_DIR}/hooks_perf.txt\"
wwhd_hooks_end:
")
    set_source_files_properties(${CMAKE_BINARY_DIR}/recomp_data.S PROPERTIES OBJECT_DEPENDS
      "${OPS_BC};${HOOKS_DIR}/hooks.txt;${HOOKS_DIR}/hooks_climb.txt;${HOOKS_DIR}/hooks_mods.txt;${HOOKS_DIR}/hooks_aspect.txt;${HOOKS_DIR}/hooks_perf.txt;${HOOKS_DIR}/release_eur.txt;${HOOKS_DIR}/release_jpn.txt")

    if(WWHD_DEVICE_RECOMP)
      # The game code is linked at startup (loader.cpp). The game functions the runtime calls
      # directly (f_X, f_X_orig in hooks and mods) become stubs jumping through slots the loader fills.
      set(JIT_NAMES "")
      foreach(f ${RUNTIME_SOURCES})
        file(READ ${f} text)
        string(REGEX MATCHALL "f_[0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F](_orig)?" found "${text}")
        list(APPEND JIT_NAMES ${found})
      endforeach()
      set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${RUNTIME_SOURCES})
      list(REMOVE_DUPLICATES JIT_NAMES)
      list(SORT JIT_NAMES)
      list(LENGTH JIT_NAMES JIT_COUNT)
      set(stubs "// generated by CMakeLists.txt (WWHD_DEVICE_RECOMP): see runtime/src/recomp/loader.cpp\n#include \"ppc.h\"\n\nextern \"C\" {\n")
      string(APPEND stubs "PpcFunc wwhd_jit_slots[${JIT_COUNT}];\nextern const unsigned wwhd_jit_count = ${JIT_COUNT};\nextern const char* const wwhd_jit_names[] = {\n")
      foreach(n ${JIT_NAMES})
        string(APPEND stubs "    \"${n}\",\n")
      endforeach()
      string(APPEND stubs "};\n")
      set(i 0)
      foreach(n ${JIT_NAMES})
        string(APPEND stubs "void ${n}(Cpu* c) { [[clang::musttail]] return wwhd_jit_slots[${i}](c); }\n")
        math(EXPR i "${i} + 1")
      endforeach()
      string(APPEND stubs "}\n")
      file(CONFIGURE OUTPUT ${CMAKE_BINARY_DIR}/runtime_stubs.cpp CONTENT "${stubs}" @ONLY)
      target_sources(wwhd PRIVATE runtime/src/recomp/loader.cpp ${CMAKE_BINARY_DIR}/runtime_stubs.cpp)
      # the code cache's version: the recompiler's sources, the instruction semantics (ops.c, the
      # Cpu layout in ppc.h) and LLVM; the hook lists are hashed at runtime (loader.cpp)
      set(RECOMP_VERSION_FILES runtime/src/recomp/compile.cpp runtime/src/recomp/compile.h runtime/src/recomp/decode.cpp
          runtime/src/recomp/decode.h runtime/src/recomp/emit.cpp runtime/src/recomp/emit.h runtime/src/recomp/program.cpp
          runtime/src/recomp/program.h runtime/src/recomp/locals.cpp runtime/src/recomp/locals.h runtime/src/recomp/ops.c
          runtime/include/ppc.h)
      list(TRANSFORM RECOMP_VERSION_FILES PREPEND ${CMAKE_SOURCE_DIR}/)
      string(REPLACE ";" "|" files_arg "${RECOMP_VERSION_FILES}")
      add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/recomp_version.h
        COMMAND ${CMAKE_COMMAND} -DOUT=${CMAKE_BINARY_DIR}/recomp_version.h "-DFILES=${files_arg}" "-DEXTRA=LLVM ${LLVM_PACKAGE_VERSION}"
                -P ${CMAKE_SOURCE_DIR}/cmake/recomp_version.cmake
        DEPENDS ${RECOMP_VERSION_FILES} ${CMAKE_SOURCE_DIR}/cmake/recomp_version.cmake VERBATIM)
      set_source_files_properties(runtime/src/recomp/loader.cpp PROPERTIES
        INCLUDE_DIRECTORIES "${LLVM_INCLUDE_DIRS};${CMAKE_BINARY_DIR}" OBJECT_DEPENDS ${CMAKE_BINARY_DIR}/recomp_version.h)
      target_compile_definitions(wwhd PRIVATE WWHD_DEVICE_RECOMP=1)
    endif()
  elseif(WWHD_DEVICE_RECOMP)
    message(FATAL_ERROR "WWHD_DEVICE_RECOMP needs LLVM in ${WWHD_LLVM_DIR} (tools/android/build-llvm.sh)")
  endif()

  set(lic "TLoZ:TWW HD Recompiled: an unofficial fan project, not affiliated with or endorsed by Nintendo.\n")
  if(WWHD_DEVICE_RECOMP)
    string(APPEND lic "This app contains no code or data from the game; it works with your own copy, from which it extracts the files and builds the game code on this device.\n")
  endif()
  string(APPEND lic "Source code: https://github.com/GreenNaugahyde/ZeldaWWHDRecompAndroid (based on https://github.com/ZeldaWWHDRecomp/ZeldaWWHDRecomp)\n\nThis app contains:\n")
  foreach(part ${LIC_PARTS})
    string(REPLACE "|" ";" f "${part}")
    list(GET f 0 what)
    list(GET f 1 license)
    string(APPEND lic "- ${what}: ${license}\n")
  endforeach()
  foreach(part ${LIC_PARTS})
    string(REPLACE "|" ";" f "${part}")
    list(GET f 0 what)
    list(LENGTH f n)
    if(n GREATER 2)
      list(GET f 2 file)
      if(file)
        if(NOT EXISTS ${file})
          message(FATAL_ERROR "license file missing: ${file}")
        endif()
        file(READ ${file} text)
        string(APPEND lic "\n\n==== ${what} ====\n\n${text}")
      endif()
    endif()
  endforeach()
  file(CONFIGURE OUTPUT ${CMAKE_BINARY_DIR}/licenses.txt CONTENT "${lic}" @ONLY)
  file(WRITE ${CMAKE_BINARY_DIR}/licenses.S
"    .section .rodata
    .global wwhd_licenses, wwhd_licenses_end
wwhd_licenses: .incbin \"${CMAKE_BINARY_DIR}/licenses.txt\"
wwhd_licenses_end:
")
  set_source_files_properties(${CMAKE_BINARY_DIR}/licenses.S PROPERTIES OBJECT_DEPENDS ${CMAKE_BINARY_DIR}/licenses.txt)
  target_sources(wwhd PRIVATE ${CMAKE_BINARY_DIR}/licenses.S)

else()
  message(FATAL_ERROR "supported targets: macOS (Metal) and Android (Vulkan); see README.md")
endif()
target_link_libraries(wwhd PRIVATE zstd_dec)
