skate3recomp-multiplayer/CMakeLists.txt
MiTHRAL 7a48eb9f35 ghost multiplayer: beta hardening - version handshake, crash-proofing, logging, persistence
Production-readiness pass toward 1.0:

- Protocol handshake: JOIN carries kGhostVersion; a mismatched client is
  REFUSED by the relay (HELLO session 0) and its poses are never forwarded,
  so two builds can never silently corrupt each other's pose stream. The
  client surfaces the refusal in the menu and skips refused LAN hosts during
  discovery.
- Crash-proofing: every received payload is bounds-checked against its
  packet size before parsing (HELLO / PoseChunk / JOIN); both the client
  network thread and the relay thread are exception-guarded (a throw logs
  and keeps the loop alive instead of hitting std::terminate). The relay no
  longer creates client records for unknown-address POSE/LEAVE floods and
  enforces a hard client cap (--max-clients, default 64).
- Logging: rate-limited error logging throughout (send failures, resolve
  failures, refusals, thread exceptions, client-cap drops). The relay core
  uses a build-flag shim (REXLOG in-game, a tiny {} formatter in the
  dependency-free standalone), with --verbose join/leave lines for the
  server. Game crashes themselves are already handled by the SDK's crash
  handlers + file logs; this layer now avoids triggering them and explains
  itself when it can't connect.
- Settings persistence: server/room/name/port/rate save into the app's cvar
  config on change, so a player's multiplayer setup survives a restart.
- Menu: status now shows protocol refusals and the last error.

Tests updated for the handshake (version-refusal case added); all suites
pass.
2026-08-26 22:07:09 -04:00

416 lines
16 KiB
CMake

