From aa6c9c10caf3630ca37cb45bafc0485b76605445 Mon Sep 17 00:00:00 2001 From: quarrel07 <178681861+quarrel07@users.noreply.github.com> Date: Sat, 1 Aug 2026 18:35:51 -0700 Subject: macOS: package the game as a proper SpaghettiKart.app bundle (#724) * macOS: package the game as a proper SpaghettiKart.app bundle Turns the bare Spaghettify executable into a self-contained SpaghettiKart.app: - cmake/macos/apple_bundle.cmake: bundle metadata (using the existing Info.plist, which already described a bundle), app icon, runtime resources (config.yml, yamls, meta, spaghetti.o2r) into Contents/Resources, dylib relinking for local Homebrew builds (no-op with static vcpkg deps), and ad-hoc codesigning so the app launches without 'damaged app' warnings - App icon: on Xcode 26+ the Icon Composer package (SpaghettiKartIcon.icon, drawn from the existing HM ship-kart icon.png art) compiles with actool into an Assets.car, giving the native Liquid Glass look on macOS 26+, plus a flattened icns for older systems; older Xcode falls back to the previous sips/iconutil flat icon. Supersedes the CreateOSXIcons target. - Game.cpp: default SHIP_HOME to ~/Library/Application Support/SpaghettiKart on macOS so first-run extraction, saves, config, and mods land there instead of scattering into the user's home folder; a user-set SHIP_HOME still wins - Info.plist: NSHighResolutionCapable for proper Retina rendering - CI: mac jobs now ship the .app (ditto zip so the executable bit survives artifact upload) - the yamls-next-to-exe copy is scoped to non-Darwin; on macOS those files live in Contents/Resources instead * macOS: ship prebuilt icon artifacts instead of compiling them at build time Commits the compiled Assets.car (Liquid Glass icon for macOS 26+) and the flattened SpaghettiKart.icns, both built from the in-repo Icon Composer package. The build now just copies them into the bundle, which removes the actool machinery and the Xcode 26 requirement entirely: every build on any machine, including CI, gets the real icon. Also adds a staleness guard: the artifacts carry a hash stamp of the icon.png they were made from, and if icon.png ever changes without them being regenerated, the build automatically generates a flat icns from the current icon.png instead, so updating the logo never requires any macOS work and an outdated icon can never ship. Regeneration steps are documented in apple_bundle.cmake. * Rename the icon layer to ship-kart-layer.png Group 2.png was the design tool's default layer name; name it for what it is. Verified the Icon Composer package still compiles with actool after the rename; the committed Assets.car is unaffected (compiled, self-contained). --------- Co-authored-by: MegaMech --- .github/workflows/macos-compile.yml | 4 +- .github/workflows/main.yml | 28 ++-- CMakeLists.txt | 17 ++- Info.plist | 6 + cmake/macos/Assets.car | Bin 0 -> 2621960 bytes cmake/macos/SpaghettiKart.icns | Bin 0 -> 72290 bytes cmake/macos/SpaghettiKartIcon.icon.source-sha256 | 1 + .../Assets/ship-kart-layer.png | Bin 0 -> 378328 bytes cmake/macos/SpaghettiKartIcon.icon/icon.json | 80 ++++++++++ cmake/macos/apple_bundle.cmake | 166 +++++++++++++++++++++ cmake/macos/entitlements.plist | 10 ++ cmake/macos/fixup_bundle.cmake | 31 ++++ src/port/Game.cpp | 8 + 13 files changed, 330 insertions(+), 21 deletions(-) create mode 100644 cmake/macos/Assets.car create mode 100644 cmake/macos/SpaghettiKart.icns create mode 100644 cmake/macos/SpaghettiKartIcon.icon.source-sha256 create mode 100644 cmake/macos/SpaghettiKartIcon.icon/Assets/ship-kart-layer.png create mode 100644 cmake/macos/SpaghettiKartIcon.icon/icon.json create mode 100644 cmake/macos/apple_bundle.cmake create mode 100644 cmake/macos/entitlements.plist create mode 100644 cmake/macos/fixup_bundle.cmake diff --git a/.github/workflows/macos-compile.yml b/.github/workflows/macos-compile.yml index d3c5dfbf8..59986d8fc 100644 --- a/.github/workflows/macos-compile.yml +++ b/.github/workflows/macos-compile.yml @@ -25,7 +25,9 @@ jobs: - name: Create Package run: | mkdir spaghetti-${{ matrix.config }} - mv build-cmake/Spaghettify spaghetti-${{ matrix.config }}/ + # The mac build now produces SpaghettiKart.app; zip with ditto so the + # executable bit survives artifact upload. + ditto -c -k --keepParent build-cmake/SpaghettiKart.app spaghetti-${{ matrix.config }}/SpaghettiKart.app.zip - name: Publish packaged artifacts uses: actions/upload-artifact@v4 with: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3b97f5122..16a0758d6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -107,13 +107,15 @@ jobs: path: ./build-cmake - name: Create Package run: | + APP=build-cmake/SpaghettiKart.app + # Insert the canonical spaghetti.o2r from the generate-port-o2r job and + # the controller database, then re-sign since the bundle contents changed. + cp build-cmake/spaghetti.o2r $APP/Contents/Resources/spaghetti.o2r + wget -O $APP/Contents/Resources/gamecontrollerdb.txt https://raw.githubusercontent.com/mdqinc/SDL_GameControllerDB/master/gamecontrollerdb.txt + codesign --force --deep --sign - --options runtime --entitlements cmake/macos/entitlements.plist $APP mkdir spaghetti-${{ matrix.config }} - mv build-cmake/Spaghettify spaghetti-${{ matrix.config }}/ - mv build-cmake/spaghetti.o2r spaghetti-${{ matrix.config }}/ - mv config.yml spaghetti-${{ matrix.config }}/ - mv yamls spaghetti-${{ matrix.config }}/ - mv meta spaghetti-${{ matrix.config }}/ - wget -O spaghetti-${{ matrix.config }}/gamecontrollerdb.txt https://raw.githubusercontent.com/mdqinc/SDL_GameControllerDB/master/gamecontrollerdb.txt + # Zip with ditto so the executable bit survives artifact upload. + ditto -c -k --keepParent $APP spaghetti-${{ matrix.config }}/SpaghettiKart.app.zip - name: Publish packaged artifacts if: matrix.config == 'Release' uses: actions/upload-artifact@v4 @@ -146,13 +148,15 @@ jobs: path: ./build-cmake - name: Create Package run: | + APP=build-cmake/SpaghettiKart.app + # Insert the canonical spaghetti.o2r from the generate-port-o2r job and + # the controller database, then re-sign since the bundle contents changed. + cp build-cmake/spaghetti.o2r $APP/Contents/Resources/spaghetti.o2r + wget -O $APP/Contents/Resources/gamecontrollerdb.txt https://raw.githubusercontent.com/mdqinc/SDL_GameControllerDB/master/gamecontrollerdb.txt + codesign --force --deep --sign - --options runtime --entitlements cmake/macos/entitlements.plist $APP mkdir spaghetti-${{ matrix.config }} - mv build-cmake/Spaghettify spaghetti-${{ matrix.config }}/ - mv build-cmake/spaghetti.o2r spaghetti-${{ matrix.config }}/ - mv config.yml spaghetti-${{ matrix.config }}/ - mv yamls spaghetti-${{ matrix.config }}/ - mv meta spaghetti-${{ matrix.config }}/ - wget -O spaghetti-${{ matrix.config }}/gamecontrollerdb.txt https://raw.githubusercontent.com/mdqinc/SDL_GameControllerDB/master/gamecontrollerdb.txt + # Zip with ditto so the executable bit survives artifact upload. + ditto -c -k --keepParent $APP spaghetti-${{ matrix.config }}/SpaghettiKart.app.zip - name: Publish packaged artifacts if: matrix.config == 'Release' uses: actions/upload-artifact@v4 diff --git a/CMakeLists.txt b/CMakeLists.txt index 883763dfe..9eac9a299 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -204,6 +204,10 @@ endif() # ############################################################################## +# On macOS these live in the .app's Contents/Resources instead (see +# cmake/macos/apple_bundle.cmake); copying them next to the executable would +# just duplicate them inside Contents/MacOS. +if(NOT CMAKE_SYSTEM_NAME MATCHES "Darwin") add_custom_command( TARGET ${PROJECT_NAME} POST_BUILD @@ -213,6 +217,7 @@ add_custom_command( -DDESTINATION_DIR=$ -P ${CMAKE_SOURCE_DIR}/cmake/StageRuntimeFiles.cmake VERBATIM) +endif() if(NOT CMAKE_SYSTEM_NAME STREQUAL "NintendoSwitch") include(ExternalProject) @@ -281,16 +286,12 @@ if(NOT CMAKE_SYSTEM_NAME STREQUAL "NintendoSwitch") endif() if(APPLE AND NOT SPAGHETTIKART_IOS) - add_custom_target( - CreateOSXIcons - COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/macosx - COMMAND sips -s format icns icon.png --out - ${CMAKE_BINARY_DIR}/macosx/spaghettikart.icns - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - COMMENT "Creating OSX icons ...") - add_dependencies(${PROJECT_NAME} CreateOSXIcons) configure_file("${CMAKE_SOURCE_DIR}/Info.plist" "${CMAKE_BINARY_DIR}/Info.plist" COPYONLY) + # Assemble SpaghettiKart.app: app icon, bundle metadata, resources, dylib + # relinking, and ad-hoc codesigning. Also generates the icns (superseding + # the old CreateOSXIcons target). + include(${CMAKE_SOURCE_DIR}/cmake/macos/apple_bundle.cmake) endif() set_property(TARGET ${PROJECT_NAME} PROPERTY APPIMAGE_DESKTOP_FILE_TERMINAL diff --git a/Info.plist b/Info.plist index d844ba858..b2600d74f 100644 --- a/Info.plist +++ b/Info.plist @@ -12,6 +12,8 @@ 1.0.0 CFBundleIconFile SpaghettiKart.icns + CFBundleIconName + SpaghettiKartIcon CFBundleIdentifier com.SpaghettiKart.SpaghettiKart CFBundleDocumentTypes @@ -33,6 +35,10 @@ public.app-category.games LSMinimumSystemVersion 10.15 + NSHighResolutionCapable + + NSSupportsAutomaticGraphicsSwitching + LSArchitecturePriority arm64 diff --git a/cmake/macos/Assets.car b/cmake/macos/Assets.car new file mode 100644 index 000000000..ab3d52f08 Binary files /dev/null and b/cmake/macos/Assets.car differ diff --git a/cmake/macos/SpaghettiKart.icns b/cmake/macos/SpaghettiKart.icns new file mode 100644 index 000000000..49d6c6e0a Binary files /dev/null and b/cmake/macos/SpaghettiKart.icns differ diff --git a/cmake/macos/SpaghettiKartIcon.icon.source-sha256 b/cmake/macos/SpaghettiKartIcon.icon.source-sha256 new file mode 100644 index 000000000..e90c4c891 --- /dev/null +++ b/cmake/macos/SpaghettiKartIcon.icon.source-sha256 @@ -0,0 +1 @@ +b211f35ff967bb6ba0efda748c5a54d6f4909f97138c6d7178c860487fe6c80f diff --git a/cmake/macos/SpaghettiKartIcon.icon/Assets/ship-kart-layer.png b/cmake/macos/SpaghettiKartIcon.icon/Assets/ship-kart-layer.png new file mode 100644 index 000000000..725f876c0 Binary files /dev/null and b/cmake/macos/SpaghettiKartIcon.icon/Assets/ship-kart-layer.png differ diff --git a/cmake/macos/SpaghettiKartIcon.icon/icon.json b/cmake/macos/SpaghettiKartIcon.icon/icon.json new file mode 100644 index 000000000..6d9bbe0d9 --- /dev/null +++ b/cmake/macos/SpaghettiKartIcon.icon/icon.json @@ -0,0 +1,80 @@ +{ + "fill" : { + "linear-gradient" : [ + "display-p3:0.01176,0.05884,0.20398,1.00000", + "display-p3:0.06274,0.10980,0.10199,1.00000" + ], + "orientation" : { + "start" : { + "x" : 0.5, + "y" : 1 + }, + "stop" : { + "x" : 0.5, + "y" : 0 + } + } + }, + "groups" : [ + { + "blur-material" : null, + "layers" : [ + { + "glass" : true, + "image-name" : "ship-kart-layer.png", + "name" : "ship-kart-layer", + "position-specializations" : [ + { + "value" : { + "scale" : 1, + "translation-in-points" : [ + 30.66035267835514, + -14.790467402525564 + ] + } + }, + { + "idiom" : "square", + "value" : { + "scale" : 0.97, + "translation-in-points" : [ + 18.04483550076793, + -6.30499999999995 + ] + } + } + ] + } + ], + "lighting" : "individual", + "refractivity" : { + "depth" : 0, + "enabled" : false, + "strength" : 0.5 + }, + "shadow" : { + "kind" : "neutral", + "opacity" : 0.6 + }, + "specular" : true, + "translucency-specializations" : [ + { + "value" : { + "enabled" : true, + "value" : 0.2 + } + }, + { + "appearance" : "dark", + "value" : { + "enabled" : true, + "value" : 0.2 + } + } + ] + } + ], + "supported-platforms" : { + "squares" : "shared" + } +} \ No newline at end of file diff --git a/cmake/macos/apple_bundle.cmake b/cmake/macos/apple_bundle.cmake new file mode 100644 index 000000000..6885950c4 --- /dev/null +++ b/cmake/macos/apple_bundle.cmake @@ -0,0 +1,166 @@ +# macOS .app bundle assembly for SpaghettiKart. +# +# Turns the plain executable into SpaghettiKart.app (MACOSX_BUNDLE), bundles the +# runtime resources the game and the first-run ROM extractor need into +# Contents/Resources, relinks any non-system dylibs into Contents/Frameworks, +# and ad-hoc codesigns the result so it launches without "damaged app" warnings. +# +# Resource layout the runtime expects on macOS: +# * Ship::Context::GetAppBundlePath() -> .app/Contents/Resources (read-only) +# - spaghetti.o2r : packed port assets (GenerateO2R target) +# - config.yml, yamls/, meta/: asset definitions + mods.toml read by the +# first-run ROM extractor (GameExtractor) +# * Ship::Context::GetAppDirectoryPath() -> SHIP_HOME +# (~/Library/Application Support/SpaghettiKart, defaulted in Game.cpp) +# - mk64.o2r is extracted here on first run from the user's ROM, alongside +# config/saves/mods, instead of scattering into the home folder + +set(MACOS_DIR ${CMAKE_SOURCE_DIR}/cmake/macos) +set(ENTITLEMENTS_FILE ${MACOS_DIR}/entitlements.plist) + +option(SPAGHETTI_BUNDLE_DEPS "Relink and bundle dylibs into the .app so it is portable" ON) + +# --------------------------------------------------------------------------- +# Bundle metadata. OUTPUT_NAME "SpaghettiKart" makes the bundle SpaghettiKart.app +# with Contents/MacOS/SpaghettiKart, matching CFBundleExecutable in Info.plist +# (the build target itself stays named "Spaghettify"). +# --------------------------------------------------------------------------- +set_target_properties(${PROJECT_NAME} PROPERTIES + OUTPUT_NAME "SpaghettiKart" + MACOSX_BUNDLE TRUE + MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/Info.plist + XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "-" + XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS ${ENTITLEMENTS_FILE} +) + +# --------------------------------------------------------------------------- +# App icon. +# +# The repo ships the compiled icon artifacts (same approach as other ports that +# ship a prebuilt Assets.car), so no special Xcode is needed at build time: +# - Assets.car : compiled asset catalog with the layered Liquid Glass +# icon, used on macOS 26+ via CFBundleIconName +# ("SpaghettiKartIcon"). +# - SpaghettiKart.icns : flattened icon for macOS versions before 26, via +# CFBundleIconFile. +# Both are built from cmake/macos/SpaghettiKartIcon.icon (Icon Composer package; +# the ship-kart art from icon.png with the subject separated from the background +# so the system can render the glass depth). +# +# Staleness guard: if icon.png ever changes without the icon artifacts being +# regenerated, the build detects it via the source hash stamp and falls back to +# generating a flat icns from the CURRENT icon.png with sips/iconutil, so an +# outdated icon can never ship and updating the logo requires no macOS work. +# +# To regenerate after a logo change (needs a Mac with Xcode 26+): +# 1. Recreate the cut-out art layer and update SpaghettiKartIcon.icon in +# Apple's Icon Composer. +# 2. xcrun actool cmake/macos/SpaghettiKartIcon.icon --compile /tmp/iconout \ +# --app-icon SpaghettiKartIcon --output-partial-info-plist /tmp/iconout/p.plist \ +# --platform macosx --target-device mac --minimum-deployment-target 11.0 +# 3. cp /tmp/iconout/Assets.car cmake/macos/Assets.car +# cp /tmp/iconout/SpaghettiKartIcon.icns cmake/macos/SpaghettiKart.icns +# 4. shasum -a 256 icon.png > cmake/macos/SpaghettiKartIcon.icon.source-sha256 +# --------------------------------------------------------------------------- +set(ICON_SOURCE_STAMP ${MACOS_DIR}/SpaghettiKartIcon.icon.source-sha256) +set(ICON_PREBUILT_CAR ${MACOS_DIR}/Assets.car) +set(ICON_PREBUILT_ICNS ${MACOS_DIR}/SpaghettiKart.icns) +set(ICNS_FILE ${CMAKE_BINARY_DIR}/macosx/SpaghettiKart.icns) +file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/macosx) + +set(ICON_SOURCE_FRESH FALSE) +if(EXISTS ${ICON_SOURCE_STAMP} AND EXISTS ${ICON_PREBUILT_CAR} AND EXISTS ${ICON_PREBUILT_ICNS}) + file(SHA256 ${CMAKE_SOURCE_DIR}/icon.png ICON_PNG_HASH) + file(READ ${ICON_SOURCE_STAMP} ICON_STAMP_HASH) + string(STRIP "${ICON_STAMP_HASH}" ICON_STAMP_HASH) + if(ICON_PNG_HASH STREQUAL ICON_STAMP_HASH) + set(ICON_SOURCE_FRESH TRUE) + else() + message(STATUS "App icon: icon.png changed since the icon artifacts were made; using a flat icon from the current icon.png") + endif() +endif() + +if(ICON_SOURCE_FRESH) + message(STATUS "App icon: prebuilt Liquid Glass catalog + flattened icns fallback") + file(COPY_FILE ${ICON_PREBUILT_ICNS} ${ICNS_FILE}) + file(COPY_FILE ${ICON_PREBUILT_CAR} ${CMAKE_BINARY_DIR}/macosx/Assets.car) + set(ICON_BUNDLE_FILES ${CMAKE_BINARY_DIR}/macosx/Assets.car ${ICNS_FILE}) + set(SPAGHETTI_ICON_HAS_GLASS TRUE) +else() + set(ICON_SRC ${CMAKE_SOURCE_DIR}/icon.png) + set(ICONSET_DIR ${CMAKE_BINARY_DIR}/macosx/SpaghettiKart.iconset) + file(MAKE_DIRECTORY ${ICONSET_DIR}) + foreach(SPEC "16;icon_16x16" "32;icon_16x16@2x" "32;icon_32x32" "64;icon_32x32@2x" + "128;icon_128x128" "256;icon_128x128@2x" "256;icon_256x256" + "512;icon_256x256@2x" "512;icon_512x512" "1024;icon_512x512@2x") + list(GET SPEC 0 SZ) + list(GET SPEC 1 NAME) + execute_process(COMMAND sips -z ${SZ} ${SZ} ${ICON_SRC} --out ${ICONSET_DIR}/${NAME}.png OUTPUT_QUIET ERROR_QUIET) + endforeach() + execute_process(COMMAND iconutil -c icns -o ${ICNS_FILE} ${ICONSET_DIR}) + set(ICON_BUNDLE_FILES ${ICNS_FILE}) + set(SPAGHETTI_ICON_HAS_GLASS FALSE) +endif() + +set_source_files_properties(${ICON_BUNDLE_FILES} PROPERTIES GENERATED TRUE MACOSX_PACKAGE_LOCATION "Resources") +target_sources(${PROJECT_NAME} PRIVATE ${ICON_BUNDLE_FILES}) + +if(NOT SPAGHETTI_ICON_HAS_GLASS) + # No asset catalog in this build: remove CFBundleIconName so macOS 26+ + # falls back to CFBundleIconFile instead of showing a generic icon. + add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD + COMMAND plutil -remove CFBundleIconName "$/Contents/Info.plist" || true + VERBATIM + ) +endif() + +# Ensure the packed port assets (spaghetti.o2r) are generated before the app +# links, so the POST_BUILD step below has them to copy into the bundle. +# (mk64.o2r is created at runtime from the user's ROM into SHIP_HOME, so it is +# intentionally not a build dependency.) +add_dependencies(${PROJECT_NAME} GenerateO2R) + +# --------------------------------------------------------------------------- +# Copy runtime resources into Contents/Resources after the app links. +# --------------------------------------------------------------------------- +set(RES_DIR "$/Contents/Resources") +add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E make_directory "${RES_DIR}" + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_SOURCE_DIR}/config.yml" "${RES_DIR}/config.yml" + COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_SOURCE_DIR}/yamls" "${RES_DIR}/yamls" + COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_SOURCE_DIR}/meta" "${RES_DIR}/meta" + # spaghetti.o2r is produced by the GenerateO2R target; copy if present (CI + # builds it in a separate job and inserts it into the bundle afterwards). + COMMAND bash -c "[ -f '${CMAKE_BINARY_DIR}/spaghetti.o2r' ] && cp '${CMAKE_BINARY_DIR}/spaghetti.o2r' '${RES_DIR}/spaghetti.o2r' || ([ -f '${CMAKE_SOURCE_DIR}/spaghetti.o2r' ] && cp '${CMAKE_SOURCE_DIR}/spaghetti.o2r' '${RES_DIR}/spaghetti.o2r' || echo 'note: spaghetti.o2r not found - build the GenerateO2R target, then rebuild')" + COMMENT "Bundling SpaghettiKart resources into the .app" + VERBATIM +) + +# --------------------------------------------------------------------------- +# Relink dylibs into Contents/Frameworks (portable .app) and codesign. +# With static vcpkg dependencies (CI) this is a near no-op; it matters for +# local builds against Homebrew/MacPorts dylibs. +# --------------------------------------------------------------------------- +if (SPAGHETTI_BUNDLE_DEPS) + add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} + -DAPP_BUNDLE=$ + -DEXECUTABLE_NAME=SpaghettiKart + -P ${MACOS_DIR}/fixup_bundle.cmake + COMMAND bash -c "install_name_tool -add_rpath '@executable_path/../Frameworks/' '$/Contents/MacOS/SpaghettiKart' 2>/dev/null || true" + # Homebrew's "sdl2" is sdl2-compat, a shim that dlopen()s libSDL3.dylib + # from @loader_path at runtime. fixup_bundle can't follow a dlopen, so if + # a dynamic libSDL2 was bundled, copy SDL3 in next to it (= @loader_path) + # or the app aborts with "Failed loading SDL3 library." SDL3 itself only + # links system frameworks. Static SDL2 builds (vcpkg) skip this entirely. + COMMAND bash -c "FRAMEWORKS='$/Contents/Frameworks'; if ls \"$FRAMEWORKS\"/libSDL2*.dylib >/dev/null 2>&1; then SDL3_LIB=$(brew --prefix sdl3 2>/dev/null)/lib/libSDL3.0.dylib; if [ -f \"$SDL3_LIB\" ]; then cp \"$SDL3_LIB\" \"$FRAMEWORKS/libSDL3.dylib\" && chmod u+w \"$FRAMEWORKS/libSDL3.dylib\"; else echo 'warning: bundled libSDL2 is sdl2-compat but Homebrew sdl3 was not found'; fi; fi" + COMMENT "Relinking dylibs into the .app bundle" + VERBATIM + ) +endif() + +add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD + COMMAND codesign --force --deep --sign - --options runtime --entitlements ${ENTITLEMENTS_FILE} "$" + COMMENT "Ad-hoc codesigning SpaghettiKart.app" + VERBATIM +) diff --git a/cmake/macos/entitlements.plist b/cmake/macos/entitlements.plist new file mode 100644 index 000000000..4579570e5 --- /dev/null +++ b/cmake/macos/entitlements.plist @@ -0,0 +1,10 @@ + + + + + + com.apple.security.cs.disable-library-validation + + + diff --git a/cmake/macos/fixup_bundle.cmake b/cmake/macos/fixup_bundle.cmake new file mode 100644 index 000000000..10b82d939 --- /dev/null +++ b/cmake/macos/fixup_bundle.cmake @@ -0,0 +1,31 @@ +# Run at build time (cmake -P) to copy non-system dylibs the executable links +# against into the .app's Contents/Frameworks and rewrite the load paths, making +# the bundle self-contained / portable. Invoked from apple_bundle.cmake. +# +# Expects -D APP_BUNDLE= -D EXECUTABLE_NAME=SpaghettiKart + +include(BundleUtilities) + +set(APP "${APP_BUNDLE}/Contents/MacOS/${EXECUTABLE_NAME}") +file(REAL_PATH "${APP}" APP) + +# Where fixup_bundle should look for the dependencies it needs to copy in. +set(SEARCH_DIRS + "/opt/homebrew/lib" # Homebrew (Apple Silicon) + "/usr/local/lib" # Homebrew (Intel) / misc + "/opt/local/lib" # MacPorts + "${APP_BUNDLE}/Contents/Frameworks" +) + +set(RESOLVED_DIRS "") +foreach(DIR IN LISTS SEARCH_DIRS) + if (EXISTS "${DIR}") + file(REAL_PATH "${DIR}" RESOLVED_DIR) + list(APPEND RESOLVED_DIRS "${RESOLVED_DIR}") + endif() +endforeach() + +message(STATUS "fixup_bundle: ${APP}") +message(STATUS "fixup_bundle search dirs: ${RESOLVED_DIRS}") + +fixup_bundle("${APP}" "" "${RESOLVED_DIRS}") diff --git a/src/port/Game.cpp b/src/port/Game.cpp index 9804fe324..c1afe80b1 100644 --- a/src/port/Game.cpp +++ b/src/port/Game.cpp @@ -1034,6 +1034,14 @@ extern "C" // `defaults write -app ApplePressAndHoldEnabled -bool false`; key repeat still works. CFPreferencesSetAppValue(CFSTR("ApplePressAndHoldEnabled"), kCFBooleanFalse, kCFPreferencesCurrentApplication); CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication); +#endif +#if defined(__APPLE__) && !defined(PLATFORM_IOS) + // Default the writable data folder to ~/Library/Application Support/SpaghettiKart + // (libultraship expands the ~ and creates the directory). Without this, a Finder + // launch has no SHIP_HOME and libultraship falls back to the current working + // directory, scattering config/saves/mods into the user's home folder. + // overwrite=0 keeps any SHIP_HOME the user already set. + setenv("SHIP_HOME", "~/Library/Application Support/SpaghettiKart", 0); #endif // load_wasm(); GameEngine::Create(); -- cgit v1.2.3