From 6f3f66d2075b70a119a51ab518e1ab8e3f6b606a Mon Sep 17 00:00:00 2001 From: Johnny D Date: Thu, 17 Sep 2026 13:51:13 -0400 Subject: [PATCH 1/3] Qualify the in-process SDL adapter against pinned SDL 3.4.16 Use official release commit fa2c02bb6e21974a89ea9824bc53c9932abe5f9c for the native arm64/Intel consumer matrix and retain its exact source artifact. No controller engine or second SDL instance is introduced. Bridge and emulator dependency updates follow after their source checks. --- .github/workflows/sdl-inprocess.yml | 6 ++++-- docs/switch2kit/sdl3.md | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/sdl-inprocess.yml b/.github/workflows/sdl-inprocess.yml index b2fdebe..d99abbf 100644 --- a/.github/workflows/sdl-inprocess.yml +++ b/.github/workflows/sdl-inprocess.yml @@ -24,12 +24,14 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with: repository: libsdl-org/SDL - ref: 147a8ee32dbf9ac02f3794964490687b6bbda1bc + ref: fa2c02bb6e21974a89ea9824bc53c9932abe5f9c path: build/SDL persist-credentials: false - name: Record exact input dependency if: matrix.os == 'macos-26' - run: git -C build/SDL archive HEAD -o "$RUNNER_TEMP/sdl-source.zip" + run: | + test "$(git -C build/SDL rev-parse HEAD)" = fa2c02bb6e21974a89ea9824bc53c9932abe5f9c + git -C build/SDL archive HEAD -o "$RUNNER_TEMP/sdl-source.zip" - name: Preserve pinned SDL source for reproductions if: matrix.os == 'macos-26' uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 diff --git a/docs/switch2kit/sdl3.md b/docs/switch2kit/sdl3.md index f8fedae..f729b48 100644 --- a/docs/switch2kit/sdl3.md +++ b/docs/switch2kit/sdl3.md @@ -12,7 +12,7 @@ target_link_libraries(your_emulator PRIVATE Switch2Kit::SDL3) switch2kit_embed(your_emulator) # macOS application bundle; host signs it afterward ``` -The source dependency builds through the [C binding](cpp.md). Enable it only for macOS 15+ hosts. The adapter itself is portable for fake-boundary tests; physical Bluetooth remains macOS-only. CI tests unmodified SDL at commit `147a8ee32dbf9ac02f3794964490687b6bbda1bc`. +The source dependency builds through the [C binding](cpp.md). Enable it only for macOS 15+ hosts. The adapter itself is portable for fake-boundary tests; physical Bluetooth remains macOS-only. The native-consumer CI uses unmodified SDL 3.4.16 at commit `fa2c02bb6e21974a89ea9824bc53c9932abe5f9c`. The adapter links the host's existing SDL target; this test pin does not replace an emulator's own SDL dependency. ## Ownership and input loop @@ -34,7 +34,7 @@ if (input && s2k_start(input) == S2K_OK) { // Existing emulator SDL processing. } - // Keep using adapter in the input loop. Destroy it before the context and SDL. + // Keep the main run loop active until shutdown. } s2k_destroy(input); ``` From 1f0d6126eb0bfc76dc5cd6547bbdd3b662fd4e38 Mon Sep 17 00:00:00 2001 From: Johnny D Date: Thu, 17 Sep 2026 14:34:37 -0400 Subject: [PATCH 2/3] Update bridge and pinned emulator builds to SDL 3.4.16 Use the host's single SDL target: Dolphin selects the hash-checked official release only with Switch2Kit enabled; Cemu resolves only sdl3 from a pinned registry while retaining its other dependency baseline. Keep the emulator revisions and existing input/motion patches intact. Move the optional bridge and host component tests to the same release. Check actual SDL header and linked runtime versions in consumers and full emulator builds, with failure tests for wrong versions and malformed expectations. Preserve existing negative controls, macOS wired compilation and disabled-backend checks. Keep dependency pins in build configuration rather than duplicating transient CI versions in user documentation. Commit the resulting files directly. Local portable/debug/release tests pass; native final-head qualification remains required. --- .github/workflows/emulator-integration.yml | 5 +- .github/workflows/sdl-inprocess.yml | 1 + .github/workflows/sdl-regressions.yml | 8 ++- Integrations/Emulators/cemu.patch | 50 ++++++++++++++--- Integrations/Emulators/dolphin.patch | 39 +++++++++++++ Integrations/Emulators/revisions.json | 14 ++++- Integrations/SDL3/CMakeLists.txt | 9 +++ Integrations/SDL3/VerifyVersion.c | 33 +++++++++++ LICENSES/README.md | 2 +- docs/switch2kit/motion-profiles.md | 2 +- docs/switch2kit/sdl3.md | 2 +- scripts/build-switch2kit-emulator.sh | 4 +- sdl/INPUT-DELIVERY.md | 4 +- sdl/README.md | 7 +-- sdl/build-sdl.sh | 8 +-- ...l3-3.4.14-s2udp.patch => sdl3-s2udp.patch} | 0 tests/emulator-host/verify.sh | 2 +- tests/sdl-inprocess/CMakeLists.txt | 3 + tests/sdl-inprocess/run.sh | 3 + tests/sdl-inprocess/verify.sh | 4 +- tests/sdl-inprocess/version_test.py | 55 +++++++++++++++++++ 21 files changed, 227 insertions(+), 28 deletions(-) create mode 100644 Integrations/SDL3/VerifyVersion.c rename sdl/{sdl3-3.4.14-s2udp.patch => sdl3-s2udp.patch} (100%) create mode 100755 tests/sdl-inprocess/run.sh create mode 100644 tests/sdl-inprocess/version_test.py diff --git a/.github/workflows/emulator-integration.yml b/.github/workflows/emulator-integration.yml index d2b7378..33c5858 100644 --- a/.github/workflows/emulator-integration.yml +++ b/.github/workflows/emulator-integration.yml @@ -21,7 +21,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with: repository: libsdl-org/SDL - ref: 147a8ee32dbf9ac02f3794964490687b6bbda1bc + ref: fa2c02bb6e21974a89ea9824bc53c9932abe5f9c path: build/SDL persist-credentials: false - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 @@ -43,6 +43,7 @@ jobs: - name: Exercise application lifecycle and concurrent enumeration env: S2K_SDL_SOURCE: ${{ github.workspace }}/build/SDL + S2K_EXPECT_SDL_VERSION: 3004016 S2K_CEMU_SOURCE: ${{ github.workspace }}/build/Cemu-motion S2K_DOLPHIN_SOURCE: ${{ github.workspace }}/build/Dolphin-motion run: bash tests/emulator-host/verify.sh @@ -180,6 +181,8 @@ jobs: build/emulator-disabled/compile_commands.json build/emulator-build/integration-native-diagnostics.txt build/emulator-build/integration-inspection.json + build/emulator-build/integration-sdl-version.txt + build/emulator-build/integration-notices.json build/emulator-build/build.ninja build/emulator-build/CMakeCache.txt build/emulator-build/compile_commands.json diff --git a/.github/workflows/sdl-inprocess.yml b/.github/workflows/sdl-inprocess.yml index d99abbf..3bf3bce 100644 --- a/.github/workflows/sdl-inprocess.yml +++ b/.github/workflows/sdl-inprocess.yml @@ -42,6 +42,7 @@ jobs: - name: Verify native consumers across checkout names from outside the repository env: S2K_SDL_SOURCE: ${{ github.workspace }}/build/SDL + S2K_EXPECT_SDL_VERSION: 3004016 run: | work=$(mktemp -d) trap 'rm -rf "$work"' EXIT diff --git a/.github/workflows/sdl-regressions.yml b/.github/workflows/sdl-regressions.yml index 9a112bc..3214b79 100644 --- a/.github/workflows/sdl-regressions.yml +++ b/.github/workflows/sdl-regressions.yml @@ -15,7 +15,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with: repository: libsdl-org/SDL - ref: 147a8ee32dbf9ac02f3794964490687b6bbda1bc + ref: fa2c02bb6e21974a89ea9824bc53c9932abe5f9c path: .ci-sdl persist-credentials: false - name: Ensure the real wired driver is compiled @@ -25,11 +25,14 @@ jobs: run: | set -euo pipefail bash -n sdl/build-sdl.sh sdl/make-gopher64-both.sh tests/sdl-usb/check-sdl.sh - git -C .ci-sdl apply "$GITHUB_WORKSPACE/sdl/sdl3-3.4.14-s2udp.patch" + git -C .ci-sdl apply "$GITHUB_WORKSPACE/sdl/sdl3-s2udp.patch" + test "$(git -C .ci-sdl rev-parse HEAD)" = fa2c02bb6e21974a89ea9824bc53c9932abe5f9c bash tests/sdl-usb/check-sdl.sh .ci-sdl before cmake -S .ci-sdl -B "$RUNNER_TEMP/sdl-build" -DSDL_SHARED=ON -DSDL_STATIC=OFF -DSDL_TESTS=OFF -DSDL_HIDAPI_LIBUSB=ON > "$RUNNER_TEMP/sdl-baseline.log" 2>&1 cmake --build "$RUNNER_TEMP/sdl-build" --parallel 3 >> "$RUNNER_TEMP/sdl-baseline.log" 2>&1 || { tail -80 "$RUNNER_TEMP/sdl-baseline.log"; exit 1; } grep '^#define HAVE_LIBUSB 1' "$RUNNER_TEMP/sdl-build"/include-config-*/build_config/SDL_build_config.h + cc -I .ci-sdl/include Integrations/SDL3/VerifyVersion.c -L "$RUNNER_TEMP/sdl-build" -Wl,-rpath,"$RUNNER_TEMP/sdl-build" -lSDL3 -o "$RUNNER_TEMP/check-sdl-version" + "$RUNNER_TEMP/check-sdl-version" 3004016 | tee "$RUNNER_TEMP/sdl-version.log" cc -I .ci-sdl/include tests/sdl-edges/check.c -L "$RUNNER_TEMP/sdl-build" -Wl,-rpath,"$RUNNER_TEMP/sdl-build" -lSDL3 -o "$RUNNER_TEMP/check-edges" set +e "$RUNNER_TEMP/check-edges" @@ -45,6 +48,7 @@ jobs: bash tests/sdl-usb/check-sdl.sh .ci-sdl after # Resolve the same test executable against ONLY the corrected dylib. # SDL3_DYNAMIC_API would load both libraries' Objective-C classes. + DYLD_LIBRARY_PATH="$GITHUB_WORKSPACE/build/sdl" "$RUNNER_TEMP/check-sdl-version" 3004016 | tee -a "$RUNNER_TEMP/sdl-version.log" DYLD_LIBRARY_PATH="$GITHUB_WORKSPACE/build/sdl" "$RUNNER_TEMP/check-edges" - name: Pro Controller gamepad, sensor and rumble integration shell: bash diff --git a/Integrations/Emulators/cemu.patch b/Integrations/Emulators/cemu.patch index 94c1395..ae27e78 100644 --- a/Integrations/Emulators/cemu.patch +++ b/Integrations/Emulators/cemu.patch @@ -1,18 +1,18 @@ diff --git a/CMakeLists.txt b/CMakeLists.txt -index 644032c..8b2256f 100644 +index 644032c..2e531d4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,8 @@ cmake_minimum_required(VERSION 3.21.1) - + option(ENABLE_VCPKG "Enable the vcpkg package manager" ON) +option(ENABLE_SWITCH2KIT "Use in-process Switch2Kit controllers on macOS 15+" OFF) +set(SWITCH2KIT_SOURCE_DIR "" CACHE PATH "Switch2Kit source checkout") option(MACOS_BUNDLE "The executable when built on macOS will be created as an application bundle" OFF) option(ALLOW_PORTABLE "Allow Cemu to be run in portable mode" ON) - + @@ -99,7 +101,9 @@ endif() - + if (APPLE) enable_language(OBJC OBJCXX) - set(CMAKE_OSX_DEPLOYMENT_TARGET "13.4") @@ -20,12 +20,17 @@ index 644032c..8b2256f 100644 + set(CMAKE_OSX_DEPLOYMENT_TARGET "13.4") + endif() endif() - + if (UNIX AND NOT APPLE) -@@ -168,6 +172,19 @@ if(ENABLE_SDL) +@@ -164,10 +168,23 @@ find_package(fmt 12.1 REQUIRED) + find_package(PNG REQUIRED) + + if(ENABLE_SDL) +- find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3) ++ find_package(SDL3 3.4.16 REQUIRED CONFIG COMPONENTS SDL3) add_compile_definitions(HAS_SDL) endif() - + +if(ENABLE_SWITCH2KIT) + if(NOT APPLE OR NOT ENABLE_SDL OR NOT MACOS_BUNDLE) + message(FATAL_ERROR "Switch2Kit requires macOS, ENABLE_SDL and MACOS_BUNDLE") @@ -856,3 +861,34 @@ index ccd1c92..5426ebf 100644 + ${SWITCH2KIT_BLUETOOTH_USAGE} +diff --git a/vcpkg.json b/vcpkg.json +index 7f5ceca..57610be 100644 +--- a/vcpkg.json ++++ b/vcpkg.json +@@ -2,6 +2,17 @@ + "name": "cemu", + "version-string": "1.0", + "builtin-baseline": "f0fb3ddba5135b80982668de39dbaa139c00d281", ++ "vcpkg-configuration": { ++ "registries": [ ++ { ++ "kind": "git", ++ "repository": "https://github.com/microsoft/vcpkg", ++ "baseline": "386d7c478221b7ee0c97bfe6ea61dcf65121d564", ++ "reference": "386d7c478221b7ee0c97bfe6ea61dcf65121d564", ++ "packages": ["sdl3"] ++ } ++ ] ++ }, + "dependencies": [ + "pugixml", + "zlib", +@@ -76,7 +87,7 @@ + }, + { + "name": "sdl3", +- "version": "3.4.10" ++ "version": "3.4.16" + }, + { + "name": "wxwidgets", diff --git a/Integrations/Emulators/dolphin.patch b/Integrations/Emulators/dolphin.patch index 050466a..6425a53 100644 --- a/Integrations/Emulators/dolphin.patch +++ b/Integrations/Emulators/dolphin.patch @@ -1,3 +1,42 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 8f62d7f..361fa75 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -701,7 +701,11 @@ if(NOT ANDROID) + endif() + + if(ENABLE_SDL) +- dolphin_find_optional_system_library(SDL3 Externals/SDL 3.2.0) ++ if(ENABLE_SWITCH2KIT) ++ add_subdirectory(Externals/SDL) ++ else() ++ dolphin_find_optional_system_library(SDL3 Externals/SDL 3.2.0) ++ endif() + add_definitions(-DHAVE_SDL3) + endif() + +diff --git a/Externals/SDL/CMakeLists.txt b/Externals/SDL/CMakeLists.txt +index 6bdd630..ea8f250 100644 +--- a/Externals/SDL/CMakeLists.txt ++++ b/Externals/SDL/CMakeLists.txt +@@ -25,7 +25,16 @@ if(LibUSB_TYPE STREQUAL Bundled) + set(SDL_HIDAPI_LIBUSB_SHARED OFF) + endif() + +-add_subdirectory(SDL) ++if(ENABLE_SWITCH2KIT) ++ include(FetchContent) ++ FetchContent_Declare(switch2kit_sdl ++ URL https://github.com/libsdl-org/SDL/releases/download/release-3.4.16/SDL3-3.4.16.tar.gz ++ URL_HASH SHA256=7322236cd12090c3eb40b9728be4d49c76f66ad17d04369584d4ecad5cf77c68 ++ DOWNLOAD_EXTRACT_TIMESTAMP FALSE) ++ FetchContent_MakeAvailable(switch2kit_sdl) ++else() ++ add_subdirectory(SDL) ++endif() + + if (TARGET SDL3) + dolphin_disable_warnings(SDL3) diff --git a/Source/Core/Common/IniFile.cpp b/Source/Core/Common/IniFile.cpp index 6e6749c..e4852eb 100644 --- a/Source/Core/Common/IniFile.cpp diff --git a/Integrations/Emulators/revisions.json b/Integrations/Emulators/revisions.json index b4c5473..6275885 100644 --- a/Integrations/Emulators/revisions.json +++ b/Integrations/Emulators/revisions.json @@ -3,6 +3,14 @@ "repository": "dolphin-emu/dolphin", "revision": "c185d27ede09771fe93a3b520c576f646f937ed9", "files": { + "CMakeLists.txt": { + "before": "f221c08e1b7d4b3b4ae3b720947e5d4bb65c1d50c6eeab8d4e3d755e9702b6ef", + "after": "ef7c110c6e5a6a7b19ef6dfb54e43be17598232e0df00c33ee98a9e29357683d" + }, + "Externals/SDL/CMakeLists.txt": { + "before": "39a06e89e801490937802eb40737947f57b3a50010be2704cddda1899a584474", + "after": "997f64930363133469f098855254dc3922e4c7b11cfb8dbaa7b878b2610dce27" + }, "Source/Core/Common/IniFile.cpp": { "before": "2a21485e089cae8ea748332a54bba18b8af5a3c20f9c765daa7dcc09148bed79", "after": "af7c128c6cb1199dd90febe370a610f0dd92292f24ff3fbd2c5dca409b9367cc" @@ -79,7 +87,7 @@ "files": { "CMakeLists.txt": { "before": "919e665f6870584b28842c464fa9b109f8ee73d76d38462d81c98c89c0f466b6", - "after": "9464bc067dfe13959c65b23929b6c73954577e1ec3915a144b9229d5a0d2ee3c" + "after": "c641f58ff6710d7ff073db32a36cf25b0740a3cb67f4a6b30901c68b73d95375" }, "src/CMakeLists.txt": { "before": "20c43274c1b987d5da11e895ba8138735ed90f5cacf609344f98a2a476bd834f", @@ -152,6 +160,10 @@ "src/resource/MacOSXBundleInfo.plist.in": { "before": "72c342af8246b92139dcb652df402261845688401fe4c90026e1cdda935ea350", "after": "2c3c01882ead71e6767bb79149a0ec408d66d06bc208514cdda2ba58c717e9e6" + }, + "vcpkg.json": { + "before": "ff3b6c02d5a0a524a6d3abc5350e4ab57d9938e4896cbb31ca805a86779622f6", + "after": "95a5edff0d1e923cb3731ebc9a152621ad6b34b7e72b578f5835cf1a11d5d7bc" } } } diff --git a/Integrations/SDL3/CMakeLists.txt b/Integrations/SDL3/CMakeLists.txt index 048f618..b468221 100644 --- a/Integrations/SDL3/CMakeLists.txt +++ b/Integrations/SDL3/CMakeLists.txt @@ -17,3 +17,12 @@ set_target_properties(Switch2KitSDL3 PROPERTIES POSITION_INDEPENDENT_CODE ON) if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU") target_compile_options(Switch2KitSDL3 PRIVATE -fexceptions) endif() + +if(SWITCH2KIT_VERIFY_SDL) + add_executable(Switch2KitSDLVersion EXCLUDE_FROM_ALL VerifyVersion.c) + target_compile_features(Switch2KitSDLVersion PRIVATE c_std_11) + target_link_libraries(Switch2KitSDLVersion PRIVATE SDL3::SDL3) + set_target_properties(Switch2KitSDLVersion PROPERTIES + MACOSX_BUNDLE FALSE + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/switch2kit-verify") +endif() diff --git a/Integrations/SDL3/VerifyVersion.c b/Integrations/SDL3/VerifyVersion.c new file mode 100644 index 0000000..14f1285 --- /dev/null +++ b/Integrations/SDL3/VerifyVersion.c @@ -0,0 +1,33 @@ +/* Build diagnostic only: use the host SDL target without starting any subsystem. */ +#include +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + long expected = SDL_VERSION; + if (argc > 2) { + fputs("Usage: SDLVersion [numeric-version]\n", stderr); + return 2; + } + if (argc == 2) { + char *end = NULL; + errno = 0; + expected = strtol(argv[1], &end, 10); + if (errno || end == argv[1] || *end || expected <= 0 || expected > INT_MAX) { + fputs("Invalid expected SDL version\n", stderr); + return 2; + } + } + const int runtime = SDL_GetVersion(); + const char *revision = SDL_GetRevision(); + printf("SDL headers=%d runtime=%d revision=%.160s\n", SDL_VERSION, runtime, + revision ? revision : ""); + if (SDL_VERSION != expected || runtime != expected) { + fprintf(stderr, "SDL version mismatch; expected %ld\n", expected); + return 1; + } + return 0; +} diff --git a/LICENSES/README.md b/LICENSES/README.md index a55a673..eeae663 100644 --- a/LICENSES/README.md +++ b/LICENSES/README.md @@ -3,6 +3,6 @@ Switch2Kit includes code from [Peterksharma/switch2mac](https://github.com/Peterksharma/switch2mac/tree/ea6719f0a1d6b6986c00aca9ed4169a85c8cc9ae). Contributors are listed in [CREDITS.md](../CREDITS.md). - [MIT-trevlars.txt](MIT-trevlars.txt): controller reference implementation, [trevlars/switch2-controllers-linux](https://github.com/trevlars/switch2-controllers-linux). -- [SDL-zlib.txt](SDL-zlib.txt): SDL-derived patches, [SDL 3.4.14](https://github.com/libsdl-org/SDL/tree/147a8ee32dbf9ac02f3794964490687b6bbda1bc). These are modified, unofficial SDL sources. +- [SDL-zlib.txt](SDL-zlib.txt): SDL-derived patches, [SDL](https://github.com/libsdl-org/SDL). These are modified, unofficial SDL sources. Dolphin and Cemu retain their upstream copyright and license notices. diff --git a/docs/switch2kit/motion-profiles.md b/docs/switch2kit/motion-profiles.md index 364f8e9..56ea27b 100644 --- a/docs/switch2kit/motion-profiles.md +++ b/docs/switch2kit/motion-profiles.md @@ -51,7 +51,7 @@ Dolphin's existing event thread feeds a per-device 256-pair ring. Each emulated SDL's aggregate `SetSensorsEnabled` callback runs under its joystick lock for first-enable/last-disable. The adapter additionally queries per-type state on each report/pump; it cannot infer an off/on transition of one type occurring entirely between pumps while another type remains enabled. Hosts changing motion policy should disable both sensors before reenabling. Each report flushes at most two virtual-sensor records through the host's existing SDL instance; no extra worker, unbounded sensor queue, fusion framework or Bluetooth callback workload is introduced. -The pinned public API and implementation are the authority: [virtual sensor submission](https://wiki.libsdl.org/SDL3/SDL_SendJoystickVirtualSensorData), [sensor descriptor](https://wiki.libsdl.org/SDL3/SDL_VirtualJoystickSensorDesc), [sensor enablement](https://wiki.libsdl.org/SDL3/SDL_SetGamepadSensorEnabled), and [sensor event timestamps](https://wiki.libsdl.org/SDL3/SDL_GamepadSensorEvent). Tests use SDL commit `147a8ee32dbf9ac02f3794964490687b6bbda1bc`. +The pinned public API and implementation are the authority: [virtual sensor submission](https://wiki.libsdl.org/SDL3/SDL_SendJoystickVirtualSensorData), [sensor descriptor](https://wiki.libsdl.org/SDL3/SDL_VirtualJoystickSensorDesc), [sensor enablement](https://wiki.libsdl.org/SDL3/SDL_SetGamepadSensorEnabled), and [sensor event timestamps](https://wiki.libsdl.org/SDL3/SDL_GamepadSensorEvent). ## Measurement status diff --git a/docs/switch2kit/sdl3.md b/docs/switch2kit/sdl3.md index f729b48..6cebb28 100644 --- a/docs/switch2kit/sdl3.md +++ b/docs/switch2kit/sdl3.md @@ -12,7 +12,7 @@ target_link_libraries(your_emulator PRIVATE Switch2Kit::SDL3) switch2kit_embed(your_emulator) # macOS application bundle; host signs it afterward ``` -The source dependency builds through the [C binding](cpp.md). Enable it only for macOS 15+ hosts. The adapter itself is portable for fake-boundary tests; physical Bluetooth remains macOS-only. The native-consumer CI uses unmodified SDL 3.4.16 at commit `fa2c02bb6e21974a89ea9824bc53c9932abe5f9c`. The adapter links the host's existing SDL target; this test pin does not replace an emulator's own SDL dependency. +The source dependency builds through the [C binding](cpp.md). Enable it only for macOS 15+ hosts. The adapter itself is portable for fake-boundary tests; physical Bluetooth remains macOS-only. The adapter links the host's existing SDL target without patching SDL. ## Ownership and input loop diff --git a/scripts/build-switch2kit-emulator.sh b/scripts/build-switch2kit-emulator.sh index 8b0c176..7e6b68c 100755 --- a/scripts/build-switch2kit-emulator.sh +++ b/scripts/build-switch2kit-emulator.sh @@ -9,12 +9,14 @@ python3 "$root/Integrations/Emulators/apply.py" "$emulator" "$source" --verify xcodebuild -version swift --version args=(-G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_OSX_DEPLOYMENT_TARGET=15.0 - -DENABLE_SWITCH2KIT=ON "-DSWITCH2KIT_SOURCE_DIR=$root") + -DENABLE_SWITCH2KIT=ON -DSWITCH2KIT_VERIFY_SDL=ON "-DSWITCH2KIT_SOURCE_DIR=$root") case "$emulator" in dolphin) args+=(-DENABLE_SDL=ON -DENABLE_QT=ON); target=dolphin-emu ;; cemu) args+=(-DENABLE_SDL=ON -DMACOS_BUNDLE=ON); target=CemuBin ;; *) echo 'Unknown emulator' >&2; exit 2 ;; esac cmake -S "$source" -B "$build" "${args[@]}" "$@" +cmake --build "$build" --target Switch2KitSDLVersion --parallel "${S2K_BUILD_JOBS:-4}" +"$build/switch2kit-verify/Switch2KitSDLVersion" 3004016 | tee "$build/integration-sdl-version.txt" cmake --build "$build" --target "$target" --parallel "${S2K_BUILD_JOBS:-4}" python3 "$root/scripts/verify-distribution-notices.py" "$emulator" "$source" "$build" diff --git a/sdl/INPUT-DELIVERY.md b/sdl/INPUT-DELIVERY.md index 0080bca..5478700 100644 --- a/sdl/INPUT-DELIVERY.md +++ b/sdl/INPUT-DELIVERY.md @@ -8,7 +8,7 @@ bash sdl/build-sdl.sh /tmp/SDL bash sdl/make-gopher64-both.sh ``` -The build script exports exact commit 147a8ee32dbf9ac02f3794964490687b6bbda1bc, +The build script exports its pinned SDL revision, applies the four patches listed in sdl/build-sdl.sh, and writes build/sdl/libSDL3.0.dylib without modifying the source checkout. The Gopher64 wrapper defaults to that rebuilt library and refuses to proceed @@ -22,7 +22,7 @@ is read. The test sends complete button taps and analog-trigger excursions between updates through actual localhost UDP sockets and SDL event APIs. The CI negative control must fail with exit 42 before the correction; other errors are not accepted as a reproduced defect. The same executable must -then pass via SDL3_DYNAMIC_API using the documented script's rebuilt library. +then pass against the documented script's rebuilt library. Nintendo BLE commands, bonding, keep-alives and report decoding are untouched. This does not recover datagrams lost before receipt, nor guarantee that a diff --git a/sdl/README.md b/sdl/README.md index 35efcaf..332737f 100644 --- a/sdl/README.md +++ b/sdl/README.md @@ -9,11 +9,10 @@ establish compatibility with every game. RetroArch and Chromium have Build SDL from the pinned source before using this integration. -The current build uses pinned SDL commit -`147a8ee32dbf9ac02f3794964490687b6bbda1bc` (`release-3.4.14`) plus all four -patches applied by [build-sdl.sh](build-sdl.sh). To build on macOS, install +The build uses the SDL revision and four patches selected by +[build-sdl.sh](build-sdl.sh). To build on macOS, install the required C/C++ build tools, CMake, and libusb, then provide an SDL git -checkout containing that commit: +checkout containing the selected revision: ```sh git clone https://github.com/libsdl-org/SDL.git /path/to/SDL diff --git a/sdl/build-sdl.sh b/sdl/build-sdl.sh index 25f1822..d8e1d5b 100755 --- a/sdl/build-sdl.sh +++ b/sdl/build-sdl.sh @@ -1,14 +1,14 @@ #!/bin/bash -# Build reviewed SDL sources; never overwrite the tracked upstream dylib. +# Build a patched SDL library without changing the supplied source checkout. # Usage: bash sdl/build-sdl.sh /path/to/SDL-git-checkout set -euo pipefail root=$(cd "$(dirname "$0")/.." && pwd) -source_dir=${1:?Provide a git checkout of libsdl-org/SDL containing release-3.4.14} -revision=147a8ee32dbf9ac02f3794964490687b6bbda1bc +source_dir=${1:?Provide a git checkout of libsdl-org/SDL containing release-3.4.16} +revision=fa2c02bb6e21974a89ea9824bc53c9932abe5f9c work=$(mktemp -d) trap 'rm -rf "$work"' EXIT git -C "$source_dir" archive "$revision" | tar -x -C "$work" -git -C "$work" apply "$root/sdl/sdl3-3.4.14-s2udp.patch" +git -C "$work" apply "$root/sdl/sdl3-s2udp.patch" git -C "$work" apply "$root/sdl/s2udp-input-edges.patch" git -C "$work" apply "$root/sdl/s2usb-device-identity.patch" git -C "$work" apply "$root/sdl/s2udp-pro-controller.patch" diff --git a/sdl/sdl3-3.4.14-s2udp.patch b/sdl/sdl3-s2udp.patch similarity index 100% rename from sdl/sdl3-3.4.14-s2udp.patch rename to sdl/sdl3-s2udp.patch diff --git a/tests/emulator-host/verify.sh b/tests/emulator-host/verify.sh index 26c646b..85dc2e1 100755 --- a/tests/emulator-host/verify.sh +++ b/tests/emulator-host/verify.sh @@ -4,7 +4,7 @@ root=$(cd "$(dirname "$0")/../.." && pwd) : "${S2K_SDL_SOURCE:?Set S2K_SDL_SOURCE to the pinned SDL source checkout}" work=$(mktemp -d) trap 'rm -rf "$work"' EXIT -args=(-G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS=-fno-exceptions "-DS2K_SDL_SOURCE=$S2K_SDL_SOURCE") +args=(-G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS=-fno-exceptions "-DS2K_SDL_SOURCE=$S2K_SDL_SOURCE" "-DS2K_EXPECT_SDL_VERSION=${S2K_EXPECT_SDL_VERSION:-}") if [ -n "${S2K_CEMU_SOURCE:-}" ]; then args+=("-DS2K_CEMU_SOURCE=$S2K_CEMU_SOURCE"); fi if [ -n "${S2K_DOLPHIN_SOURCE:-}" ]; then args+=("-DS2K_DOLPHIN_SOURCE=$S2K_DOLPHIN_SOURCE"); fi if [ "$(uname -s)" = Darwin ]; then diff --git a/tests/sdl-inprocess/CMakeLists.txt b/tests/sdl-inprocess/CMakeLists.txt index fb342da..6fa48a4 100644 --- a/tests/sdl-inprocess/CMakeLists.txt +++ b/tests/sdl-inprocess/CMakeLists.txt @@ -19,7 +19,10 @@ if(NOT APPLE) set(SDL_PIPEWIRE OFF CACHE BOOL "" FORCE) endif() add_subdirectory("${S2K_SDL_SOURCE}" sdl EXCLUDE_FROM_ALL) +set(SWITCH2KIT_VERIFY_SDL ON) add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/../../Integrations/SDL3" switch2kit) +add_test(NAME sdl-version COMMAND Switch2KitSDLVersion ${S2K_EXPECT_SDL_VERSION}) +add_custom_target(sdl-version-check ALL DEPENDS Switch2KitSDLVersion) get_filename_component(_root "${CMAKE_CURRENT_LIST_DIR}/../.." ABSOLUTE) set(_fixture "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}S2KSDLFixture${CMAKE_SHARED_LIBRARY_SUFFIX}") set(_args) diff --git a/tests/sdl-inprocess/run.sh b/tests/sdl-inprocess/run.sh new file mode 100755 index 0000000..2bc1776 --- /dev/null +++ b/tests/sdl-inprocess/run.sh @@ -0,0 +1,3 @@ +#!/bin/bash +set -euo pipefail +PYTHONDONTWRITEBYTECODE=1 python3 "$(dirname "$0")/version_test.py" diff --git a/tests/sdl-inprocess/verify.sh b/tests/sdl-inprocess/verify.sh index f16d155..d0bf9b0 100755 --- a/tests/sdl-inprocess/verify.sh +++ b/tests/sdl-inprocess/verify.sh @@ -4,6 +4,6 @@ ROOT=$(cd "$(dirname "$0")/../.." && pwd -P) : "${S2K_SDL_SOURCE:?Set S2K_SDL_SOURCE to the SDL source checkout}" WORK=$(mktemp -d) trap 'rm -rf "$WORK"' EXIT -cmake -S "$ROOT/tests/sdl-inprocess" -B "$WORK" -DCMAKE_BUILD_TYPE=Release -DS2K_SDL_SOURCE="$S2K_SDL_SOURCE" -cmake --build "$WORK" --parallel 3 --target sdl-inprocess sdl-motion +cmake -S "$ROOT/tests/sdl-inprocess" -B "$WORK" -DCMAKE_BUILD_TYPE=Release -DS2K_SDL_SOURCE="$S2K_SDL_SOURCE" -DS2K_EXPECT_SDL_VERSION="${S2K_EXPECT_SDL_VERSION:-}" +cmake --build "$WORK" --parallel 3 --target sdl-inprocess sdl-motion Switch2KitSDLVersion ctest --test-dir "$WORK" --output-on-failure diff --git a/tests/sdl-inprocess/version_test.py b/tests/sdl-inprocess/version_test.py new file mode 100644 index 0000000..8c844dd --- /dev/null +++ b/tests/sdl-inprocess/version_test.py @@ -0,0 +1,55 @@ +"""Failure tests for the compiled diagnostic, with only SDL's version queries faked.""" +import os +from pathlib import Path +import subprocess +import tempfile +import unittest + +ROOT = Path(__file__).resolve().parents[2] + + +class VersionTests(unittest.TestCase): + def setUp(self): + self.temporary = tempfile.TemporaryDirectory(prefix="SDL version ") + self.addCleanup(self.temporary.cleanup) + self.root = Path(self.temporary.name) + (self.root / "SDL3").mkdir() + (self.root / "SDL3/SDL_version.h").write_text( + "#define SDL_VERSION 3004016\n" + "int SDL_GetVersion(void);\nconst char *SDL_GetRevision(void);\n") + (self.root / "version.c").write_text( + '#include \n' + 'int SDL_GetVersion(void) { return atoi(getenv("RUNTIME_VERSION")); }\n' + 'const char *SDL_GetRevision(void) { return "test-boundary"; }\n') + self.binary = self.root / "check" + subprocess.run([os.environ.get("CC", "cc"), "-std=c11", "-Wall", "-Wextra", "-Werror", + "-I", str(self.root), str(ROOT / "Integrations/SDL3/VerifyVersion.c"), + str(self.root / "version.c"), "-o", str(self.binary)], check=True) + + def invoke(self, *args, runtime="3004016"): + return subprocess.run([str(self.binary), *args], + env={**os.environ, "RUNTIME_VERSION": runtime}, + capture_output=True, text=True, timeout=5) + + def test_matching_header_and_runtime(self): + for args in ((), ("3004016",)): + with self.subTest(args=args): + result = self.invoke(*args) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn("headers=3004016 runtime=3004016", result.stdout) + + def test_old_library_under_new_headers_is_rejected(self): + self.assertEqual(self.invoke("3004016", runtime="3004014").returncode, 1) + + def test_matching_but_unexpected_version_is_rejected(self): + self.assertEqual(self.invoke("3004014").returncode, 1) + + def test_bad_expected_versions_are_rejected(self): + for value in ("", "0", "-1", "3.4.16", "3004016junk", "999999999999999999999999"): + with self.subTest(value=value): + self.assertEqual(self.invoke(value).returncode, 2) + self.assertEqual(self.invoke("3004016", "extra").returncode, 2) + + +if __name__ == "__main__": + unittest.main() From b697ea04f55d1d24db665ff1f4e14b25916f912c Mon Sep 17 00:00:00 2001 From: Johnny D Date: Thu, 17 Sep 2026 15:04:11 -0400 Subject: [PATCH 3/3] Keep Cemu system SDL compatibility when Switch2Kit is disabled Require the upgraded SDL version only for enabled native integration. Preserve Cemu's existing unrestricted system-SDL discovery when Switch2Kit is off, while the pinned vcpkg build selects the upgraded package. Add two CMake-executed regression cases for disabled integration and disabled SDL. The system-SDL case fails against the preceding patch. Retain all existing assertions and update the affected upstream-file digest. Clarify that the bridge builder exports, rather than fetches, its pinned source. --- Integrations/Emulators/cemu.patch | 10 ++++-- Integrations/Emulators/revisions.json | 2 +- sdl/README.md | 2 +- tests/emulator-host/patch_tests.py | 45 +++++++++++++++++++++++++++ 4 files changed, 54 insertions(+), 5 deletions(-) diff --git a/Integrations/Emulators/cemu.patch b/Integrations/Emulators/cemu.patch index ae27e78..ecb0713 100644 --- a/Integrations/Emulators/cemu.patch +++ b/Integrations/Emulators/cemu.patch @@ -1,5 +1,5 @@ diff --git a/CMakeLists.txt b/CMakeLists.txt -index 644032c..2e531d4 100644 +index 644032c..ac2d18c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,8 @@ @@ -22,12 +22,16 @@ index 644032c..2e531d4 100644 endif() if (UNIX AND NOT APPLE) -@@ -164,10 +168,23 @@ find_package(fmt 12.1 REQUIRED) +@@ -164,10 +168,27 @@ find_package(fmt 12.1 REQUIRED) find_package(PNG REQUIRED) if(ENABLE_SDL) - find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3) -+ find_package(SDL3 3.4.16 REQUIRED CONFIG COMPONENTS SDL3) ++ if(ENABLE_SWITCH2KIT) ++ find_package(SDL3 3.4.16 REQUIRED CONFIG COMPONENTS SDL3) ++ else() ++ find_package(SDL3 REQUIRED CONFIG COMPONENTS SDL3) ++ endif() add_compile_definitions(HAS_SDL) endif() diff --git a/Integrations/Emulators/revisions.json b/Integrations/Emulators/revisions.json index 6275885..5dc717d 100644 --- a/Integrations/Emulators/revisions.json +++ b/Integrations/Emulators/revisions.json @@ -87,7 +87,7 @@ "files": { "CMakeLists.txt": { "before": "919e665f6870584b28842c464fa9b109f8ee73d76d38462d81c98c89c0f466b6", - "after": "c641f58ff6710d7ff073db32a36cf25b0740a3cb67f4a6b30901c68b73d95375" + "after": "af38bf120a0cacc9b203f64f59f212d77cba02ae9c5b032ed966d9029bb5172f" }, "src/CMakeLists.txt": { "before": "20c43274c1b987d5da11e895ba8138735ed90f5cacf609344f98a2a476bd834f", diff --git a/sdl/README.md b/sdl/README.md index 332737f..7d74661 100644 --- a/sdl/README.md +++ b/sdl/README.md @@ -84,4 +84,4 @@ firmware coverage, or every controller's rumble behavior. ## SDL source -The build script fetches the pinned SDL release and applies the patches in this directory. SDL's license is included in that source checkout. +The build script exports the pinned SDL revision and applies the patches in this directory. SDL's license is included in that source checkout. diff --git a/tests/emulator-host/patch_tests.py b/tests/emulator-host/patch_tests.py index c080db3..442ea9f 100644 --- a/tests/emulator-host/patch_tests.py +++ b/tests/emulator-host/patch_tests.py @@ -87,6 +87,51 @@ def test_verify_rejects_modified_integration_without_repairing_it(self): self.invoke("--verify") self.assertEqual((self.source / "input.cpp").read_text(), "local change after applying\n") + def sdl_lookup(self, native, sdl=True): + # Execute the actual SDL-selection block delivered by the Cemu patch. + # Only package discovery is replaced; CMake evaluates the conditions. + text = (root / "Integrations/Emulators/cemu.patch").read_text() + first_file = text.split("diff --git a/src/CMakeLists.txt", 1)[0] + lines = [line[1:] for line in first_file.splitlines() + if line.startswith(("+", " ")) and not line.startswith("+++")] + start = lines.index("if(ENABLE_SDL)") + block, depth = [], 0 + for line in lines[start:]: + block.append(line) + if line.strip().startswith("if("): + depth += 1 + elif line.strip() == "endif()": + depth -= 1 + if depth == 0: + break + self.assertEqual(depth, 0) + source = self.here / ("policy-" + str(native) + "-" + str(sdl)) + source.mkdir() + (source / "CMakeLists.txt").write_text( + 'cmake_minimum_required(VERSION 3.24)\n' + 'project(SDLSelection LANGUAGES NONE)\n' + 'macro(find_package)\n' + ' file(WRITE "${CMAKE_BINARY_DIR}/lookup.txt" "${ARGV}")\n' + 'endmacro()\n' + "\n".join(block) + "\n") + build = source / "build" + subprocess.run(["cmake", "-S", str(source), "-B", str(build), + "-DENABLE_SWITCH2KIT=" + ("ON" if native else "OFF"), + "-DENABLE_SDL=" + ("ON" if sdl else "OFF")], + check=True, capture_output=True, text=True, timeout=20) + result = build / "lookup.txt" + return result.read_text().split(";") if result.exists() else None + + def test_cemu_disabled_backend_preserves_system_sdl_discovery(self): + self.assertEqual(self.sdl_lookup(False), + ["SDL3", "REQUIRED", "CONFIG", "COMPONENTS", "SDL3"]) + enabled = self.sdl_lookup(True) + self.assertEqual(enabled[0], "SDL3") + self.assertRegex(enabled[1], r"^3\.[0-9]+\.[0-9]+$") + self.assertEqual(enabled[2:], ["REQUIRED", "CONFIG", "COMPONENTS", "SDL3"]) + + def test_cemu_without_sdl_does_not_discover_it(self): + self.assertIsNone(self.sdl_lookup(False, sdl=False)) + def test_pins_and_patch_paths_match(self): directory = root / "Integrations/Emulators" revisions = json.loads((directory / "revisions.json").read_text())