cmake_minimum_required(VERSION 3.25)
if(POLICY CMP0091)
cmake_policy(SET CMP0091 NEW)
endif()
option(SKATE3_STATIC_MSVC_RUNTIME
"Link Windows MSVC-ABI builds with the static Visual C++ runtime"
ON)
if(SKATE3_STATIC_MSVC_RUNTIME)
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
endif()
project(skate3 VERSION 2.0.0 LANGUAGES CXX)
if(WIN32)
enable_language(RC)
endif()
if(APPLE)
enable_language(OBJCXX)
endif()
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
include(cmake/Skate3Version.cmake)
skate3_resolve_version(SKATE3_FULL_VERSION
FLOOR_VERSION ${PROJECT_VERSION}
SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
string(REGEX REPLACE "-.*$" "" SKATE3_NUMERIC_VERSION "${SKATE3_FULL_VERSION}")
# Four-part comma-separated form for Windows VERSIONINFO resources.
string(REPLACE "." ";" SKATE3_RC_VERSION_PARTS "${SKATE3_NUMERIC_VERSION}")
list(LENGTH SKATE3_RC_VERSION_PARTS SKATE3_RC_VERSION_PART_COUNT)
while(SKATE3_RC_VERSION_PART_COUNT LESS 4)
list(APPEND SKATE3_RC_VERSION_PARTS 0)
list(LENGTH SKATE3_RC_VERSION_PARTS SKATE3_RC_VERSION_PART_COUNT)
endwhile()
list(JOIN SKATE3_RC_VERSION_PARTS "," SKATE3_RC_VERSION)
set(SKATE3_TARGET_PROCESSOR "${CMAKE_SYSTEM_PROCESSOR}")
if(APPLE AND CMAKE_OSX_ARCHITECTURES)
list(LENGTH CMAKE_OSX_ARCHITECTURES SKATE3_OSX_ARCH_COUNT)
if(SKATE3_OSX_ARCH_COUNT EQUAL 1)
set(SKATE3_TARGET_PROCESSOR "${CMAKE_OSX_ARCHITECTURES}")
endif()
endif()
if(WIN32)
if(SKATE3_TARGET_PROCESSOR MATCHES "AMD64|x86_64")
set(SKATE3_BUILD_PLATFORM "win-amd64")
elseif(SKATE3_TARGET_PROCESSOR MATCHES "aarch64|ARM64")
set(SKATE3_BUILD_PLATFORM "win-arm64")
else()
set(SKATE3_BUILD_PLATFORM "win-${SKATE3_TARGET_PROCESSOR}")
endif()
elseif(APPLE)
if(SKATE3_TARGET_PROCESSOR MATCHES "aarch64|arm64|ARM64")
set(SKATE3_BUILD_PLATFORM "macos-arm64")
else()
message(FATAL_ERROR "Unsupported macOS architecture: ${SKATE3_TARGET_PROCESSOR}. macOS builds are ARM64-only.")
endif()
elseif(UNIX AND NOT APPLE)
if(SKATE3_TARGET_PROCESSOR MATCHES "AMD64|x86_64")
set(SKATE3_BUILD_PLATFORM "linux-amd64")
elseif(SKATE3_TARGET_PROCESSOR MATCHES "aarch64|ARM64")
set(SKATE3_BUILD_PLATFORM "linux-arm64")
else()
set(SKATE3_BUILD_PLATFORM "linux-${SKATE3_TARGET_PROCESSOR}")
endif()
else()
set(SKATE3_BUILD_PLATFORM "${CMAKE_SYSTEM_NAME}-${SKATE3_TARGET_PROCESSOR}")
endif()
string(TIMESTAMP SKATE3_BUILD_TIMESTAMP "%Y%m%d_%H%M")
configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/src/skate3_version.h.in
${CMAKE_CURRENT_BINARY_DIR}/include/skate3_version.h
@ONLY
)
set(REXSDK_DIR
"${CMAKE_CURRENT_SOURCE_DIR}/third_party/rexglue-sdk"
CACHE PATH "Path to the rexglue SDK source tree")
set(SKATE3_GAME_DATA_ROOT
"${CMAKE_CURRENT_SOURCE_DIR}/game"
CACHE PATH "Path to an extracted Skate 3 retail game dump")
set(SKATE3_TITLE_UPDATE_PACKAGE
""
CACHE FILEPATH "Optional Skate 3 title update STFS package used for codegen")
option(SKATE3_ENABLE_X86_64_V3
"Build x86_64 desktop binaries for x86-64-v3 CPUs (AVX2/BMI2)"
OFF)
if(NOT APPLE AND CMAKE_SYSTEM_PROCESSOR MATCHES "AMD64|x86_64")
if(SKATE3_ENABLE_X86_64_V3)
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU")
add_compile_options(-march=x86-64-v3)
set(SNAPPY_REQUIRE_AVX2 ON CACHE BOOL
"Match Snappy feature detection to the project x86-64-v3 target" FORCE)
set(SNAPPY_HAVE_SSSE3 1 CACHE INTERNAL
"x86-64-v3 requires SSSE3; Snappy checks do not see directory compile options" FORCE)
set(SNAPPY_HAVE_X86_CRC32 1 CACHE INTERNAL
"x86-64-v3 requires SSE4.2 CRC32; Snappy checks do not see directory compile options" FORCE)
set(SNAPPY_HAVE_BMI2 1 CACHE INTERNAL
"x86-64-v3 requires BMI2; Snappy checks do not see directory compile options" FORCE)
elseif(MSVC)
add_compile_options(/arch:AVX2)
endif()
else()
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU")
add_compile_options(-march=x86-64 -mtune=generic -mssse3)
endif()
set(SNAPPY_REQUIRE_AVX OFF CACHE BOOL
"Do not require AVX for non-AVX x86_64 release builds" FORCE)
set(SNAPPY_REQUIRE_AVX2 OFF CACHE BOOL
"Do not require AVX2 for non-AVX x86_64 release builds" FORCE)
set(SNAPPY_HAVE_SSSE3 0 CACHE INTERNAL
"Keep Snappy on its scalar path for non-AVX x86_64 release builds" FORCE)
set(SNAPPY_HAVE_X86_CRC32 0 CACHE INTERNAL
"Non-AVX x86_64 release builds must not require SSE4.2 CRC32" FORCE)
set(SNAPPY_HAVE_BMI2 0 CACHE INTERNAL
"Non-AVX x86_64 release builds must not require BMI2" FORCE)
endif()
else()
set(SNAPPY_REQUIRE_AVX OFF CACHE BOOL
"Do not require AVX for generic release builds" FORCE)
set(SNAPPY_REQUIRE_AVX2 OFF CACHE BOOL
"Do not require AVX2 for generic release builds" FORCE)
set(SNAPPY_HAVE_SSSE3 0 CACHE INTERNAL
"Generic release builds must not require SSSE3" FORCE)
set(SNAPPY_HAVE_X86_CRC32 0 CACHE INTERNAL
"Generic release builds must not require SSE4.2 CRC32" FORCE)
set(SNAPPY_HAVE_BMI2 0 CACHE INTERNAL
"Generic release builds must not require BMI2" FORCE)
endif()
file(TO_CMAKE_PATH "${CMAKE_CURRENT_SOURCE_DIR}" SKATE3_SOURCE_DIR_CONFIG)
file(TO_CMAKE_PATH "${SKATE3_GAME_DATA_ROOT}" SKATE3_GAME_DATA_ROOT_CONFIG)
set(SKATE3_EFFECTIVE_TITLE_UPDATE_PACKAGE "${SKATE3_TITLE_UPDATE_PACKAGE}")
if(SKATE3_EFFECTIVE_TITLE_UPDATE_PACKAGE STREQUAL "")
set(SKATE3_LOCAL_TITLE_UPDATE_PACKAGE
"${CMAKE_CURRENT_SOURCE_DIR}/TU_12K2276_000000C000000.00000000000O3")
if(EXISTS "${SKATE3_LOCAL_TITLE_UPDATE_PACKAGE}")
set(SKATE3_EFFECTIVE_TITLE_UPDATE_PACKAGE "${SKATE3_LOCAL_TITLE_UPDATE_PACKAGE}")
endif()
endif()
set(SKATE3_PATCHED_DEFAULT_XEX_LINE "")
set(SKATE3_PATCHED_EAWEBKIT_XEX_LINE "")
set(SKATE3_CODEGEN_ARGS "")
set(SKATE3_FUNCTION_CONFIG_INCLUDES
" \"${SKATE3_SOURCE_DIR_CONFIG}/config/skate3_functions.toml\",")
find_package(Python3 REQUIRED COMPONENTS Interpreter)
if(NOT SKATE3_EFFECTIVE_TITLE_UPDATE_PACKAGE STREQUAL "")
if(NOT EXISTS "${SKATE3_EFFECTIVE_TITLE_UPDATE_PACKAGE}")
message(FATAL_ERROR
"SKATE3_TITLE_UPDATE_PACKAGE does not exist: ${SKATE3_EFFECTIVE_TITLE_UPDATE_PACKAGE}")
endif()
set(SKATE3_TITLE_UPDATE_CODEGEN_ROOT
"${CMAKE_CURRENT_BINARY_DIR}/title_update_codegen")
set(SKATE3_TITLE_UPDATE_DEFAULT_XEX
"${SKATE3_TITLE_UPDATE_CODEGEN_ROOT}/default.xex")
set(SKATE3_TITLE_UPDATE_DEFAULT_XEXP
"${SKATE3_TITLE_UPDATE_CODEGEN_ROOT}/default.xexp")
set(SKATE3_TITLE_UPDATE_EAWEBKIT_XEX
"${SKATE3_TITLE_UPDATE_CODEGEN_ROOT}/data/webkit/EAWebkit.xex")
set(SKATE3_TITLE_UPDATE_EAWEBKIT_XEXP
"${SKATE3_TITLE_UPDATE_CODEGEN_ROOT}/data/webkit/EAWebkit.xexp")
add_custom_command(
OUTPUT
"${SKATE3_TITLE_UPDATE_DEFAULT_XEX}"
"${SKATE3_TITLE_UPDATE_DEFAULT_XEXP}"
"${SKATE3_TITLE_UPDATE_EAWEBKIT_XEX}"
"${SKATE3_TITLE_UPDATE_EAWEBKIT_XEXP}"
COMMAND "${Python3_EXECUTABLE}"
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/ExtractTitleUpdateXexp.py"
--tu "${SKATE3_EFFECTIVE_TITLE_UPDATE_PACKAGE}"
--game-root "${SKATE3_GAME_DATA_ROOT}"
--out-root "${SKATE3_TITLE_UPDATE_CODEGEN_ROOT}"
DEPENDS
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/ExtractTitleUpdateXexp.py"
"${SKATE3_EFFECTIVE_TITLE_UPDATE_PACKAGE}"
"${SKATE3_GAME_DATA_ROOT}/default.xex"
"${SKATE3_GAME_DATA_ROOT}/data/webkit/EAWebkit.xex"
COMMENT "Staging Skate 3 title update patches for codegen"
VERBATIM
)
add_custom_target(skate3-title-update-codegen-inputs
DEPENDS
"${SKATE3_TITLE_UPDATE_DEFAULT_XEX}"
"${SKATE3_TITLE_UPDATE_DEFAULT_XEXP}"
"${SKATE3_TITLE_UPDATE_EAWEBKIT_XEX}"
"${SKATE3_TITLE_UPDATE_EAWEBKIT_XEXP}"
)
file(TO_CMAKE_PATH "${SKATE3_TITLE_UPDATE_DEFAULT_XEX}"
SKATE3_TITLE_UPDATE_DEFAULT_XEX_CONFIG)
file(TO_CMAKE_PATH "${SKATE3_TITLE_UPDATE_EAWEBKIT_XEX}"
SKATE3_TITLE_UPDATE_EAWEBKIT_XEX_CONFIG)
set(SKATE3_PATCHED_DEFAULT_XEX_LINE
"patched_file_path = \"${SKATE3_TITLE_UPDATE_DEFAULT_XEX_CONFIG}\"")
set(SKATE3_PATCHED_EAWEBKIT_XEX_LINE
"patched_file_path = \"${SKATE3_TITLE_UPDATE_EAWEBKIT_XEX_CONFIG}\"")
set(SKATE3_FUNCTION_CONFIG_INCLUDES
" \"${SKATE3_SOURCE_DIR_CONFIG}/config/skate3_tu_functions.toml\",")
message(STATUS
"Skate 3 title update codegen enabled: ${SKATE3_EFFECTIVE_TITLE_UPDATE_PACKAGE}")
endif()
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/manifests")
configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/manifests/skate3.toml.in
${CMAKE_CURRENT_BINARY_DIR}/manifests/skate3.toml
@ONLY
)
configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/manifests/eawebkit.toml.in
${CMAKE_CURRENT_BINARY_DIR}/manifests/eawebkit.toml
@ONLY
)
set(REXGLUE_ENABLE_TRACY OFF CACHE BOOL "Disable Tracy instrumentation for normal Skate 3 builds" FORCE)
set(REXGLUE_ENABLE_PERF_COUNTERS OFF CACHE BOOL "Enable lightweight rexglue perf counter instrumentation")
if(WIN32)
set(REXGLUE_RUNTIME_RC_VERSION "${SKATE3_RC_VERSION}")
set(REXGLUE_RUNTIME_RC_VERSION_STRING "${SKATE3_FULL_VERSION}")
set(REXGLUE_RUNTIME_RC_PRODUCT_NAME "Skate3Recomp")
set(REXGLUE_RUNTIME_RC_COMPANY "Skate3Recomp project")
set(REXGLUE_RUNTIME_RC_DESCRIPTION "Skate3Recomp guest runtime")
set(REXGLUE_RUNTIME_RC_COPYRIGHT "(c) Skate3Recomp contributors")
endif()
include(cmake/RexGlue.cmake)
include(cmake/CodegenTargets.cmake)
set(SKATE3_COMMON_SOURCES
src/exception_compat.cpp
src/skate3_app_common.cpp
src/skate3_demo_path.cpp
src/skate3_draw_distance.cpp
src/skate3_fov.cpp
src/skate3_iso_installer.cpp
src/native/skate3_native_diagnostics.cpp
src/native/skate3_native_entity.cpp
src/native/skate3_native_ghost.cpp
src/skate3_native_ghost_dialog.cpp
src/native/skate3_native_ghost_relay.cpp
src/native/skate3_native_guest_read.cpp
src/native/skate3_native_lw.cpp
src/native/skate3_native_palette.cpp
src/skate3_native_debug_dialog.cpp
src/skate3_native_render.cpp
src/skate3_native_scene.cpp
src/skate3_native_scene_gpu.cpp
src/skate3_native_scene_post.cpp
src/skate3_screenshot.cpp
src/skate3_shader_disasm.cpp
src/skate3_title_update_installer.cpp
src/skate3_user_settings.cpp
src/skate3_win_icon.cpp
third_party/rexglue-sdk/thirdparty/crypto/sha256.cpp
)
if(APPLE)
list(APPEND SKATE3_COMMON_SOURCES src/skate3_iso_installer_macos.mm)
endif()
set(SKATE3_SOURCES
src/main.cpp
${SKATE3_COMMON_SOURCES}
)
if(WIN32)
add_executable(skate3 WIN32 ${SKATE3_SOURCES})
configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/src/skate3_app.manifest
${CMAKE_CURRENT_BINARY_DIR}/skate3_app.manifest
COPYONLY
)
target_link_options(skate3 PRIVATE LINKER:/MANIFESTINPUT:skate3_app.manifest)
configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/src/skate3.rc.in
${CMAKE_CURRENT_BINARY_DIR}/skate3.rc
@ONLY
)
target_sources(skate3 PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/skate3.rc)
else()
add_executable(skate3 ${SKATE3_SOURCES})
endif()
target_include_directories(skate3 PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/src
${CMAKE_CURRENT_BINARY_DIR}/include
${REXSDK_DIR}/thirdparty/xxHash
)
set(SKATE3_NATIVE_SHADER_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/src/native/shaders/scene.hlsl
${CMAKE_CURRENT_SOURCE_DIR}/src/native/shaders/shadow_blur.hlsl
${CMAKE_CURRENT_SOURCE_DIR}/src/native/shaders/resolve.hlsl
${CMAKE_CURRENT_SOURCE_DIR}/src/native/shaders/blur.hlsl
${CMAKE_CURRENT_SOURCE_DIR}/src/native/shaders/outline.hlsl
${CMAKE_CURRENT_SOURCE_DIR}/src/native/shaders/overlay2d.hlsl
${CMAKE_CURRENT_SOURCE_DIR}/src/native/shaders/spline.hlsl
${CMAKE_CURRENT_SOURCE_DIR}/src/native/shaders/photo_fx.hlsl
${CMAKE_CURRENT_SOURCE_DIR}/src/native/shaders/ssao.hlsl
${CMAKE_CURRENT_SOURCE_DIR}/src/native/shaders/hdr.hlsl
${CMAKE_CURRENT_SOURCE_DIR}/src/native/shaders/ssr.hlsl
)
# Shared .hlsli headers, inlined into the embedded sources by
# EmbedShaders.cmake: the embed must rerun when any of them changes.
file(GLOB SKATE3_NATIVE_SHADER_INCLUDES
${CMAKE_CURRENT_SOURCE_DIR}/src/native/shaders/*.hlsli)
set(SKATE3_NATIVE_SHADER_HEADER
${CMAKE_CURRENT_BINARY_DIR}/include/skate3_native_shaders.h)
add_custom_command(
OUTPUT ${SKATE3_NATIVE_SHADER_HEADER}
COMMAND ${CMAKE_COMMAND}
-DSHADER_DIR=${CMAKE_CURRENT_SOURCE_DIR}/src/native/shaders
-DOUT=${SKATE3_NATIVE_SHADER_HEADER}
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/EmbedShaders.cmake
DEPENDS ${SKATE3_NATIVE_SHADER_SOURCES}
${SKATE3_NATIVE_SHADER_INCLUDES}
${CMAKE_CURRENT_SOURCE_DIR}/cmake/EmbedShaders.cmake
COMMENT "Embedding native renderer shaders"
VERBATIM)
target_sources(skate3 PRIVATE ${SKATE3_NATIVE_SHADER_HEADER})
target_compile_definitions(skate3 PRIVATE SKATE3_BUILD_CONFIG="$<CONFIG>")
if(NOT SKATE3_EFFECTIVE_TITLE_UPDATE_PACKAGE STREQUAL "")
target_compile_definitions(skate3 PRIVATE SKATE3_HAS_TITLE_UPDATE=1)
else()
target_compile_definitions(skate3 PRIVATE SKATE3_HAS_TITLE_UPDATE=0)
endif()
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/generated/sources.cmake")
include(generated/sources.cmake)
target_sources(skate3 PRIVATE ${GENERATED_SOURCES})
target_include_directories(skate3 PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/generated)
else()
message(STATUS "generated/sources.cmake not found; run the generate-all target before building skate3")
endif()
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/generated/eawebkit/sources.cmake")
set(_SKATE3_SAVED_GENERATED_SOURCES ${GENERATED_SOURCES})
include(generated/eawebkit/sources.cmake)
target_sources(skate3 PRIVATE ${GENERATED_SOURCES})
target_include_directories(skate3 PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/generated/eawebkit)
set(GENERATED_SOURCES ${_SKATE3_SAVED_GENERATED_SOURCES})
unset(_SKATE3_SAVED_GENERATED_SOURCES)
else()
message(STATUS "generated/eawebkit/sources.cmake not found; run the generate-all target before building skate3")
endif()
target_link_libraries(skate3 PRIVATE rex::runtime)
rexglue_configure_target(skate3)
if(APPLE)
target_link_options(skate3 PRIVATE -Wl,-stack_size,0x4000000)
target_link_libraries(skate3 PRIVATE "-framework Cocoa")
endif()
if(WIN32)
target_link_libraries(skate3 PRIVATE comdlg32 winhttp d3dcompiler gdiplus)
endif()
add_custom_target(skate3_stage_runtime_library
COMMAND ${CMAKE_COMMAND} -E make_directory "$<TARGET_FILE_DIR:skate3>"
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"$<TARGET_FILE:rexruntime>"
"$<TARGET_FILE_DIR:skate3>"
DEPENDS rexruntime
VERBATIM
)
add_dependencies(skate3 skate3_stage_runtime_library)
if(NOT SKATE3_EFFECTIVE_TITLE_UPDATE_PACKAGE STREQUAL "")
add_custom_target(skate3_stage_title_update_patches
COMMAND ${CMAKE_COMMAND} -E make_directory
"$<TARGET_FILE_DIR:skate3>/game/data/webkit"
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${SKATE3_TITLE_UPDATE_DEFAULT_XEXP}"
"$<TARGET_FILE_DIR:skate3>/game/default.xexp"
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${SKATE3_TITLE_UPDATE_EAWEBKIT_XEXP}"
"$<TARGET_FILE_DIR:skate3>/game/data/webkit/EAWebkit.xexp"
DEPENDS skate3-title-update-codegen-inputs
VERBATIM
)
add_dependencies(skate3 skate3_stage_title_update_patches)
endif()
# Ghost multiplayer relay server (standalone): run on a public VPS (or
# behind a free tunnel) so players connect OUTBOUND and need no port
# forwarding. Pure sockets, no rexglue dependency. See tools/ghost_relay_main.cpp.
add_executable(ghost_relay
tools/ghost_relay_main.cpp
src/native/skate3_native_ghost_relay.cpp
)
target_include_directories(ghost_relay PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/native)
target_compile_features(ghost_relay PRIVATE cxx_std_23)
target_compile_definitions(ghost_relay PRIVATE SKATE3_RELAY_STANDALONE)
if(WIN32)
target_link_libraries(ghost_relay PRIVATE ws2_32)
endif()
if(MSVC)
target_compile_options(skate3 PRIVATE /utf-8)
else()
target_compile_options(skate3 PRIVATE -finput-charset=UTF-8 -fexec-charset=UTF-8)
endif()