Compare commits

..

6 Commits

Author SHA1 Message Date
Adrian Castro f77a98bfe6 fix(ci): update platform artifact workflow gate 2026-08-19 20:32:35 +02:00
Adrian Castro dd024d0e50 ci: comment all platform build artifacts 2026-08-19 20:26:21 +02:00
Adrian Castro 770798b4b6 fix(build): create macOS runtime cache before fetching 2026-08-19 20:21:53 +02:00
Adrian Castro f1e5fa4ec3 build(mac): establish LÖVE 12 Metal parity 2026-08-19 20:14:04 +02:00
Adrian Castro 5cf53bb13f build(mac): use LÖVE 12 Metal runtime 2026-08-19 20:07:54 +02:00
Adrian Castro 95764c7d97 fix(build): strip macOS staging metadata before signing 2026-08-19 20:07:54 +02:00
22 changed files with 488 additions and 239 deletions
+59 -1
View File
@@ -26,6 +26,64 @@ permissions:
contents: read contents: read
jobs: jobs:
macos-changes:
name: detect macOS changes
runs-on: ubuntu-latest
outputs:
changed: ${{ steps.paths.outputs.changed }}
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- id: paths
env:
BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }}
HEAD_SHA: ${{ github.sha }}
run: |
if [ -z "$BASE_SHA" ] || [ "$BASE_SHA" = "0000000000000000000000000000000000000000" ]; then
echo "changed=true" >> "$GITHUB_OUTPUT"
exit 0
fi
if git diff --name-only "$BASE_SHA" "$HEAD_SHA" | grep -Eq '^(conf\.lua$|scripts/(build\.sh|build_love_macos\.sh|run\.sh|setup\.sh)$|Play-Mac\.command$|mobile/macos/|src/core/SaveData\.lua$|\.github/workflows/(ci|release)\.yml$)'; then
echo "changed=true" >> "$GITHUB_OUTPUT"
else
echo "changed=false" >> "$GITHUB_OUTPUT"
fi
macos-build:
name: macOS LÖVE 12 build
needs: macos-changes
if: needs.macos-changes.outputs.changed == 'true'
runs-on: ${{ fromJSON(github.repository == 'bryanthaboi/gen1recomp' && '["self-hosted", "macOS"]' || '"macos-latest"') }}
steps:
- uses: actions/checkout@v7
- name: Build LÖVE 12 macOS runtime
run: scripts/build_love_macos.sh --fetch
- name: Build and verify macOS app
env:
LOVE_APP: ${{ github.workspace }}/.bazinga/love12/love.app
MAC_STAGE_DIR: ${{ runner.temp }}/gen1recomp-mac-stage
run: |
set -euo pipefail
scripts/build.sh mac --no-notarize --identity - --version 0.0.0
unzip -tqq 'dist/mac/gen1recomp++-macos.zip'
app="$MAC_STAGE_DIR/gen1recomp++.app"
[ -d "$app" ]
[ -x "$app/Contents/MacOS/gen1recomp++" ]
[ "$(/usr/libexec/PlistBuddy -c 'Print :CFBundleExecutable' "$app/Contents/Info.plist")" = 'gen1recomp++' ]
[ "$(/usr/libexec/PlistBuddy -c 'Print :CFBundleDisplayName' "$app/Contents/Info.plist")" = 'gen1recomp++' ]
[ "$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' "$app/Contents/Info.plist")" = 'com.theboisclub.gen1recompplusplus' ]
[ "$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' "$app/Contents/Info.plist")" = 0.0.0 ]
codesign --verify --deep --strict "$app"
otool -L "$app/Contents/Frameworks/love.framework/love" | grep -q '/Metal.framework/'
- name: Upload macOS build
uses: actions/upload-artifact@v7
with:
name: gen1recomp++-macos
path: dist/mac/gen1recomp++-macos.zip
if-no-files-found: error
retention-days: 7
ios-changes: ios-changes:
name: detect iOS changes name: detect iOS changes
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -120,7 +178,7 @@ jobs:
echo "changed=true" >> "$GITHUB_OUTPUT" echo "changed=true" >> "$GITHUB_OUTPUT"
exit 0 exit 0
fi fi
if git diff --name-only "$BASE_SHA" "$HEAD_SHA" | grep -Eq '^(scripts/build_switch\.sh$|scripts/switch/|docs/switch-.*\.md$|tests/switch_ci_workflows_test\.lua$|tests/switch_transfer_docs_test\.lua$|\.github/workflows/(ci|release|switch-artifact-comment)\.yml$|src/core/(NxAssetOverlay|Platform|GameVersion)\.lua$|src/import/CacheFs\.lua$|tests/engine/(assets_version_fallback|nx_generated_guard|nx_yellow_boot|switch_diagnostics|cache_fs_gold_nx_load)_test\.lua$|tests/engine/platform_nx)'; then if git diff --name-only "$BASE_SHA" "$HEAD_SHA" | grep -Eq '^(scripts/build_switch\.sh$|scripts/switch/|docs/switch-.*\.md$|tests/switch_ci_workflows_test\.lua$|tests/switch_transfer_docs_test\.lua$|\.github/workflows/(ci|release|platform-artifact-comment)\.yml$|src/core/(NxAssetOverlay|Platform|GameVersion)\.lua$|src/import/CacheFs\.lua$|tests/engine/(assets_version_fallback|nx_generated_guard|nx_yellow_boot|switch_diagnostics|cache_fs_gold_nx_load)_test\.lua$|tests/engine/platform_nx)'; then
echo "changed=true" >> "$GITHUB_OUTPUT" echo "changed=true" >> "$GITHUB_OUTPUT"
else else
echo "changed=false" >> "$GITHUB_OUTPUT" echo "changed=false" >> "$GITHUB_OUTPUT"
@@ -1,55 +0,0 @@
name: iOS artifact comment
on:
workflow_run:
workflows: [ci]
types: [completed]
permissions:
contents: read
issues: write
pull-requests: write
jobs:
comment:
if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
steps:
- id: artifact
env:
GH_TOKEN: ${{ github.token }}
RUN_ID: ${{ github.event.workflow_run.id }}
HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
HEAD_REPOSITORY: ${{ github.event.workflow_run.head_repository.full_name }}
run: |
artifact_id="$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/$RUN_ID/artifacts" --jq '.artifacts[] | select(.name == "gen1recomp++-ios-ipa") | .id')"
[ -n "$artifact_id" ] || exit 0
head_owner="${HEAD_REPOSITORY%%/*}"
pr_number="$(gh api "repos/$GITHUB_REPOSITORY/pulls?state=open&head=$head_owner:$HEAD_BRANCH" --jq '.[0].number // empty')"
[ -n "$pr_number" ] || exit 0
echo "artifact_url=https://github.com/$GITHUB_REPOSITORY/actions/runs/$RUN_ID/artifacts/$artifact_id" >> "$GITHUB_OUTPUT"
echo "pr_number=$pr_number" >> "$GITHUB_OUTPUT"
# Upsert via comment-tag only — do not delete-all bot comments (clobbers Switch).
- name: Get build info
id: build-info
env:
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
run: |
commit_hash="$(printf '%s' "$HEAD_SHA" | cut -c1-7)"
build_time="$(date "+%Y-%m-%d %H:%M:%S")"
echo "hash=$commit_hash" >> "$GITHUB_OUTPUT"
echo "time=$build_time" >> "$GITHUB_OUTPUT"
- name: comment iOS artifact
if: steps.artifact.outputs.pr_number != ''
uses: thollander/actions-comment-pull-request@v3
with:
message: |
[gen1recomp++.ipa](${{ steps.artifact.outputs.artifact_url }})
**Commit**: [#${{ steps.build-info.outputs.hash }}](https://github.com/${{ github.event.workflow_run.head_repository.full_name }}/commit/${{ github.event.workflow_run.head_sha }})
**Build Time**: `${{ steps.build-info.outputs.time }}`
<sub>This comment was automatically generated. [View workflow run](https://github.com/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }})</sub>
pr-number: ${{ steps.artifact.outputs.pr_number }}
comment-tag: ios-build-result
github-token: ${{ github.token }}
@@ -0,0 +1,68 @@
name: platform artifact comment
on:
workflow_run:
workflows: [ci]
types: [completed]
permissions:
contents: read
issues: write
pull-requests: write
jobs:
comment:
if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
steps:
- id: artifacts
env:
GH_TOKEN: ${{ github.token }}
RUN_ID: ${{ github.event.workflow_run.id }}
HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
HEAD_REPOSITORY: ${{ github.event.workflow_run.head_repository.full_name }}
run: |
set -euo pipefail
artifacts="$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/$RUN_ID/artifacts")"
head_owner="${HEAD_REPOSITORY%%/*}"
pr_number="$(gh api "repos/$GITHUB_REPOSITORY/pulls?state=open&head=$head_owner:$HEAD_BRANCH" --jq '.[0].number // empty')"
[ -n "$pr_number" ] || exit 0
message="## CI build artifacts"
found=0
while IFS='|' read -r platform artifact_name artifact_file; do
artifact_id="$(printf '%s' "$artifacts" | jq -r --arg name "$artifact_name" '[.artifacts[] | select(.name == $name) | .id][0] // empty')"
if [ -n "$artifact_id" ] && [ "$artifact_id" != null ]; then
artifact_url="https://github.com/$GITHUB_REPOSITORY/actions/runs/$RUN_ID/artifacts/$artifact_id"
message+=$'\n'"- **$platform**: [$artifact_file]($artifact_url)"
found=1
fi
done <<'ARTIFACTS'
macOS|gen1recomp++-macos|gen1recomp++-macos.zip
iOS|gen1recomp++-ios-ipa|gen1recomp++.ipa
Nintendo Switch|gen1recomp-switch-nro|gen1recomp-switch.nro
Xbox UWP|gen1recomp-xbox-uwp|gen1recomp-xbox-uwp.zip
Linux arm64|gen1recomp-linux-arm64|gen1recomp-linux-arm64.AppImage
ARTIFACTS
[ "$found" -eq 1 ] || exit 0
commit_hash="$(printf '%s' "${{ github.event.workflow_run.head_sha }}" | cut -c1-7)"
build_time="$(date -u "+%Y-%m-%d %H:%M:%S UTC")"
message+=$'\n\n'"**Commit**: [#$commit_hash](https://github.com/$HEAD_REPOSITORY/commit/${{ github.event.workflow_run.head_sha }})"
message+=$'\n'"**Build Time**: \`$build_time\`"
message+=$'\n\n'"<sub>This comment was automatically generated. [View workflow run](https://github.com/$GITHUB_REPOSITORY/actions/runs/$RUN_ID)</sub>"
echo "pr_number=$pr_number" >> "$GITHUB_OUTPUT"
{
echo 'message<<BUILD_MESSAGE'
printf '%s\n' "$message"
echo 'BUILD_MESSAGE'
} >> "$GITHUB_OUTPUT"
- name: comment platform artifacts
if: steps.artifacts.outputs.pr_number != ''
uses: thollander/actions-comment-pull-request@v3
with:
message: ${{ steps.artifacts.outputs.message }}
pr-number: ${{ steps.artifacts.outputs.pr_number }}
comment-tag: platform-build-result
github-token: ${{ github.token }}
+10 -3
View File
@@ -344,9 +344,16 @@ jobs:
echo "Identities available to codesign:" echo "Identities available to codesign:"
security find-identity -v -p codesigning "$KEYCHAIN_PATH" security find-identity -v -p codesigning "$KEYCHAIN_PATH"
- name: Build LÖVE 12 macOS runtime
run: |
set -euo pipefail
scripts/build_love_macos.sh --fetch
- name: Build macOS + Windows + Linux - name: Build macOS + Windows + Linux
env: env:
GEN1TLS_DLL: ${{ github.workspace }}/dist/native/win-x64/gen1tls.dll GEN1TLS_DLL: ${{ github.workspace }}/dist/native/win-x64/gen1tls.dll
LOVE_APP: ${{ github.workspace }}/.bazinga/love12/love.app
MAC_STAGE_DIR: ${{ runner.temp }}/gen1recomp-mac-stage
run: | run: |
set -euo pipefail set -euo pipefail
# Sign in-build (identity auto-detected from the temp keychain); # Sign in-build (identity auto-detected from the temp keychain);
@@ -428,8 +435,8 @@ jobs:
set -a; . "$ci_dir/notary.env"; set +a set -a; . "$ci_dir/notary.env"; set +a
echo "::add-mask::$APPLE_APP_PASSWORD" echo "::add-mask::$APPLE_APP_PASSWORD"
app=".bazinga/work/gen1recomp.app" app="$RUNNER_TEMP/gen1recomp-mac-stage/gen1recomp++.app"
zip="dist/mac/gen1recomp-macos.zip" zip="dist/mac/gen1recomp++-macos.zip"
[ -d "$app" ] || { echo "::error::signed app not found at $app"; exit 1; } [ -d "$app" ] || { echo "::error::signed app not found at $app"; exit 1; }
if [ -z "${APPLE_ID:-}" ] || [ -z "${APPLE_APP_PASSWORD:-}" ] || [ -z "${APPLE_TEAM_ID:-}" ]; then if [ -z "${APPLE_ID:-}" ] || [ -z "${APPLE_APP_PASSWORD:-}" ] || [ -z "${APPLE_TEAM_ID:-}" ]; then
echo "::error::notary.env is missing APPLE_ID / APPLE_APP_PASSWORD / APPLE_TEAM_ID." echo "::error::notary.env is missing APPLE_ID / APPLE_APP_PASSWORD / APPLE_TEAM_ID."
@@ -474,7 +481,7 @@ jobs:
outdir="dist/release" outdir="dist/release"
rm -rf "$outdir" rm -rf "$outdir"
mkdir -p "$outdir" mkdir -p "$outdir"
cp "dist/mac/gen1recomp-macos.zip" "$outdir/gen1recomp-${v}-macos.zip" cp "dist/mac/gen1recomp++-macos.zip" "$outdir/gen1recomp++-${v}-macos.zip"
cp "dist/win/gen1recomp-win64.zip" "$outdir/gen1recomp-${v}-windows.zip" cp "dist/win/gen1recomp-win64.zip" "$outdir/gen1recomp-${v}-windows.zip"
cp "dist/linux/gen1recomp-linux.zip" "$outdir/gen1recomp-${v}-linux.zip" cp "dist/linux/gen1recomp-linux.zip" "$outdir/gen1recomp-${v}-linux.zip"
@@ -1,55 +0,0 @@
name: Switch artifact comment
on:
workflow_run:
workflows: [ci]
types: [completed]
permissions:
contents: read
issues: write
pull-requests: write
jobs:
comment:
if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
steps:
- id: artifact
env:
GH_TOKEN: ${{ github.token }}
RUN_ID: ${{ github.event.workflow_run.id }}
HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
HEAD_REPOSITORY: ${{ github.event.workflow_run.head_repository.full_name }}
run: |
artifact_id="$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/$RUN_ID/artifacts" --jq '.artifacts[] | select(.name == "gen1recomp-switch-nro") | .id')"
[ -n "$artifact_id" ] || exit 0
head_owner="${HEAD_REPOSITORY%%/*}"
pr_number="$(gh api "repos/$GITHUB_REPOSITORY/pulls?state=open&head=$head_owner:$HEAD_BRANCH" --jq '.[0].number // empty')"
[ -n "$pr_number" ] || exit 0
echo "artifact_url=https://github.com/$GITHUB_REPOSITORY/actions/runs/$RUN_ID/artifacts/$artifact_id" >> "$GITHUB_OUTPUT"
echo "pr_number=$pr_number" >> "$GITHUB_OUTPUT"
# Upsert via comment-tag only — do not delete-all bot comments (clobbers iOS).
- name: Get build info
id: build-info
env:
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
run: |
commit_hash="$(printf '%s' "$HEAD_SHA" | cut -c1-7)"
build_time="$(date "+%Y-%m-%d %H:%M:%S")"
echo "hash=$commit_hash" >> "$GITHUB_OUTPUT"
echo "time=$build_time" >> "$GITHUB_OUTPUT"
- name: comment Switch artifact
if: steps.artifact.outputs.pr_number != ''
uses: thollander/actions-comment-pull-request@v3
with:
message: |
[gen1recomp-switch.nro](${{ steps.artifact.outputs.artifact_url }})
**Commit**: [#${{ steps.build-info.outputs.hash }}](https://github.com/${{ github.event.workflow_run.head_repository.full_name }}/commit/${{ github.event.workflow_run.head_sha }})
**Build Time**: `${{ steps.build-info.outputs.time }}`
<sub>This comment was automatically generated. [View workflow run](https://github.com/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }})</sub>
pr-number: ${{ steps.artifact.outputs.pr_number }}
comment-tag: switch-build-result
github-token: ${{ github.token }}
+13 -18
View File
@@ -27,9 +27,15 @@ ask() { # ask "question" -> yes by default
printf '\n \033[1mPokémon Red - LÖVE2D port\033[0m\n\n' printf '\n \033[1mPokémon Red - LÖVE2D port\033[0m\n\n'
have_love() { have_love() {
command -v love >/dev/null 2>&1 && return 0 local app version
[ -x "/Applications/love.app/Contents/MacOS/love" ] && return 0 for app in ".bazinga/love12/love.app" "/Applications/love12.app" "$HOME/Applications/love12.app" \
[ -x "$HOME/Applications/love.app/Contents/MacOS/love" ] && return 0 "/Applications/love.app" "$HOME/Applications/love.app"; do
[ -x "$app/Contents/MacOS/love" ] || continue
version="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' "$app/Contents/Info.plist" 2>/dev/null || true)"
printf '%s' "$version" | grep -Eq '^12(\.|$)' || continue
otool -L "$app/Contents/Frameworks/love.framework/love" 2>/dev/null \
| grep -q '/Metal.framework/' && return 0
done
return 1 return 1
} }
@@ -57,22 +63,11 @@ if ! command -v python3 >/dev/null 2>&1; then
fi fi
fi fi
# ----------------------------------------------------------------- Homebrew if ! have_love && ! command -v xcodebuild >/dev/null 2>&1; then
if ! have_love && ! command -v brew >/dev/null 2>&1 \ warn "LÖVE 12 is not installed and Xcode is required to build it for macOS."
&& [ ! -x /opt/homebrew/bin/brew ] && [ ! -x /usr/local/bin/brew ]; then warn "Install Xcode from the App Store, launch it once, then run this again."
warn "LÖVE (the game engine) is not installed; the easiest installer is Homebrew" pause_exit 1
if ask "Install Homebrew now? (asks for your macOS password)"; then
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" \
|| { err "Homebrew install failed"; pause_exit 1; }
else
warn "OK, download LÖVE 11.x yourself from https://love2d.org,"
warn "drop love.app into /Applications, then run this again."
pause_exit 1
fi
fi fi
# make brew visible in THIS shell (fresh installs aren't on PATH yet)
[ -x /opt/homebrew/bin/brew ] && eval "$(/opt/homebrew/bin/brew shellenv)"
[ -x /usr/local/bin/brew ] && eval "$(/usr/local/bin/brew shellenv)"
# ------------------------------------------------------------------- build # ------------------------------------------------------------------- build
echo echo
+3 -2
View File
@@ -194,8 +194,9 @@ By default the game keeps your save, options, and the private ROM-derived
data cache in your OS's normal per-user app data folder. To keep everything data cache in your OS's normal per-user app data folder. To keep everything
next to the game instead (handy for a USB stick or portable drive you carry next to the game instead (handy for a USB stick or portable drive you carry
between computers), drop an empty file named `portable.txt` next to the app between computers), drop an empty file named `portable.txt` next to the app
(next to `gen1recomp.app`/`.exe`, or next to `main.lua`/`conf.lua` when (next to `gen1recomp++.app` on macOS or `gen1recomp.exe` on Windows, or next
running from source), then launch the game. Portable mode is desktop-only to `main.lua`/`conf.lua` when running from source), then launch the game.
Portable mode is desktop-only
(Windows, Linux, macOS); it has no effect on Android or iOS, where the app (Windows, Linux, macOS); it has no effect on Android or iOS, where the app
runs from a read-only package. runs from a read-only package.
-20
View File
@@ -193,26 +193,6 @@ get_controls
[ -f "${controlfolder}/mod_${CFW_NAME}.txt" ] && source "${controlfolder}/mod_${CFW_NAME}.txt" [ -f "${controlfolder}/mod_${CFW_NAME}.txt" ] && source "${controlfolder}/mod_${CFW_NAME}.txt"
GAMEDIR="$SHDIR/gen1recomp" GAMEDIR="$SHDIR/gen1recomp"
# Anbernic stock keeps the launcher and the game folder side by side, so the
# SHDIR-relative path above is correct there and is tried first.
#
# Other firmwares (muOS, and PortMaster's layout on several devices) keep
# launcher scripts and port data in SEPARATE trees -- scripts under roms/ports,
# data under ports -- so the sibling folder holds no game.
#
# Probe for the BINARY, not the directory: on a split layout this script has
# usually already created "$SHDIR/gen1recomp/conf" and log.txt on an earlier
# failed run (see mkdir/tee below), so an existence test matches a decoy of our
# own making. Stock is unaffected -- its sibling holds the real binary and wins
# on the first test.
if [ ! -f "$GAMEDIR/bin/love.aarch64" ]; then
for candidate in "/$directory/ports/gen1recomp" \
"/mnt/sdcard/ports/gen1recomp" \
"/mnt/mmc/ports/gen1recomp" \
"/roms/ports/gen1recomp"; do
if [ -f "$candidate/bin/love.aarch64" ]; then GAMEDIR="$candidate"; break; fi
done
fi
CONFDIR="$GAMEDIR/conf" CONFDIR="$GAMEDIR/conf"
mkdir -p "$CONFDIR" mkdir -p "$CONFDIR"
+1 -1
View File
@@ -61,7 +61,7 @@ function love.conf(t)
t.window.minwidth = 480 t.window.minwidth = 480
t.window.minheight = 360 t.window.minheight = 360
end end
t.version = love._os == "iOS" and "12.0" or "11.5" t.version = (love._os == "iOS" or love._os == "OS X") and "12.0" or "11.5"
t.window.vsync = 1 t.window.vsync = 1
t.modules.audio = not companion t.modules.audio = not companion
t.modules.joystick = not companion t.modules.joystick = not companion
+2 -2
View File
@@ -188,8 +188,8 @@ or the Switch-related workflow YAML), CI runs:
head is that repo (same-repo push/PR). Fork CI never runs fused. Fork PRs into the main repo also skip Switch fused (offline selftest still runs) so untrusted head code is not executed on the self-hosted Mac; iOS device build eligibility is unchanged. Fused also waits for a successful offline selftest before starting on the Mac runner. head is that repo (same-repo push/PR). Fork CI never runs fused. Fork PRs into the main repo also skip Switch fused (offline selftest still runs) so untrusted head code is not executed on the self-hosted Mac; iOS device build eligibility is unchanged. Fused also waits for a successful offline selftest before starting on the Mac runner.
3. On successful PR fused builds, a follow-up workflow posts a PR comment 3. On successful PR fused builds, a follow-up workflow posts a PR comment
linking the Actions artifact named `gen1recomp-switch-nro` linking the Actions artifact named `gen1recomp-switch-nro`
(comment tag `switch-build-result`; see (comment tag `platform-build-result`; see
`.github/workflows/switch-artifact-comment.yml`). `.github/workflows/platform-artifact-comment.yml`).
Unrelated PRs do not burn the self-hosted Mac on Switch packaging. Unrelated PRs do not burn the self-hosted Mac on Switch packaging.
+1
View File
@@ -0,0 +1 @@
a5522634f1e581a1ebab73bf3ab4bd7a853b7a3e
+1
View File
@@ -0,0 +1 @@
97d4465e8c81099f79696ea4b4bb8b1f9083bc1a
+1
View File
@@ -0,0 +1 @@
12.0
+19
View File
@@ -0,0 +1,19 @@
# macOS build
The desktop app uses the pinned LÖVE 12 runtime built from the LÖVE source
tree and the matching Apple dependency repository. The runtime enables Metal
and is fused into the branded `gen1recomp++.app` bundle.
Build the runtime and desktop app from the repository root:
```bash
scripts/build_love_macos.sh --fetch
LOVE_APP="$PWD/.bazinga/love12/love.app" scripts/build.sh mac --no-notarize --identity -
```
The source and dependency revisions are recorded in `LOVE_SOURCE_REF` and
`APPLE_DEPENDENCIES_REF`. Delete `.bazinga/love12/source` or use `--clean`
when changing those pins.
The packaged executable is `gen1recomp++` inside `gen1recomp++.app`, and the
bundle declares LÖVE 12.0 compatibility.
+75 -15
View File
@@ -8,7 +8,7 @@
# [--notary-profile NAME] [--no-notarize] # [--notary-profile NAME] [--no-notarize]
# [--release] # ios only: release config instead of debug # [--release] # ios only: release config instead of debug
# #
# Output: dist/mac/gen1recomp-macos.zip # Output: dist/mac/gen1recomp++-macos.zip
# dist/win/gen1recomp-win64.zip # dist/win/gen1recomp-win64.zip
# dist/linux/gen1recomp-linux.zip (fused x86_64 AppImage) # dist/linux/gen1recomp-linux.zip (fused x86_64 AppImage)
# dist/android/debug/*.apk (full gradle output stays under # dist/android/debug/*.apk (full gradle output stays under
@@ -27,13 +27,18 @@ ENTITLEMENTS="$ROOT/scripts/macos-entitlements.plist"
APP_NAME="gen1recomp" APP_NAME="gen1recomp"
BUNDLE_ID="com.theboisclub.pokemonred" BUNDLE_ID="com.theboisclub.pokemonred"
MAC_APP_NAME="gen1recomp++"
MAC_BUNDLE_ID="com.theboisclub.gen1recompplusplus"
LOVE_VERSION="11.5" LOVE_VERSION="11.5"
LOVE_MAC_VERSION="$(tr -d '[:space:]' < "$ROOT/mobile/macos/LOVE_VERSION" 2>/dev/null || echo 12.0)"
LOVE_MAC_APP="$HERE/love12/love.app"
VERSION="$(git -C "$ROOT" rev-parse --short HEAD 2>/dev/null || echo dev)" VERSION="$(git -C "$ROOT" rev-parse --short HEAD 2>/dev/null || echo dev)"
VERSION_EXPLICIT=false VERSION_EXPLICIT=false
IDENTITY="" IDENTITY=""
TARGET="all" TARGET="all"
NOTARY_PROFILE="notary-profile" NOTARY_PROFILE="notary-profile"
NOTARIZE=true NOTARIZE=true
FETCH_MAC_RUNTIME=false
IOS_RELEASE=false IOS_RELEASE=false
IOS_IPA=false IOS_IPA=false
@@ -41,6 +46,22 @@ say() { printf '\033[1;32m==>\033[0m %s\n' "$*"; }
warn() { printf '\033[1;33mwarn:\033[0m %s\n' "$*" >&2; } warn() { printf '\033[1;33mwarn:\033[0m %s\n' "$*" >&2; }
fail() { printf '\033[1;31merror:\033[0m %s\n' "$*" >&2; exit 1; } fail() { printf '\033[1;31merror:\033[0m %s\n' "$*" >&2; exit 1; }
strip_bundle_metadata() {
local bundle="$1"
xattr -rc "$bundle"
xattr -rd com.apple.FinderInfo "$bundle" 2>/dev/null || true
xattr -rd 'com.apple.fileprovider.fpfs#P' "$bundle" 2>/dev/null || true
}
valid_love12_app() {
local app="$1" version
[ -x "$app/Contents/MacOS/love" ] || return 1
version="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' "$app/Contents/Info.plist" 2>/dev/null || true)"
printf '%s' "$version" | grep -Eq '^12(\.|$)' || return 1
otool -L "$app/Contents/Frameworks/love.framework/love" 2>/dev/null \
| grep -q '/Metal.framework/'
}
while [ $# -gt 0 ]; do while [ $# -gt 0 ]; do
case "$1" in case "$1" in
mac|win|linux|android|ios|all) TARGET="$1" ;; mac|win|linux|android|ios|all) TARGET="$1" ;;
@@ -48,6 +69,7 @@ while [ $# -gt 0 ]; do
--identity) IDENTITY="$2"; shift ;; --identity) IDENTITY="$2"; shift ;;
--notary-profile) NOTARY_PROFILE="$2"; shift ;; --notary-profile) NOTARY_PROFILE="$2"; shift ;;
--no-notarize) NOTARIZE=false ;; --no-notarize) NOTARIZE=false ;;
--fetch) FETCH_MAC_RUNTIME=true ;;
--release) IOS_RELEASE=true ;; --release) IOS_RELEASE=true ;;
--ipa) IOS_IPA=true ;; --ipa) IOS_IPA=true ;;
*) fail "unknown argument: $1" ;; *) fail "unknown argument: $1" ;;
@@ -150,24 +172,55 @@ make_ico() { # $1 = output .ico path
# --------------------------------------------------------------- macOS # --------------------------------------------------------------- macOS
build_mac() { build_mac() {
say "building macOS app" say "building macOS app"
local love_app="${LOVE_APP:-/Applications/love.app}" local love_app="${LOVE_APP:-}"
[ -d "$love_app" ] || fail "LÖVE.app not found at $love_app (install it or set LOVE_APP=/path/to/love.app)" if [ -z "$love_app" ]; then
for candidate in "$LOVE_MAC_APP" "/Applications/love12.app" "$HOME/Applications/love12.app" \
"/Applications/love.app" "$HOME/Applications/love.app"; do
if [ -d "$candidate" ] && valid_love12_app "$candidate"; then
love_app="$candidate"
break
fi
done
fi
if [ ! -d "$love_app" ] && [ -z "${LOVE_APP:-}" ] && $FETCH_MAC_RUNTIME; then
"$ROOT/scripts/build_love_macos.sh" --fetch
love_app="$LOVE_MAC_APP"
fi
[ -d "$love_app" ] || fail "LÖVE 12 Metal app not found; run scripts/build_love_macos.sh --fetch or set LOVE_APP=/path/to/love.app"
local love_version
love_version=$(/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" "$love_app/Contents/Info.plist" 2>/dev/null || true)
printf '%s' "$love_version" | grep -Eq '^12(\.|$)' \
|| fail "macOS build requires LÖVE $LOVE_MAC_VERSION at $love_app (set LOVE_APP to a LÖVE 12 app)"
[ -f "$love_app/Contents/Frameworks/love.framework/love" ] \
|| fail "macOS build requires love.framework at $love_app"
otool -L "$love_app/Contents/Frameworks/love.framework/love" \
| grep -q '/Metal.framework/' \
|| fail "macOS build requires a LÖVE runtime linked to Metal at $love_app"
local out_app="$WORK/$APP_NAME.app" local stage_dir="${MAC_STAGE_DIR:-${RUNNER_TEMP:-/tmp}/gen1recomp-mac-stage}"
local out_app="$stage_dir/$MAC_APP_NAME.app"
mkdir -p "$stage_dir"
rm -rf "$out_app" rm -rf "$out_app"
cp -R "$love_app" "$out_app" ditto --norsrc "$love_app" "$out_app"
local love_executable="$out_app/Contents/MacOS/love"
local app_executable="$out_app/Contents/MacOS/$MAC_APP_NAME"
[ -f "$love_executable" ] || fail "LÖVE app is missing Contents/MacOS/love"
mv "$love_executable" "$app_executable"
# drop any bundled placeholder .love and fuse ours in # drop any bundled placeholder .love and fuse ours in
find "$out_app/Contents/Resources" -maxdepth 1 -name '*.love' -delete find "$out_app/Contents/Resources" -maxdepth 1 -name '*.love' -delete
cp "$LOVE_FILE" "$out_app/Contents/Resources/game.love" cp "$LOVE_FILE" "$out_app/Contents/Resources/game.love"
local plist="$out_app/Contents/Info.plist" local plist="$out_app/Contents/Info.plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleName $APP_NAME" "$plist" 2>/dev/null \ /usr/libexec/PlistBuddy -c "Set :CFBundleName $MAC_APP_NAME" "$plist" 2>/dev/null \
|| /usr/libexec/PlistBuddy -c "Add :CFBundleName string $APP_NAME" "$plist" || /usr/libexec/PlistBuddy -c "Add :CFBundleName string $MAC_APP_NAME" "$plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleDisplayName $APP_NAME" "$plist" 2>/dev/null \ /usr/libexec/PlistBuddy -c "Set :CFBundleDisplayName $MAC_APP_NAME" "$plist" 2>/dev/null \
|| /usr/libexec/PlistBuddy -c "Add :CFBundleDisplayName string $APP_NAME" "$plist" || /usr/libexec/PlistBuddy -c "Add :CFBundleDisplayName string $MAC_APP_NAME" "$plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier $BUNDLE_ID" "$plist" 2>/dev/null \ /usr/libexec/PlistBuddy -c "Set :CFBundleExecutable $MAC_APP_NAME" "$plist" 2>/dev/null \
|| /usr/libexec/PlistBuddy -c "Add :CFBundleIdentifier string $BUNDLE_ID" "$plist" || /usr/libexec/PlistBuddy -c "Add :CFBundleExecutable string $MAC_APP_NAME" "$plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier $MAC_BUNDLE_ID" "$plist" 2>/dev/null \
|| /usr/libexec/PlistBuddy -c "Add :CFBundleIdentifier string $MAC_BUNDLE_ID" "$plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $VERSION" "$plist" 2>/dev/null \ /usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $VERSION" "$plist" 2>/dev/null \
|| /usr/libexec/PlistBuddy -c "Add :CFBundleShortVersionString string $VERSION" "$plist" || /usr/libexec/PlistBuddy -c "Add :CFBundleShortVersionString string $VERSION" "$plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $VERSION" "$plist" 2>/dev/null \ /usr/libexec/PlistBuddy -c "Set :CFBundleVersion $VERSION" "$plist" 2>/dev/null \
@@ -189,6 +242,11 @@ build_mac() {
/usr/libexec/PlistBuddy -c "Set :CFBundleIconFile OS X AppIcon" "$plist" 2>/dev/null \ /usr/libexec/PlistBuddy -c "Set :CFBundleIconFile OS X AppIcon" "$plist" 2>/dev/null \
|| /usr/libexec/PlistBuddy -c "Add :CFBundleIconFile string 'OS X AppIcon'" "$plist" || /usr/libexec/PlistBuddy -c "Add :CFBundleIconFile string 'OS X AppIcon'" "$plist"
strip_bundle_metadata "$out_app"
rm -rf "$WORK/$MAC_APP_NAME.app"
ln -s "$out_app" "$WORK/$MAC_APP_NAME.app"
local id="$IDENTITY" local id="$IDENTITY"
if [ -z "$id" ]; then if [ -z "$id" ]; then
id="$(security find-identity -v -p codesigning 2>/dev/null | grep 'Developer ID Application' | head -1 | sed -E 's/^[^"]*"(.*)"$/\1/' || true)" id="$(security find-identity -v -p codesigning 2>/dev/null | grep 'Developer ID Application' | head -1 | sed -E 's/^[^"]*"(.*)"$/\1/' || true)"
@@ -210,9 +268,9 @@ build_mac() {
warn "keychain profile '$NOTARY_PROFILE' not found/working, skipping notarization." warn "keychain profile '$NOTARY_PROFILE' not found/working, skipping notarization."
warn "set it up with: xcrun notarytool store-credentials \"$NOTARY_PROFILE\" --apple-id ... --team-id ... --password ..." warn "set it up with: xcrun notarytool store-credentials \"$NOTARY_PROFILE\" --apple-id ... --team-id ... --password ..."
else else
local notarize_zip="$WORK/$APP_NAME-notarize.zip" local notarize_zip="$stage_dir/$MAC_APP_NAME-notarize.zip"
rm -f "$notarize_zip" rm -f "$notarize_zip"
(cd "$WORK" && ditto -c -k --keepParent "$APP_NAME.app" "$notarize_zip") (cd "$stage_dir" && ditto -c -k --keepParent "$MAC_APP_NAME.app" "$notarize_zip")
say "submitting to Apple notary service (this can take a few minutes)" say "submitting to Apple notary service (this can take a few minutes)"
xcrun notarytool submit "$notarize_zip" --keychain-profile "$NOTARY_PROFILE" --wait xcrun notarytool submit "$notarize_zip" --keychain-profile "$NOTARY_PROFILE" --wait
say "stapling notarization ticket" say "stapling notarization ticket"
@@ -221,9 +279,11 @@ build_mac() {
fi fi
fi fi
local zip_out="$DIST/mac/$APP_NAME-macos.zip" strip_bundle_metadata "$out_app"
local zip_out="$DIST/mac/$MAC_APP_NAME-macos.zip"
rm -f "$zip_out" rm -f "$zip_out"
(cd "$WORK" && ditto -c -k --sequesterRsrc --keepParent "$APP_NAME.app" "$zip_out") (cd "$stage_dir" && ditto -c -k --norsrc --keepParent "$MAC_APP_NAME.app" "$zip_out")
say "macOS build: $zip_out" say "macOS build: $zip_out"
} }
+152
View File
@@ -0,0 +1,152 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
MACOS_DIR="$ROOT/mobile/macos"
CACHE="$ROOT/.bazinga/love12"
SOURCE_DIR="${LOVE_SOURCE_DIR:-$CACHE/source}"
RUNTIME_APP="${LOVE_APP_OUTPUT:-$CACHE/love.app}"
BUILD_DIR="$CACHE/build"
LOVE_VERSION="$(tr -d '[:space:]' < "$MACOS_DIR/LOVE_VERSION")"
LOVE_SOURCE_REF="$(tr -d '[:space:]' < "$MACOS_DIR/LOVE_SOURCE_REF")"
APPLE_DEPENDENCIES_REF="$(tr -d '[:space:]' < "$MACOS_DIR/APPLE_DEPENDENCIES_REF")"
LOVE_SOURCE_REPO="${LOVE_SOURCE_REPO:-https://github.com/love2d/love.git}"
APPLE_DEPENDENCIES_REPO="${APPLE_DEPENDENCIES_REPO:-https://github.com/love2d/love-apple-dependencies.git}"
FETCH=false
CLEAN=false
say() { printf '\033[1;32m==>\033[0m %s\n' "$*"; }
fail() { printf '\033[1;31merror:\033[0m %s\n' "$*" >&2; exit 1; }
strip_bundle_metadata() {
local bundle="$1"
xattr -rc "$bundle"
xattr -rd com.apple.FinderInfo "$bundle" 2>/dev/null || true
xattr -rd 'com.apple.fileprovider.fpfs#P' "$bundle" 2>/dev/null || true
}
while [ $# -gt 0 ]; do
case "$1" in
--fetch) FETCH=true ;;
--clean) CLEAN=true ;;
-h|--help)
printf '%s\n' 'usage: scripts/build_love_macos.sh [--fetch] [--clean]'
exit 0
;;
*) fail "unknown argument: $1" ;;
esac
shift
done
[ "$(uname -s)" = "Darwin" ] || fail "macOS LÖVE builds require Darwin"
command -v git >/dev/null 2>&1 || fail "git is required to fetch LÖVE sources"
command -v xcodebuild >/dev/null 2>&1 || fail "xcodebuild is required to build LÖVE for macOS"
command -v xattr >/dev/null 2>&1 || fail "xattr is required to normalize the runtime bundle"
source_ready() {
[ -d "$SOURCE_DIR/platform/xcode/love.xcodeproj" ] \
&& [ -d "$SOURCE_DIR/platform/xcode/macosx/Frameworks/Lua.framework" ] \
&& [ -d "$SOURCE_DIR/platform/xcode/shared/Frameworks/SDL3.xcframework" ]
}
source_is_pinned() {
[ "$(git -C "$SOURCE_DIR" rev-parse HEAD 2>/dev/null || true)" = "$LOVE_SOURCE_REF" ] \
&& [ -f "$SOURCE_DIR/.gen1recomp-apple-dependencies-ref" ] \
&& [ "$(tr -d '[:space:]' < "$SOURCE_DIR/.gen1recomp-apple-dependencies-ref")" = "$APPLE_DEPENDENCIES_REF" ]
}
fetch_repo() {
local repo_url="$1"
local ref="$2"
local destination="$3"
mkdir -p "$destination"
git -C "$destination" init -q
git -C "$destination" remote add origin "$repo_url"
git -C "$destination" fetch --depth 1 origin "$ref"
git -C "$destination" checkout --detach -q FETCH_HEAD
}
fetch_sources() {
local tmp
mkdir -p "$CACHE"
tmp="$(mktemp -d "$CACHE/fetch.XXXXXX")"
say "fetching LÖVE source $LOVE_SOURCE_REF"
fetch_repo "$LOVE_SOURCE_REPO" "$LOVE_SOURCE_REF" "$tmp/love"
say "fetching Apple dependencies $APPLE_DEPENDENCIES_REF"
fetch_repo "$APPLE_DEPENDENCIES_REPO" "$APPLE_DEPENDENCIES_REF" "$tmp/dependencies"
mkdir -p "$tmp/love/platform/xcode/macosx/Frameworks" "$tmp/love/platform/xcode/shared"
cp -R "$tmp/dependencies/macOS/Frameworks/." "$tmp/love/platform/xcode/macosx/Frameworks/"
cp -R "$tmp/dependencies/shared/." "$tmp/love/platform/xcode/shared/"
rm -rf "$SOURCE_DIR"
mkdir -p "$(dirname "$SOURCE_DIR")"
mv "$tmp/love" "$SOURCE_DIR"
printf '%s\n' "$APPLE_DEPENDENCIES_REF" > "$SOURCE_DIR/.gen1recomp-apple-dependencies-ref"
rm -rf "$tmp"
say "LÖVE source ready at $SOURCE_DIR"
}
if $CLEAN; then
[ -z "${LOVE_SOURCE_DIR:-}" ] \
|| fail "--clean cannot be used with LOVE_SOURCE_DIR"
rm -rf "$SOURCE_DIR" "$BUILD_DIR" "$RUNTIME_APP"
fi
if ! source_ready || ! source_is_pinned; then
if ! $FETCH; then
fail "pinned LÖVE 12 sources are missing at $SOURCE_DIR; run scripts/build_love_macos.sh --fetch"
fi
[ -z "${LOVE_SOURCE_DIR:-}" ] \
|| fail "LOVE_SOURCE_DIR is not the pinned LÖVE commit $LOVE_SOURCE_REF"
fetch_sources
fi
PROJECT="$SOURCE_DIR/platform/xcode/love.xcodeproj"
rm -rf "$BUILD_DIR" "$RUNTIME_APP"
mkdir -p "$BUILD_DIR"
say "building LÖVE 12 macOS runtime"
xcodebuild \
-quiet \
-project "$PROJECT" \
-target love-macosx \
-configuration Release \
-sdk macosx \
SYMROOT="$BUILD_DIR" \
OBJROOT="$BUILD_DIR/Intermediates" \
ARCHS="arm64 x86_64" \
ONLY_ACTIVE_ARCH=NO \
MACOSX_DEPLOYMENT_TARGET=12.0 \
CODE_SIGNING_ALLOWED=NO \
CODE_SIGNING_REQUIRED=NO \
CODE_SIGN_IDENTITY=-
BUILT_APP="$BUILD_DIR/Release/love.app"
[ -d "$BUILT_APP" ] || fail "xcodebuild produced no LÖVE app at $BUILT_APP"
ditto --norsrc "$BUILT_APP" "$RUNTIME_APP"
strip_bundle_metadata "$RUNTIME_APP"
version="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' "$RUNTIME_APP/Contents/Info.plist" 2>/dev/null || true)"
version_re="$(printf '%s' "$LOVE_VERSION" | sed 's/\./\\./g')"
printf '%s' "$version" | grep -Eq "^${version_re}(\.|$)" \
|| fail "built runtime reports LÖVE version '$version'"
[ -x "$RUNTIME_APP/Contents/MacOS/love" ] \
|| fail "built runtime is missing Contents/MacOS/love"
[ -f "$RUNTIME_APP/Contents/Frameworks/love.framework/love" ] \
|| fail "built runtime is missing love.framework"
otool -L "$RUNTIME_APP/Contents/Frameworks/love.framework/love" \
| grep -q '/Metal.framework/' \
|| fail "built LÖVE runtime is not linked to Metal"
archs="$(lipo -archs "$RUNTIME_APP/Contents/MacOS/love")"
printf '%s' "$archs" | grep -qw arm64 \
|| fail "built LÖVE app is missing arm64"
printf '%s' "$archs" | grep -qw x86_64 \
|| fail "built LÖVE app is missing x86_64"
framework_archs="$(lipo -archs "$RUNTIME_APP/Contents/Frameworks/love.framework/love")"
printf '%s' "$framework_archs" | grep -qw arm64 \
|| fail "built LÖVE framework is missing arm64"
printf '%s' "$framework_archs" | grep -qw x86_64 \
|| fail "built LÖVE framework is missing x86_64"
say "LÖVE 12 macOS runtime: $RUNTIME_APP"
+24 -2
View File
@@ -32,8 +32,30 @@ find_love() {
return 1 return 1
} }
LOVE_BIN="$(find_love)" \ find_love12() {
|| fail "LÖVE not found, run scripts/setup.sh (or install from https://love2d.org)" local app version
for app in "${LOVE_APP:-}" "$ROOT/.bazinga/love12/love.app" "/Applications/love12.app" "$HOME/Applications/love12.app" \
"/Applications/love.app" "$HOME/Applications/love.app"; do
[ -n "$app" ] || continue
if [ -x "$app/Contents/MacOS/love" ]; then
version="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' "$app/Contents/Info.plist" 2>/dev/null || true)"
if printf '%s' "$version" | grep -Eq '^12(\.|$)' \
&& otool -L "$app/Contents/Frameworks/love.framework/love" 2>/dev/null | grep -q '/Metal.framework/'; then
echo "$app/Contents/MacOS/love"
return
fi
fi
done
return 1
}
if [ "$(uname -s)" = "Darwin" ]; then
LOVE_BIN="$(find_love12)" \
|| fail "LÖVE 12 with Metal not found, run scripts/setup.sh or scripts/build_love_macos.sh --fetch"
else
LOVE_BIN="$(find_love)" \
|| fail "LÖVE not found, run scripts/setup.sh (or install from https://love2d.org)"
fi
# SDL2 on Wayland crashes during desktop drag-and-drop in certain compositors; # SDL2 on Wayland crashes during desktop drag-and-drop in certain compositors;
# default to X11/XWayland when available to ensure rock-solid drag-drop stability. # default to X11/XWayland when available to ensure rock-solid drag-drop stability.
+25 -4
View File
@@ -70,11 +70,32 @@ find_love() {
return 1 return 1
} }
if LOVE_BIN="$(find_love)"; then valid_love12_app() {
local app="$1" version
[ -x "$app/Contents/MacOS/love" ] || return 1
version="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' "$app/Contents/Info.plist" 2>/dev/null || true)"
printf '%s' "$version" | grep -Eq '^12(\.|$)' || return 1
otool -L "$app/Contents/Frameworks/love.framework/love" 2>/dev/null \
| grep -q '/Metal.framework/'
}
if [ "$(uname -s)" = "Darwin" ]; then
if valid_love12_app "$ROOT/.bazinga/love12/love.app"; then
say "LÖVE 12 found: $ROOT/.bazinga/love12/love.app"
elif valid_love12_app "/Applications/love12.app"; then
say "LÖVE 12 found: /Applications/love12.app"
elif valid_love12_app "$HOME/Applications/love12.app"; then
say "LÖVE 12 found: $HOME/Applications/love12.app"
elif valid_love12_app "/Applications/love.app"; then
say "LÖVE 12 found: /Applications/love.app"
elif valid_love12_app "$HOME/Applications/love.app"; then
say "LÖVE 12 found: $HOME/Applications/love.app"
else
say "building LÖVE 12 for macOS"
"$ROOT/scripts/build_love_macos.sh" --fetch
fi
elif LOVE_BIN="$(find_love)"; then
say "LÖVE found: $LOVE_BIN" say "LÖVE found: $LOVE_BIN"
elif [ "$(uname -s)" = "Darwin" ] && command -v brew >/dev/null 2>&1; then
say "installing LÖVE via Homebrew"
brew install --cask love
else else
fail "LÖVE 11.x is not installed; install it from https://love2d.org" fail "LÖVE 11.x is not installed; install it from https://love2d.org"
fi fi
+6 -19
View File
@@ -140,7 +140,7 @@ function SaveData.gameFolders()
local src = love.filesystem.getSource and love.filesystem.getSource() local src = love.filesystem.getSource and love.filesystem.getSource()
local sbd = love.filesystem.getSourceBaseDirectory local sbd = love.filesystem.getSourceBaseDirectory
and love.filesystem.getSourceBaseDirectory() and love.filesystem.getSourceBaseDirectory()
-- A packaged macOS build nests the game inside gen1recomp.app/Contents/ -- A packaged macOS build nests the game inside gen1recomp++.app/Contents/
-- Resources, so getSource()/getSourceBaseDirectory() point INSIDE the -- Resources, so getSource()/getSourceBaseDirectory() point INSIDE the
-- bundle -- not where the player drops portable.txt (next to the .app). -- bundle -- not where the player drops portable.txt (next to the .app).
-- Recover the folder containing the .app so a packaged app finds its -- Recover the folder containing the .app so a packaged app finds its
@@ -1279,26 +1279,13 @@ function SaveData.ensurePlaythroughId(save, injectedFs)
local isFresh = save == freshPlaythrough local isFresh = save == freshPlaythrough
if isFresh then freshPlaythrough = nil end if isFresh then freshPlaythrough = nil end
local byVersion = opts.playthroughIds and opts.playthroughIds[version] local byVersion = opts.playthroughIds and opts.playthroughIds[version]
local existing = byVersion and byVersion[scope] id = not isFresh and byVersion and byVersion[scope] or nil
id = not isFresh and existing or nil
if type(id) ~= "string" or id == "" then if type(id) ~= "string" or id == "" then
id = SaveData.newPlaythroughId() id = SaveData.newPlaythroughId()
-- A fresh skeleton still gets its own id (two unsaved New Games sharing a opts.playthroughIds = opts.playthroughIds or {}
-- slot must stay distinct), and it is still persisted when the slot has no opts.playthroughIds[version] = opts.playthroughIds[version] or {}
-- binding yet -- that is the contract a tool relies on to resolve opts.playthroughIds[version][scope] = id
-- `selected` at the title after a restart, before any normal SAVE. SaveData.saveOptions(opts, injectedFs)
--
-- What it must NOT do is OVERWRITE a binding that already exists. newGame()
-- marks a skeleton on the boot frame, before any save is loaded, and mods
-- initialise inside that window -- so a mod touching storage at init
-- replaced the real save's id with a throwaway, stranding that save's mod
-- storage and repeating on every launch.
if not (isFresh and type(existing) == "string" and existing ~= "") then
opts.playthroughIds = opts.playthroughIds or {}
opts.playthroughIds[version] = opts.playthroughIds[version] or {}
opts.playthroughIds[version][scope] = id
SaveData.saveOptions(opts, injectedFs)
end
end end
save.meta.playthroughId = id save.meta.playthroughId = id
return id return id
+1 -1
View File
@@ -15,7 +15,7 @@ love = love or require("tests.love_stub")
-- scripts/build.sh mac layout: the game is an archive inside the .app, the -- scripts/build.sh mac layout: the game is an archive inside the .app, the
-- player's portable folder is the one holding the .app. -- player's portable folder is the one holding the .app.
local PORTABLE = "/Users/p/Games" local PORTABLE = "/Users/p/Games"
local APP = PORTABLE .. "/gen1recomp.app" local APP = PORTABLE .. "/gen1recomp++.app"
local SOURCE = APP .. "/Contents/Resources/game.love" local SOURCE = APP .. "/Contents/Resources/game.love"
local BASE = APP .. "/Contents/MacOS" local BASE = APP .. "/Contents/MacOS"
+23 -26
View File
@@ -26,12 +26,11 @@ end
-- Also gates the NX runtime modules and the NX engine suites so an NX -- Also gates the NX runtime modules and the NX engine suites so an NX
-- runtime regression cannot slip past switch-selftest / switch-build. -- runtime regression cannot slip past switch-selftest / switch-build.
local SWITCH_PATH_REGEX = local SWITCH_PATH_REGEX =
[[^(scripts/build_switch\.sh$|scripts/switch/|docs/switch-.*\.md$|tests/switch_ci_workflows_test\.lua$|tests/switch_transfer_docs_test\.lua$|\.github/workflows/(ci|release|switch-artifact-comment)\.yml$|src/core/(NxAssetOverlay|Platform|GameVersion)\.lua$|src/import/CacheFs\.lua$|tests/engine/(assets_version_fallback|nx_generated_guard|nx_yellow_boot|switch_diagnostics|cache_fs_gold_nx_load)_test\.lua$|tests/engine/platform_nx)]] [[^(scripts/build_switch\.sh$|scripts/switch/|docs/switch-.*\.md$|tests/switch_ci_workflows_test\.lua$|tests/switch_transfer_docs_test\.lua$|\.github/workflows/(ci|release|platform-artifact-comment)\.yml$|src/core/(NxAssetOverlay|Platform|GameVersion)\.lua$|src/import/CacheFs\.lua$|tests/engine/(assets_version_fallback|nx_generated_guard|nx_yellow_boot|switch_diagnostics|cache_fs_gold_nx_load)_test\.lua$|tests/engine/platform_nx)]]
local ci = read(".github/workflows/ci.yml") local ci = read(".github/workflows/ci.yml")
local release = read(".github/workflows/release.yml") local release = read(".github/workflows/release.yml")
local comment_wf = read(".github/workflows/switch-artifact-comment.yml") local comment_wf = read(".github/workflows/platform-artifact-comment.yml")
local ios_comment_wf = read(".github/workflows/ios-artifact-comment.yml")
-- --- SWCI-01: path detector --- -- --- SWCI-01: path detector ---
mustContain(ci, "switch-changes:", "ci.yml") mustContain(ci, "switch-changes:", "ci.yml")
@@ -129,28 +128,26 @@ do
end end
-- --- SWCI-06 / SWCI-07 / SWFIX-01: PR artifact comment (no delete-all clobber) --- -- --- SWCI-06 / SWCI-07 / SWFIX-01: PR artifact comment (no delete-all clobber) ---
mustContain(comment_wf, "workflows: [ci]", "switch-artifact-comment") mustContain(comment_wf, "workflows: [ci]", "platform-artifact-comment")
mustContain(comment_wf, "gen1recomp-switch-nro", "switch-artifact-comment") for _, artifact in ipairs({
mustContain(comment_wf, "comment-tag: switch-build-result", "switch-artifact-comment") "gen1recomp++-macos",
mustContain(comment_wf, "pull_request", "switch-artifact-comment") "gen1recomp++-ios-ipa",
mustContain(comment_wf, "conclusion == 'success'", "switch-artifact-comment") "gen1recomp-switch-nro",
mustContain(comment_wf, 'exit 0', "switch-artifact-comment no-op") "gen1recomp-xbox-uwp",
mustContain(comment_wf, "**Commit**:", "switch-artifact-comment") "gen1recomp-linux-arm64",
mustContain(comment_wf, "**Build Time**:", "switch-artifact-comment") }) do
mustContain(comment_wf, "View workflow run", "switch-artifact-comment") mustContain(comment_wf, artifact, "platform-artifact-comment")
mustContain(comment_wf, "thollander/actions-comment-pull-request@v3", "switch-artifact-comment") end
mustNotContain(comment_wf, "delete-comment", "switch-artifact-comment") mustContain(comment_wf, "comment-tag: platform-build-result", "platform-artifact-comment")
mustNotContain(comment_wf, "izhangzhihao/delete-comment", "switch-artifact-comment") mustContain(comment_wf, "pull_request", "platform-artifact-comment")
mustContain(comment_wf, "conclusion == 'success'", "platform-artifact-comment")
mustContain(ios_comment_wf, "comment-tag: ios-build-result", "ios-artifact-comment") mustContain(comment_wf, 'exit 0', "platform-artifact-comment no-op")
mustContain(ios_comment_wf, "thollander/actions-comment-pull-request@v3", "ios-artifact-comment") mustContain(comment_wf, "**Commit**:", "platform-artifact-comment")
mustNotContain(ios_comment_wf, "delete-comment", "ios-artifact-comment") mustContain(comment_wf, "**Build Time**:", "platform-artifact-comment")
mustNotContain(ios_comment_wf, "izhangzhihao/delete-comment", "ios-artifact-comment") mustContain(comment_wf, "View workflow run", "platform-artifact-comment")
-- Distinct tags so both commenters can coexist on the same PR mustContain(comment_wf, "thollander/actions-comment-pull-request@v3", "platform-artifact-comment")
check(comment_wf:find("comment-tag: switch-build-result", 1, true) mustNotContain(comment_wf, "delete-comment", "platform-artifact-comment")
and ios_comment_wf:find("comment-tag: ios-build-result", 1, true) mustNotContain(comment_wf, "izhangzhihao/delete-comment", "platform-artifact-comment")
and comment_wf:find("comment-tag: ios-build-result", 1, true) == nil,
"iOS and Switch comment-tags must be distinct and present")
-- --- SWCI-08 / SWCI-09: docs CI vs release --- -- --- SWCI-08 / SWCI-09: docs CI vs release ---
local build_doc = read("docs/switch-build.md") local build_doc = read("docs/switch-build.md")
@@ -161,7 +158,7 @@ mustContain(build_doc, "ubuntu-latest", "switch-build.md")
mustContain(build_doc, "selftest_build_switch.sh", "switch-build.md") mustContain(build_doc, "selftest_build_switch.sh", "switch-build.md")
mustContain(build_doc, "main repo", "switch-build.md") mustContain(build_doc, "main repo", "switch-build.md")
mustContain(build_doc, "gen1recomp-switch-nro", "switch-build.md") mustContain(build_doc, "gen1recomp-switch-nro", "switch-build.md")
mustContain(build_doc, "switch-build-result", "switch-build.md") mustContain(build_doc, "platform-build-result", "switch-build.md")
mustContain(build_doc, "hard gate", "switch-build.md") mustContain(build_doc, "hard gate", "switch-build.md")
mustContain(build_doc, "continue-on-error", "switch-build.md") mustContain(build_doc, "continue-on-error", "switch-build.md")
mustContain(build_doc, "nacptool", "switch-build.md") mustContain(build_doc, "nacptool", "switch-build.md")
+4 -15
View File
@@ -988,25 +988,14 @@ def cmd_add_release_workflow(args, repo):
# ---------------------------------------------------------------- lint # ---------------------------------------------------------------- lint
def ahash(image): def ahash(image):
"""Ink-mask hash over the 8x8 downscale: background vs ink, split at """Ink-mask hash over the 8x8 downscale: background (the lightest GB
THIS image's own average brightness rather than a fixed shade. Swapping shade) vs ink. Swapping the three ink shades -- the classic recolor --
the three ink shades -- the classic recolor -- leaves the mask intact, leaves the mask intact, which is exactly what MK302 wants to catch."""
which is exactly what MK302 wants to catch.
A fixed cutoff (e.g. "<=200 is ink") only makes sense for sprites with a
light background to split against; a mostly-opaque 16x16 icon has almost
no pixel above that cutoff, so every such icon collapsed onto the same
"all ink" hash and was flagged as a near-duplicate of anything else that
also collapsed -- which was most of them, boulder.png included.
Thresholding against the image's own mean keeps the split meaningful
(and roughly balanced) no matter how light or dark the source is."""
from PIL import Image from PIL import Image
small = image.convert("L").resize((8, 8), Image.LANCZOS) small = image.convert("L").resize((8, 8), Image.LANCZOS)
raw = (small.get_flattened_data() if hasattr(small, "get_flattened_data") raw = (small.get_flattened_data() if hasattr(small, "get_flattened_data")
else small.getdata()) else small.getdata())
raw = list(raw) return sum((1 << i) for i, p in enumerate(raw) if p <= 200)
average = sum(raw) / len(raw)
return sum((1 << i) for i, p in enumerate(raw) if p <= average)
def hamming(a, b): def hamming(a, b):