diff --git a/SunPad.xcodeproj/project.pbxproj b/SunPad.xcodeproj/project.pbxproj index cab04b3..eced61e 100644 --- a/SunPad.xcodeproj/project.pbxproj +++ b/SunPad.xcodeproj/project.pbxproj @@ -114,7 +114,7 @@ BB0000000000000000000202 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; BB0000000000000000000203 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; BB0000000000000000000204 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; - BB0000000000000000000205 /* gGMSE01_recomp.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = gGMSE01_recomp.dylib; path = Provisioned/appletvos/gGMSE01_recomp.dylib; sourceTree = ""; }; + BB0000000000000000000205 /* gGMSE01_recomp.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = gGMSE01_recomp.dylib; path = "Provisioned/$(PLATFORM_NAME)/gGMSE01_recomp.dylib"; sourceTree = ""; }; BB0000000000000000000206 /* SunPadTV.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SunPadTV.app; sourceTree = BUILT_PRODUCTS_DIR; }; BB0000000000000000000207 /* tvOS Sys */ = {isa = PBXFileReference; lastKnownFileType = folder; name = Sys; path = build/tvos-deps/ModernGekko/vendor/dolphin/Data/Sys; sourceTree = SOURCE_ROOT; }; /* End PBXFileReference section */ @@ -598,7 +598,7 @@ "$(SRCROOT)/build/tvos-deps/ModernGekko/include", "$(SRCROOT)/build/tvos-deps/ModernGekko/vendor/dolphin/Source/Core", "$(SRCROOT)/build/tvos-deps/ModernGekko/vendor/dolphin/GXRuntime/include", - "$(SRCROOT)/build/tvos-deps/ModernGekko/build-tvos-appletvos-public", + "$(SRCROOT)/build/tvos-deps/ModernGekko/build-tvos-$(PLATFORM_NAME)-public", "$(SRCROOT)/build/tvos-deps/ModernGekko/vendor/fmt/fmt/include", "$(SRCROOT)/build/tvos-deps/ModernGekko/vendor/dolphin/Externals/picojson", ); @@ -609,7 +609,7 @@ ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", - "$(SRCROOT)/apple/tvos/Provisioned/appletvos/libs", + "$(SRCROOT)/apple/tvos/Provisioned/$(PLATFORM_NAME)/libs", ); MARKETING_VERSION = 0.1.0; OTHER_LDFLAGS = ( @@ -632,7 +632,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.sunpad.SunPad.tv; PRODUCT_NAME = SunPadTV; SDKROOT = appletvos; - SUPPORTED_PLATFORMS = appletvos; + SUPPORTED_PLATFORMS = "appletvos appletvsimulator"; TARGETED_DEVICE_FAMILY = 3; TVOS_DEPLOYMENT_TARGET = 17.0; }; @@ -653,7 +653,7 @@ "$(SRCROOT)/build/tvos-deps/ModernGekko/include", "$(SRCROOT)/build/tvos-deps/ModernGekko/vendor/dolphin/Source/Core", "$(SRCROOT)/build/tvos-deps/ModernGekko/vendor/dolphin/GXRuntime/include", - "$(SRCROOT)/build/tvos-deps/ModernGekko/build-tvos-appletvos-public", + "$(SRCROOT)/build/tvos-deps/ModernGekko/build-tvos-$(PLATFORM_NAME)-public", "$(SRCROOT)/build/tvos-deps/ModernGekko/vendor/fmt/fmt/include", "$(SRCROOT)/build/tvos-deps/ModernGekko/vendor/dolphin/Externals/picojson", ); @@ -664,7 +664,7 @@ ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", - "$(SRCROOT)/apple/tvos/Provisioned/appletvos/libs", + "$(SRCROOT)/apple/tvos/Provisioned/$(PLATFORM_NAME)/libs", ); MARKETING_VERSION = 0.1.0; OTHER_LDFLAGS = ( @@ -687,7 +687,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.sunpad.SunPad.tv; PRODUCT_NAME = SunPadTV; SDKROOT = appletvos; - SUPPORTED_PLATFORMS = appletvos; + SUPPORTED_PLATFORMS = "appletvos appletvsimulator"; TARGETED_DEVICE_FAMILY = 3; TVOS_DEPLOYMENT_TARGET = 17.0; }; diff --git a/apple/tvos/SunPadTVAppDelegate.mm b/apple/tvos/SunPadTVAppDelegate.mm index 01da3af..fcfc7f8 100644 --- a/apple/tvos/SunPadTVAppDelegate.mm +++ b/apple/tvos/SunPadTVAppDelegate.mm @@ -2,6 +2,7 @@ #import #import #import +#import #import #import "SunPadControllerMapping.h" @@ -362,6 +363,7 @@ - (void)attemptStart { buttons:@[]]; return; } +#if !TARGET_OS_SIMULATOR [self reconcileController]; if (!SunPadTVHasExtendedController()) { UIButton *find = [self buttonWithTitle:@"Find Controller" action:^{ @@ -377,6 +379,7 @@ - (void)attemptStart { buttons:@[find]]; return; } +#endif [NSFileManager.defaultManager createDirectoryAtPath:SunPadTVSupportRoot() withIntermediateDirectories:YES attributes:nil error:nil]; SunPadSettings *settings = SunPadSettings.sharedSettings; diff --git a/docs/TVOS.md b/docs/TVOS.md index 1eda804..f3bbf8e 100644 --- a/docs/TVOS.md +++ b/docs/TVOS.md @@ -40,6 +40,18 @@ xcodebuild -project SunPad.xcodeproj -scheme SunPadTV -configuration Release \ Package and audit the unsigned preview only after the app build succeeds. +For the simulator gate, build and provision the same core and GMSE01 module for +the tvOS Simulator before building the `SunPadTV` target: + +```sh +./scripts/tvos-build-core-simulator.sh +xcodebuild -project SunPad.xcodeproj -scheme SunPadTV -configuration Release \ + -destination 'platform=tvOS Simulator,name=Apple TV 4K (3rd generation) (at 1080p)' \ + -derivedDataPath /tmp/SunPadTVSimulatorDerivedData build +``` + +The simulator bypasses only the physical Extended Gamepad requirement. Device +builds still fail closed until an Extended Gamepad is connected. ## Physical acceptance still required - Signed app installs and opens on a physical Apple TV. diff --git a/scripts/check-repository.sh b/scripts/check-repository.sh index 42a6c91..ed6c4f8 100755 --- a/scripts/check-repository.sh +++ b/scripts/check-repository.sh @@ -33,6 +33,7 @@ python3 tests/test_tvos_contract.py test -x scripts/package-ios.sh test -x scripts/audit-ios-package.sh test -x scripts/tvos-build-core-device.sh +test -x scripts/tvos-build-core-simulator.sh test -x scripts/prepare-tvos-dependencies.sh test -x scripts/tvos-provision-device.sh test -x scripts/stage-tvos-game-data.sh diff --git a/scripts/tvos-build-core-device.sh b/scripts/tvos-build-core-device.sh index 60c7df7..65b371f 100755 --- a/scripts/tvos-build-core-device.sh +++ b/scripts/tvos-build-core-device.sh @@ -5,7 +5,12 @@ ROOT="$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)" MG="${SUNPAD_TVOS_MODERNGEKKO_ROOT:-$ROOT/build/tvos-deps/ModernGekko}" TPL="$ROOT/ref/ModernGekko-Template" TOOLCHAIN="$ROOT/scripts/tvos-device-toolchain.cmake" -BUILD="$MG/build-tvos-appletvos-public" +SDK="${SUNPAD_TVOS_SDK:-appletvos}" +[[ "$SDK" = appletvos || "$SDK" = appletvsimulator ]] || { + echo "unsupported tvOS SDK: $SDK" >&2 + exit 64 +} +BUILD="$MG/build-tvos-$SDK-public" MODULE_BUILD="${SUNPAD_TVOS_MODULE_BUILD:-/tmp/sunpad-module-tvos}" "$ROOT/scripts/prepare-tvos-dependencies.sh" @@ -13,7 +18,7 @@ MODULE_BUILD="${SUNPAD_TVOS_MODULE_BUILD:-/tmp/sunpad-module-tvos}" CMAKE_COMMON=( -DCMAKE_TOOLCHAIN_FILE="$TOOLCHAIN" -DCMAKE_SYSTEM_NAME=tvOS -DCMAKE_SYSTEM_PROCESSOR=arm64 - -DCMAKE_OSX_SYSROOT=appletvos -DCMAKE_OSX_ARCHITECTURES=arm64 + -DCMAKE_OSX_SYSROOT="$SDK" -DCMAKE_OSX_ARCHITECTURES=arm64 -DCMAKE_OSX_DEPLOYMENT_TARGET=17.0 -DCMAKE_BUILD_TYPE=Release -DUSE_SYSTEM_FMT=OFF -DENABLE_QT=OFF -DENABLE_TESTS=OFF -DUSE_DISCORD_PRESENCE=OFF -DUSE_MGBA=OFF @@ -21,7 +26,7 @@ CMAKE_COMMON=( -DENABLE_ANALYTICS=OFF -DUSE_UPNP=OFF -DMODERNGEKKO_ENABLE_DOLPHIN_TESTS=OFF -DENABLE_CUBEB=OFF -DENABLE_VULKAN=OFF - -DUSE_SYSTEM_LZ4=OFF -DUSE_SYSTEM_ZSTD=OFF + -DUSE_SYSTEM_LZ4=OFF -DUSE_SYSTEM_XXHASH=OFF -DUSE_SYSTEM_ZSTD=OFF -DHAVE_PIPE2=0 -DUSE_SANITIZERS=OFF "-DCMAKE_C_FLAGS=-ffile-prefix-map=$ROOT=." "-DCMAKE_CXX_FLAGS=-ffile-prefix-map=$ROOT=." @@ -56,7 +61,7 @@ fi cmake -S "$MG/vendor/dolphin/module-template" -B "$MODULE_BUILD" -G Ninja \ -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE="$TOOLCHAIN" \ -DCMAKE_SYSTEM_NAME=tvOS -DCMAKE_SYSTEM_PROCESSOR=arm64 \ - -DCMAKE_OSX_SYSROOT=appletvos -DCMAKE_OSX_ARCHITECTURES=arm64 \ + -DCMAKE_OSX_SYSROOT="$SDK" -DCMAKE_OSX_ARCHITECTURES=arm64 \ -DCMAKE_OSX_DEPLOYMENT_TARGET=17.0 -DGAME_ID=GMSE01 \ -DGENERATED_DIR="$GEN" -DGXRUNTIME_DIR="$MG/vendor/dolphin/GXRuntime" \ -DCHASSIS_ABI_DIR="$MG/vendor/dolphin/Source/Core/Core/PowerPC/StaticRecomp" diff --git a/scripts/tvos-build-core-simulator.sh b/scripts/tvos-build-core-simulator.sh new file mode 100755 index 0000000..5d6330d --- /dev/null +++ b/scripts/tvos-build-core-simulator.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)" +export SUNPAD_TVOS_SDK=appletvsimulator +export SUNPAD_TVOS_MODULE_BUILD="${SUNPAD_TVOS_MODULE_BUILD:-/tmp/sunpad-module-tvos-appletvsimulator}" +exec "$ROOT/scripts/tvos-build-core-device.sh" diff --git a/scripts/tvos-provision-device.sh b/scripts/tvos-provision-device.sh index 94c4d81..9bc5b38 100755 --- a/scripts/tvos-provision-device.sh +++ b/scripts/tvos-provision-device.sh @@ -3,9 +3,15 @@ set -euo pipefail ROOT="$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)" MG="${SUNPAD_TVOS_MODERNGEKKO_ROOT:-$ROOT/build/tvos-deps/ModernGekko}" -TVOS_BUILD="${SUNPAD_TVOS_BUILD:-$MG/build-tvos-appletvos-public}" -MODULE="${SUNPAD_TVOS_MODULE_PATH:-/tmp/sunpad-module-tvos/gGMSE01_recomp.dylib}" -OUT="${SUNPAD_TVOS_PROVISIONED_OUT:-$ROOT/apple/tvos/Provisioned/appletvos}" +SDK="${SUNPAD_TVOS_SDK:-appletvos}" +[[ "$SDK" = appletvos || "$SDK" = appletvsimulator ]] || { + echo "unsupported tvOS SDK: $SDK" >&2 + exit 64 +} +TVOS_BUILD="${SUNPAD_TVOS_BUILD:-$MG/build-tvos-$SDK-public}" +MODULE_BUILD="${SUNPAD_TVOS_MODULE_BUILD:-/tmp/sunpad-module-tvos}" +MODULE="${SUNPAD_TVOS_MODULE_PATH:-$MODULE_BUILD/gGMSE01_recomp.dylib}" +OUT="${SUNPAD_TVOS_PROVISIONED_OUT:-$ROOT/apple/tvos/Provisioned/$SDK}" LIBS_DIR="$OUT/libs" [[ -d "$TVOS_BUILD" ]] || { echo "missing tvOS core build: $TVOS_BUILD" >&2; exit 1; } @@ -60,5 +66,7 @@ done libtool -static -o "$LIBS_DIR/libSunPadCore.a" "${LIBS[@]}" cp "$MODULE" "$OUT/gGMSE01_recomp.dylib" -vtool -show-build "$OUT/gGMSE01_recomp.dylib" | grep -Eq 'platform +TVOS$' +platform=TVOS +[[ "$SDK" = appletvsimulator ]] && platform=TVOSSIMULATOR +vtool -show-build "$OUT/gGMSE01_recomp.dylib" | grep -Eq "platform +$platform$" echo "tvOS core and GMSE01 module provisioned: $OUT" diff --git a/tests/test_tvos_contract.py b/tests/test_tvos_contract.py index 203bf06..3d4a265 100644 --- a/tests/test_tvos_contract.py +++ b/tests/test_tvos_contract.py @@ -27,6 +27,9 @@ def test_target_and_metadata_are_project_owned(self): with (ROOT / "apple/tvos/Info.plist").open("rb") as handle: info = plistlib.load(handle) self.assertIn("TARGETED_DEVICE_FAMILY = 3;", project) + self.assertIn('SUPPORTED_PLATFORMS = "appletvos appletvsimulator";', project) + self.assertIn("Provisioned/$(PLATFORM_NAME)/libs", project) + self.assertIn("Provisioned/$(PLATFORM_NAME)/gGMSE01_recomp.dylib", project) self.assertTrue(info["GCSupportsControllerUserInteraction"]) self.assertEqual( info["GCSupportedGameControllers"], [{"ProfileName": "ExtendedGamepad"}] @@ -57,7 +60,12 @@ def test_build_produces_core_and_tvos_module(self): self.assertIn("-DCMAKE_SYSTEM_NAME=tvOS", build) self.assertIn("gGMSE01_recomp.dylib", provision) self.assertNotIn("Xcode-27", build + provision) - self.assertIn("platform +TVOS", provision) + self.assertIn("platform=TVOS", provision) + simulator = self.text("scripts/tvos-build-core-simulator.sh") + self.assertIn("SUNPAD_TVOS_SDK=appletvsimulator", simulator) + self.assertIn('SDK="${SUNPAD_TVOS_SDK:-appletvos}"', build) + self.assertIn("TVOSSIMULATOR", provision) + self.assertIn("#if !TARGET_OS_SIMULATOR", self.text("apple/tvos/SunPadTVAppDelegate.mm")) def test_device_workflow_preserves_scope(self): stage = self.text("scripts/stage-tvos-game-data.sh")