Compare commits
274 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f4a1dd9e16 | |||
| 6f75a64c46 | |||
| cd37aa175c | |||
| cb6cfb5556 | |||
| 439e97aee6 | |||
| 5528525888 | |||
| a49ba66c36 | |||
| 9de4db8531 | |||
| dbc48f377a | |||
| 1d77d42fe2 | |||
| 683fee8028 | |||
| a83d18fc51 | |||
| b9e8b00af0 | |||
| e2820e02c5 | |||
| f92364a002 | |||
| 9696c8d3e2 | |||
| 18b2bcd0a7 | |||
| f56970350a | |||
| 863f371e68 | |||
| 24c5114745 | |||
| 7def560726 | |||
| 6cbd0de77d | |||
| 9a8101df6a | |||
| 78744a6853 | |||
| efce1bb116 | |||
| 104c95a942 | |||
| f6392e8932 | |||
| 88e34ef65c | |||
| 8f0117a145 | |||
| 1f878c3098 | |||
| f0ed2efe07 | |||
| bbf48c7e9e | |||
| 99d54b82b6 | |||
| 49776ca1eb | |||
| 272305f3a4 | |||
| 89f023c7dd | |||
| afc5a52978 | |||
| 12a04f4188 | |||
| 0fe7a96781 | |||
| a06a49bfed | |||
| 86b90fd4b8 | |||
| d18824cfae | |||
| 12b4c93279 | |||
| af47e19e1a | |||
| 4e666303d0 | |||
| 0fa8206321 | |||
| 78ac154998 | |||
| bbcaac7b71 | |||
| 719ba49a85 | |||
| 54ae20c69b | |||
| f864837cbe | |||
| 21191b8e80 | |||
| 6426e913b6 | |||
| 3d3e42c6a3 | |||
| 6fe106f55c | |||
| ba8ac3d143 | |||
| 1963a5453e | |||
| 59a383736c | |||
| 1820f411ae | |||
| 8327942a9c | |||
| aec7f6001a | |||
| d377518632 | |||
| ad9b03fe56 | |||
| 3b490bc42f | |||
| dacd9abe73 | |||
| f74e21782b | |||
| 8fbe819493 | |||
| 626080d228 | |||
| f56e82de81 | |||
| 7d3950658a | |||
| 3c1246ee06 | |||
| d69965e3b1 | |||
| 30edac03a6 | |||
| dd4aface93 | |||
| b1350fdecd | |||
| f2d9b74490 | |||
| 84d09d0894 | |||
| e61f231316 | |||
| 33998aab5d | |||
| 9ce7379946 | |||
| b692474e24 | |||
| 6cfc85ca83 | |||
| 7b060c19f4 | |||
| 604b9338f9 | |||
| 3a6557ffe2 | |||
| fe139ca7f4 | |||
| 5c617d3e32 | |||
| 8d11c04d43 | |||
| 92f73d8bed | |||
| 8da51aa7ba | |||
| 78f87111de | |||
| e3fcdd0776 | |||
| 6ea59f1a79 | |||
| 51fb4db82e | |||
| 7f78de8718 | |||
| 76aab74bf1 | |||
| a2bf08c6ff | |||
| b831076839 | |||
| 45575a1fd8 | |||
| 56d92246cb | |||
| 4a00f2335c | |||
| e9cb830d71 | |||
| c2666d2b43 | |||
| 9ceec3070d | |||
| f89977a8e7 | |||
| 8d512b95cb | |||
| 0914387c16 | |||
| 105672c132 | |||
| 0dd6ecc219 | |||
| 9ef5c8dead | |||
| 12ff4dba2e | |||
| ff9992dff8 | |||
| 98c08e4b22 | |||
| 0bd3b26acc | |||
| 59b55a8f37 | |||
| 67e6b1fb04 | |||
| 17243a7d8b | |||
| 16b6b98ede | |||
| e4ce1063a1 | |||
| f099593136 | |||
| f944507520 | |||
| 6824949247 | |||
| 4f54255518 | |||
| cfe482e1fc | |||
| 0e5ea26e36 | |||
| d5886e69aa | |||
| 32fa215243 | |||
| dd5d8afd82 | |||
| f19f4e9341 | |||
| 3aec2ca89b | |||
| 0f7261dd92 | |||
| 0886573d34 | |||
| 5c041ea857 | |||
| 30cd9afc92 | |||
| cceb74a025 | |||
| 42540076c1 | |||
| a528868656 | |||
| c82af28fd4 | |||
| d72e5db0a5 | |||
| 9907cfb0a0 | |||
| a3cd5a132e | |||
| 0971b47bec | |||
| f2ea250364 | |||
| d86ac70d9e | |||
| be8023a994 | |||
| a0ab949399 | |||
| 30ed68ae22 | |||
| f2aa376304 | |||
| 1b48862923 | |||
| 25417ffac8 | |||
| 4dfbc5a828 | |||
| f0f5bc7551 | |||
| f002db2929 | |||
| c5791574f5 | |||
| 8c1fbfb429 | |||
| 8e5501a23b | |||
| 50fd0e9c7d | |||
| 17843c2f7a | |||
| 54c8d2706b | |||
| 44bfb1b93f | |||
| 7a78992881 | |||
| 6ed41c3b87 | |||
| 7414c176d9 | |||
| d7581dbece | |||
| eeb8c89d3d | |||
| 2591fde764 | |||
| a8f3a3155c | |||
| 2a927651c5 | |||
| 62ef647811 | |||
| fe4491a87b | |||
| 105bf65e02 | |||
| 8120f113af | |||
| 7e0c64cb78 | |||
| 4afb54c54f | |||
| 74f6b68034 | |||
| 5d1e7ff3c1 | |||
| 3c628140f7 | |||
| 01f68d4038 | |||
| 8bf99c3318 | |||
| f0829ed54f | |||
| 21276e20d1 | |||
| 9fb9648e4c | |||
| af7689a1bc | |||
| 59dba325b9 | |||
| d13064ee64 | |||
| 669c9f4d8b | |||
| 228306f883 | |||
| 0a2ef854e6 | |||
| 61c471343a | |||
| 175bff4b29 | |||
| 95eb1c3a35 | |||
| cf50976d65 | |||
| 3fcee7f951 | |||
| 17a4c83970 | |||
| 9712fb1e9b | |||
| cbf622f4aa | |||
| 7c1cdea2eb | |||
| 2bfa491b6a | |||
| 41fbfc9ba6 | |||
| 0bb0518093 | |||
| 41f549b271 | |||
| ba8aa7b263 | |||
| 243cb6a477 | |||
| c987fefede | |||
| 2a038f7112 | |||
| 548e75a15e | |||
| 339c85b590 | |||
| 0f0420314b | |||
| a08b5e65f7 | |||
| 99b89c4e5e | |||
| 20586e0c48 | |||
| b54d0c605a | |||
| 2f75af902c | |||
| 9147a6413e | |||
| 9a467d4bf1 | |||
| e40f9337b6 | |||
| 1f0712d2d5 | |||
| 345bc9519a | |||
| 0b30bb2eff | |||
| f0e88aa581 | |||
| 317893bd61 | |||
| bd181271d9 | |||
| 4df09010a8 | |||
| 1a5b2b96dc | |||
| 43d89813fc | |||
| 5bc76ce6fe | |||
| 8654444d1a | |||
| 772c39edb8 | |||
| 74e7474349 | |||
| 3aefe41ea1 | |||
| 2223c31e93 | |||
| 568f0fa9eb | |||
| e91cf0f0c2 | |||
| 8a1f583d88 | |||
| 7518319c80 | |||
| 304df44742 | |||
| e92bab6f4f | |||
| 43755c5457 | |||
| d4455c508f | |||
| 7d01242cd1 | |||
| 9d77189cec | |||
| c8adbdcc64 | |||
| ac6dfe7134 | |||
| b1ad7c7254 | |||
| 6fb5602cb0 | |||
| b942d37944 | |||
| b7ee191b6c | |||
| 4ea24a0a6f | |||
| 6ad8c8d73d | |||
| 8982952fa4 | |||
| b135878824 | |||
| e9463d39aa | |||
| dcee7ca8e4 | |||
| 1653aa0a5d | |||
| 2699c9a2f9 | |||
| efd81d8e34 | |||
| 925b224eda | |||
| 7504753ea8 | |||
| da60f40dfc | |||
| 560ebc5997 | |||
| 361d4b81df | |||
| 83cf1fdb00 | |||
| 228a0baf81 | |||
| df7cea4387 | |||
| 13b55a797e | |||
| 1ec33b1374 | |||
| c06991e03d | |||
| 2fcd7abbfd | |||
| 5ca17cfc99 | |||
| 05f1e3852c | |||
| 3b22a45a23 | |||
| f172d3129f | |||
| a7a84b6fab | |||
| d4afa09e03 |
@@ -44,7 +44,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 '^(mobile/ios/|scripts/build_ios\.sh$|\.github/workflows/(ci|release)\.yml$)'; then
|
if git diff --name-only "$BASE_SHA" "$HEAD_SHA" | grep -Eq '^(mobile/ios/|scripts/build_ios\.sh$)'; then
|
||||||
echo "changed=true" >> "$GITHUB_OUTPUT"
|
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||||
else
|
else
|
||||||
echo "changed=false" >> "$GITHUB_OUTPUT"
|
echo "changed=false" >> "$GITHUB_OUTPUT"
|
||||||
@@ -102,6 +102,275 @@ jobs:
|
|||||||
if: ${{ always() && github.repository == 'bryanthaboi/gen1recomp' }}
|
if: ${{ always() && github.repository == 'bryanthaboi/gen1recomp' }}
|
||||||
run: security delete-keychain "$RUNNER_TEMP/gen1recomp-ci-signing.keychain-db" 2>/dev/null || true
|
run: security delete-keychain "$RUNNER_TEMP/gen1recomp-ci-signing.keychain-db" 2>/dev/null || true
|
||||||
|
|
||||||
|
switch-changes:
|
||||||
|
name: detect Switch 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 '^(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)_test\.lua$|tests/engine/platform_nx)'; then
|
||||||
|
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "changed=false" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
switch-selftest:
|
||||||
|
name: Switch offline selftest
|
||||||
|
needs: switch-changes
|
||||||
|
if: needs.switch-changes.outputs.changed == 'true'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
- name: install luajit
|
||||||
|
run: sudo apt-get update && sudo apt-get install -y luajit
|
||||||
|
- name: Switch offline selftest
|
||||||
|
run: bash scripts/switch/selftest_build_switch.sh
|
||||||
|
- name: verify_payload self-test
|
||||||
|
run: bash scripts/switch/verify_payload.sh --self-test
|
||||||
|
- name: Switch CI workflow content gate
|
||||||
|
run: luajit tests/switch_ci_workflows_test.lua
|
||||||
|
- name: Switch transfer docs content gate
|
||||||
|
run: luajit tests/switch_transfer_docs_test.lua
|
||||||
|
# NX runtime regressions gate this job via switch-changes; run the NX
|
||||||
|
# engine suites here too so a PR touching them gets feedback on the
|
||||||
|
# fork-safe ubuntu runner before the self-hosted Mac build.
|
||||||
|
- name: NX engine suites (headless)
|
||||||
|
run: |
|
||||||
|
luajit tests/engine/assets_version_fallback_test.lua
|
||||||
|
luajit tests/engine/nx_generated_guard_test.lua
|
||||||
|
luajit tests/engine/nx_yellow_boot_test.lua
|
||||||
|
|
||||||
|
switch-build:
|
||||||
|
name: Switch fused build
|
||||||
|
needs: [switch-changes, switch-selftest]
|
||||||
|
if: |
|
||||||
|
always()
|
||||||
|
&& needs.switch-changes.outputs.changed == 'true'
|
||||||
|
&& needs.switch-selftest.result == 'success'
|
||||||
|
&& github.repository == 'bryanthaboi/gen1recomp'
|
||||||
|
&& (github.event_name != 'pull_request'
|
||||||
|
|| github.event.pull_request.head.repo.full_name == github.repository)
|
||||||
|
runs-on: ["self-hosted", "macOS"]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
- name: Build Switch fused NRO
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
VER="$(printf '%s' "$GITHUB_SHA" | cut -c1-7)"
|
||||||
|
scripts/build_switch.sh --fetch --fused --version "$VER"
|
||||||
|
echo "SWITCH_VER=$VER" >> "$GITHUB_ENV"
|
||||||
|
- name: upload Switch NRO artifact
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: gen1recomp-switch-nro
|
||||||
|
path: |
|
||||||
|
dist/switch/gen1recomp-${{ env.SWITCH_VER }}-switch.nro
|
||||||
|
dist/switch/gen1recomp-${{ env.SWITCH_VER }}-switch.nro.sha256
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
|
xbox-uwp-changes:
|
||||||
|
name: detect Xbox UWP 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 '^(ports/uwp/|scripts/build_xbox_uwp\.sh$|scripts/xbox-uwp/|scripts/pack_love\.sh$|\.github/workflows/(ci|release)\.yml$|src/core/Platform\.lua$|src/import/(CacheFs|RomImporter)\.lua$|src/update/Check\.lua$|tests/engine/(platform_nx|uwp_native_picker)_test\.lua$|tests/rom_importer_double_pick_test\.lua$)'; then
|
||||||
|
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "changed=false" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
xbox-uwp-selftest:
|
||||||
|
name: Xbox UWP offline selftest
|
||||||
|
needs: xbox-uwp-changes
|
||||||
|
if: needs.xbox-uwp-changes.outputs.changed == 'true'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
- name: Xbox UWP offline selftest
|
||||||
|
run: bash scripts/xbox-uwp/selftest_build_xbox_uwp.sh
|
||||||
|
- name: Build shared payload
|
||||||
|
run: |
|
||||||
|
scripts/pack_love.sh \
|
||||||
|
--output .bazinga/work/ci-game.love \
|
||||||
|
--listing .bazinga/work/ci-love-listing.txt \
|
||||||
|
--version 0.0.0
|
||||||
|
- name: Upload shared payload
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: gen1recomp-xbox-uwp-payload
|
||||||
|
path: .bazinga/work/ci-game.love
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 1
|
||||||
|
|
||||||
|
xbox-uwp-build:
|
||||||
|
name: Xbox UWP build
|
||||||
|
needs: [xbox-uwp-changes, xbox-uwp-selftest]
|
||||||
|
if: |
|
||||||
|
always()
|
||||||
|
&& needs.xbox-uwp-changes.outputs.changed == 'true'
|
||||||
|
&& needs.xbox-uwp-selftest.result == 'success'
|
||||||
|
runs-on: windows-2022
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
- name: Download shared payload
|
||||||
|
uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
name: gen1recomp-xbox-uwp-payload
|
||||||
|
path: .bazinga/work
|
||||||
|
- name: Build Xbox UWP package
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
bash scripts/build_xbox_uwp.sh \
|
||||||
|
--release \
|
||||||
|
--version 0.0.0 \
|
||||||
|
--game-love .bazinga/work/ci-game.love
|
||||||
|
- name: Upload Xbox UWP package
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: gen1recomp-xbox-uwp
|
||||||
|
path: |
|
||||||
|
dist/xbox-uwp/gen1recomp-0.0.0-xbox-uwp.zip
|
||||||
|
dist/xbox-uwp/gen1recomp-0.0.0-xbox-uwp.zip.sha256
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
|
linux-arm64-changes:
|
||||||
|
name: detect Linux arm64 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 '^(scripts/build_linux_arm64\.sh$|scripts/linux-arm64/|scripts/pack_love\.sh$|docs/linux-arm64-build\.md$|\.github/workflows/(ci|release)\.yml$)'; then
|
||||||
|
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "changed=false" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
linux-arm64-selftest:
|
||||||
|
name: Linux arm64 offline selftest
|
||||||
|
needs: linux-arm64-changes
|
||||||
|
if: needs.linux-arm64-changes.outputs.changed == 'true'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
# Deliberately on x86_64: everything this gate checks (pins, the
|
||||||
|
# host-arch guard, the dependency exclude list, the AppRun fusion
|
||||||
|
# contract) is answerable without a container or an aarch64 machine,
|
||||||
|
# so the slow native job below only ever starts on a sane tree.
|
||||||
|
- name: Linux arm64 offline selftest
|
||||||
|
run: bash scripts/linux-arm64/selftest_build_linux_arm64.sh
|
||||||
|
|
||||||
|
linux-arm64-build:
|
||||||
|
name: Linux arm64 AppImage build
|
||||||
|
needs: [linux-arm64-changes, linux-arm64-selftest]
|
||||||
|
if: |
|
||||||
|
always()
|
||||||
|
&& needs.linux-arm64-changes.outputs.changed == 'true'
|
||||||
|
&& needs.linux-arm64-selftest.result == 'success'
|
||||||
|
# No fork restriction, unlike switch-build: this needs no secrets and no
|
||||||
|
# self-hosted hardware, just GitHub's free arm64 runner for public repos,
|
||||||
|
# so contributors get the same coverage on their own PRs.
|
||||||
|
runs-on: ubuntu-24.04-arm
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
- name: Build the aarch64 AppImage
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
scripts/build_linux_arm64.sh --version 0.0.0
|
||||||
|
- name: Verify the AppImage is self-contained and bullseye-compatible
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
image="dist/linux-arm64/gen1recomp-0.0.0-linux-arm64.AppImage"
|
||||||
|
|
||||||
|
# --appimage-extract needs no FUSE, so this works on a runner
|
||||||
|
# without /dev/fuse and still exercises the real payload.
|
||||||
|
"$image" --appimage-extract >/dev/null
|
||||||
|
for required in AppRun bin/love game.love lib/liblove-11.5.so; do
|
||||||
|
[ -e "squashfs-root/$required" ] \
|
||||||
|
|| { echo "::error::AppImage is missing $required"; exit 1; }
|
||||||
|
done
|
||||||
|
|
||||||
|
# Every bundled object must resolve once AppRun's LD_LIBRARY_PATH is
|
||||||
|
# applied; an unresolved soname here is a user-visible launch crash.
|
||||||
|
#
|
||||||
|
# This runs on a HEADLESS runner on purpose, and that is the point.
|
||||||
|
# The first version of this build bundled Debian's SDL2, which
|
||||||
|
# hard-links libpulse/libasound/libX11/libwayland, so it only ever
|
||||||
|
# started on a full desktop -- a bare runner is what exposed it.
|
||||||
|
missing="$(LD_LIBRARY_PATH="$PWD/squashfs-root/lib" \
|
||||||
|
ldd squashfs-root/bin/love squashfs-root/lib/*.so* 2>/dev/null \
|
||||||
|
| grep 'not found' || true)"
|
||||||
|
[ -z "$missing" ] || { echo "::error::unresolved deps:"; echo "$missing"; exit 1; }
|
||||||
|
|
||||||
|
# Nothing may hard-link a driver, session or audio-stack library:
|
||||||
|
# those must be reached through dlopen so the AppImage runs on a box
|
||||||
|
# with only ALSA, only Wayland, or only KMSDRM.
|
||||||
|
linked="$(for f in squashfs-root/bin/love squashfs-root/lib/*.so*; do
|
||||||
|
objdump -p "$f" 2>/dev/null | awk '/NEEDED/{print $2}'
|
||||||
|
done | sort -u | grep -E '^lib(pulse|asound|X11|wayland|GL|EGL|drm|gbm|xcb|cairo|sndio|dbus)' || true)"
|
||||||
|
[ -z "$linked" ] \
|
||||||
|
|| { echo "::error::these must be dlopened, not linked:"; echo "$linked"; exit 1; }
|
||||||
|
|
||||||
|
# The whole point of compiling on bullseye. If a future change moves
|
||||||
|
# the builder to a newer base, the glibc floor silently rises and
|
||||||
|
# every user on an older distro gets "GLIBC_2.xx not found" -- catch
|
||||||
|
# it here instead of in a release.
|
||||||
|
floor="$(objdump -T squashfs-root/bin/love squashfs-root/lib/*.so* 2>/dev/null \
|
||||||
|
| grep -o 'GLIBC_[0-9.]*' | sort -V | tail -1)"
|
||||||
|
echo "highest required glibc symbol version: $floor"
|
||||||
|
[ -n "$floor" ] \
|
||||||
|
|| { echo "::error::found no versioned glibc symbols -- objdump read nothing"; exit 1; }
|
||||||
|
highest="$(printf '%s\n' "$floor" "GLIBC_2.31" | sort -V | tail -1)"
|
||||||
|
[ "$highest" = "GLIBC_2.31" ] \
|
||||||
|
|| { echo "::error::AppImage requires $floor, above the bullseye 2.31 floor"; exit 1; }
|
||||||
|
- name: Upload the AppImage
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: gen1recomp-linux-arm64
|
||||||
|
path: |
|
||||||
|
dist/linux-arm64/gen1recomp-0.0.0-linux-arm64.AppImage
|
||||||
|
dist/linux-arm64/gen1recomp-0.0.0-linux-arm64.AppImage.sha256
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
headless:
|
headless:
|
||||||
name: headless suites (no ROM)
|
name: headless suites (no ROM)
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
@@ -29,13 +29,7 @@ jobs:
|
|||||||
[ -n "$pr_number" ] || exit 0
|
[ -n "$pr_number" ] || exit 0
|
||||||
echo "artifact_url=https://github.com/$GITHUB_REPOSITORY/actions/runs/$RUN_ID/artifacts/$artifact_id" >> "$GITHUB_OUTPUT"
|
echo "artifact_url=https://github.com/$GITHUB_REPOSITORY/actions/runs/$RUN_ID/artifacts/$artifact_id" >> "$GITHUB_OUTPUT"
|
||||||
echo "pr_number=$pr_number" >> "$GITHUB_OUTPUT"
|
echo "pr_number=$pr_number" >> "$GITHUB_OUTPUT"
|
||||||
- name: Delete existing comment
|
# Upsert via comment-tag only — do not delete-all bot comments (clobbers Switch).
|
||||||
if: steps.artifact.outputs.pr_number != ''
|
|
||||||
uses: izhangzhihao/delete-comment@master
|
|
||||||
with:
|
|
||||||
github_token: ${{ github.token }}
|
|
||||||
delete_user_name: github-actions[bot]
|
|
||||||
issue_number: ${{ steps.artifact.outputs.pr_number }}
|
|
||||||
- name: Get build info
|
- name: Get build info
|
||||||
id: build-info
|
id: build-info
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
name: Release
|
name: Release
|
||||||
|
|
||||||
# Builds the macOS, Windows, and Linux desktop apps, an Android APK, an iOS
|
# Builds the macOS, Windows, and Linux desktop apps, an Android APK, an iOS
|
||||||
# IPA, and the Anbernic RG34XXSP (Stock OS 64-bit MOD / PortMaster) port on
|
# IPA, a Nintendo Switch SD-ready zip (experimental), Xbox UWP, and the Anbernic
|
||||||
# the self-hosted Mac runner, and publishes them as a GitHub Release.
|
# RG34XXSP (Stock OS 64-bit MOD / PortMaster) port, then publishes them as a
|
||||||
|
# GitHub Release.
|
||||||
#
|
#
|
||||||
# Versioning:
|
# Versioning:
|
||||||
# - First ever release is 0.1.0.
|
# - First ever release is 0.1.0.
|
||||||
@@ -42,22 +43,17 @@ concurrency:
|
|||||||
cancel-in-progress: false
|
cancel-in-progress: false
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
version:
|
||||||
runs-on: ${{ fromJSON(github.repository == 'bryanthaboi/gen1recomp' && '["self-hosted", "macOS"]' || '"macos-latest"') }}
|
name: determine release version
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
version: ${{ steps.ver.outputs.version }}
|
||||||
|
tag: ${{ steps.ver.outputs.tag }}
|
||||||
steps:
|
steps:
|
||||||
# The self-hosted runner lives under the machine owner's home
|
- uses: actions/checkout@v7
|
||||||
# directory; mask it first so absolute paths in every later step's
|
|
||||||
# output show up as *** in the public workflow logs.
|
|
||||||
- name: Mask runner paths
|
|
||||||
run: echo "::add-mask::$HOME"
|
|
||||||
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v7
|
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
fetch-tags: true
|
fetch-tags: true
|
||||||
|
|
||||||
- name: Determine version
|
- name: Determine version
|
||||||
id: ver
|
id: ver
|
||||||
env:
|
env:
|
||||||
@@ -65,7 +61,6 @@ jobs:
|
|||||||
GH_TOKEN: ${{ github.token }}
|
GH_TOKEN: ${{ github.token }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
semver_re='^[0-9]+\.[0-9]+\.[0-9]+$'
|
semver_re='^[0-9]+\.[0-9]+\.[0-9]+$'
|
||||||
|
|
||||||
# 1) Explicit override from a manual run.
|
# 1) Explicit override from a manual run.
|
||||||
@@ -95,7 +90,6 @@ jobs:
|
|||||||
| grep -E "$semver_re" \
|
| grep -E "$semver_re" \
|
||||||
| sort -t. -k1,1n -k2,2n -k3,3n \
|
| sort -t. -k1,1n -k2,2n -k3,3n \
|
||||||
| tail -1 || true)"
|
| tail -1 || true)"
|
||||||
|
|
||||||
if [ -z "$latest" ]; then
|
if [ -z "$latest" ]; then
|
||||||
version="0.1.0"
|
version="0.1.0"
|
||||||
echo "No existing release tag; starting at $version"
|
echo "No existing release tag; starting at $version"
|
||||||
@@ -123,10 +117,157 @@ jobs:
|
|||||||
echo "::error::Release $tag already exists. Pick a different version."
|
echo "::error::Release $tag already exists. Pick a different version."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "version=$version" >> "$GITHUB_OUTPUT"
|
echo "version=$version" >> "$GITHUB_OUTPUT"
|
||||||
echo "tag=$tag" >> "$GITHUB_OUTPUT"
|
echo "tag=$tag" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
love-payload:
|
||||||
|
name: build release game.love
|
||||||
|
needs: version
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
- name: Build shared payload
|
||||||
|
run: |
|
||||||
|
scripts/pack_love.sh \
|
||||||
|
--output dist/payload/game.love \
|
||||||
|
--listing dist/payload/love-listing.txt \
|
||||||
|
--version "${{ needs.version.outputs.version }}"
|
||||||
|
- name: Upload shared payload
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: gen1recomp-release-love
|
||||||
|
path: dist/payload/game.love
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 1
|
||||||
|
|
||||||
|
linux-arm64:
|
||||||
|
name: build Linux arm64 AppImage
|
||||||
|
needs: [version, love-payload]
|
||||||
|
# GitHub's free arm64 runner for public repos. It has to be arm64: the
|
||||||
|
# AppImage compiles LÖVE natively inside a Debian bullseye arm64
|
||||||
|
# container, and the qemu-emulated alternative takes hours.
|
||||||
|
runs-on: ubuntu-24.04-arm
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
- name: Download shared payload
|
||||||
|
uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
name: gen1recomp-release-love
|
||||||
|
path: .bazinga/work
|
||||||
|
- name: Build Linux arm64 AppImage
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
scripts/build_linux_arm64.sh \
|
||||||
|
--version "${{ needs.version.outputs.version }}" \
|
||||||
|
--game-love .bazinga/work/game.love
|
||||||
|
- name: Upload Linux arm64 release
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: gen1recomp-linux-arm64-release
|
||||||
|
path: |
|
||||||
|
dist/linux-arm64/gen1recomp-${{ needs.version.outputs.version }}-linux-arm64.AppImage
|
||||||
|
dist/linux-arm64/gen1recomp-${{ needs.version.outputs.version }}-linux-arm64.AppImage.sha256
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 1
|
||||||
|
|
||||||
|
xbox-uwp:
|
||||||
|
name: build Xbox UWP release
|
||||||
|
needs: [version, love-payload]
|
||||||
|
runs-on: windows-2022
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
- name: Download shared payload
|
||||||
|
uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
name: gen1recomp-release-love
|
||||||
|
path: .bazinga/work
|
||||||
|
- name: Prepare signing certificate
|
||||||
|
shell: pwsh
|
||||||
|
env:
|
||||||
|
CERTIFICATE_BASE64: ${{ secrets.XBOX_UWP_SIGNING_CERTIFICATE }}
|
||||||
|
CERTIFICATE_PASSWORD: ${{ secrets.XBOX_UWP_SIGNING_PASSWORD }}
|
||||||
|
CANONICAL_REPOSITORY: ${{ github.repository == 'bryanthaboi/gen1recomp' }}
|
||||||
|
run: |
|
||||||
|
if ($env:CANONICAL_REPOSITORY -eq 'true' -and
|
||||||
|
[string]::IsNullOrWhiteSpace($env:CERTIFICATE_BASE64)) {
|
||||||
|
throw 'XBOX_UWP_SIGNING_CERTIFICATE is not configured.'
|
||||||
|
}
|
||||||
|
if ([string]::IsNullOrWhiteSpace($env:CERTIFICATE_BASE64)) {
|
||||||
|
"UWP_PUBLISHER=CN=Gen1Recomp" | Out-File $env:GITHUB_ENV -Append
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
$pfx = Join-Path $env:RUNNER_TEMP 'gen1recomp-uwp.pfx'
|
||||||
|
[IO.File]::WriteAllBytes($pfx, [Convert]::FromBase64String($env:CERTIFICATE_BASE64))
|
||||||
|
$flags = [Security.Cryptography.X509Certificates.X509KeyStorageFlags]::EphemeralKeySet
|
||||||
|
$cert = [Security.Cryptography.X509Certificates.X509Certificate2]::new(
|
||||||
|
$pfx, $env:CERTIFICATE_PASSWORD, $flags)
|
||||||
|
$cer = Join-Path $env:RUNNER_TEMP 'gen1recomp-uwp.cer'
|
||||||
|
[IO.File]::WriteAllBytes(
|
||||||
|
$cer,
|
||||||
|
$cert.Export([Security.Cryptography.X509Certificates.X509ContentType]::Cert))
|
||||||
|
Import-Certificate -FilePath $cer -CertStoreLocation Cert:\LocalMachine\TrustedPeople | Out-Null
|
||||||
|
"UWP_PFX=$pfx" | Out-File $env:GITHUB_ENV -Append
|
||||||
|
"UWP_CERT_THUMBPRINT=$($cert.Thumbprint)" | Out-File $env:GITHUB_ENV -Append
|
||||||
|
"UWP_PUBLISHER=$($cert.Subject)" | Out-File $env:GITHUB_ENV -Append
|
||||||
|
- name: Build Xbox UWP package
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
bash scripts/build_xbox_uwp.sh \
|
||||||
|
--release \
|
||||||
|
--version "${{ needs.version.outputs.version }}" \
|
||||||
|
--publisher "$UWP_PUBLISHER" \
|
||||||
|
--game-love .bazinga/work/game.love
|
||||||
|
- name: Sign and stage Xbox UWP release
|
||||||
|
shell: pwsh
|
||||||
|
env:
|
||||||
|
CERTIFICATE_PASSWORD: ${{ secrets.XBOX_UWP_SIGNING_PASSWORD }}
|
||||||
|
run: |
|
||||||
|
if (-not $env:UWP_PFX) {
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
scripts/xbox-uwp/stage_release.ps1 `
|
||||||
|
-Version '${{ needs.version.outputs.version }}' `
|
||||||
|
-Configuration Release `
|
||||||
|
-BuildInfo .bazinga/work/xbox-uwp-build-info.json `
|
||||||
|
-CertificatePath $env:UWP_PFX `
|
||||||
|
-CertificatePassword $env:CERTIFICATE_PASSWORD
|
||||||
|
- name: Upload Xbox UWP release
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: gen1recomp-xbox-uwp-release
|
||||||
|
path: |
|
||||||
|
dist/xbox-uwp/gen1recomp-${{ needs.version.outputs.version }}-xbox-uwp.zip
|
||||||
|
dist/xbox-uwp/gen1recomp-${{ needs.version.outputs.version }}-xbox-uwp.zip.sha256
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 1
|
||||||
|
- name: Remove signing certificate
|
||||||
|
if: always()
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
if ($env:UWP_CERT_THUMBPRINT) {
|
||||||
|
Remove-Item "Cert:\LocalMachine\TrustedPeople\$env:UWP_CERT_THUMBPRINT" -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
if ($env:UWP_PFX) {
|
||||||
|
Remove-Item $env:UWP_PFX -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
release:
|
||||||
|
needs: [version, xbox-uwp, linux-arm64]
|
||||||
|
runs-on: ${{ fromJSON(github.repository == 'bryanthaboi/gen1recomp' && '["self-hosted", "macOS"]' || '"macos-latest"') }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
# The self-hosted runner lives under the machine owner's home
|
||||||
|
# directory; mask it first so absolute paths in every later step's
|
||||||
|
# output show up as *** in the public workflow logs.
|
||||||
|
- name: Mask runner paths
|
||||||
|
run: echo "::add-mask::$HOME"
|
||||||
|
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v7
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
fetch-tags: true
|
||||||
|
|
||||||
- name: Import signing certificate into a temporary keychain
|
- name: Import signing certificate into a temporary keychain
|
||||||
if: github.repository == 'bryanthaboi/gen1recomp'
|
if: github.repository == 'bryanthaboi/gen1recomp'
|
||||||
run: |
|
run: |
|
||||||
@@ -171,12 +312,12 @@ jobs:
|
|||||||
# notarize separately below so it uses secret credentials, not a
|
# notarize separately below so it uses secret credentials, not a
|
||||||
# login-keychain profile. "all" also builds the Linux AppImage,
|
# login-keychain profile. "all" also builds the Linux AppImage,
|
||||||
# which needs no signing/notarization.
|
# which needs no signing/notarization.
|
||||||
scripts/build.sh all --version "${{ steps.ver.outputs.version }}" --no-notarize
|
scripts/build.sh all --version "${{ needs.version.outputs.version }}" --no-notarize
|
||||||
|
|
||||||
- name: Build Android
|
- name: Build Android
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
scripts/build_android.sh --version "${{ steps.ver.outputs.version }}"
|
scripts/build_android.sh --version "${{ needs.version.outputs.version }}"
|
||||||
|
|
||||||
- name: Install xcbeautify
|
- name: Install xcbeautify
|
||||||
run: |
|
run: |
|
||||||
@@ -190,18 +331,29 @@ jobs:
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
if [ "$CANONICAL_REPOSITORY" = true ]; then
|
if [ "$CANONICAL_REPOSITORY" = true ]; then
|
||||||
scripts/build_ios.sh --fetch --device --release \
|
scripts/build_ios.sh --fetch --device --release \
|
||||||
--version "${{ steps.ver.outputs.version }}"
|
--version "${{ needs.version.outputs.version }}"
|
||||||
else
|
else
|
||||||
scripts/build_ios.sh --fetch --release \
|
scripts/build_ios.sh --fetch --release \
|
||||||
--version "${{ steps.ver.outputs.version }}"
|
--version "${{ needs.version.outputs.version }}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
- name: Build Switch
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
# Hard-fail gate: Switch ships with every release (never soft-fail).
|
||||||
|
# PR CI is path-gated (ubuntu selftest + canonical fused); release
|
||||||
|
# always builds Switch regardless of which files changed.
|
||||||
|
# Needs native switch-tools (nacptool/elf2nro) and/or Docker on the
|
||||||
|
# Mac self-hosted runner; see docs/switch-build.md.
|
||||||
|
scripts/build_switch.sh --fetch --fused \
|
||||||
|
--version "${{ needs.version.outputs.version }}"
|
||||||
|
|
||||||
- name: Build Anbernic RG34XXSP port
|
- name: Build Anbernic RG34XXSP port
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
# Self-contained aarch64 PortMaster-style pack; pulls the LÖVE 11.5
|
# Self-contained aarch64 PortMaster-style pack; pulls the LÖVE 11.5
|
||||||
# runtime from PortMaster-GUI, so it needs no signing/notarization.
|
# runtime from PortMaster-GUI, so it needs no signing/notarization.
|
||||||
./build-rg34xxsp.sh --version "${{ steps.ver.outputs.version }}"
|
./build-rg34xxsp.sh --version "${{ needs.version.outputs.version }}"
|
||||||
|
|
||||||
- name: Notarize & staple macOS app
|
- name: Notarize & staple macOS app
|
||||||
if: github.repository == 'bryanthaboi/gen1recomp'
|
if: github.repository == 'bryanthaboi/gen1recomp'
|
||||||
@@ -238,18 +390,41 @@ jobs:
|
|||||||
ditto -c -k --sequesterRsrc --keepParent "$app" "$zip"
|
ditto -c -k --sequesterRsrc --keepParent "$app" "$zip"
|
||||||
echo "Notarized + stapled ✓"
|
echo "Notarized + stapled ✓"
|
||||||
|
|
||||||
|
- name: Download Xbox UWP release
|
||||||
|
if: github.repository == 'bryanthaboi/gen1recomp'
|
||||||
|
uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
name: gen1recomp-xbox-uwp-release
|
||||||
|
path: dist/xbox-uwp
|
||||||
|
|
||||||
|
- name: Download Linux arm64 release
|
||||||
|
if: github.repository == 'bryanthaboi/gen1recomp'
|
||||||
|
uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
name: gen1recomp-linux-arm64-release
|
||||||
|
path: dist/linux-arm64
|
||||||
|
|
||||||
- name: Stage release assets
|
- name: Stage release assets
|
||||||
if: github.repository == 'bryanthaboi/gen1recomp'
|
if: github.repository == 'bryanthaboi/gen1recomp'
|
||||||
id: assets
|
id: assets
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
v="${{ steps.ver.outputs.version }}"
|
v="${{ needs.version.outputs.version }}"
|
||||||
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"
|
||||||
|
|
||||||
|
# arm64 desktop Linux (Raspberry Pi, Armbian, arm64 VMs). Built on
|
||||||
|
# its own runner because LÖVE publishes no aarch64 binary and the
|
||||||
|
# AppImage has to be compiled natively; ships as a runnable
|
||||||
|
# AppImage rather than a zip so `chmod +x && ./it` just works.
|
||||||
|
arm64_appimage="dist/linux-arm64/gen1recomp-${v}-linux-arm64.AppImage"
|
||||||
|
[ -f "$arm64_appimage" ] || { echo "::error::$arm64_appimage not found (expected from the linux-arm64 job)"; exit 1; }
|
||||||
|
cp "$arm64_appimage" "$outdir/gen1recomp-${v}-linux-arm64.AppImage"
|
||||||
|
chmod +x "$outdir/gen1recomp-${v}-linux-arm64.AppImage"
|
||||||
apk="$(find dist/android/debug -name '*.apk' | head -1)"
|
apk="$(find dist/android/debug -name '*.apk' | head -1)"
|
||||||
[ -n "$apk" ] || { echo "::error::no Android APK found under dist/android/debug"; exit 1; }
|
[ -n "$apk" ] || { echo "::error::no Android APK found under dist/android/debug"; exit 1; }
|
||||||
cp "$apk" "$outdir/gen1recomp-${v}-android.apk"
|
cp "$apk" "$outdir/gen1recomp-${v}-android.apk"
|
||||||
@@ -258,6 +433,16 @@ jobs:
|
|||||||
[ -f "$ipa" ] || { echo "::error::$ipa not found (expected from scripts/build_ios.sh --device)"; exit 1; }
|
[ -f "$ipa" ] || { echo "::error::$ipa not found (expected from scripts/build_ios.sh --device)"; exit 1; }
|
||||||
cp "$ipa" "$outdir/gen1recomp-${v}-ios.ipa"
|
cp "$ipa" "$outdir/gen1recomp-${v}-ios.ipa"
|
||||||
|
|
||||||
|
swzip="dist/switch/gen1recomp-${v}-switch.zip"
|
||||||
|
[ -f "$swzip" ] || { echo "::error::$swzip not found (expected from scripts/build_switch.sh --fused → pack_sd_zip.sh)"; exit 1; }
|
||||||
|
cp "$swzip" "$outdir/gen1recomp-${v}-switch.zip"
|
||||||
|
# Local fused .nro stays under dist/switch/ for PR CI / debug; release
|
||||||
|
# publishes the SD-ready zip only.
|
||||||
|
|
||||||
|
uwp="dist/xbox-uwp/gen1recomp-${v}-xbox-uwp.zip"
|
||||||
|
[ -f "$uwp" ] || { echo "::error::$uwp not found (expected from the Xbox UWP job)"; exit 1; }
|
||||||
|
cp "$uwp" "$outdir/gen1recomp-${v}-xbox-uwp.zip"
|
||||||
|
|
||||||
# Anbernic handheld port (suffix names the CFW it targets, so a
|
# Anbernic handheld port (suffix names the CFW it targets, so a
|
||||||
# future RG35XX/other-CFW pack can ship alongside it).
|
# future RG35XX/other-CFW pack can ship alongside it).
|
||||||
rg34="dist/rg34xxsp/gen1recomp-rg34xxsp-stockos64-mod.zip"
|
rg34="dist/rg34xxsp/gen1recomp-rg34xxsp-stockos64-mod.zip"
|
||||||
@@ -284,8 +469,8 @@ jobs:
|
|||||||
GH_TOKEN: ${{ github.token }}
|
GH_TOKEN: ${{ github.token }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
v="${{ steps.ver.outputs.version }}"
|
v="${{ needs.version.outputs.version }}"
|
||||||
tag="${{ steps.ver.outputs.tag }}"
|
tag="${{ needs.version.outputs.tag }}"
|
||||||
|
|
||||||
# Issues this release closes. Three sources, deduped by number:
|
# Issues this release closes. Three sources, deduped by number:
|
||||||
# 1. GitHub's own "closing issues" links on every PR whose
|
# 1. GitHub's own "closing issues" links on every PR whose
|
||||||
@@ -369,18 +554,25 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
printf 'Release notes:\n%s\n' "$notes"
|
printf 'Release notes:\n%s\n' "$notes"
|
||||||
|
|
||||||
|
release_files=(
|
||||||
|
"dist/release/gen1recomp-${v}-macos.zip"
|
||||||
|
"dist/release/gen1recomp-${v}-windows.zip"
|
||||||
|
"dist/release/gen1recomp-${v}-linux.zip"
|
||||||
|
"dist/release/gen1recomp-${v}-linux-arm64.AppImage"
|
||||||
|
"dist/release/gen1recomp-${v}-android.apk"
|
||||||
|
"dist/release/gen1recomp-${v}-ios.ipa"
|
||||||
|
"dist/release/gen1recomp-${v}-switch.zip"
|
||||||
|
"dist/release/gen1recomp-${v}-xbox-uwp.zip"
|
||||||
|
"dist/release/gen1recomp-${v}-rg34xxsp-stockos64-mod.zip"
|
||||||
|
"dist/release/gen1recomp-${v}.love"
|
||||||
|
"dist/release/sha256sums.txt"
|
||||||
|
)
|
||||||
|
|
||||||
gh release create "$tag" \
|
gh release create "$tag" \
|
||||||
--target "$GITHUB_SHA" \
|
--target "$GITHUB_SHA" \
|
||||||
--title "$v" \
|
--title "$v" \
|
||||||
--notes "$notes" \
|
--notes "$notes" \
|
||||||
"dist/release/gen1recomp-${v}-macos.zip" \
|
"${release_files[@]}"
|
||||||
"dist/release/gen1recomp-${v}-windows.zip" \
|
|
||||||
"dist/release/gen1recomp-${v}-linux.zip" \
|
|
||||||
"dist/release/gen1recomp-${v}-android.apk" \
|
|
||||||
"dist/release/gen1recomp-${v}-ios.ipa" \
|
|
||||||
"dist/release/gen1recomp-${v}-rg34xxsp-stockos64-mod.zip" \
|
|
||||||
"dist/release/gen1recomp-${v}.love" \
|
|
||||||
"dist/release/sha256sums.txt"
|
|
||||||
|
|
||||||
echo "Published release $tag"
|
echo "Published release $tag"
|
||||||
|
|
||||||
@@ -388,7 +580,7 @@ jobs:
|
|||||||
if: github.repository == 'bryanthaboi/gen1recomp'
|
if: github.repository == 'bryanthaboi/gen1recomp'
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
v="${{ steps.ver.outputs.version }}"
|
v="${{ needs.version.outputs.version }}"
|
||||||
ipa="dist/release/gen1recomp-${v}-ios.ipa"
|
ipa="dist/release/gen1recomp-${v}-ios.ipa"
|
||||||
app_repo="mobile/ios/app-repo.json"
|
app_repo="mobile/ios/app-repo.json"
|
||||||
[ -f "$ipa" ] || { echo "::error::$ipa not found"; exit 1; }
|
[ -f "$ipa" ] || { echo "::error::$ipa not found"; exit 1; }
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
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 }}
|
||||||
@@ -31,7 +31,10 @@ mobile/ios/love-src/
|
|||||||
mobile/ios/cache/
|
mobile/ios/cache/
|
||||||
mobile/ios/build/
|
mobile/ios/build/
|
||||||
|
|
||||||
# Final packaged build artifacts (mac/win/web/android/ios) — see scripts/build.sh
|
# love-nx vendor binaries (fetch per docs/switch-development.md; also covered by .*)
|
||||||
|
.bazinga/love-nx/
|
||||||
|
|
||||||
|
# Final packaged build artifacts (mac/win/web/android/ios/switch) — see scripts/build.sh
|
||||||
/dist/
|
/dist/
|
||||||
|
|
||||||
# Legacy manual convenience-copy location (superseded by /dist/android/)
|
# Legacy manual convenience-copy location (superseded by /dist/android/)
|
||||||
@@ -43,3 +46,8 @@ mobile/dist/
|
|||||||
|
|
||||||
# per-machine iOS bundle-id pin (see scripts/build_ios.sh)
|
# per-machine iOS bundle-id pin (see scripts/build_ios.sh)
|
||||||
mobile/ios/bundle_id.local
|
mobile/ios/bundle_id.local
|
||||||
|
|
||||||
|
# Xbox UWP build output
|
||||||
|
/ports/uwp/build/
|
||||||
|
/ports/uwp/third_party/*/source/
|
||||||
|
/ports/uwp/third_party/angle/depot_tools/
|
||||||
|
|||||||
@@ -205,6 +205,39 @@ even on a different computer, as long as the same folder comes along.
|
|||||||
already written to either location is touched automatically, so copy files
|
already written to either location is touched automatically, so copy files
|
||||||
over yourself if you want to carry existing progress across the switch.
|
over yourself if you want to carry existing progress across the switch.
|
||||||
|
|
||||||
|
## Launch Options
|
||||||
|
|
||||||
|
By default the app opens the launcher so you can pick a game. Launch options
|
||||||
|
skip it and start one game directly, which is what you want for a one-click
|
||||||
|
entry: a desktop shortcut per game, a Steam entry, or a handheld frontend.
|
||||||
|
|
||||||
|
| Option | Effect |
|
||||||
|
| --- | --- |
|
||||||
|
| `--game=red` | boot Red, skipping the launcher (`blue` and `yellow` too, or just `r` / `b` / `y`) |
|
||||||
|
| `--slot=2` | load that save slot; takes a slot number or a slot id |
|
||||||
|
| `--launcher` | open the launcher anyway, so you can edit a shortcut you already made |
|
||||||
|
|
||||||
|
|
||||||
|
## Linux on arm64 (Raspberry Pi)
|
||||||
|
|
||||||
|
Alongside the x86_64 `gen1recomp-*-linux.zip`, every release ships
|
||||||
|
`gen1recomp-*-linux-arm64.AppImage` for 64-bit ARM desktop Linux — Raspberry
|
||||||
|
Pi 4/5, Armbian and other SBC distros, and arm64 VMs on Apple Silicon:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
chmod +x gen1recomp-*-linux-arm64.AppImage
|
||||||
|
./gen1recomp-*-linux-arm64.AppImage
|
||||||
|
```
|
||||||
|
|
||||||
|
LÖVE publishes no aarch64 binary of any kind, so this artifact compiles the
|
||||||
|
engine — and SDL2, OpenAL and the codecs — from source inside a Debian
|
||||||
|
bullseye arm64 container. It needs only glibc 2.29+, libstdc++, freetype and
|
||||||
|
zlib on the host; OpenGL, X11, Wayland, KMSDRM, ALSA and PulseAudio are all
|
||||||
|
dlopened, so the same image runs on a full desktop, a Wayland-only session or
|
||||||
|
a KMSDRM handheld with no X server. Build instructions and the reasoning are
|
||||||
|
in [docs/linux-arm64-build.md](docs/linux-arm64-build.md).
|
||||||
|
|
||||||
|
|
||||||
## iOS
|
## iOS
|
||||||
|
|
||||||
Every release ships `gen1recomp-*-ios.ipa`. Sideload it with AltStore
|
Every release ships `gen1recomp-*-ios.ipa`. Sideload it with AltStore
|
||||||
@@ -229,6 +262,27 @@ ships with every release as `gen1recomp-*-rg34xxsp-stockos64-mod.zip`.
|
|||||||
Install steps, controls, and troubleshooting live in
|
Install steps, controls, and troubleshooting live in
|
||||||
[docs/anbernic-rg34xxsp.md](docs/anbernic-rg34xxsp.md).
|
[docs/anbernic-rg34xxsp.md](docs/anbernic-rg34xxsp.md).
|
||||||
|
|
||||||
|
## Nintendo Switch
|
||||||
|
|
||||||
|
Releases ship an SD-ready `gen1recomp-*-switch.zip` (issue
|
||||||
|
[#531](https://github.com/bryanthaboi/gen1recomp/issues/531)). Runtime target
|
||||||
|
is pinned [love-nx](https://github.com/retronx-team/love-nx) `11.5-nx1`.
|
||||||
|
Requires a console that can run Switch homebrew. Hardware evidence: **OLED**
|
||||||
|
(author) and **V1 / Erista** boot (community).
|
||||||
|
|
||||||
|
- Players: [docs/switch-install.md](docs/switch-install.md) — download the
|
||||||
|
zip, extract at the microSD root (install or update), title-override
|
||||||
|
launch, import your own legal ROM, Joy-Con controls and shortcuts.
|
||||||
|
- Builders: [docs/switch-build.md](docs/switch-build.md) — `--fetch` /
|
||||||
|
`--loose` / `--fused`, toolchain, Docker fallback, and **CI vs release**
|
||||||
|
(path-gated ubuntu selftest, canonical fused PR artifact, release hard-fail).
|
||||||
|
|
||||||
|
Limitations, Dusklight-derived method, and how we tested:
|
||||||
|
[docs/switch-development.md](docs/switch-development.md) and
|
||||||
|
[docs/switch-hardware-evidence.md](docs/switch-hardware-evidence.md). Community
|
||||||
|
help — especially HOS / love-nx packaging and broader hardware coverage — is
|
||||||
|
welcome.
|
||||||
|
|
||||||
## Modding
|
## Modding
|
||||||
|
|
||||||
The game ships a native mod platform: content registries, events and hooks,
|
The game ships a native mod platform: content registries, events and hooks,
|
||||||
@@ -276,4 +330,7 @@ This project would not be possible without [pret](https://github.com/pret) >
|
|||||||
the pret band of decompiling maniacs > and their
|
the pret band of decompiling maniacs > and their
|
||||||
[pokered](https://github.com/pret/pokered) disassembly.
|
[pokered](https://github.com/pret/pokered) disassembly.
|
||||||
|
|
||||||
|
Nintendo Switch port: [andrewqsantos](https://github.com/andrewqsantos).
|
||||||
|
Switch hardware testing (V1 boot): [booshankles](https://github.com/booshankles).
|
||||||
|
|
||||||
<p align="center"><a href="https://boisclub.games"><img src="https://raw.githubusercontent.com/bryanthaboi/gen1recomp/refs/heads/dev/assets/logo/bcg.png"></a></p>
|
<p align="center"><a href="https://boisclub.games"><img src="https://raw.githubusercontent.com/bryanthaboi/gen1recomp/refs/heads/dev/assets/logo/bcg.png"></a></p>
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# Plain Pixel font
|
||||||
|
|
||||||
|
"Plain Pixel Font" by Douglas Vautour (Burpy Fresh) is licensed under
|
||||||
|
CC-BY 4.0: https://burpyfresh.itch.io
|
||||||
|
|
||||||
|
Version 0.009 (CJK character additions), unmodified. Characters for most
|
||||||
|
languages have a 5x11 base but can extend vertically; double-width
|
||||||
|
characters such as Hiragana and Katakana are 11x11.
|
||||||
|
|
||||||
|
Bundled so a translation mod can opt into TTF text rendering
|
||||||
|
(`mod.content.font:register("ttf", {})`; see the Translation support
|
||||||
|
section of docs/new-features.md) instead of drawing hundreds of glyph-page
|
||||||
|
tiles. The tile font extracted from the player's ROM stays the default.
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 28 KiB |
@@ -89,7 +89,7 @@ mkdir -p "$GAME_SRC"
|
|||||||
# tools/save-editor is part of that payload: the launcher's Edit button on a
|
# tools/save-editor is part of that payload: the launcher's Edit button on a
|
||||||
# save row opens it in-process (main.lua).
|
# save row opens it in-process (main.lua).
|
||||||
(cd "$ROOT" && zip -q -9 -r "$WORK/game-payload.zip" \
|
(cd "$ROOT" && zip -q -9 -r "$WORK/game-payload.zip" \
|
||||||
main.lua conf.lua src data assets tools/save-editor \
|
main.lua conf.lua src libs data assets tools/save-editor \
|
||||||
tools/rom_manifest.json tools/rom_manifest_blue.json \
|
tools/rom_manifest.json tools/rom_manifest_blue.json \
|
||||||
-x '*.DS_Store' 'data/generated/*' 'assets/generated/*')
|
-x '*.DS_Store' 'data/generated/*' 'assets/generated/*')
|
||||||
if unzip -Z1 "$WORK/game-payload.zip" \
|
if unzip -Z1 "$WORK/game-payload.zip" \
|
||||||
|
|||||||
@@ -58,7 +58,17 @@ function love.conf(t)
|
|||||||
-- engine before conf runs (LÖVE 11.x / 11.5).
|
-- engine before conf runs (LÖVE 11.x / 11.5).
|
||||||
local osName = love._os
|
local osName = love._os
|
||||||
local mobile = osName == "Android" or osName == "iOS"
|
local mobile = osName == "Android" or osName == "iOS"
|
||||||
if mobile then
|
local nx = osName == "NX"
|
||||||
|
if nx then
|
||||||
|
-- Switch (love-nx): hint handheld 720p. SDL auto-switches portable↔dock
|
||||||
|
-- (720p↔1080p) only when the window is resizable and not exclusive
|
||||||
|
-- fullscreen; NxDisplay.sync also applies the size on boot and dock change.
|
||||||
|
t.window.width = 1280
|
||||||
|
t.window.height = 720
|
||||||
|
t.window.fullscreen = false
|
||||||
|
t.window.resizable = true
|
||||||
|
t.window.highdpi = false
|
||||||
|
elseif mobile then
|
||||||
-- resizable is what unlocks orientation. SDL's Android backend, given no
|
-- resizable is what unlocks orientation. SDL's Android backend, given no
|
||||||
-- SDL_HINT_ORIENTATIONS (LÖVE sets none), calls setRequestedOrientation
|
-- SDL_HINT_ORIENTATIONS (LÖVE sets none), calls setRequestedOrientation
|
||||||
-- at window creation -- FULL_SENSOR when the window is resizable (rotates
|
-- at window creation -- FULL_SENSOR when the window is resizable (rotates
|
||||||
@@ -70,6 +80,10 @@ function love.conf(t)
|
|||||||
-- just work. FULL_SENSOR ignores the device's rotation lock, so
|
-- just work. FULL_SENSOR ignores the device's rotation lock, so
|
||||||
-- GameActivity.setOrientationBis remaps it to FULL_USER after SDL has
|
-- GameActivity.setOrientationBis remaps it to FULL_USER after SDL has
|
||||||
-- run: same orientations allowed, but auto-rotate being off now wins.
|
-- run: same orientations allowed, but auto-rotate being off now wins.
|
||||||
|
-- A persisted ORIENTATION lock (#592) overrides all of this after boot:
|
||||||
|
-- src/core/Orientation.lua sets SDL_HINT_ORIENTATIONS over the FFI and
|
||||||
|
-- re-triggers the request, from main.lua for the launcher and from
|
||||||
|
-- Game:applyOptions in game.
|
||||||
-- iOS follows the Info.plist orientations
|
-- iOS follows the Info.plist orientations
|
||||||
-- (see mobile/ios/overlays/love-ios.plist, now portrait + landscape).
|
-- (see mobile/ios/overlays/love-ios.plist, now portrait + landscape).
|
||||||
t.window.resizable = true
|
t.window.resizable = true
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ return {
|
|||||||
-- else -> .WhatsLostIsLostText (player has TM_DIG)
|
-- else -> .WhatsLostIsLostText (player has TM_DIG)
|
||||||
TEXT_CERULEANTRASHEDHOUSE_FISHING_GURU = {
|
TEXT_CERULEANTRASHEDHOUSE_FISHING_GURU = {
|
||||||
{ "check_item", "TM_DIG" },
|
{ "check_item", "TM_DIG" },
|
||||||
{ "jump_if_true", 4 },
|
{ "jump_if_true", 5 },
|
||||||
{ "show_text", "_CeruleanTrashedHouseFishingGuruTheyStoleATMText" },
|
{ "show_text", "_CeruleanTrashedHouseFishingGuruTheyStoleATMText" },
|
||||||
{ "jump", 5 },
|
{ "jump", "end" },
|
||||||
{ "show_text", "_CeruleanTrashedHouseFishingGuruWhatsLostIsLostText" },
|
{ "show_text", "_CeruleanTrashedHouseFishingGuruWhatsLostIsLostText" },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
-- Fuchsia City (pokered/scripts/FuchsiaCity.asm)
|
||||||
|
--
|
||||||
|
-- The exhibit signs are text_asm bodies: PrintText of a single text_far,
|
||||||
|
-- then DisplayPokedex for the exhibited species. The preview marks the
|
||||||
|
-- species seen but not owned, same as the S.S. Anne passenger's Snorlax.
|
||||||
|
--
|
||||||
|
-- The fossil sign branches on the Mt. Moon fossil events. The exhibit
|
||||||
|
-- holds the fossil the player did NOT take: taking the Dome Fossil puts
|
||||||
|
-- Omanyte on display, taking the Helix Fossil puts Kabuto. With neither
|
||||||
|
-- event set the sign only prints its undetermined line and no dex entry
|
||||||
|
-- opens.
|
||||||
|
--
|
||||||
|
-- The other text pointers (city sign, Safari Game signs, mart/center/gym
|
||||||
|
-- and warden signs, the four NPCs, the exhibited-mon FuchsiaCityPokemonText
|
||||||
|
-- rows) are plain text_far wrappers that resolve through Data:resolveText,
|
||||||
|
-- so they are not ported here.
|
||||||
|
return {
|
||||||
|
FUCHSIA_CITY = {
|
||||||
|
talk = {
|
||||||
|
-- FuchsiaCityChanseySignText: PrintText(_FuchsiaCityChanseySignText),
|
||||||
|
-- then DisplayPokedex CHANSEY.
|
||||||
|
TEXT_FUCHSIACITY_CHANSEY_SIGN = {
|
||||||
|
{ "show_text", "_FuchsiaCityChanseySignText" },
|
||||||
|
{ "mark_seen", "CHANSEY" },
|
||||||
|
{ "push_screen", "DexEntryMenu", "CHANSEY" },
|
||||||
|
},
|
||||||
|
|
||||||
|
-- FuchsiaCityVoltorbSignText: PrintText(_FuchsiaCityVoltorbSignText),
|
||||||
|
-- then DisplayPokedex VOLTORB.
|
||||||
|
TEXT_FUCHSIACITY_VOLTORB_SIGN = {
|
||||||
|
{ "show_text", "_FuchsiaCityVoltorbSignText" },
|
||||||
|
{ "mark_seen", "VOLTORB" },
|
||||||
|
{ "push_screen", "DexEntryMenu", "VOLTORB" },
|
||||||
|
},
|
||||||
|
|
||||||
|
-- FuchsiaCityKangaskhanSignText: PrintText(_FuchsiaCityKangaskhanSignText),
|
||||||
|
-- then DisplayPokedex KANGASKHAN.
|
||||||
|
TEXT_FUCHSIACITY_KANGASKHAN_SIGN = {
|
||||||
|
{ "show_text", "_FuchsiaCityKangaskhanSignText" },
|
||||||
|
{ "mark_seen", "KANGASKHAN" },
|
||||||
|
{ "push_screen", "DexEntryMenu", "KANGASKHAN" },
|
||||||
|
},
|
||||||
|
|
||||||
|
-- FuchsiaCitySlowpokeSignText: PrintText(_FuchsiaCitySlowpokeSignText),
|
||||||
|
-- then DisplayPokedex SLOWPOKE.
|
||||||
|
TEXT_FUCHSIACITY_SLOWPOKE_SIGN = {
|
||||||
|
{ "show_text", "_FuchsiaCitySlowpokeSignText" },
|
||||||
|
{ "mark_seen", "SLOWPOKE" },
|
||||||
|
{ "push_screen", "DexEntryMenu", "SLOWPOKE" },
|
||||||
|
},
|
||||||
|
|
||||||
|
-- FuchsiaCityLaprasSignText: PrintText(_FuchsiaCityLaprasSignText),
|
||||||
|
-- then DisplayPokedex LAPRAS.
|
||||||
|
TEXT_FUCHSIACITY_LAPRAS_SIGN = {
|
||||||
|
{ "show_text", "_FuchsiaCityLaprasSignText" },
|
||||||
|
{ "mark_seen", "LAPRAS" },
|
||||||
|
{ "push_screen", "DexEntryMenu", "LAPRAS" },
|
||||||
|
},
|
||||||
|
|
||||||
|
-- FuchsiaCityFossilSignText: CheckEvent EVENT_GOT_DOME_FOSSIL /
|
||||||
|
-- CheckEventReuseA EVENT_GOT_HELIX_FOSSIL pick the text and the
|
||||||
|
-- displayed entry; with neither set only the undetermined line prints.
|
||||||
|
TEXT_FUCHSIACITY_FOSSIL_SIGN = {
|
||||||
|
{ "check_flag", "EVENT_GOT_DOME_FOSSIL" }, -- 1
|
||||||
|
{ "jump_if_true", 7 }, -- 2
|
||||||
|
{ "check_flag", "EVENT_GOT_HELIX_FOSSIL" }, -- 3
|
||||||
|
{ "jump_if_true", 11 }, -- 4
|
||||||
|
{ "show_text", "_FuchsiaCityFossilSignUndeterminedText" }, -- 5
|
||||||
|
{ "jump", "end" }, -- 6
|
||||||
|
{ "show_text", "_FuchsiaCityFossilSignOmanyteText" }, -- 7
|
||||||
|
{ "mark_seen", "OMANYTE" }, -- 8
|
||||||
|
{ "push_screen", "DexEntryMenu", "OMANYTE" }, -- 9
|
||||||
|
{ "jump", "end" }, -- 10
|
||||||
|
{ "show_text", "_FuchsiaCityFossilSignKabutoText" }, -- 11
|
||||||
|
{ "mark_seen", "KABUTO" }, -- 12
|
||||||
|
{ "push_screen", "DexEntryMenu", "KABUTO" }, -- 13
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -8,9 +8,9 @@ return {
|
|||||||
TEXT_LAVENDERMART_COOLTRAINER_M = {
|
TEXT_LAVENDERMART_COOLTRAINER_M = {
|
||||||
{ "face_player" },
|
{ "face_player" },
|
||||||
{ "check_flag", "EVENT_RESCUED_MR_FUJI" },
|
{ "check_flag", "EVENT_RESCUED_MR_FUJI" },
|
||||||
{ "jump_if_true", 5 },
|
{ "jump_if_true", 6 },
|
||||||
{ "show_text", "_LavenderMartCooltrainerMReviveText" },
|
{ "show_text", "_LavenderMartCooltrainerMReviveText" },
|
||||||
{ "jump", 6 },
|
{ "jump", "end" },
|
||||||
{ "show_text", "_LavenderMartCooltrainerMNuggetText" },
|
{ "show_text", "_LavenderMartCooltrainerMNuggetText" },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ M.MR_FUJIS_HOUSE = {
|
|||||||
TEXT_MRFUJISHOUSE_SUPER_NERD = {
|
TEXT_MRFUJISHOUSE_SUPER_NERD = {
|
||||||
{ "face_player" }, -- 1
|
{ "face_player" }, -- 1
|
||||||
{ "check_flag", "EVENT_RESCUED_MR_FUJI" }, -- 2
|
{ "check_flag", "EVENT_RESCUED_MR_FUJI" }, -- 2
|
||||||
{ "jump_if_true", 5 }, -- 3
|
{ "jump_if_true", 6 }, -- 3
|
||||||
{ "show_text", "_MrFujisHouseSuperNerdMrFujiIsntHereText" }, -- 4
|
{ "show_text", "_MrFujisHouseSuperNerdMrFujiIsntHereText" }, -- 4
|
||||||
{ "jump", 6 }, -- 5
|
{ "jump", "end" }, -- 5
|
||||||
{ "show_text", "_MrFujisHouseSuperNerdMrFujiHadBeenPrayingText" }, -- 6
|
{ "show_text", "_MrFujisHouseSuperNerdMrFujiHadBeenPrayingText" }, -- 6
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -25,9 +25,9 @@ M.MR_FUJIS_HOUSE = {
|
|||||||
TEXT_MRFUJISHOUSE_LITTLE_GIRL = {
|
TEXT_MRFUJISHOUSE_LITTLE_GIRL = {
|
||||||
{ "face_player" }, -- 1
|
{ "face_player" }, -- 1
|
||||||
{ "check_flag", "EVENT_RESCUED_MR_FUJI" }, -- 2
|
{ "check_flag", "EVENT_RESCUED_MR_FUJI" }, -- 2
|
||||||
{ "jump_if_true", 5 }, -- 3
|
{ "jump_if_true", 6 }, -- 3
|
||||||
{ "show_text", "_MrFujisHouseLittleGirlThisIsMrFujisHouseText" }, -- 4
|
{ "show_text", "_MrFujisHouseLittleGirlThisIsMrFujisHouseText" }, -- 4
|
||||||
{ "jump", 6 }, -- 5
|
{ "jump", "end" }, -- 5
|
||||||
{ "show_text", "_MrFujisHouseLittleGirlPokemonAreNiceToHugText" }, -- 6
|
{ "show_text", "_MrFujisHouseLittleGirlPokemonAreNiceToHugText" }, -- 6
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -16,9 +16,13 @@ local function push(game, s, done)
|
|||||||
game.stack:push(TextBox.new(game, s, done))
|
game.stack:push(TextBox.new(game, s, done))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- PrintText on a text_end string returns with the box still drawn and
|
||||||
|
-- YesNoChoice then draws the menu above it (InitYesNoTextBoxParameters,
|
||||||
|
-- engine/menus/text_box.asm); no A press clears the question first. Ride
|
||||||
|
-- TextBox's opts.choice, the same as Commands.ask (#854).
|
||||||
local function ask(game, s, cb)
|
local function ask(game, s, cb)
|
||||||
local ChoiceBox = require("src.ui.ChoiceBox")
|
local TextBox = require("src.render.TextBox")
|
||||||
push(game, s, function() game.stack:push(ChoiceBox.new(game, cb)) end)
|
game.stack:push(TextBox.new(game, s, nil, { choice = cb }))
|
||||||
end
|
end
|
||||||
|
|
||||||
M.PEWTER_CITY = {
|
M.PEWTER_CITY = {
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ return {
|
|||||||
TEXT_ROUTE16GATE1F_GUARD = {
|
TEXT_ROUTE16GATE1F_GUARD = {
|
||||||
{ "face_player" },
|
{ "face_player" },
|
||||||
{ "check_item", "BICYCLE" },
|
{ "check_item", "BICYCLE" },
|
||||||
{ "jump_if_true", 5 },
|
{ "jump_if_true", 6 },
|
||||||
{ "show_text", "_Route16Gate1FGuardNoPedestriansAllowedText" },
|
{ "show_text", "_Route16Gate1FGuardNoPedestriansAllowedText" },
|
||||||
{ "jump", 6 },
|
{ "jump", "end" },
|
||||||
{ "show_text", "_Route16Gate1FGuardCyclingRoadExplanationText" },
|
{ "show_text", "_Route16Gate1FGuardCyclingRoadExplanationText" },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ return {
|
|||||||
TEXT_ROUTE18GATE1F_GUARD = {
|
TEXT_ROUTE18GATE1F_GUARD = {
|
||||||
{ "face_player" },
|
{ "face_player" },
|
||||||
{ "check_item", "BICYCLE" },
|
{ "check_item", "BICYCLE" },
|
||||||
{ "jump_if_true", 5 },
|
{ "jump_if_true", 6 },
|
||||||
{ "show_text", "_Route18Gate1FGuardYouNeedABicycleText" },
|
{ "show_text", "_Route18Gate1FGuardYouNeedABicycleText" },
|
||||||
{ "jump", 6 },
|
{ "jump", "end" },
|
||||||
{ "show_text", "_Route18Gate1FGuardCyclingRoadUphillText" },
|
{ "show_text", "_Route18Gate1FGuardCyclingRoadUphillText" },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ return {
|
|||||||
TEXT_SILPHCO10F_SILPH_WORKER_F = {
|
TEXT_SILPHCO10F_SILPH_WORKER_F = {
|
||||||
{ "face_player" },
|
{ "face_player" },
|
||||||
{ "check_flag", "EVENT_BEAT_SILPH_CO_GIOVANNI" },
|
{ "check_flag", "EVENT_BEAT_SILPH_CO_GIOVANNI" },
|
||||||
{ "jump_if_true", 5 },
|
{ "jump_if_true", 6 },
|
||||||
{ "show_text", "_SilphCo10FSilphWorkerFImScaredText" },
|
{ "show_text", "_SilphCo10FSilphWorkerFImScaredText" },
|
||||||
{ "jump", 6 },
|
{ "jump", "end" },
|
||||||
{ "show_text", "_SilphCo10FSilphWorkerFQuietAboutMyCryingText" },
|
{ "show_text", "_SilphCo10FSilphWorkerFQuietAboutMyCryingText" },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ return {
|
|||||||
-- not set: _SilphCo3FSilphWorkerMWhatShouldIDoText
|
-- not set: _SilphCo3FSilphWorkerMWhatShouldIDoText
|
||||||
TEXT_SILPHCO3F_SILPH_WORKER_M = {
|
TEXT_SILPHCO3F_SILPH_WORKER_M = {
|
||||||
{ "check_flag", "EVENT_BEAT_SILPH_CO_GIOVANNI" },
|
{ "check_flag", "EVENT_BEAT_SILPH_CO_GIOVANNI" },
|
||||||
{ "jump_if_true", 4 },
|
{ "jump_if_true", 5 },
|
||||||
{ "show_text", "_SilphCo3FSilphWorkerMWhatShouldIDoText" },
|
{ "show_text", "_SilphCo3FSilphWorkerMWhatShouldIDoText" },
|
||||||
{ "jump", 5 },
|
{ "jump", "end" },
|
||||||
{ "show_text", "_SilphCo3FSilphWorkerMYouSavedUsText" },
|
{ "show_text", "_SilphCo3FSilphWorkerMYouSavedUsText" },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ return {
|
|||||||
TEXT_SILPHCO4F_SILPH_WORKER_M = {
|
TEXT_SILPHCO4F_SILPH_WORKER_M = {
|
||||||
{"face_player"},
|
{"face_player"},
|
||||||
{"check_flag", "EVENT_BEAT_SILPH_CO_GIOVANNI"},
|
{"check_flag", "EVENT_BEAT_SILPH_CO_GIOVANNI"},
|
||||||
{"jump_if_true", 5},
|
{"jump_if_true", 6},
|
||||||
{"show_text", "_SilphCo4FSilphWorkerMImHidingText"},
|
{"show_text", "_SilphCo4FSilphWorkerMImHidingText"},
|
||||||
{"jump", 6},
|
{"jump", "end"},
|
||||||
{"show_text", "_SilphCo4FSilphWorkerMTeamRocketIsGoneText"},
|
{"show_text", "_SilphCo4FSilphWorkerMTeamRocketIsGoneText"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ return {
|
|||||||
TEXT_SILPHCO5F_SILPH_WORKER_M = {
|
TEXT_SILPHCO5F_SILPH_WORKER_M = {
|
||||||
{"face_player"},
|
{"face_player"},
|
||||||
{"check_flag", "EVENT_BEAT_SILPH_CO_GIOVANNI"},
|
{"check_flag", "EVENT_BEAT_SILPH_CO_GIOVANNI"},
|
||||||
{"jump_if_true", 5},
|
{"jump_if_true", 6},
|
||||||
{"show_text", "_SilphCo5FSilphWorkerMThatsYouRightText"},
|
{"show_text", "_SilphCo5FSilphWorkerMThatsYouRightText"},
|
||||||
{"jump", 6},
|
{"jump", "end"},
|
||||||
{"show_text", "_SilphCo5FSilphWorkerMYoureOurHeroText"},
|
{"show_text", "_SilphCo5FSilphWorkerMYoureOurHeroText"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ return {
|
|||||||
TEXT_SILPHCO6F_SILPH_WORKER_M1 = {
|
TEXT_SILPHCO6F_SILPH_WORKER_M1 = {
|
||||||
{ "face_player" },
|
{ "face_player" },
|
||||||
{ "check_flag", "EVENT_BEAT_SILPH_CO_GIOVANNI" },
|
{ "check_flag", "EVENT_BEAT_SILPH_CO_GIOVANNI" },
|
||||||
{ "jump_if_true", 5 },
|
{ "jump_if_true", 6 },
|
||||||
{ "show_text", "_SilphCo6FSilphWorkerM1TookOverTheBuildingText" },
|
{ "show_text", "_SilphCo6FSilphWorkerM1TookOverTheBuildingText" },
|
||||||
{ "jump", 6 },
|
{ "jump", "end" },
|
||||||
{ "show_text", "_SilphCo6FSilphWorkerM1BackToWorkText" },
|
{ "show_text", "_SilphCo6FSilphWorkerM1BackToWorkText" },
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -21,9 +21,9 @@ return {
|
|||||||
TEXT_SILPHCO6F_SILPH_WORKER_M2 = {
|
TEXT_SILPHCO6F_SILPH_WORKER_M2 = {
|
||||||
{ "face_player" },
|
{ "face_player" },
|
||||||
{ "check_flag", "EVENT_BEAT_SILPH_CO_GIOVANNI" },
|
{ "check_flag", "EVENT_BEAT_SILPH_CO_GIOVANNI" },
|
||||||
{ "jump_if_true", 5 },
|
{ "jump_if_true", 6 },
|
||||||
{ "show_text", "_SilphCo6FSilphWorkerMHelpMePleaseText" },
|
{ "show_text", "_SilphCo6FSilphWorkerMHelpMePleaseText" },
|
||||||
{ "jump", 6 },
|
{ "jump", "end" },
|
||||||
{ "show_text", "_SilphCo6FSilphWorkerMWeGotEngagedText" },
|
{ "show_text", "_SilphCo6FSilphWorkerMWeGotEngagedText" },
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -31,9 +31,9 @@ return {
|
|||||||
TEXT_SILPHCO6F_SILPH_WORKER_F1 = {
|
TEXT_SILPHCO6F_SILPH_WORKER_F1 = {
|
||||||
{ "face_player" },
|
{ "face_player" },
|
||||||
{ "check_flag", "EVENT_BEAT_SILPH_CO_GIOVANNI" },
|
{ "check_flag", "EVENT_BEAT_SILPH_CO_GIOVANNI" },
|
||||||
{ "jump_if_true", 5 },
|
{ "jump_if_true", 6 },
|
||||||
{ "show_text", "_SilphCo6FSilphWorkerF1SuchACowardText" },
|
{ "show_text", "_SilphCo6FSilphWorkerF1SuchACowardText" },
|
||||||
{ "jump", 6 },
|
{ "jump", "end" },
|
||||||
{ "show_text", "_SilphCo6FSilphWorkerF1HaveToMarryHimText" },
|
{ "show_text", "_SilphCo6FSilphWorkerF1HaveToMarryHimText" },
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -41,9 +41,9 @@ return {
|
|||||||
TEXT_SILPHCO6F_SILPH_WORKER_F2 = {
|
TEXT_SILPHCO6F_SILPH_WORKER_F2 = {
|
||||||
{ "face_player" },
|
{ "face_player" },
|
||||||
{ "check_flag", "EVENT_BEAT_SILPH_CO_GIOVANNI" },
|
{ "check_flag", "EVENT_BEAT_SILPH_CO_GIOVANNI" },
|
||||||
{ "jump_if_true", 5 },
|
{ "jump_if_true", 6 },
|
||||||
{ "show_text", "_SilphCo6FSilphWorkerF2TeamRocketConquerWorldText" },
|
{ "show_text", "_SilphCo6FSilphWorkerF2TeamRocketConquerWorldText" },
|
||||||
{ "jump", 6 },
|
{ "jump", "end" },
|
||||||
{ "show_text", "_SilphCo6FSilphWorkerF2TeamRocketRanText" },
|
{ "show_text", "_SilphCo6FSilphWorkerF2TeamRocketRanText" },
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -51,9 +51,9 @@ return {
|
|||||||
TEXT_SILPHCO6F_SILPH_WORKER_M3 = {
|
TEXT_SILPHCO6F_SILPH_WORKER_M3 = {
|
||||||
{ "face_player" },
|
{ "face_player" },
|
||||||
{ "check_flag", "EVENT_BEAT_SILPH_CO_GIOVANNI" },
|
{ "check_flag", "EVENT_BEAT_SILPH_CO_GIOVANNI" },
|
||||||
{ "jump_if_true", 5 },
|
{ "jump_if_true", 6 },
|
||||||
{ "show_text", "_SilphCo6FSilphWorkerM3TargetedSilphText" },
|
{ "show_text", "_SilphCo6FSilphWorkerM3TargetedSilphText" },
|
||||||
{ "jump", 6 },
|
{ "jump", "end" },
|
||||||
{ "show_text", "_SilphCo6FSilphWorkerM3WorkForSilphText" },
|
{ "show_text", "_SilphCo6FSilphWorkerM3WorkForSilphText" },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ return {
|
|||||||
-- set: _SilphCo7FSilphWorkerM2CancelledMasterBallText
|
-- set: _SilphCo7FSilphWorkerM2CancelledMasterBallText
|
||||||
TEXT_SILPHCO7F_SILPH_WORKER_M2 = {
|
TEXT_SILPHCO7F_SILPH_WORKER_M2 = {
|
||||||
{ "check_flag", "EVENT_BEAT_SILPH_CO_GIOVANNI" },
|
{ "check_flag", "EVENT_BEAT_SILPH_CO_GIOVANNI" },
|
||||||
{ "jump_if_true", 4 },
|
{ "jump_if_true", 5 },
|
||||||
{ "show_text", "_SilphCo7FSilphWorkerM2AfterTheMasterBallText" },
|
{ "show_text", "_SilphCo7FSilphWorkerM2AfterTheMasterBallText" },
|
||||||
{ "jump", 5 },
|
{ "jump", "end" },
|
||||||
{ "show_text", "_SilphCo7FSilphWorkerM2CancelledMasterBallText" },
|
{ "show_text", "_SilphCo7FSilphWorkerM2CancelledMasterBallText" },
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -22,9 +22,9 @@ return {
|
|||||||
-- set: _SilphCo7FSilphWorkerM3YouChasedOffTeamRocketText
|
-- set: _SilphCo7FSilphWorkerM3YouChasedOffTeamRocketText
|
||||||
TEXT_SILPHCO7F_SILPH_WORKER_M3 = {
|
TEXT_SILPHCO7F_SILPH_WORKER_M3 = {
|
||||||
{ "check_flag", "EVENT_BEAT_SILPH_CO_GIOVANNI" },
|
{ "check_flag", "EVENT_BEAT_SILPH_CO_GIOVANNI" },
|
||||||
{ "jump_if_true", 4 },
|
{ "jump_if_true", 5 },
|
||||||
{ "show_text", "_SilphCo7FSilphWorkerM3ItWouldBeBadText" },
|
{ "show_text", "_SilphCo7FSilphWorkerM3ItWouldBeBadText" },
|
||||||
{ "jump", 5 },
|
{ "jump", "end" },
|
||||||
{ "show_text", "_SilphCo7FSilphWorkerM3YouChasedOffTeamRocketText" },
|
{ "show_text", "_SilphCo7FSilphWorkerM3YouChasedOffTeamRocketText" },
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -34,9 +34,9 @@ return {
|
|||||||
-- set: _SilphCo7FSilphWorkerM4SafeAtLastText
|
-- set: _SilphCo7FSilphWorkerM4SafeAtLastText
|
||||||
TEXT_SILPHCO7F_SILPH_WORKER_M4 = {
|
TEXT_SILPHCO7F_SILPH_WORKER_M4 = {
|
||||||
{ "check_flag", "EVENT_BEAT_SILPH_CO_GIOVANNI" },
|
{ "check_flag", "EVENT_BEAT_SILPH_CO_GIOVANNI" },
|
||||||
{ "jump_if_true", 4 },
|
{ "jump_if_true", 5 },
|
||||||
{ "show_text", "_SilphCo7FSilphWorkerM4ItsReallyDangerousHereText" },
|
{ "show_text", "_SilphCo7FSilphWorkerM4ItsReallyDangerousHereText" },
|
||||||
{ "jump", 5 },
|
{ "jump", "end" },
|
||||||
{ "show_text", "_SilphCo7FSilphWorkerM4SafeAtLastText" },
|
{ "show_text", "_SilphCo7FSilphWorkerM4SafeAtLastText" },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ return {
|
|||||||
-- set: _SilphCo8FSilphWorkerMThanksForSavingUsText
|
-- set: _SilphCo8FSilphWorkerMThanksForSavingUsText
|
||||||
TEXT_SILPHCO8F_SILPH_WORKER_M = {
|
TEXT_SILPHCO8F_SILPH_WORKER_M = {
|
||||||
{ "check_flag", "EVENT_BEAT_SILPH_CO_GIOVANNI" },
|
{ "check_flag", "EVENT_BEAT_SILPH_CO_GIOVANNI" },
|
||||||
{ "jump_if_true", 4 },
|
{ "jump_if_true", 5 },
|
||||||
{ "show_text", "_SilphCo8FSilphWorkerMSilphIsFinishedText" },
|
{ "show_text", "_SilphCo8FSilphWorkerMSilphIsFinishedText" },
|
||||||
{ "jump", 5 },
|
{ "jump", "end" },
|
||||||
{ "show_text", "_SilphCo8FSilphWorkerMThanksForSavingUsText" },
|
{ "show_text", "_SilphCo8FSilphWorkerMThanksForSavingUsText" },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -23,9 +23,13 @@ local function push(game, s, done)
|
|||||||
game.stack:push(TextBox.new(game, s, done))
|
game.stack:push(TextBox.new(game, s, done))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- PrintText on a text_end string returns with the box still drawn and
|
||||||
|
-- YesNoChoice then draws the menu above it (InitYesNoTextBoxParameters,
|
||||||
|
-- engine/menus/text_box.asm); no A press clears the question first. Ride
|
||||||
|
-- TextBox's opts.choice, the same as Commands.ask (#854).
|
||||||
local function ask(game, s, cb)
|
local function ask(game, s, cb)
|
||||||
local ChoiceBox = require("src.ui.ChoiceBox")
|
local TextBox = require("src.render.TextBox")
|
||||||
push(game, s, function() game.stack:push(ChoiceBox.new(game, cb)) end)
|
game.stack:push(TextBox.new(game, s, nil, { choice = cb }))
|
||||||
end
|
end
|
||||||
|
|
||||||
M.VIRIDIAN_CITY = {
|
M.VIRIDIAN_CITY = {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ local files = {
|
|||||||
"data.scripts.flavor.cerulean_trashed_house",
|
"data.scripts.flavor.cerulean_trashed_house",
|
||||||
"data.scripts.flavor.copycats_house_1f",
|
"data.scripts.flavor.copycats_house_1f",
|
||||||
"data.scripts.flavor.copycats_house_2f",
|
"data.scripts.flavor.copycats_house_2f",
|
||||||
|
"data.scripts.flavor.fuchsia_city",
|
||||||
"data.scripts.flavor.game_corner",
|
"data.scripts.flavor.game_corner",
|
||||||
"data.scripts.flavor.lavender_cubone_house",
|
"data.scripts.flavor.lavender_cubone_house",
|
||||||
"data.scripts.flavor.lavender_mart",
|
"data.scripts.flavor.lavender_mart",
|
||||||
|
|||||||
@@ -18,6 +18,26 @@ local M = {
|
|||||||
VIRIDIAN_GYM = { city = "VIRIDIAN CITY", leader = "GIOVANNI", badge = "EARTHBADGE" },
|
VIRIDIAN_GYM = { city = "VIRIDIAN CITY", leader = "GIOVANNI", badge = "EARTHBADGE" },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- The originals' middle branch shared by every leader's text_asm: beaten
|
||||||
|
-- but EVENT_GOT_TM* unset means the bag was full when the victory script
|
||||||
|
-- ran GiveItem, so talking to the leader re-runs the ReceiveTM script.
|
||||||
|
-- Returns true when the retry took over the talk. A save from before
|
||||||
|
-- #797 already holds the TM without the flag; treat the owned TM as
|
||||||
|
-- received so those saves fall through to the advice text instead of
|
||||||
|
-- collecting a second copy.
|
||||||
|
local function retryTmGive(game, ow, victoryKey, done)
|
||||||
|
local reward = require("data.scripts.victories")[victoryKey]
|
||||||
|
if not (reward and reward.gotFlag) then return false end
|
||||||
|
if game.save.flags[reward.gotFlag] then return false end
|
||||||
|
local owned = game.save.inventory and game.save.inventory[reward.item] or 0
|
||||||
|
if owned > 0 then
|
||||||
|
game.save.flags[reward.gotFlag] = true
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
ow:offerGymTm(reward, done)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
-- scripts/PewterGym.asm PewterGymBrockText (text_asm): CheckEvent
|
-- scripts/PewterGym.asm PewterGymBrockText (text_asm): CheckEvent
|
||||||
-- EVENT_BEAT_BROCK branches his dialogue. Before the badge he prints
|
-- EVENT_BEAT_BROCK branches his dialogue. Before the badge he prints
|
||||||
-- _PewterGymBrockPreBattleText and engages the leader battle
|
-- _PewterGymBrockPreBattleText and engages the leader battle
|
||||||
@@ -25,12 +45,14 @@ local M = {
|
|||||||
-- badge/TM34 rewards and EVENT_BEAT_BROCK come from
|
-- badge/TM34 rewards and EVENT_BEAT_BROCK come from
|
||||||
-- data/scripts/victories.lua OPP_BROCK#1). After the badge his
|
-- data/scripts/victories.lua OPP_BROCK#1). After the badge his
|
||||||
-- .afterBeat branch prints _PewterGymBrockPostBattleAdviceText ("Go to
|
-- .afterBeat branch prints _PewterGymBrockPostBattleAdviceText ("Go to
|
||||||
-- the GYM in CERULEAN..."). The original's middle branch (beat but
|
-- the GYM in CERULEAN..."). The middle branch (beat but TM34 not yet
|
||||||
-- TM34 not yet handed over, CheckEventReuseA EVENT_GOT_TM34) is
|
-- handed over, CheckEventReuseA EVENT_GOT_TM34 -> call
|
||||||
-- unreachable in the port: the TM is granted with the victory.
|
-- PewterGymScriptReceiveTM34) retries the TM give when the bag was full
|
||||||
|
-- at the victory (#797).
|
||||||
M.PEWTER_GYM.talk = {
|
M.PEWTER_GYM.talk = {
|
||||||
TEXT_PEWTERGYM_BROCK = function(game, ow, npc, done)
|
TEXT_PEWTERGYM_BROCK = function(game, ow, npc, done)
|
||||||
if game.save.flags.EVENT_BEAT_BROCK then
|
if game.save.flags.EVENT_BEAT_BROCK then
|
||||||
|
if retryTmGive(game, ow, "OPP_BROCK#1", done) then return end
|
||||||
local TextBox = require("src.render.TextBox")
|
local TextBox = require("src.render.TextBox")
|
||||||
game.stack:push(TextBox.new(game,
|
game.stack:push(TextBox.new(game,
|
||||||
game.data.text._PewterGymBrockPostBattleAdviceText
|
game.data.text._PewterGymBrockPostBattleAdviceText
|
||||||
@@ -48,16 +70,17 @@ M.PEWTER_GYM.talk = {
|
|||||||
-- (engageTrainer shows that same pre-battle text via resolveText; the
|
-- (engageTrainer shows that same pre-battle text via resolveText; the
|
||||||
-- badge/TM rewards and the beat flag come from data/scripts/victories.lua)
|
-- badge/TM rewards and the beat flag come from data/scripts/victories.lua)
|
||||||
-- -- and once beaten print the post-battle advice text. As with Brock,
|
-- -- and once beaten print the post-battle advice text. As with Brock,
|
||||||
-- the originals' middle branch (beaten but the TM not yet handed over,
|
-- the middle branch (beaten but the TM not yet handed over,
|
||||||
-- CheckEventReuseA EVENT_GOT_TM*) is unreachable in the port: the TM is
|
-- CheckEventReuseA EVENT_GOT_TM*) retries the TM give when the bag was
|
||||||
-- granted with the victory.
|
-- full at the victory.
|
||||||
-- afterAdvice, when given, takes over `done`: it is handed (game, ow, npc,
|
-- afterAdvice, when given, takes over `done`: it is handed (game, ow, npc,
|
||||||
-- done) and must call done() itself once whatever it's doing (e.g. a fade
|
-- done) and must call done() itself once whatever it's doing (e.g. a fade
|
||||||
-- around a HideObject) finishes, rather than having it invoked
|
-- around a HideObject) finishes, rather than having it invoked
|
||||||
-- automatically. Only Giovanni's farewell uses this.
|
-- automatically. Only Giovanni's farewell uses this.
|
||||||
local function leaderTalk(beatFlag, adviceLabel, fallback, afterAdvice)
|
local function leaderTalk(beatFlag, adviceLabel, fallback, afterAdvice, victoryKey)
|
||||||
return function(game, ow, npc, done)
|
return function(game, ow, npc, done)
|
||||||
if game.save.flags[beatFlag] then
|
if game.save.flags[beatFlag] then
|
||||||
|
if victoryKey and retryTmGive(game, ow, victoryKey, done) then return end
|
||||||
local TextBox = require("src.render.TextBox")
|
local TextBox = require("src.render.TextBox")
|
||||||
local finish = done
|
local finish = done
|
||||||
if afterAdvice then
|
if afterAdvice then
|
||||||
@@ -79,42 +102,42 @@ end
|
|||||||
M.CERULEAN_GYM.talk = {
|
M.CERULEAN_GYM.talk = {
|
||||||
TEXT_CERULEANGYM_MISTY = leaderTalk("EVENT_BEAT_MISTY",
|
TEXT_CERULEANGYM_MISTY = leaderTalk("EVENT_BEAT_MISTY",
|
||||||
"_CeruleanGymMistyTM11ExplanationText",
|
"_CeruleanGymMistyTM11ExplanationText",
|
||||||
"TM11 teaches\nBUBBLEBEAM!"),
|
"TM11 teaches\nBUBBLEBEAM!", nil, "OPP_MISTY#1"),
|
||||||
}
|
}
|
||||||
|
|
||||||
-- scripts/VermilionGym.asm VermilionGymLTSurgeText .got_tm24_already
|
-- scripts/VermilionGym.asm VermilionGymLTSurgeText .got_tm24_already
|
||||||
M.VERMILION_GYM.talk = {
|
M.VERMILION_GYM.talk = {
|
||||||
TEXT_VERMILIONGYM_LT_SURGE = leaderTalk("EVENT_BEAT_LT_SURGE",
|
TEXT_VERMILIONGYM_LT_SURGE = leaderTalk("EVENT_BEAT_LT_SURGE",
|
||||||
"_VermilionGymLTSurgePostBattleAdviceText",
|
"_VermilionGymLTSurgePostBattleAdviceText",
|
||||||
"A little word of\nadvice, kid!"),
|
"A little word of\nadvice, kid!", nil, "OPP_LT_SURGE#1"),
|
||||||
}
|
}
|
||||||
|
|
||||||
-- scripts/CeladonGym.asm CeladonGymErikaText .afterBeat
|
-- scripts/CeladonGym.asm CeladonGymErikaText .afterBeat
|
||||||
M.CELADON_GYM.talk = {
|
M.CELADON_GYM.talk = {
|
||||||
TEXT_CELADONGYM_ERIKA = leaderTalk("EVENT_BEAT_ERIKA",
|
TEXT_CELADONGYM_ERIKA = leaderTalk("EVENT_BEAT_ERIKA",
|
||||||
"_CeladonGymErikaPostBattleAdviceText",
|
"_CeladonGymErikaPostBattleAdviceText",
|
||||||
"You are cataloging\nPOKéMON? I must\nsay I'm impressed."),
|
"You are cataloging\nPOKéMON? I must\nsay I'm impressed.", nil, "OPP_ERIKA#1"),
|
||||||
}
|
}
|
||||||
|
|
||||||
-- scripts/FuchsiaGym.asm FuchsiaGymKogaText .afterBeat
|
-- scripts/FuchsiaGym.asm FuchsiaGymKogaText .afterBeat
|
||||||
M.FUCHSIA_GYM.talk = {
|
M.FUCHSIA_GYM.talk = {
|
||||||
TEXT_FUCHSIAGYM_KOGA = leaderTalk("EVENT_BEAT_KOGA",
|
TEXT_FUCHSIAGYM_KOGA = leaderTalk("EVENT_BEAT_KOGA",
|
||||||
"_FuchsiaGymKogaPostBattleAdviceText",
|
"_FuchsiaGymKogaPostBattleAdviceText",
|
||||||
"When afflicted by\nTOXIC, POKéMON\nsuffer more and\nmore as battle\nprogresses!"),
|
"When afflicted by\nTOXIC, POKéMON\nsuffer more and\nmore as battle\nprogresses!", nil, "OPP_KOGA#1"),
|
||||||
}
|
}
|
||||||
|
|
||||||
-- scripts/SaffronGym.asm SaffronGymSabrinaText .afterBeat
|
-- scripts/SaffronGym.asm SaffronGymSabrinaText .afterBeat
|
||||||
M.SAFFRON_GYM.talk = {
|
M.SAFFRON_GYM.talk = {
|
||||||
TEXT_SAFFRONGYM_SABRINA = leaderTalk("EVENT_BEAT_SABRINA",
|
TEXT_SAFFRONGYM_SABRINA = leaderTalk("EVENT_BEAT_SABRINA",
|
||||||
"_SaffronGymSabrinaPostBattleAdviceText",
|
"_SaffronGymSabrinaPostBattleAdviceText",
|
||||||
"Everyone has\npsychic power!\nPeople just don't\nrealize it!"),
|
"Everyone has\npsychic power!\nPeople just don't\nrealize it!", nil, "OPP_SABRINA#1"),
|
||||||
}
|
}
|
||||||
|
|
||||||
-- scripts/CinnabarGym.asm CinnabarGymBlaineText .afterBeat
|
-- scripts/CinnabarGym.asm CinnabarGymBlaineText .afterBeat
|
||||||
M.CINNABAR_GYM.talk = {
|
M.CINNABAR_GYM.talk = {
|
||||||
TEXT_CINNABARGYM_BLAINE = leaderTalk("EVENT_BEAT_BLAINE",
|
TEXT_CINNABARGYM_BLAINE = leaderTalk("EVENT_BEAT_BLAINE",
|
||||||
"_CinnabarGymBlainePostBattleAdviceText",
|
"_CinnabarGymBlainePostBattleAdviceText",
|
||||||
"FIRE BLAST is the\nultimate fire\ntechnique!"),
|
"FIRE BLAST is the\nultimate fire\ntechnique!", nil, "OPP_BLAINE#1"),
|
||||||
}
|
}
|
||||||
|
|
||||||
-- scripts/ViridianGym.asm ViridianGymGiovanniText .afterBeat: after the
|
-- scripts/ViridianGym.asm ViridianGymGiovanniText .afterBeat: after the
|
||||||
@@ -142,7 +165,7 @@ M.VIRIDIAN_GYM.talk = {
|
|||||||
"VIRIDIAN_GYM", "VIRIDIANGYM_GIOVANNI")
|
"VIRIDIAN_GYM", "VIRIDIANGYM_GIOVANNI")
|
||||||
end
|
end
|
||||||
end, done))
|
end, done))
|
||||||
end),
|
end, "OPP_GIOVANNI#3"),
|
||||||
}
|
}
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
@@ -766,6 +766,28 @@ local function silphRocketsLeave(game, ow, onlyMap)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- SilphCo11FGiovanniAfterBattleScript (scripts/SilphCo11F.asm) is the whole
|
||||||
|
-- aftermath: DisplayTextID TEXT_SILPHCO11F_GIOVANNI_YOU_RUINED_OUR_PLANS,
|
||||||
|
-- GBFadeOutToBlack, SilphCo11FTeamRocketLeavesScript, Delay3,
|
||||||
|
-- GBFadeInFromBlack, then SetEvent. The port had only the hide pass, so the
|
||||||
|
-- speech never played and every rocket blinked out in front of the player
|
||||||
|
-- (#722). Same hide list as silphRocketsLeave, spelled as script rows so the
|
||||||
|
-- fade can hold over it.
|
||||||
|
local function silphAftermathRows()
|
||||||
|
local rows = {
|
||||||
|
{ "show_text", "_SilphCo11FGiovanniYouRuinedOurPlansText" },
|
||||||
|
{ "fade", "out" },
|
||||||
|
}
|
||||||
|
for _, floor in ipairs(SILPH_ROCKET_OBJECTS) do
|
||||||
|
for _, name in ipairs(floor[2]) do
|
||||||
|
rows[#rows + 1] = { "hide_object", floor[1], name }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
rows[#rows + 1] = { "wait", 3 } -- Delay3
|
||||||
|
rows[#rows + 1] = { "fade", "in" }
|
||||||
|
return rows
|
||||||
|
end
|
||||||
|
|
||||||
M.SILPH_CO_11F = {
|
M.SILPH_CO_11F = {
|
||||||
-- Giovanni's battle is a COORDINATE TRIGGER, not a talk.
|
-- Giovanni's battle is a COORDINATE TRIGGER, not a talk.
|
||||||
-- SilphCo11FDefaultScript (scripts/SilphCo11F.asm) checks
|
-- SilphCo11FDefaultScript (scripts/SilphCo11F.asm) checks
|
||||||
@@ -778,9 +800,13 @@ M.SILPH_CO_11F = {
|
|||||||
-- line) would touch, and the whole Silph ending -- the flag, the Master
|
-- line) would touch, and the whole Silph ending -- the flag, the Master
|
||||||
-- Ball, the Saffron streets clearing -- silently never happened.
|
-- Ball, the Saffron streets clearing -- silently never happened.
|
||||||
--
|
--
|
||||||
-- engageTrainer shows TEXT_SILPHCO11F_GIOVANNI as the battle text and,
|
-- SilphCo11FDefaultScript orders it DisplayTextID TEXT_SILPHCO11F_GIOVANNI
|
||||||
-- via victories.lua OPP_GIOVANNI#2, sets the event on a win; a loss
|
-- FIRST, then MoveSprite .GiovanniMovement: he speaks from behind the desk
|
||||||
-- sets nothing, so the trigger re-arms exactly as vanilla does.
|
-- and only then walks the three tiles down. Moving him before the box made
|
||||||
|
-- him cross the room in silence and deliver the speech point-blank (#869),
|
||||||
|
-- so the box comes first here and engageTrainer skips its own battle text.
|
||||||
|
-- victories.lua OPP_GIOVANNI#2 sets the event on a win; a loss sets
|
||||||
|
-- nothing, so the trigger re-arms exactly as vanilla does.
|
||||||
onStep = function(game, ow, x, y)
|
onStep = function(game, ow, x, y)
|
||||||
if game.save.flags.EVENT_BEAT_SILPH_CO_GIOVANNI then return false end
|
if game.save.flags.EVENT_BEAT_SILPH_CO_GIOVANNI then return false end
|
||||||
if not ((x == 6 and y == 13) or (x == 7 and y == 12)) then return false end
|
if not ((x == 6 and y == 13) or (x == 7 and y == 12)) then return false end
|
||||||
@@ -789,17 +815,28 @@ M.SILPH_CO_11F = {
|
|||||||
if npc.def and npc.def.name == "SILPHCO11F_GIOVANNI" then gio = npc break end
|
if npc.def and npc.def.name == "SILPHCO11F_GIOVANNI" then gio = npc break end
|
||||||
end
|
end
|
||||||
if not gio or ow:trainerDefeated(gio) then return false end
|
if not gio or ow:trainerDefeated(gio) then return false end
|
||||||
ow:scriptMove(gio, "down", 3, function()
|
local TextBox = require("src.render.TextBox")
|
||||||
gio:facePlayer(ow.player)
|
game.stack:push(TextBox.new(game,
|
||||||
ow:engageTrainer(gio, function()
|
game.data.text._SilphCo11FGiovanniText
|
||||||
-- SilphCo11FTeamRocketLeavesScript: every Silph rocket leaves
|
or "Ah {PLAYER}!\nSo we meet again!",
|
||||||
-- after the loss (the street rockets are handled by
|
function()
|
||||||
-- M.SAFFRON_CITY.onEnter in story4.lua).
|
ow:scriptMove(gio, "down", 3, function()
|
||||||
if game.save.flags.EVENT_BEAT_SILPH_CO_GIOVANNI then
|
gio:facePlayer(ow.player)
|
||||||
silphRocketsLeave(game, ow)
|
ow:engageTrainer(gio, function()
|
||||||
end
|
-- SilphCo11FGiovanniAfterBattleScript: the "Blast it all!"
|
||||||
end)
|
-- speech, then SilphCo11FTeamRocketLeavesScript behind a fade so
|
||||||
end)
|
-- every Silph rocket leaves off-screen (the street rockets are
|
||||||
|
-- handled by M.SAFFRON_CITY.onEnter in story4.lua). Queued, not
|
||||||
|
-- run here: the battle's own callbacks are still unwinding, so
|
||||||
|
-- queueScript starts it on the first idle overworld frame --
|
||||||
|
-- after the end-battle "Arrgh!!" box victories.lua OPP_GIOVANNI#2
|
||||||
|
-- pushes (#722).
|
||||||
|
if game.save.flags.EVENT_BEAT_SILPH_CO_GIOVANNI then
|
||||||
|
ow:queueScript(silphAftermathRows())
|
||||||
|
end
|
||||||
|
end, nil, true)
|
||||||
|
end)
|
||||||
|
end))
|
||||||
return true
|
return true
|
||||||
end,
|
end,
|
||||||
onEnter = function(game, ow)
|
onEnter = function(game, ow)
|
||||||
@@ -981,40 +1018,72 @@ M.VICTORY_ROAD_3F = {
|
|||||||
local championsRoomRivalScript = {
|
local championsRoomRivalScript = {
|
||||||
{ "face_player" }, -- 1
|
{ "face_player" }, -- 1
|
||||||
{ "check_flag", "EVENT_BEAT_CHAMPION_RIVAL_THIS_RUN" }, -- 2
|
{ "check_flag", "EVENT_BEAT_CHAMPION_RIVAL_THIS_RUN" }, -- 2
|
||||||
{ "jump_if_true", 25 }, -- 3 past end
|
-- "end" rather than a row number past the tail: this script grew by a row
|
||||||
|
-- when the follow-Oak walk landed (#704), which silently turned the old
|
||||||
|
-- numeric 26 into a jump ONTO the closing HALL_OF_FAME warp instead of past
|
||||||
|
-- it, so a returning champion warped straight into the induction.
|
||||||
|
{ "jump_if_true", "end" }, -- 3
|
||||||
{ "show_text", "_ChampionsRoomRivalIntroText" }, -- 4
|
{ "show_text", "_ChampionsRoomRivalIntroText" }, -- 4
|
||||||
{ "rival_battle", "OPP_RIVAL3", 1 }, -- 5
|
-- ChampionsRoomRivalReadyToBattleScript plays MUSIC_FINAL_BATTLE after
|
||||||
{ "jump_if_false", 25 }, -- 6 past end
|
-- the intro text, before the battle itself (#706); pushBattle's wipe-time
|
||||||
{ "set_flag", "EVENT_BEAT_CHAMPION_RIVAL_THIS_RUN" }, -- 7
|
-- playBattle("final") then no-ops on the same song, so the theme stays
|
||||||
{ "set_flag", "EVENT_BEAT_CHAMPION_RIVAL" }, -- 8
|
-- continuous into the fight
|
||||||
|
{ "play_music", "Music_FinalBattle" }, -- 5
|
||||||
|
{ "rival_battle", "OPP_RIVAL3", 1 }, -- 6
|
||||||
|
-- losing halts here; the numeric target this replaced pointed at the
|
||||||
|
-- closing warp, which inducted a player who had just lost the fight (#704)
|
||||||
|
{ "jump_if_false", "end" }, -- 7
|
||||||
|
{ "set_flag", "EVENT_BEAT_CHAMPION_RIVAL_THIS_RUN" }, -- 8
|
||||||
|
{ "set_flag", "EVENT_BEAT_CHAMPION_RIVAL" }, -- 9
|
||||||
-- ChampionsRoomRivalDefeatedScript re-displays TEXT_CHAMPIONSROOM_RIVAL,
|
-- ChampionsRoomRivalDefeatedScript re-displays TEXT_CHAMPIONSROOM_RIVAL,
|
||||||
-- whose text_asm takes the EVENT_BEAT_CHAMPION_RIVAL branch =
|
-- whose text_asm takes the EVENT_BEAT_CHAMPION_RIVAL branch =
|
||||||
-- _ChampionsRoomRivalAfterBattleText (the in-battle _RivalDefeatedText
|
-- _ChampionsRoomRivalAfterBattleText (the in-battle _RivalDefeatedText
|
||||||
-- is the port's generic "<PLAYER> defeated BLUE!" engine line instead).
|
-- is the port's generic "<PLAYER> defeated BLUE!" engine line instead).
|
||||||
{ "show_text", "_ChampionsRoomRivalAfterBattleText" }, -- 9
|
{ "show_text", "_ChampionsRoomRivalAfterBattleText" }, -- 10
|
||||||
-- ChampionsRoomOakArrivesScript: Music_Cities1AlternateTempo
|
-- ChampionsRoomOakArrivesScript: Music_Cities1AlternateTempo
|
||||||
-- (Cities1, kept into HALL_OF_FAME like BIT_NO_MAP_MUSIC after
|
-- (Cities1, kept into HALL_OF_FAME like BIT_NO_MAP_MUSIC after
|
||||||
-- defeating RIVAL3), then Oak's "{PLAYER}!" + reveal + walk in
|
-- defeating RIVAL3), then Oak's "{PLAYER}!" + reveal + walk in.
|
||||||
{ "play_music", "Music_Cities1", { keep = true } }, -- 10
|
-- audio/alternate_tempo.asm Music_Cities1AlternateTempo is not a plain
|
||||||
{ "show_text", "_ChampionsRoomOakText" }, -- 11
|
-- PlayMusic: it fades the current song out (wAudioFadeOutControl = 10),
|
||||||
{ "show_object", "CHAMPIONS_ROOM", "CHAMPIONSROOM_OAK" }, -- 12
|
-- waits 100 frames for the fade, then restarts Cities1 with channel 1
|
||||||
{ "move_npc", 2, "up", 5 }, -- 13 OakEntranceAfterVictoryMovement
|
-- pointed at Music_Cities1_Ch1_AlternateTempo -- `tempo 232` where the
|
||||||
|
-- normal Music_Cities1_Ch1 opens `tempo 144`, i.e. the slower, heavier
|
||||||
|
-- reading of the town theme this scene is known for (#847).
|
||||||
|
{ "fade_music", 10 }, -- 11
|
||||||
|
{ "wait", 100 }, -- 12
|
||||||
|
{ "play_music", "Music_Cities1", { keep = true, tempo = 232 } }, -- 13
|
||||||
|
{ "show_text", "_ChampionsRoomOakText" }, -- 14
|
||||||
|
{ "show_object", "CHAMPIONS_ROOM", "CHAMPIONSROOM_OAK" }, -- 15
|
||||||
|
{ "move_npc", 2, "up", 5 }, -- 16 OakEntranceAfterVictoryMovement
|
||||||
-- OakCongratulatesPlayerScript: rival faces left, Oak faces down
|
-- OakCongratulatesPlayerScript: rival faces left, Oak faces down
|
||||||
{ "face_object", 1, "left" }, -- 14
|
{ "face_object", 1, "left" }, -- 17
|
||||||
{ "face_object", 2, "down" }, -- 15
|
{ "face_object", 2, "down" }, -- 18
|
||||||
{ "show_text", "_ChampionsRoomOakCongratulatesPlayerText" }, -- 16
|
{ "show_text", "_ChampionsRoomOakCongratulatesPlayerText" }, -- 19
|
||||||
-- OakDisappointedWithRivalScript: Oak turns to the rival (right)
|
-- OakDisappointedWithRivalScript: Oak turns to the rival (right)
|
||||||
{ "face_object", 2, "right" }, -- 17
|
{ "face_object", 2, "right" }, -- 20
|
||||||
{ "show_text", "_ChampionsRoomOakDisappointedWithRivalText" }, -- 18
|
{ "show_text", "_ChampionsRoomOakDisappointedWithRivalText" }, -- 21
|
||||||
-- OakComeWithMeScript: Oak faces down again, then exits up
|
-- OakComeWithMeScript: Oak faces down again, then exits up
|
||||||
{ "face_object", 2, "down" }, -- 19
|
{ "face_object", 2, "down" }, -- 22
|
||||||
{ "show_text", "_ChampionsRoomOakComeWithMeText" }, -- 20
|
{ "show_text", "_ChampionsRoomOakComeWithMeText" }, -- 23
|
||||||
{ "move_npc", 2, "up", 2 }, -- 21 OakExitChampionsRoomMovement
|
{ "move_npc", 2, "up", 2 }, -- 24 OakExitChampionsRoomMovement
|
||||||
{ "hide_object", "CHAMPIONS_ROOM", "CHAMPIONSROOM_OAK" }, -- 22
|
{ "hide_object", "CHAMPIONS_ROOM", "CHAMPIONSROOM_OAK" }, -- 25
|
||||||
|
-- ChampionsRoomPlayerFollowsOakScript / WalkToHallOfFame_RLEMovement
|
||||||
|
-- (PAD_UP 4, PAD_LEFT 1): the player walks out after Oak instead of the
|
||||||
|
-- screen just fading on the spot (#704). The entrance walk leaves the
|
||||||
|
-- player at (4,3) and both north-wall warps sit on row 0, so the original
|
||||||
|
-- only ever spends three of those simulated steps -- CheckWarpsNoCollision
|
||||||
|
-- takes the HALL_OF_FAME warp the moment the walk lands on (4,0) and the
|
||||||
|
-- trailing UP/LEFT are dropped. Scripted steps ignore collision here just
|
||||||
|
-- as they do in the original (CollisionCheckOnLand skips its checks while
|
||||||
|
-- wSimulatedJoypadStatesIndex is non-zero), so stepping through the
|
||||||
|
-- rival's cell at (4,2) is the ported behavior, not a clip. Re-reported
|
||||||
|
-- as a clip in #847 and re-checked against home/overworld.asm
|
||||||
|
-- CollisionCheckOnLand, which is still the authority: do not "fix" it.
|
||||||
|
{ "move_player", "up", 3 }, -- 26
|
||||||
-- hand the induction off to the HALL_OF_FAME room (consumed by its
|
-- hand the induction off to the HALL_OF_FAME room (consumed by its
|
||||||
-- onEnter), then warp up into it (destWarp 1 lands at (4,7) facing up)
|
-- onEnter), then warp up into it (destWarp 1 lands at (4,7) facing up)
|
||||||
{ "set_field", "pendingHallOfFame", true }, -- 23
|
{ "set_field", "pendingHallOfFame", true }, -- 27
|
||||||
{ "warp", "HALL_OF_FAME", 4, 7, "up" }, -- 24
|
{ "warp", "HALL_OF_FAME", 4, 7, "up" }, -- 28
|
||||||
}
|
}
|
||||||
|
|
||||||
M.CHAMPIONS_ROOM = {
|
M.CHAMPIONS_ROOM = {
|
||||||
|
|||||||
@@ -151,9 +151,27 @@ M.POKEMON_TOWER_6F = {
|
|||||||
-- trick, and the speedrun route this bot follows depends on it.
|
-- trick, and the speedrun route this bot follows depends on it.
|
||||||
if result == "win" or battle.pokeDollEscape then
|
if result == "win" or battle.pokeDollEscape then
|
||||||
game.save.flags.EVENT_BEAT_GHOST_MAROWAK = true
|
game.save.flags.EVENT_BEAT_GHOST_MAROWAK = true
|
||||||
game.stack:push(TextBox.new(game,
|
-- PokemonTower6FMarowakDepartedText (scripts/PokemonTower6F.asm)
|
||||||
t._PokemonTower6FSoulWasCalmedText
|
-- is two texts, not one: the CUBONE's-mother line first, then
|
||||||
or "The mother's soul\nwas calmed.\012It departed to\nthe afterlife!"))
|
-- PlayCry RESTLESS_SOUL (EQU MAROWAK, constants/pokemon_constants
|
||||||
|
-- .asm:209) + WaitForSoundToFinish + DelayFrames 30 before the
|
||||||
|
-- calmed line; the port dropped the first text and the cry
|
||||||
|
-- (#867). play_cry arms the next show_text, so the cry rides
|
||||||
|
-- the calmed box's open with the button prompt kept, and the
|
||||||
|
-- wait row stands in for the asm's 30-frame gap.
|
||||||
|
local rows = {
|
||||||
|
{ "show_text", t._PokemonTower6FGhostWasCubonesMotherText
|
||||||
|
or "The GHOST was the\nrestless soul of\vCUBONE's mother!" },
|
||||||
|
{ "play_cry", "MAROWAK", true },
|
||||||
|
{ "wait", 30 },
|
||||||
|
{ "show_text", t._PokemonTower6FSoulWasCalmedText
|
||||||
|
or "The mother's soul\nwas calmed.\012It departed to\nthe afterlife!" },
|
||||||
|
}
|
||||||
|
if ow.runner then
|
||||||
|
ow.runner:run(rows)
|
||||||
|
elseif ow.queueScript then
|
||||||
|
ow:queueScript(rows)
|
||||||
|
end
|
||||||
elseif result ~= "lose" then
|
elseif result ~= "lose" then
|
||||||
-- .did_not_defeat: one simulated step right, off the trigger,
|
-- .did_not_defeat: one simulated step right, off the trigger,
|
||||||
-- so fleeing does not leave you standing on a cell that
|
-- so fleeing does not leave you standing on a cell that
|
||||||
@@ -517,6 +535,14 @@ M.GAME_CORNER = {
|
|||||||
done()
|
done()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
-- GameCornerRocketText hands the battle its own loss line through
|
||||||
|
-- SaveEndBattleTextPointers (.BattleEndText ->
|
||||||
|
-- _GameCornerRocketBattleEndText, "Dang!"), and PrintEndBattleText
|
||||||
|
-- prints it ON the battle screen between TrainerDefeatedText and
|
||||||
|
-- MoneyForWinningText (engine/battle/core.asm TrainerBattleVictory).
|
||||||
|
-- He is a text_asm trainer with no def_trainers header, so there is no
|
||||||
|
-- header.won for engageTrainer to find and the line has to be handed
|
||||||
|
-- over here or it never shows at all (#862).
|
||||||
ow:engageTrainer(npc, function()
|
ow:engageTrainer(npc, function()
|
||||||
if not ow:trainerDefeated(npc) then
|
if not ow:trainerDefeated(npc) then
|
||||||
done()
|
done()
|
||||||
@@ -527,19 +553,44 @@ M.GAME_CORNER = {
|
|||||||
game.data.text._GameCornerRocketAfterBattleText
|
game.data.text._GameCornerRocketAfterBattleText
|
||||||
or "Our hideout might\nbe discovered! I\nbetter tell BOSS!",
|
or "Our hideout might\nbe discovered! I\nbetter tell BOSS!",
|
||||||
function()
|
function()
|
||||||
-- #198: GameCornerRocketExitScript (scripts/GameCorner.asm)
|
-- #198/#862: GameCornerRocketBattleScript (scripts/GameCorner.asm)
|
||||||
-- ApplyMovementData walks the grunt one tile UP into the poster
|
-- picks the exit walk from where the player is standing, because
|
||||||
-- (the hideout's secret entrance at 9,4) before HideObject, so
|
-- the grunt on (9,5) has to get past him: wYCoord == 6 (talked to
|
||||||
-- he leaves the floor rather than popping out of existence on
|
-- from the south) or wXCoord == 8 (from the west) leaves the row
|
||||||
-- (9,5). scriptMove locks player input (#scriptMoves>0) and
|
-- clear and takes GameCornerMovement_Rocket_WalkDirect, five steps
|
||||||
-- ignores collision, so we despawn + unfreeze (done) only once
|
-- RIGHT; otherwise the player is east of him on (10,5) and
|
||||||
-- the step lands.
|
-- GameCornerMovement_Rocket_WalkAroundPlayer steps DOWN, right, UP
|
||||||
ow:scriptMove(npc, "up", 1, function()
|
-- and right again to go AROUND him. pokeyellow's copy of the
|
||||||
hideRocket()
|
-- around-path takes one extra RIGHT on the lower row before coming
|
||||||
done()
|
-- back up (it also has to clear Pikachu); both versions end on
|
||||||
end)
|
-- (15,5). He never steps UP: (9,4) is the poster wall, which is
|
||||||
|
-- where the old single UP step sent him.
|
||||||
|
local px = ow.player and ow.player.cellX
|
||||||
|
local py = ow.player and ow.player.cellY
|
||||||
|
local path
|
||||||
|
if py == 6 or px == 8 then
|
||||||
|
path = { { "right", 5 } }
|
||||||
|
elseif require("src.core.GameVersion").isYellow() then
|
||||||
|
path = { { "down", 1 }, { "right", 3 }, { "up", 1 }, { "right", 3 } }
|
||||||
|
else
|
||||||
|
path = { { "down", 1 }, { "right", 2 }, { "up", 1 }, { "right", 4 } }
|
||||||
|
end
|
||||||
|
-- GameCornerRocketExitScript only HideObjects him once
|
||||||
|
-- BIT_SCRIPTED_NPC_MOVEMENT clears, i.e. after the last step.
|
||||||
|
-- scriptMove locks player input (#scriptMoves>0) and ignores
|
||||||
|
-- collision, so the despawn + unfreeze (done) ride the final step.
|
||||||
|
local function step(i)
|
||||||
|
if i > #path then
|
||||||
|
hideRocket()
|
||||||
|
done()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
ow:scriptMove(npc, path[i][1], path[i][2],
|
||||||
|
function() step(i + 1) end)
|
||||||
|
end
|
||||||
|
step(1)
|
||||||
end))
|
end))
|
||||||
end)
|
end, game.data.text._GameCornerRocketBattleEndText or "Dang!")
|
||||||
end,
|
end,
|
||||||
-- GameCornerClerk1Text (scripts/GameCorner.asm): the offer, a
|
-- GameCornerClerk1Text (scripts/GameCorner.asm): the offer, a
|
||||||
-- YesNoChoice, then ¥1000 for 50 coins. Yellow drops the "1" from the
|
-- YesNoChoice, then ¥1000 for 50 coins. Yellow drops the "1" from the
|
||||||
@@ -549,21 +600,57 @@ M.GAME_CORNER = {
|
|||||||
-- handler is bound to both text ids just below (#552).
|
-- handler is bound to both text ids just below (#552).
|
||||||
TEXT_GAMECORNER_CLERK1 = function(game, ow, npc, done)
|
TEXT_GAMECORNER_CLERK1 = function(game, ow, npc, done)
|
||||||
local TextBox = require("src.render.TextBox")
|
local TextBox = require("src.render.TextBox")
|
||||||
local ChoiceBox = require("src.ui.ChoiceBox")
|
local Font = require("src.render.Font")
|
||||||
|
local Strings = require("src.core.Strings")
|
||||||
local t = game.data.text
|
local t = game.data.text
|
||||||
local function line(suffix, fallback)
|
local function line(suffix, fallback)
|
||||||
return t["_GameCornerClerk1" .. suffix]
|
return t["_GameCornerClerk1" .. suffix]
|
||||||
or t["_GameCornerClerk" .. suffix]
|
or t["_GameCornerClerk" .. suffix]
|
||||||
or fallback
|
or fallback
|
||||||
end
|
end
|
||||||
|
-- GameCornerDrawCoinBox (scripts/GameCorner.asm; pokeyellow's copy is
|
||||||
|
-- identical): TextBoxBorder at hlcoord 11,0 with b=5 c=7, a 9x7-tile
|
||||||
|
-- window in the top right holding MONEY at (12,2) over the amount on
|
||||||
|
-- row 3 and COIN at (12,4) over the count on row 5. Both
|
||||||
|
-- PrintBCDNumber calls pass LEADING_ZEROES, whose bit 7 SUPPRESSES
|
||||||
|
-- leading zeroes (home/print_bcd.asm), and neither passes LEFT_ALIGN,
|
||||||
|
-- so both numbers read plain and right-aligned against the inner edge
|
||||||
|
-- at column 18. The asm draws the box before the offer and redraws it
|
||||||
|
-- after the purchase, so it stands for the whole exchange: a draw-only
|
||||||
|
-- state under the dialogue gets that lifetime, since StateStack draws
|
||||||
|
-- every state above the last opaque one and updates only the top
|
||||||
|
-- (src/core/StateStack.lua), and reading save each frame is the
|
||||||
|
-- redraw (#624).
|
||||||
|
local coinBox = { draw = function()
|
||||||
|
Font.drawBox(11, 0, 9, 7)
|
||||||
|
love.graphics.setColor(0, 0, 0, 1)
|
||||||
|
Font.draw(Strings("MONEY"), 96, 16)
|
||||||
|
local money = ("¥%d"):format(game.save.money or 0)
|
||||||
|
Font.draw(money, 152 - Font.width(money), 24)
|
||||||
|
Font.draw(Strings("COIN"), 96, 32)
|
||||||
|
local coins = ("%d"):format(game.save.coins or 0)
|
||||||
|
Font.draw(coins, 152 - Font.width(coins), 40)
|
||||||
|
love.graphics.setColor(1, 1, 1, 1)
|
||||||
|
end }
|
||||||
|
game.stack:push(coinBox)
|
||||||
|
-- Every branch below finishes here. A TextBox pops itself before its
|
||||||
|
-- onDone runs, so the coin box is top of the stack again by then and
|
||||||
|
-- this pop takes it down, never someone else's state.
|
||||||
|
local function finish()
|
||||||
|
game.stack:pop()
|
||||||
|
done()
|
||||||
|
end
|
||||||
|
-- YesNoChoice is called with the offer still printed, so the prompt
|
||||||
|
-- has to ride the open text box (opts.choice) instead of being pushed
|
||||||
|
-- after it closes, which is what made the question vanish (#624).
|
||||||
game.stack:push(TextBox.new(game,
|
game.stack:push(TextBox.new(game,
|
||||||
line("DoYouNeedSomeGameCoinsText",
|
line("DoYouNeedSomeGameCoinsText",
|
||||||
"Do you need some\ngame coins?\f¥1000 for 50."), function()
|
"Do you need some\ngame coins?\f¥1000 for 50."),
|
||||||
game.stack:push(ChoiceBox.new(game, function(yes)
|
nil, { choice = function(yes)
|
||||||
if not yes then
|
if not yes then
|
||||||
game.stack:push(TextBox.new(game,
|
game.stack:push(TextBox.new(game,
|
||||||
line("PleaseComePlaySometimeText",
|
line("PleaseComePlaySometimeText",
|
||||||
"No? Please come\nplay sometime!"), done))
|
"No? Please come\nplay sometime!"), finish))
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
-- scripts/GameCorner.asm GameCornerClerk1Text: coins need
|
-- scripts/GameCorner.asm GameCornerClerk1Text: coins need
|
||||||
@@ -571,29 +658,30 @@ M.GAME_CORNER = {
|
|||||||
if not game.save.inventory.COIN_CASE then
|
if not game.save.inventory.COIN_CASE then
|
||||||
game.stack:push(TextBox.new(game,
|
game.stack:push(TextBox.new(game,
|
||||||
line("DontHaveCoinCaseText",
|
line("DontHaveCoinCaseText",
|
||||||
"You don't have a\nCOIN CASE!"), done))
|
"You don't have a\nCOIN CASE!"), finish))
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
if (game.save.coins or 0) >= 9990 then
|
if (game.save.coins or 0) >= 9990 then
|
||||||
game.stack:push(TextBox.new(game,
|
game.stack:push(TextBox.new(game,
|
||||||
line("CoinCaseIsFullText",
|
line("CoinCaseIsFullText",
|
||||||
"Oops! Your COIN\nCASE is full."), done))
|
"Oops! Your COIN\nCASE is full."), finish))
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
if game.save.money < 1000 then
|
if game.save.money < 1000 then
|
||||||
game.stack:push(TextBox.new(game,
|
game.stack:push(TextBox.new(game,
|
||||||
line("CantAffordTheCoinsText",
|
line("CantAffordTheCoinsText",
|
||||||
"You can't afford\nthe coins!"), done))
|
"You can't afford\nthe coins!"), finish))
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
game.save.money = game.save.money - 1000
|
game.save.money = game.save.money - 1000
|
||||||
game.save.coins = math.min(9999, (game.save.coins or 0) + 50)
|
game.save.coins = math.min(9999, (game.save.coins or 0) + 50)
|
||||||
|
-- the thanks text is the plain _GameCornerClerk1ThanksHereAre50-
|
||||||
|
-- CoinsText; the new count belongs in the coin box the asm
|
||||||
|
-- redraws here, not appended to the line (#624)
|
||||||
game.stack:push(TextBox.new(game,
|
game.stack:push(TextBox.new(game,
|
||||||
line("ThanksHereAre50CoinsText",
|
line("ThanksHereAre50CoinsText",
|
||||||
"Thanks! Here are\nyour 50 coins!")
|
"Thanks! Here are\nyour 50 coins!"), finish))
|
||||||
.. ("\fCOINS: %d"):format(game.save.coins), done))
|
end }))
|
||||||
end))
|
|
||||||
end))
|
|
||||||
end,
|
end,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -606,99 +694,188 @@ M.GAME_CORNER.talk.TEXT_GAMECORNER_CLERK =
|
|||||||
M.GAME_CORNER.talk.TEXT_GAMECORNER_CLERK1
|
M.GAME_CORNER.talk.TEXT_GAMECORNER_CLERK1
|
||||||
|
|
||||||
-- Game Corner prize lists (data/events/prizes.asm, prize_mon_levels.asm).
|
-- Game Corner prize lists (data/events/prizes.asm, prize_mon_levels.asm).
|
||||||
-- The six mon prizes differ between Red and Blue; the three TM prizes are
|
-- Each counter owns ONE window of three prizes, not the whole catalogue:
|
||||||
-- identical, so they are shared and appended to each version's mon list.
|
-- GetPrizeMenuId (engine/events/prize_menu.asm) subtracts
|
||||||
|
-- TEXT_GAMECORNERPRIZEROOM_PRIZE_VENDOR_1 from hTextID and indexes
|
||||||
|
-- PrizeDifferentMenuPtrs with the result, so vendor 1 sells
|
||||||
|
-- PrizeMenuMon1Entries, vendor 2 PrizeMenuMon2Entries and vendor 3
|
||||||
|
-- PrizeMenuTMsEntries (#623). The mon windows and their levels differ per
|
||||||
|
-- version; the TM window is identical in all three, so it is shared.
|
||||||
local PRIZE_TMS = {
|
local PRIZE_TMS = {
|
||||||
{ kind = "item", item = "TM_DRAGON_RAGE", cost = 3300 },
|
{ kind = "item", item = "TM_DRAGON_RAGE", cost = 3300 },
|
||||||
{ kind = "item", item = "TM_HYPER_BEAM", cost = 5500 },
|
{ kind = "item", item = "TM_HYPER_BEAM", cost = 5500 },
|
||||||
{ kind = "item", item = "TM_SUBSTITUTE", cost = 7700 },
|
{ kind = "item", item = "TM_SUBSTITUTE", cost = 7700 },
|
||||||
}
|
}
|
||||||
local RED_PRIZES = {
|
local RED_PRIZE_WINDOWS = {
|
||||||
{ kind = "mon", species = "ABRA", level = 9, cost = 180 },
|
{
|
||||||
{ kind = "mon", species = "CLEFAIRY", level = 8, cost = 500 },
|
{ kind = "mon", species = "ABRA", level = 9, cost = 180 },
|
||||||
{ kind = "mon", species = "NIDORINA", level = 17, cost = 1200 },
|
{ kind = "mon", species = "CLEFAIRY", level = 8, cost = 500 },
|
||||||
{ kind = "mon", species = "DRATINI", level = 18, cost = 2800 },
|
{ kind = "mon", species = "NIDORINA", level = 17, cost = 1200 },
|
||||||
{ kind = "mon", species = "SCYTHER", level = 25, cost = 5500 },
|
},
|
||||||
{ kind = "mon", species = "PORYGON", level = 26, cost = 9999 },
|
{
|
||||||
PRIZE_TMS[1], PRIZE_TMS[2], PRIZE_TMS[3],
|
{ kind = "mon", species = "DRATINI", level = 18, cost = 2800 },
|
||||||
|
{ kind = "mon", species = "SCYTHER", level = 25, cost = 5500 },
|
||||||
|
{ kind = "mon", species = "PORYGON", level = 26, cost = 9999 },
|
||||||
|
},
|
||||||
|
PRIZE_TMS,
|
||||||
}
|
}
|
||||||
local BLUE_PRIZES = {
|
local BLUE_PRIZE_WINDOWS = {
|
||||||
{ kind = "mon", species = "ABRA", level = 6, cost = 120 },
|
{
|
||||||
{ kind = "mon", species = "CLEFAIRY", level = 12, cost = 750 },
|
{ kind = "mon", species = "ABRA", level = 6, cost = 120 },
|
||||||
{ kind = "mon", species = "NIDORINO", level = 17, cost = 1200 },
|
{ kind = "mon", species = "CLEFAIRY", level = 12, cost = 750 },
|
||||||
{ kind = "mon", species = "PINSIR", level = 20, cost = 2500 },
|
{ kind = "mon", species = "NIDORINO", level = 17, cost = 1200 },
|
||||||
{ kind = "mon", species = "DRATINI", level = 24, cost = 4600 },
|
},
|
||||||
{ kind = "mon", species = "PORYGON", level = 18, cost = 6500 },
|
{
|
||||||
PRIZE_TMS[1], PRIZE_TMS[2], PRIZE_TMS[3],
|
{ kind = "mon", species = "PINSIR", level = 20, cost = 2500 },
|
||||||
|
{ kind = "mon", species = "DRATINI", level = 24, cost = 4600 },
|
||||||
|
{ kind = "mon", species = "PORYGON", level = 18, cost = 6500 },
|
||||||
|
},
|
||||||
|
PRIZE_TMS,
|
||||||
|
}
|
||||||
|
-- Yellow keeps the three windows but restocks both mon counters
|
||||||
|
-- (pokeyellow/data/events/prizes.asm, prize_mon_levels.asm)
|
||||||
|
local YELLOW_PRIZE_WINDOWS = {
|
||||||
|
{
|
||||||
|
{ kind = "mon", species = "ABRA", level = 15, cost = 230 },
|
||||||
|
{ kind = "mon", species = "VULPIX", level = 18, cost = 1000 },
|
||||||
|
{ kind = "mon", species = "WIGGLYTUFF", level = 22, cost = 2680 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{ kind = "mon", species = "SCYTHER", level = 30, cost = 6500 },
|
||||||
|
{ kind = "mon", species = "PINSIR", level = 30, cost = 6500 },
|
||||||
|
{ kind = "mon", species = "PORYGON", level = 26, cost = 9999 },
|
||||||
|
},
|
||||||
|
PRIZE_TMS,
|
||||||
}
|
}
|
||||||
|
|
||||||
local function activePrizes()
|
local function prizeWindow(n)
|
||||||
return require("src.core.GameVersion").isBlue() and BLUE_PRIZES or RED_PRIZES
|
local GameVersion = require("src.core.GameVersion")
|
||||||
|
local windows = RED_PRIZE_WINDOWS
|
||||||
|
if GameVersion.isBlue() then
|
||||||
|
windows = BLUE_PRIZE_WINDOWS
|
||||||
|
elseif GameVersion.isYellow() then
|
||||||
|
windows = YELLOW_PRIZE_WINDOWS
|
||||||
|
end
|
||||||
|
return windows[n]
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Prize counters (engine/menus/prize_menu.asm CeladonPrizeMenu; the prize
|
-- Prize counters (engine/events/prize_menu.asm CeladonPrizeMenu; the prize
|
||||||
-- list itself is data/events/prizes.asm, prize_mon_levels.asm). Gen1 gates
|
-- list itself is data/events/prizes.asm, prize_mon_levels.asm). Gen1 gates
|
||||||
-- the prize window on the COIN CASE: it does IsItemInBag COIN_CASE first, and
|
-- the prize window on the COIN CASE: it does IsItemInBag COIN_CASE first, and
|
||||||
-- with no case prints RequireCoinCaseText and returns without ever opening a
|
-- with no case prints RequireCoinCaseText and returns without ever opening a
|
||||||
-- window; only with the case does it print ExchangeCoinsForPrizesText and then
|
-- window; only with the case does it print ExchangeCoinsForPrizesText and then
|
||||||
-- show the prizes. #194: the port used to open the window unconditionally and
|
-- show the prizes. #194: the port used to open the window unconditionally and
|
||||||
-- skip both text boxes.
|
-- skip both text boxes. wMaxMenuItem is 3, i.e. this window's three prizes
|
||||||
local function prizeCounter(game, ow, npc, done)
|
-- plus the NO THANKS row, and HandlePrizeChoice confirms the pick with
|
||||||
local ListMenu = require("src.ui.ListMenu")
|
-- SoYouWantPrizeText + YesNoChoice before any coins move; every branch then
|
||||||
local Commands = require("src.script.Commands")
|
-- rets out of CeladonPrizeMenu, so one transaction ends the conversation and
|
||||||
local TextBox = require("src.render.TextBox")
|
-- buying again means talking to the counter again (#623).
|
||||||
local t = game.data.text
|
local function prizeCounter(window)
|
||||||
-- IsItemInBag COIN_CASE: without the case, deny and open no window
|
return function(game, ow, npc, done)
|
||||||
-- (COIN_CASE is a numeric count in save.inventory, nil when absent).
|
local ListMenu = require("src.ui.ListMenu")
|
||||||
if not game.save.inventory.COIN_CASE then
|
local Commands = require("src.script.Commands")
|
||||||
|
local TextBox = require("src.render.TextBox")
|
||||||
|
local t = game.data.text
|
||||||
|
-- IsItemInBag COIN_CASE: without the case, deny and open no window
|
||||||
|
-- (COIN_CASE is a numeric count in save.inventory, nil when absent).
|
||||||
|
if not game.save.inventory.COIN_CASE then
|
||||||
|
game.stack:push(TextBox.new(game,
|
||||||
|
t._RequireCoinCaseText or "A COIN CASE is\nrequired!", done))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
-- ExchangeCoinsForPrizesText plays before the prize window opens.
|
||||||
game.stack:push(TextBox.new(game,
|
game.stack:push(TextBox.new(game,
|
||||||
t._RequireCoinCaseText or "A COIN CASE is\nrequired!", done))
|
t._ExchangeCoinsForPrizesText or "We exchange your\ncoins for prizes.",
|
||||||
return
|
function()
|
||||||
end
|
local items = {}
|
||||||
-- ExchangeCoinsForPrizesText plays before the prize window opens.
|
for _, p in ipairs(prizeWindow(window)) do
|
||||||
game.stack:push(TextBox.new(game,
|
local label
|
||||||
t._ExchangeCoinsForPrizesText or "We exchange your\ncoins for prizes.",
|
if p.kind == "mon" then
|
||||||
function()
|
label = ("%s L%d"):format(game.data.pokemon[p.species].name, p.level)
|
||||||
local items = {}
|
else
|
||||||
for _, p in ipairs(activePrizes()) do
|
label = game.data.items[p.item].name
|
||||||
local label
|
end
|
||||||
if p.kind == "mon" then
|
table.insert(items,
|
||||||
label = ("%s L%d"):format(game.data.pokemon[p.species].name, p.level)
|
{ label = label, right = tostring(p.cost), value = p })
|
||||||
else
|
|
||||||
label = game.data.items[p.item].name
|
|
||||||
end
|
end
|
||||||
table.insert(items,
|
-- NoThanksText (data/events/prizes.asm) sits under the three prizes
|
||||||
{ label = label, right = tostring(p.cost), value = p })
|
table.insert(items, { label = "NO THANKS" })
|
||||||
end
|
local list
|
||||||
local list
|
-- close the window first: every ending in HandlePrizeChoice leaves
|
||||||
list = ListMenu.new(game, "PRIZES (COINS)", items, {
|
-- the menu for good, and the closing line belongs over the map
|
||||||
footer = ("COINS %d"):format(game.save.coins or 0),
|
local function finish(msg)
|
||||||
onChoose = function(item)
|
list:close()
|
||||||
local p = item.value
|
game.stack:push(TextBox.new(game, msg, done))
|
||||||
|
end
|
||||||
|
local function buy(p)
|
||||||
if (game.save.coins or 0) < p.cost then
|
if (game.save.coins or 0) < p.cost then
|
||||||
list.footer = "Not enough coins!"
|
finish(t._SorryNeedMoreCoinsText or "Sorry, you need\nmore coins.")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
-- HasEnoughCoins passed, so hand the prize over first and only
|
||||||
|
-- subtract once it landed: the asm rets before .subtractCoins when
|
||||||
|
-- the bag is full, or when both the party and every box are full
|
||||||
|
local roomless = t._OopsYouDontHaveEnoughRoomText
|
||||||
|
or "Oops! You don't\nhave enough room."
|
||||||
|
if p.kind == "mon" then
|
||||||
|
-- no runner here, so give_pokemon reports through ctx.lastCheck
|
||||||
|
-- and skips the AskName prompt (Commands.give_pokemon)
|
||||||
|
local ctx = { save = game.save, game = game }
|
||||||
|
Commands.give_pokemon(ctx, p.species, p.level)
|
||||||
|
if not ctx.lastCheck then
|
||||||
|
finish(roomless)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
elseif not require("src.inventory.Bag").add(
|
||||||
|
game.save, p.item, 1, game.data) then
|
||||||
|
finish(roomless)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
game.save.coins = game.save.coins - p.cost
|
game.save.coins = game.save.coins - p.cost
|
||||||
if p.kind == "mon" then
|
-- no thank-you line: HereYouGoText is unreferenced in the asm,
|
||||||
Commands.give_pokemon({ save = game.save, game = game },
|
-- which just redraws the coin box (PrintPrizePrice) and returns
|
||||||
p.species, p.level)
|
list:close()
|
||||||
else
|
done()
|
||||||
game.save.inventory[p.item] = (game.save.inventory[p.item] or 0) + 1
|
end
|
||||||
end
|
list = ListMenu.new(game, "PRIZES (COINS)", items, {
|
||||||
list.footer = ("Got it! COINS %d"):format(game.save.coins)
|
footer = ("COINS %d"):format(game.save.coins or 0),
|
||||||
end,
|
onChoose = function(item)
|
||||||
onCancel = done,
|
local p = item.value
|
||||||
})
|
if not p then -- NO THANKS is the B exit (cp 3 -> .noChoice)
|
||||||
game.stack:push(list)
|
list:close()
|
||||||
end))
|
done()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local name = (p.kind == "mon")
|
||||||
|
and game.data.pokemon[p.species].name
|
||||||
|
or game.data.items[p.item].name
|
||||||
|
-- SoYouWantPrizeText names the prize out of wNameBuffer, which
|
||||||
|
-- is not one of TextBox's RAM tokens, so fill it in here
|
||||||
|
local ask = (t._SoYouWantPrizeText
|
||||||
|
or "So, you want\n{RAM:wNameBuffer}?")
|
||||||
|
:gsub("{RAM:wNameBuffer}", name)
|
||||||
|
game.stack:push(TextBox.new(game, ask, nil, {
|
||||||
|
choice = function(yes)
|
||||||
|
if not yes then
|
||||||
|
finish(t._OhFineThenText or "Oh, fine then.")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
buy(p)
|
||||||
|
end,
|
||||||
|
}))
|
||||||
|
end,
|
||||||
|
onCancel = done,
|
||||||
|
})
|
||||||
|
game.stack:push(list)
|
||||||
|
end))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
M.GAME_CORNER_PRIZE_ROOM = {
|
M.GAME_CORNER_PRIZE_ROOM = {
|
||||||
talk = { -- the three prize counters are bg events
|
talk = { -- the three prize counters are bg events, one window each
|
||||||
TEXT_GAMECORNERPRIZEROOM_PRIZE_VENDOR_1 = prizeCounter,
|
TEXT_GAMECORNERPRIZEROOM_PRIZE_VENDOR_1 = prizeCounter(1),
|
||||||
TEXT_GAMECORNERPRIZEROOM_PRIZE_VENDOR_2 = prizeCounter,
|
TEXT_GAMECORNERPRIZEROOM_PRIZE_VENDOR_2 = prizeCounter(2),
|
||||||
TEXT_GAMECORNERPRIZEROOM_PRIZE_VENDOR_3 = prizeCounter,
|
TEXT_GAMECORNERPRIZEROOM_PRIZE_VENDOR_3 = prizeCounter(3),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,9 +13,18 @@ local function push(game, s, done)
|
|||||||
game.stack:push(TextBox.new(game, s, done))
|
game.stack:push(TextBox.new(game, s, done))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- The question stays on screen under the YES/NO menu. The dojo prize
|
||||||
|
-- balls are the clearest case: FightingDojoHitmonleePokeBallText
|
||||||
|
-- (scripts/FightingDojo.asm) is `call PrintText` on a text_end string --
|
||||||
|
-- no prompt, so no WaitForTextScrollButtonPress -- immediately followed
|
||||||
|
-- by `call YesNoChoice`, and InitYesNoTextBoxParameters
|
||||||
|
-- (engine/menus/text_box.asm) puts the menu above the dialogue box
|
||||||
|
-- rather than replacing it. Ride TextBox's opts.choice, the same as
|
||||||
|
-- Commands.ask, instead of popping the box with an A press and leaving a
|
||||||
|
-- bare ChoiceBox over the overworld (#854).
|
||||||
local function ask(game, s, cb)
|
local function ask(game, s, cb)
|
||||||
local ChoiceBox = require("src.ui.ChoiceBox")
|
local TextBox = require("src.render.TextBox")
|
||||||
push(game, s, function() game.stack:push(ChoiceBox.new(game, cb)) end)
|
game.stack:push(TextBox.new(game, s, nil, { choice = cb }))
|
||||||
end
|
end
|
||||||
|
|
||||||
-- fill the extracted text placeholders ({NUM:...}, {RAM:...}, {PLAYER})
|
-- fill the extracted text placeholders ({NUM:...}, {RAM:...}, {PLAYER})
|
||||||
@@ -155,19 +164,26 @@ local function dojoBall(species, ownBall, otherBall, askKey)
|
|||||||
push(game, "You'll have to\nbeat the master\nfirst!", done)
|
push(game, "You'll have to\nbeat the master\nfirst!", done)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
ask(game, t[askKey] or ("You want\n" .. species .. "?"), function(yes)
|
-- Examining a ball shows that species' POKéDEX entry first
|
||||||
if not yes then done() return end
|
-- (DisplayPokedex in FightingDojo.asm, which also marks it seen),
|
||||||
flags["EVENT_GOT_" .. species] = true
|
-- then the yes/no take-it prompt (#853).
|
||||||
flags.EVENT_DEFEATED_FIGHTING_DOJO = true
|
local Commands = require("src.script.Commands")
|
||||||
local Commands = require("src.script.Commands")
|
local ctx = { save = game.save, game = game, overworld = ow }
|
||||||
local ctx = { save = game.save, game = game, overworld = ow }
|
Commands.mark_seen(ctx, species)
|
||||||
Commands.give_pokemon(ctx, species, 30)
|
local DexEntryMenu = require("src.ui.DexEntryMenu")
|
||||||
-- Hide ONLY the chosen ball; the other stays (FightingDojo.asm hides
|
game.stack:push(DexEntryMenu.new(game, species, function()
|
||||||
-- just the picked object's index) and routes to the greedy line above
|
ask(game, t[askKey] or ("You want\n" .. species .. "?"), function(yes)
|
||||||
-- when talked to (#197).
|
if not yes then done() return end
|
||||||
Commands.hide_object(ctx, "FIGHTING_DOJO", ownBall)
|
flags["EVENT_GOT_" .. species] = true
|
||||||
push(game, ("%s got\n%s!"):format(game.save.player.name, species), done)
|
flags.EVENT_DEFEATED_FIGHTING_DOJO = true
|
||||||
end)
|
Commands.give_pokemon(ctx, species, 30)
|
||||||
|
-- Hide ONLY the chosen ball; the other stays (FightingDojo.asm hides
|
||||||
|
-- just the picked object's index) and routes to the greedy line above
|
||||||
|
-- when talked to (#197).
|
||||||
|
Commands.hide_object(ctx, "FIGHTING_DOJO", ownBall)
|
||||||
|
push(game, ("%s got\n%s!"):format(game.save.player.name, species), done)
|
||||||
|
end)
|
||||||
|
end))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ local function gift(opts)
|
|||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
if opts.pre then say(opts.pre, "", give) else give() end
|
if opts.pre then say(opts.pre, opts.preFallback or "", give) else give() end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -118,11 +118,21 @@ M.CINNABAR_LAB_METRONOME_ROOM = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
-- TM42 Dream Eater (scripts/ViridianCity.asm, the fisher; no pre text)
|
-- TM42 Dream Eater (scripts/ViridianCity.asm, the fisher). The fisher's
|
||||||
|
-- YouCanHaveThisText prints before GiveItem, so this gift needs a pre
|
||||||
|
-- text (#775). Like the SilphCo2F worker (#393) that label carries no
|
||||||
|
-- leading underscore, and on Red it sits outside the extractor's symbol
|
||||||
|
-- set, so the literal from text/ViridianCity.asm rides along as the
|
||||||
|
-- fallback; Yellow resolves the ROM string instead.
|
||||||
M.VIRIDIAN_CITY = {
|
M.VIRIDIAN_CITY = {
|
||||||
talk = {
|
talk = {
|
||||||
TEXT_VIRIDIANCITY_FISHER = gift({
|
TEXT_VIRIDIANCITY_FISHER = gift({
|
||||||
flag = "EVENT_GOT_TM42", item = "TM_DREAM_EATER",
|
flag = "EVENT_GOT_TM42", item = "TM_DREAM_EATER",
|
||||||
|
pre = "ViridianCityFisherYouCanHaveThisText",
|
||||||
|
preFallback = "Yawn!\nI must have dozed\voff in the sun."
|
||||||
|
.. "\fI had this dream\nabout a DROWZEE\veating my dream."
|
||||||
|
.. "\vWhat's this?\vWhere did this TM\vcome from?"
|
||||||
|
.. "\fThis is spooky!\nHere, you can\vhave this TM.",
|
||||||
received = "_ViridianCityFisherReceivedTM42Text",
|
received = "_ViridianCityFisherReceivedTM42Text",
|
||||||
explain = "_ViridianCityFisherTM42ExplanationText",
|
explain = "_ViridianCityFisherTM42ExplanationText",
|
||||||
noRoom = "_ViridianCityFisherTM42NoRoomText",
|
noRoom = "_ViridianCityFisherTM42NoRoomText",
|
||||||
@@ -176,6 +186,20 @@ M.ROUTE_18_GATE_2F = {
|
|||||||
{ "face_player" },
|
{ "face_player" },
|
||||||
{ "trade", 6, "EVENT_TRADED_SLOWBRO_FOR_LICKITUNG" }, -- MARC
|
{ "trade", 6, "EVENT_TRADED_SLOWBRO_FOR_LICKITUNG" }, -- MARC
|
||||||
},
|
},
|
||||||
|
-- Yellow replaces the youngster with a cook trading SPIKE
|
||||||
|
-- (TANGELA -> PARASECT): pokeyellow/scripts/Route18Gate2F.asm
|
||||||
|
-- Route18Gate2FCookText runs TRADE_FOR_SPIKE, index 6 in the Yellow
|
||||||
|
-- TradeMons table that Data:applyVersionedFieldData swaps in. Red
|
||||||
|
-- maps have no COOK object here and Yellow maps have no YOUNGSTER,
|
||||||
|
-- so each version only ever fires its own row (#651). Both rows
|
||||||
|
-- share the Red-flavoured done flag on purpose: a .sav tracks
|
||||||
|
-- "trade slot 6 completed" in one wCompletedInGameTradeFlags bit
|
||||||
|
-- either version reads, and the save codec maps that bit to this
|
||||||
|
-- flag name (src/save_convert/GenSave.lua EXTRA_FLAG_BITS).
|
||||||
|
TEXT_ROUTE18GATE2F_COOK = {
|
||||||
|
{ "face_player" },
|
||||||
|
{ "trade", 6, "EVENT_TRADED_SLOWBRO_FOR_LICKITUNG" }, -- SPIKE (Yellow)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,9 +12,13 @@ local function push(game, s, done)
|
|||||||
game.stack:push(TextBox.new(game, s, done))
|
game.stack:push(TextBox.new(game, s, done))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- PrintText on a text_end string returns with the box still drawn and
|
||||||
|
-- YesNoChoice then draws the menu above it (InitYesNoTextBoxParameters,
|
||||||
|
-- engine/menus/text_box.asm); no A press clears the question first. Ride
|
||||||
|
-- TextBox's opts.choice, the same as Commands.ask (#854).
|
||||||
local function ask(game, s, cb)
|
local function ask(game, s, cb)
|
||||||
local ChoiceBox = require("src.ui.ChoiceBox")
|
local TextBox = require("src.render.TextBox")
|
||||||
push(game, s, function() game.stack:push(ChoiceBox.new(game, cb)) end)
|
game.stack:push(TextBox.new(game, s, nil, { choice = cb }))
|
||||||
end
|
end
|
||||||
|
|
||||||
-- -------------------------------------------------------------------
|
-- -------------------------------------------------------------------
|
||||||
|
|||||||
@@ -18,6 +18,16 @@
|
|||||||
-- script). Leaders are not def_trainers entries, so engageTrainer has
|
-- script). Leaders are not def_trainers entries, so engageTrainer has
|
||||||
-- no header.won -- checkVictoryRewards shows this chain instead of a
|
-- no header.won -- checkVictoryRewards shows this chain instead of a
|
||||||
-- synthetic "received badge/TM" stub.
|
-- synthetic "received badge/TM" stub.
|
||||||
|
--
|
||||||
|
-- Gym entries split the TM hand-over out of `dialogue`, mirroring the
|
||||||
|
-- originals' GiveItem check (`call GiveItem` / `jr nc, .BagFull`):
|
||||||
|
-- `tmPre` is the ReceiveTM script's lead-in (badge info / "Wait! Take
|
||||||
|
-- this!"), shown at the victory and again when a beaten leader retries
|
||||||
|
-- the hand-over; `tmDialogue` shows only when the TM actually goes in
|
||||||
|
-- the bag; `noRoom` is the "make room" line shown instead when the bag
|
||||||
|
-- is full; `gotFlag` (pokered's EVENT_GOT_TM*) is set only on a
|
||||||
|
-- successful give, which is what makes the leader's talk script retry
|
||||||
|
-- later (gyms.lua).
|
||||||
|
|
||||||
local function range(prefix, first, last)
|
local function range(prefix, first, last)
|
||||||
local t = {}
|
local t = {}
|
||||||
@@ -33,6 +43,8 @@ return {
|
|||||||
-- escort NPC and the first Route 22 rival stay gone after the badge.
|
-- escort NPC and the first Route 22 rival stay gone after the badge.
|
||||||
["OPP_BROCK#1"] = { badge = "BOULDERBADGE", flag = "EVENT_BEAT_BROCK",
|
["OPP_BROCK#1"] = { badge = "BOULDERBADGE", flag = "EVENT_BEAT_BROCK",
|
||||||
item = "TM_BIDE",
|
item = "TM_BIDE",
|
||||||
|
gotFlag = "EVENT_GOT_TM34",
|
||||||
|
noRoom = "_PewterGymTM34NoRoomText",
|
||||||
deactivate = { "EVENT_BEAT_PEWTER_GYM_TRAINER_0" },
|
deactivate = { "EVENT_BEAT_PEWTER_GYM_TRAINER_0" },
|
||||||
hide = {
|
hide = {
|
||||||
{ "PEWTER_CITY", "PEWTERCITY_YOUNGSTER" },
|
{ "PEWTER_CITY", "PEWTERCITY_YOUNGSTER" },
|
||||||
@@ -41,75 +53,113 @@ return {
|
|||||||
dialogue = {
|
dialogue = {
|
||||||
"_PewterGymBrockReceivedBoulderBadgeText",
|
"_PewterGymBrockReceivedBoulderBadgeText",
|
||||||
"_PewterGymBrockBoulderBadgeInfoText",
|
"_PewterGymBrockBoulderBadgeInfoText",
|
||||||
"_PewterGymBrockWaitTakeThisText",
|
},
|
||||||
|
tmPre = { "_PewterGymBrockWaitTakeThisText" },
|
||||||
|
tmDialogue = {
|
||||||
"_PewterGymReceivedTM34Text",
|
"_PewterGymReceivedTM34Text",
|
||||||
"_TM34ExplanationText",
|
"_TM34ExplanationText",
|
||||||
} },
|
} },
|
||||||
["OPP_MISTY#1"] = { badge = "CASCADEBADGE", flag = "EVENT_BEAT_MISTY",
|
["OPP_MISTY#1"] = { badge = "CASCADEBADGE", flag = "EVENT_BEAT_MISTY",
|
||||||
item = "TM_BUBBLEBEAM",
|
item = "TM_BUBBLEBEAM",
|
||||||
|
gotFlag = "EVENT_GOT_TM11",
|
||||||
|
noRoom = "_CeruleanGymMistyTM11NoRoomText",
|
||||||
deactivate = range("EVENT_BEAT_CERULEAN_GYM_TRAINER_", 0, 1),
|
deactivate = range("EVENT_BEAT_CERULEAN_GYM_TRAINER_", 0, 1),
|
||||||
dialogue = {
|
dialogue = {
|
||||||
"_CeruleanGymMistyReceivedCascadeBadgeText",
|
"_CeruleanGymMistyReceivedCascadeBadgeText",
|
||||||
"_CeruleanGymMistyCascadeBadgeInfoText",
|
},
|
||||||
|
tmPre = { "_CeruleanGymMistyCascadeBadgeInfoText" },
|
||||||
|
tmDialogue = {
|
||||||
"_CeruleanGymMistyReceivedTM11Text",
|
"_CeruleanGymMistyReceivedTM11Text",
|
||||||
} },
|
} },
|
||||||
["OPP_LT_SURGE#1"] = { badge = "THUNDERBADGE", flag = "EVENT_BEAT_LT_SURGE",
|
["OPP_LT_SURGE#1"] = { badge = "THUNDERBADGE", flag = "EVENT_BEAT_LT_SURGE",
|
||||||
item = "TM_THUNDERBOLT",
|
item = "TM_THUNDERBOLT",
|
||||||
|
gotFlag = "EVENT_GOT_TM24",
|
||||||
|
noRoom = "_VermilionGymLTSurgeTM24NoRoomText",
|
||||||
deactivate = range("EVENT_BEAT_VERMILION_GYM_TRAINER_", 0, 2),
|
deactivate = range("EVENT_BEAT_VERMILION_GYM_TRAINER_", 0, 2),
|
||||||
dialogue = {
|
dialogue = {
|
||||||
"_VermilionGymLTSurgeReceivedThunderBadgeText",
|
"_VermilionGymLTSurgeReceivedThunderBadgeText",
|
||||||
"_VermilionGymLTSurgeThunderBadgeInfoText",
|
},
|
||||||
|
tmPre = { "_VermilionGymLTSurgeThunderBadgeInfoText" },
|
||||||
|
tmDialogue = {
|
||||||
"_VermilionGymLTSurgeReceivedTM24Text",
|
"_VermilionGymLTSurgeReceivedTM24Text",
|
||||||
"_TM24ExplanationText",
|
"_TM24ExplanationText",
|
||||||
} },
|
} },
|
||||||
["OPP_ERIKA#1"] = { badge = "RAINBOWBADGE", flag = "EVENT_BEAT_ERIKA",
|
["OPP_ERIKA#1"] = { badge = "RAINBOWBADGE", flag = "EVENT_BEAT_ERIKA",
|
||||||
item = "TM_MEGA_DRAIN",
|
item = "TM_MEGA_DRAIN",
|
||||||
|
gotFlag = "EVENT_GOT_TM21",
|
||||||
|
noRoom = "_CeladonGymTM21NoRoomText",
|
||||||
deactivate = range("EVENT_BEAT_CELADON_GYM_TRAINER_", 0, 6),
|
deactivate = range("EVENT_BEAT_CELADON_GYM_TRAINER_", 0, 6),
|
||||||
dialogue = {
|
dialogue = {
|
||||||
"_CeladonGymErikaReceivedRainbowBadgeText",
|
"_CeladonGymErikaReceivedRainbowBadgeText",
|
||||||
"_CeladonGymRainbowBadgeInfoText",
|
},
|
||||||
|
tmPre = { "_CeladonGymRainbowBadgeInfoText" },
|
||||||
|
tmDialogue = {
|
||||||
"_CeladonGymReceivedTM21Text",
|
"_CeladonGymReceivedTM21Text",
|
||||||
"_TM21ExplanationText",
|
"_TM21ExplanationText",
|
||||||
} },
|
} },
|
||||||
["OPP_KOGA#1"] = { badge = "SOULBADGE", flag = "EVENT_BEAT_KOGA",
|
["OPP_KOGA#1"] = { badge = "SOULBADGE", flag = "EVENT_BEAT_KOGA",
|
||||||
item = "TM_TOXIC",
|
item = "TM_TOXIC",
|
||||||
|
gotFlag = "EVENT_GOT_TM06",
|
||||||
|
noRoom = "_FuchsiaGymKogaTM06NoRoomText",
|
||||||
deactivate = range("EVENT_BEAT_FUCHSIA_GYM_TRAINER_", 0, 5),
|
deactivate = range("EVENT_BEAT_FUCHSIA_GYM_TRAINER_", 0, 5),
|
||||||
dialogue = {
|
dialogue = {
|
||||||
"_FuchsiaGymKogaReceivedSoulBadgeText",
|
"_FuchsiaGymKogaReceivedSoulBadgeText",
|
||||||
"_FuchsiaGymKogaSoulBadgeInfoText",
|
},
|
||||||
|
tmPre = { "_FuchsiaGymKogaSoulBadgeInfoText" },
|
||||||
|
tmDialogue = {
|
||||||
"_FuchsiaGymKogaReceivedTM06Text",
|
"_FuchsiaGymKogaReceivedTM06Text",
|
||||||
"_FuchsiaGymKogaTM06ExplanationText",
|
"_FuchsiaGymKogaTM06ExplanationText",
|
||||||
} },
|
} },
|
||||||
["OPP_SABRINA#1"] = { badge = "MARSHBADGE", flag = "EVENT_BEAT_SABRINA",
|
["OPP_SABRINA#1"] = { badge = "MARSHBADGE", flag = "EVENT_BEAT_SABRINA",
|
||||||
item = "TM_PSYWAVE",
|
item = "TM_PSYWAVE",
|
||||||
|
gotFlag = "EVENT_GOT_TM46",
|
||||||
|
noRoom = "_SaffronGymSabrinaTM46NoRoomText",
|
||||||
deactivate = range("EVENT_BEAT_SAFFRON_GYM_TRAINER_", 0, 6),
|
deactivate = range("EVENT_BEAT_SAFFRON_GYM_TRAINER_", 0, 6),
|
||||||
dialogue = {
|
dialogue = {
|
||||||
"_SaffronGymSabrinaReceivedMarshBadgeText",
|
"_SaffronGymSabrinaReceivedMarshBadgeText",
|
||||||
"_SaffronGymSabrinaMarshBadgeInfoText",
|
},
|
||||||
|
tmPre = { "_SaffronGymSabrinaMarshBadgeInfoText" },
|
||||||
|
tmDialogue = {
|
||||||
"_SaffronGymSabrinaReceivedTM46Text",
|
"_SaffronGymSabrinaReceivedTM46Text",
|
||||||
"_TM46ExplanationText",
|
"_TM46ExplanationText",
|
||||||
} },
|
} },
|
||||||
["OPP_BLAINE#1"] = { badge = "VOLCANOBADGE", flag = "EVENT_BEAT_BLAINE",
|
["OPP_BLAINE#1"] = { badge = "VOLCANOBADGE", flag = "EVENT_BEAT_BLAINE",
|
||||||
item = "TM_FIRE_BLAST",
|
item = "TM_FIRE_BLAST",
|
||||||
|
gotFlag = "EVENT_GOT_TM38",
|
||||||
|
noRoom = "_CinnabarGymBlaineTM38NoRoomText",
|
||||||
deactivate = range("EVENT_BEAT_CINNABAR_GYM_TRAINER_", 0, 6),
|
deactivate = range("EVENT_BEAT_CINNABAR_GYM_TRAINER_", 0, 6),
|
||||||
dialogue = {
|
dialogue = {
|
||||||
"_CinnabarGymBlaineReceivedVolcanoBadgeText",
|
"_CinnabarGymBlaineReceivedVolcanoBadgeText",
|
||||||
"_CinnabarGymBlaineVolcanoBadgeInfoText",
|
},
|
||||||
|
tmPre = { "_CinnabarGymBlaineVolcanoBadgeInfoText" },
|
||||||
|
tmDialogue = {
|
||||||
"_CinnabarGymBlaineReceivedTM38Text",
|
"_CinnabarGymBlaineReceivedTM38Text",
|
||||||
"_CinnabarGymBlaineTM38ExplanationText",
|
"_CinnabarGymBlaineTM38ExplanationText",
|
||||||
} },
|
} },
|
||||||
["OPP_GIOVANNI#3"] = { badge = "EARTHBADGE", flag = "EVENT_BEAT_GIOVANNI",
|
["OPP_GIOVANNI#3"] = { badge = "EARTHBADGE", flag = "EVENT_BEAT_GIOVANNI",
|
||||||
item = "TM_FISSURE",
|
item = "TM_FISSURE",
|
||||||
|
gotFlag = "EVENT_GOT_TM27",
|
||||||
|
noRoom = "_ViridianGymGiovanniTM27NoRoomText",
|
||||||
deactivate = range("EVENT_BEAT_VIRIDIAN_GYM_TRAINER_", 0, 7),
|
deactivate = range("EVENT_BEAT_VIRIDIAN_GYM_TRAINER_", 0, 7),
|
||||||
dialogue = {
|
dialogue = {
|
||||||
"_ViridianGymGiovanniReceivedEarthBadgeText",
|
"_ViridianGymGiovanniReceivedEarthBadgeText",
|
||||||
"_ViridianGymGiovanniEarthBadgeInfoText",
|
},
|
||||||
|
tmPre = { "_ViridianGymGiovanniEarthBadgeInfoText" },
|
||||||
|
tmDialogue = {
|
||||||
"_ViridianGymGiovanniReceivedTM27Text",
|
"_ViridianGymGiovanniReceivedTM27Text",
|
||||||
"_ViridianGymGiovanniTM27ExplanationText",
|
"_ViridianGymGiovanniTM27ExplanationText",
|
||||||
} },
|
} },
|
||||||
|
|
||||||
-- Silph Co. Giovanni: unlocks the president's Master Ball gift
|
-- Silph Co. Giovanni: unlocks the president's Master Ball gift.
|
||||||
["OPP_GIOVANNI#2"] = { flag = "EVENT_BEAT_SILPH_CO_GIOVANNI" },
|
-- SilphCo11FGiovanniStartBattleScript (scripts/SilphCo11F.asm) hands the
|
||||||
|
-- battle SilphCo10FGiovanniILostAgainText through SaveEndBattleTextPointers,
|
||||||
|
-- but he has no def_trainers header on 11F, so engageTrainer finds no
|
||||||
|
-- header.won to give it -- this chain is the port's stand-in for that loss
|
||||||
|
-- line (#722). The "Blast it all!" speech, the fade and the rockets
|
||||||
|
-- leaving are SilphCo11FGiovanniAfterBattleScript, ported in M.SILPH_CO_11F
|
||||||
|
-- (data/scripts/story.lua).
|
||||||
|
["OPP_GIOVANNI#2"] = { flag = "EVENT_BEAT_SILPH_CO_GIOVANNI",
|
||||||
|
dialogue = { "_SilphCo10FGiovanniILostAgainText" } },
|
||||||
|
|
||||||
-- Fighting Dojo Karate Master (scripts/FightingDojo.asm
|
-- Fighting Dojo Karate Master (scripts/FightingDojo.asm
|
||||||
-- FightingDojoKarateMasterPostBattleScript sets EVENT_BEAT_KARATE_MASTER,
|
-- FightingDojoKarateMasterPostBattleScript sets EVENT_BEAT_KARATE_MASTER,
|
||||||
|
|||||||
@@ -62,10 +62,15 @@ M.MT_MOON_B2F = {
|
|||||||
{ "walk_npc", 6, { "left", "left", "left", "left", "left" } },
|
{ "walk_npc", 6, { "left", "left", "left", "left", "left" } },
|
||||||
{ "face_object", 6, "left" },
|
{ "face_object", 6, "left" },
|
||||||
{ "show_text", "_MtMoonJessieJamesText2" },
|
{ "show_text", "_MtMoonJessieJamesText2" },
|
||||||
|
-- MtMoonB2FScript12 arms _MtMoonJessieJamesText3 with
|
||||||
|
-- SaveEndBattleTextPointers before it sets wCurOpponent, so
|
||||||
|
-- TrainerBattleVictory prints it on the battle screen as "ROCKET: A
|
||||||
|
-- brat beat us?" between TrainerDefeatedText and MoneyForWinningText.
|
||||||
|
-- Its one-word first line only reads right behind that tag (#866).
|
||||||
|
{ "save_end_battle_text", "_MtMoonJessieJamesText3" },
|
||||||
{ "start_battle", "trainer", "OPP_ROCKET", 42 },
|
{ "start_battle", "trainer", "OPP_ROCKET", 42 },
|
||||||
{ "check_battle_result", "win" },
|
{ "check_battle_result", "win" },
|
||||||
{ "jump_if_false", "end" },
|
{ "jump_if_false", "end" },
|
||||||
{ "show_text", "_MtMoonJessieJamesText3" },
|
|
||||||
{ "show_text", "_MtMoonJessieJamesText4" },
|
{ "show_text", "_MtMoonJessieJamesText4" },
|
||||||
{ "stop_music" },
|
{ "stop_music" },
|
||||||
{ "play_music", "Music_MeetJessieJames" },
|
{ "play_music", "Music_MeetJessieJames" },
|
||||||
@@ -85,7 +90,8 @@ M.MT_MOON_B2F = {
|
|||||||
-- motto plays from off-screen FIRST, then the duo pops in at (25,10) /
|
-- motto plays from off-screen FIRST, then the duo pops in at (25,10) /
|
||||||
-- (24,10) and whichever of them shares the player's column ($18=24 or
|
-- (24,10) and whichever of them shares the player's column ($18=24 or
|
||||||
-- $19=25, EVENT_ROCKET_HIDEOUT_4_JESSIE_JAMES_ON_LEFT) walks the three
|
-- $19=25, EVENT_ROCKET_HIDEOUT_4_JESSIE_JAMES_ON_LEFT) walks the three
|
||||||
-- tiles down to loom over the player while the other steps one. A loss
|
-- tiles down to loom over the player while the other walks four and ends
|
||||||
|
-- up beside him. A loss
|
||||||
-- re-hides them (RocketHideoutB4FResetScripts via EVENT_6A0), so the
|
-- re-hides them (RocketHideoutB4FResetScripts via EVENT_6A0), so the
|
||||||
-- trigger re-arms clean.
|
-- trigger re-arms clean.
|
||||||
-- -------------------------------------------------------------------
|
-- -------------------------------------------------------------------
|
||||||
@@ -106,7 +112,7 @@ M.ROCKET_HIDEOUT_B4F = {
|
|||||||
if f.EVENT_BEAT_ROCKET_HIDEOUT_4_JESSIE_JAMES then return false end
|
if f.EVENT_BEAT_ROCKET_HIDEOUT_4_JESSIE_JAMES then return false end
|
||||||
-- ON_LEFT: player under James's column (25); movement data pairs
|
-- ON_LEFT: player under James's column (25); movement data pairs
|
||||||
-- RocketHideoutB4FJessieJamesMovementData_45605/45606 swap so the
|
-- RocketHideoutB4FJessieJamesMovementData_45605/45606 swap so the
|
||||||
-- column-mate walks 3, the other 1.
|
-- column-mate walks 3, the other 4.
|
||||||
local onLeft = (x == 25)
|
local onLeft = (x == 25)
|
||||||
ow.runner:run({
|
ow.runner:run({
|
||||||
{ "stop_music" },
|
{ "stop_music" },
|
||||||
@@ -116,16 +122,30 @@ M.ROCKET_HIDEOUT_B4F = {
|
|||||||
{ "emote", "player", "shock", 30 },
|
{ "emote", "player", "shock", 30 },
|
||||||
{ "show_object", "ROCKET_HIDEOUT_B4F", "ROCKETHIDEOUTB4F_JAMES" },
|
{ "show_object", "ROCKET_HIDEOUT_B4F", "ROCKETHIDEOUTB4F_JAMES" },
|
||||||
{ "show_object", "ROCKET_HIDEOUT_B4F", "ROCKETHIDEOUTB4F_JESSIE" },
|
{ "show_object", "ROCKET_HIDEOUT_B4F", "ROCKETHIDEOUTB4F_JESSIE" },
|
||||||
-- James (object 2) then Jessie (object 3), Script4..Script9 order
|
-- James (object 2) then Jessie (object 3), Script4..Script9 order.
|
||||||
{ "walk_npc", 2, onLeft and { "down", "down", "down" } or { "down" } },
|
-- RocketHideoutB4FJessieJamesMovementData_45605 is a lone $4 that FALLS
|
||||||
|
-- THROUGH into _45606 ($4 $4 $4 $ff), so MoveSprite_ (home/pathfinding.asm)
|
||||||
|
-- reads _45605 as FOUR steps and _45606 as three; $4 is DOWN in Yellow's
|
||||||
|
-- Func_5288 lookup (engine/overworld/movement.asm), which walks with no
|
||||||
|
-- collision test. From (25,10)/(24,10) against a player on y=14 the
|
||||||
|
-- column-mate stops three down, right above him, and the other walks the
|
||||||
|
-- full four to stand alongside -- which is what the facings below assume.
|
||||||
|
-- Reading _45605 as a single step stranded whoever was off-column three
|
||||||
|
-- tiles away, so James never reached the player (#865).
|
||||||
|
{ "walk_npc", 2, onLeft and { "down", "down", "down" }
|
||||||
|
or { "down", "down", "down", "down" } },
|
||||||
{ "face_object", 2, onLeft and "down" or "left" },
|
{ "face_object", 2, onLeft and "down" or "left" },
|
||||||
{ "walk_npc", 3, onLeft and { "down" } or { "down", "down", "down" } },
|
{ "walk_npc", 3, onLeft and { "down", "down", "down", "down" }
|
||||||
|
or { "down", "down", "down" } },
|
||||||
{ "face_object", 3, onLeft and "right" or "down" },
|
{ "face_object", 3, onLeft and "right" or "down" },
|
||||||
{ "show_text", "_RocketHideoutJessieJamesText2" },
|
{ "show_text", "_RocketHideoutJessieJamesText2" },
|
||||||
|
-- RocketHideoutB4FScript10 saves _RocketHideoutJessieJamesText3 as the
|
||||||
|
-- end-battle text, so it prints as "ROCKET: Such a dreadful twerp!" on
|
||||||
|
-- the battle screen ahead of MoneyForWinningText (#866).
|
||||||
|
{ "save_end_battle_text", "_RocketHideoutJessieJamesText3" },
|
||||||
{ "start_battle", "trainer", "OPP_ROCKET", 43 },
|
{ "start_battle", "trainer", "OPP_ROCKET", 43 },
|
||||||
{ "check_battle_result", "win" },
|
{ "check_battle_result", "win" },
|
||||||
{ "jump_if_false", "lost" },
|
{ "jump_if_false", "lost" },
|
||||||
{ "show_text", "_RocketHideoutJessieJamesText3" },
|
|
||||||
{ "show_text", "_RocketHideoutJessieJamesText4" },
|
{ "show_text", "_RocketHideoutJessieJamesText4" },
|
||||||
{ "stop_music" },
|
{ "stop_music" },
|
||||||
{ "play_music", "Music_MeetJessieJames" },
|
{ "play_music", "Music_MeetJessieJames" },
|
||||||
@@ -175,16 +195,27 @@ M.POKEMON_TOWER_7F = {
|
|||||||
{ "show_text", "_PokemonTowerJessieJamesText1" },
|
{ "show_text", "_PokemonTowerJessieJamesText1" },
|
||||||
{ "face_player_dir", "up" },
|
{ "face_player_dir", "up" },
|
||||||
{ "emote", "player", "shock", 30 },
|
{ "emote", "player", "shock", 30 },
|
||||||
-- Jessie (object 1) then James (object 2), Script1..Script6 order
|
-- Jessie (object 1) then James (object 2), Script1..Script6 order.
|
||||||
{ "walk_npc", 1, onLeft and { "down" } or { "down", "down", "down" } },
|
-- Same fall-through blob as the hideout: PokemonTower7FMovementData_60d7a
|
||||||
|
-- is a lone $4 running into _60d7b ($4 $4 $4 $FF), so _60d7a is FOUR
|
||||||
|
-- steps and _60d7b is three. From (10,8)/(11,8) against a player on
|
||||||
|
-- y=12 the column-mate halts one tile above him and the other closes the
|
||||||
|
-- full four to his side; the single-step reading is why James only
|
||||||
|
-- "moved a bit" here (#865).
|
||||||
|
{ "walk_npc", 1, onLeft and { "down", "down", "down", "down" }
|
||||||
|
or { "down", "down", "down" } },
|
||||||
{ "face_object", 1, onLeft and "right" or "down" },
|
{ "face_object", 1, onLeft and "right" or "down" },
|
||||||
{ "walk_npc", 2, onLeft and { "down", "down", "down" } or { "down" } },
|
{ "walk_npc", 2, onLeft and { "down", "down", "down" }
|
||||||
|
or { "down", "down", "down", "down" } },
|
||||||
{ "face_object", 2, onLeft and "down" or "left" },
|
{ "face_object", 2, onLeft and "down" or "left" },
|
||||||
{ "show_text", "_PokemonTowerJessieJamesText2" },
|
{ "show_text", "_PokemonTowerJessieJamesText2" },
|
||||||
|
-- PokemonTower7FScript7 saves _PokemonTowerJessieJamesText3 as the
|
||||||
|
-- end-battle text: "ROCKET: You will regret this!" on the battle screen,
|
||||||
|
-- before the prize money (#866).
|
||||||
|
{ "save_end_battle_text", "_PokemonTowerJessieJamesText3" },
|
||||||
{ "start_battle", "trainer", "OPP_ROCKET", 44 },
|
{ "start_battle", "trainer", "OPP_ROCKET", 44 },
|
||||||
{ "check_battle_result", "win" },
|
{ "check_battle_result", "win" },
|
||||||
{ "jump_if_false", "end" },
|
{ "jump_if_false", "end" },
|
||||||
{ "show_text", "_PokemonTowerJessieJamesText3" },
|
|
||||||
{ "show_text", "_PokemonTowerJessieJamesText4" },
|
{ "show_text", "_PokemonTowerJessieJamesText4" },
|
||||||
{ "stop_music" },
|
{ "stop_music" },
|
||||||
{ "play_music", "Music_MeetJessieJames" },
|
{ "play_music", "Music_MeetJessieJames" },
|
||||||
@@ -254,10 +285,12 @@ M.SILPH_CO_11F = {
|
|||||||
{ "walk_npc", 6, jessieDirs },
|
{ "walk_npc", 6, jessieDirs },
|
||||||
{ "face_object", 6, jessieFace },
|
{ "face_object", 6, jessieFace },
|
||||||
{ "show_text", "_SilphCoJessieJamesText2" },
|
{ "show_text", "_SilphCoJessieJamesText2" },
|
||||||
|
-- SilphCo11FScript11 saves _SilphCoJessieJamesText3 (SilphCo11FText_624c2)
|
||||||
|
-- as the end-battle text: "ROCKET: Like always..." before the money (#866).
|
||||||
|
{ "save_end_battle_text", "_SilphCoJessieJamesText3" },
|
||||||
{ "start_battle", "trainer", "OPP_ROCKET", 45 },
|
{ "start_battle", "trainer", "OPP_ROCKET", 45 },
|
||||||
{ "check_battle_result", "win" },
|
{ "check_battle_result", "win" },
|
||||||
{ "jump_if_false", "end" },
|
{ "jump_if_false", "end" },
|
||||||
{ "show_text", "_SilphCoJessieJamesText3" },
|
|
||||||
{ "show_text", "_SilphCoJessieJamesText4" },
|
{ "show_text", "_SilphCoJessieJamesText4" },
|
||||||
{ "stop_music" },
|
{ "stop_music" },
|
||||||
{ "play_music", "Music_MeetJessieJames" },
|
{ "play_music", "Music_MeetJessieJames" },
|
||||||
|
|||||||
@@ -24,7 +24,8 @@
|
|||||||
-- ViridianCityPostInitialCatchTraining): stepping into (19,9) -- the gap
|
-- ViridianCityPostInitialCatchTraining): stepping into (19,9) -- the gap
|
||||||
-- east of the sleeper's cell -- faces the old man right and the player
|
-- east of the sleeper's cell -- faces the old man right and the player
|
||||||
-- left, prints the apology, and without any choice runs the demo battle
|
-- left, prints the apology, and without any choice runs the demo battle
|
||||||
-- (BATTLE_TYPE_OLD_MAN, RATTATA lvl 5). After it, the same text pointer
|
-- (BATTLE_TYPE_OLD_MAN, RATTATA lvl 5), which he FAILS -- the ball shakes
|
||||||
|
-- three times and breaks open. After it, the same text pointer
|
||||||
-- now prints _ViridianCityOldManLosingMyTouchText ("That didn't work!
|
-- now prints _ViridianCityOldManLosingMyTouchText ("That didn't work!
|
||||||
-- I must be losing my touch."), the old man walks off (down 6 with the
|
-- I must be losing my touch."), the old man walks off (down 6 with the
|
||||||
-- player on (19,9), right 1 otherwise, Pikachu nudged out of the way
|
-- player on (19,9), right 1 otherwise, Pikachu nudged out of the way
|
||||||
@@ -63,7 +64,12 @@ end
|
|||||||
local function oldMan2Rows(game, ow, npc)
|
local function oldMan2Rows(game, ow, npc)
|
||||||
local rows = {
|
local rows = {
|
||||||
{ "show_text", "_ViridianCityOldManHadMyCoffeeNowText" },
|
{ "show_text", "_ViridianCityOldManHadMyCoffeeNowText" },
|
||||||
{ "old_man_demo" },
|
-- ViridianCityOldManInitialCatchTrainingScript sets
|
||||||
|
-- EVENT_INITIAL_CATCH_TRAINING before the battle runs, and
|
||||||
|
-- ItemUseBall's .oldManBattle branch turns that event into anim data
|
||||||
|
-- $63: three shakes, then the ball breaks open. The losing-my-touch
|
||||||
|
-- line below only follows a throw that failed (#636).
|
||||||
|
{ "old_man_demo", "fail" },
|
||||||
{ "set_flag", "EVENT_COMPLETED_CATCH_TRAINING" },
|
{ "set_flag", "EVENT_COMPLETED_CATCH_TRAINING" },
|
||||||
{ "show_text", "_ViridianCityOldManLosingMyTouchText" },
|
{ "show_text", "_ViridianCityOldManLosingMyTouchText" },
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -234,8 +234,12 @@ What was ported from pokered's engine code and where it came from.
|
|||||||
pre-battle text and engages the leader battle (badge/TM via
|
pre-battle text and engages the leader battle (badge/TM via
|
||||||
data/scripts/victories.lua); post-badge talk prints the leader's
|
data/scripts/victories.lua); post-badge talk prints the leader's
|
||||||
post-battle advice text (Misty's is her TM11 explanation). The
|
post-battle advice text (Misty's is her TM11 explanation). The
|
||||||
originals' middle branch (beaten but TM not handed over) is
|
originals' middle branch (beaten but TM not handed over,
|
||||||
unreachable since the TM is granted with the victory. Giovanni's
|
CheckEventReuseA EVENT_GOT_TM*) is ported too: the victory's GiveItem
|
||||||
|
goes through the bag's capacity check, a full bag shows the leader's
|
||||||
|
"make room" text instead of the received lines and leaves
|
||||||
|
EVENT_GOT_TM* unset, and talking to the leader re-runs the ReceiveTM
|
||||||
|
script until the TM goes in (#797). Giovanni's
|
||||||
farewell (`ViridianGymGiovanniText` .afterBeat) hides him inside a
|
farewell (`ViridianGymGiovanniText` .afterBeat) hides him inside a
|
||||||
fade-to-black/fade-in Transition matching ViridianGym.asm's
|
fade-to-black/fade-in Transition matching ViridianGym.asm's
|
||||||
GBFadeOutToBlack → HideObject → GBFadeInFromBlack, persisted
|
GBFadeOutToBlack → HideObject → GBFadeInFromBlack, persisted
|
||||||
|
|||||||
@@ -45,6 +45,9 @@ same system picker and install the chosen archive on return.
|
|||||||
draws one chip per game plus a MODS chip and rebuilds `self.tabRects` every
|
draws one chip per game plus a MODS chip and rebuilds `self.tabRects` every
|
||||||
frame so `mousepressed` can dispatch clicks; switching tabs mid-import is
|
frame so `mousepressed` can dispatch clicks; switching tabs mid-import is
|
||||||
allowed (a dropped ROM still routes by SHA-1 regardless of which tab shows).
|
allowed (a dropped ROM still routes by SHA-1 regardless of which tab shows).
|
||||||
|
On **NX**, **Scan again** is stricter: it only starts an import whose SHA-1
|
||||||
|
matches the open game tab, so a shared `imports/` folder with Red+Yellow
|
||||||
|
cannot jump Yellow → Red.
|
||||||
|
|
||||||
- A game tab (`_drawGamePanel`) shows the ROM card, the SAVE FILES card, the
|
- A game tab (`_drawGamePanel`) shows the ROM card, the SAVE FILES card, the
|
||||||
Play button, and the SAVE SLOT card in a responsive two-column grid (see
|
Play button, and the SAVE SLOT card in a responsive two-column grid (see
|
||||||
@@ -150,6 +153,17 @@ before `Game:load`, so **it never loads a mod's entry chunk**; only
|
|||||||
- `LauncherMods.uninstall(id)` removes `mods/<id>/` and clears
|
- `LauncherMods.uninstall(id)` removes `mods/<id>/` and clears
|
||||||
`options.mods[id]` so a later reinstall starts from the loader's default
|
`options.mods[id]` so a later reinstall starts from the loader's default
|
||||||
(enabled). The mods panel Delete control calls this and re-derives the list.
|
(enabled). The mods panel Delete control calls this and re-derives the list.
|
||||||
|
- A mod that declares `github` shows its total GitHub downloads (every
|
||||||
|
release's summed asset `download_count`, from the same cached release
|
||||||
|
fetch the update check uses) as a highlighted body line like "12,345
|
||||||
|
downloads across all releases - Released 2024-05-31 - Updated 2026-07-01"
|
||||||
|
(first and latest `published_at`). Old cache entries written before the
|
||||||
|
counts existed show no line rather than a wrong zero; a manual check
|
||||||
|
refreshes them.
|
||||||
|
- The MODS panel sorts its rows by Name, Popularity (downloads),
|
||||||
|
Release date (first release), or Last updated, chosen by chips under the
|
||||||
|
header and persisted in `options.modSort`. Mods without release data
|
||||||
|
(no `github` field, or a stale cache) sink to the bottom of data sorts.
|
||||||
|
|
||||||
## Import / Export save
|
## Import / Export save
|
||||||
|
|
||||||
@@ -157,9 +171,14 @@ The SAVE FILES card wires a raw Gen1 `.sav` battery image to the save slots
|
|||||||
through `src/import/SaveFileIO.lua`, which sits on top of
|
through `src/import/SaveFileIO.lua`, which sits on top of
|
||||||
`src/save_convert/SaveConvert.lua` and the slot API in `SaveData`.
|
`src/save_convert/SaveConvert.lua` and the slot API in `SaveData`.
|
||||||
|
|
||||||
- **Import save** is live once the game's ROM is imported (playable). It opens
|
- **Import save** is live once the game's ROM is imported (playable).
|
||||||
a native `.sav` picker (`chooseSav` on desktop; on Android,
|
On desktop it opens a native `.sav` picker (`chooseSav`); on Android,
|
||||||
`love.system.pickFile("sav")` → `picked_save.sav`, same SAF path as ROMs).
|
`love.system.pickFile("sav")` → `picked_save.sav`, same SAF path as ROMs.
|
||||||
|
On **NX (Switch)** there is no picker: copy a `.sav` into
|
||||||
|
`getSaveDirectory()/imports/saves/<red|blue|yellow>/` via MTP / SD / FTP
|
||||||
|
(one folder per game), then press **Import save** on that game’s tab to
|
||||||
|
ensure the inbox and rescan (same pattern as the ROM `imports/` and mod
|
||||||
|
`imports/mods/` inboxes). Hidden `._*.sav` AppleDouble sidecars are skipped.
|
||||||
`SaveFileIO.importToSlot` reads the bytes (an absolute path, a save-dir
|
`SaveFileIO.importToSlot` reads the bytes (an absolute path, a save-dir
|
||||||
relative name, a dropped LOVE file, or raw bytes),
|
relative name, a dropped LOVE file, or raw bytes),
|
||||||
guards the 32768-byte size, runs `SaveConvert.importSav` (which also rejects
|
guards the 32768-byte size, runs `SaveConvert.importSav` (which also rejects
|
||||||
@@ -167,19 +186,29 @@ through `src/import/SaveFileIO.lua`, which sits on top of
|
|||||||
writes it (`SaveData.writeSlot`), and makes it active (`SaveData.setActiveSlot`).
|
writes it (`SaveData.writeSlot`), and makes it active (`SaveData.setActiveSlot`).
|
||||||
The meta stamp is re-stamped off `gen1_import` to the current numeric format
|
The meta stamp is re-stamped off `gen1_import` to the current numeric format
|
||||||
so `SaveData.load`'s migration pass accepts the slot. On success the SAVE SLOT
|
so `SaveData.load`'s migration pass accepts the slot. On success the SAVE SLOT
|
||||||
panel is refreshed with the new slot selected.
|
panel is refreshed with the new slot selected. On **NX**, a successful inbox
|
||||||
|
import retires the file to `*.sav.imported` and records a content hash in
|
||||||
|
`imports/saves/<game>/.imported-sha1` so a second **Import save** (or the same
|
||||||
|
bytes under a new name) does not clone slots; failures leave the original
|
||||||
|
`.sav`. Only that game’s folder is scanned.
|
||||||
- **Export save** is live only when the active slot actually holds a save
|
- **Export save** is live only when the active slot actually holds a save
|
||||||
(checked against `listSlots`). `SaveFileIO.exportActiveSlot` loads the active
|
(checked against `listSlots`). `SaveFileIO.exportActiveSlot` loads the active
|
||||||
slot, encodes it back with `SaveConvert.exportSav` (a slot never keeps
|
slot, encodes it back with `SaveConvert.exportSav` (a slot never keeps
|
||||||
`rawImport`, so this is a zero-filled template export, which is valid), and
|
`rawImport`, so this is a zero-filled template export, which is valid), and
|
||||||
writes `exports/gen1recomp-<version>-<slotId>.sav` in the save directory
|
writes `exports/<version>/gen1recomp-<version>-<slotId>.sav` under the same
|
||||||
(`love.filesystem.createDirectory("exports")`). On desktop it returns the
|
root `persistFs` writes slots to: the portable game folder when `portable.txt`
|
||||||
absolute path (`love.filesystem.getSaveDirectory()`), which the notice line
|
marks the install, otherwise the save directory (`exports/` and
|
||||||
shows with an "Open folder" affordance (`love.system.openURL("file://" .. dir)`).
|
`exports/<version>/` are created as needed; #752). On desktop it returns the
|
||||||
|
absolute path (`SaveData.portableBaseDir()` when portable, else
|
||||||
|
`love.filesystem.getSaveDirectory()`), which the notice line shows with an
|
||||||
|
"Open folder" affordance (`love.system.openURL("file://" .. dir)`).
|
||||||
On Android the bytes are also staged as `pending_export.sav` and
|
On Android the bytes are also staged as `pending_export.sav` and
|
||||||
`love.system.createFile(suggestedName)` opens `ACTION_CREATE_DOCUMENT` so the
|
`love.system.createFile(suggestedName)` opens `ACTION_CREATE_DOCUMENT` so the
|
||||||
player can save to Downloads / Drive / etc.; on return `export_done.flag`
|
player can save to Downloads / Drive / etc.; on return `export_done.flag`
|
||||||
makes focus show "Save exported."
|
makes focus show "Save exported."
|
||||||
|
On **NX**, export success sets a notice with the `exports/<game>/` path and an
|
||||||
|
MTP-oriented hint — no `openURL` / Open folder (pull the file via MTP /
|
||||||
|
SD / FTP instead).
|
||||||
- **Drag-drop.** `filedropped` routes a `.sav` to the import path for the
|
- **Drag-drop.** `filedropped` routes a `.sav` to the import path for the
|
||||||
currently active game tab; when a non-game tab (mods, or the locked yellow
|
currently active game tab; when a non-game tab (mods, or the locked yellow
|
||||||
placeholder) is showing it defaults to red, the always-present first game
|
placeholder) is showing it defaults to red, the always-present first game
|
||||||
|
|||||||
@@ -0,0 +1,202 @@
|
|||||||
|
# Linux arm64 (aarch64) AppImage
|
||||||
|
|
||||||
|
Releases ship `gen1recomp-<version>-linux-arm64.AppImage` alongside the
|
||||||
|
existing x86_64 `gen1recomp-<version>-linux.zip`. It targets 64-bit ARM
|
||||||
|
desktop Linux: Raspberry Pi 4/5 running Raspberry Pi OS, Armbian and other
|
||||||
|
SBC distros, arm64 VMs on Apple Silicon, Ampere/Graviton desktops, and the
|
||||||
|
aarch64 handhelds that run a full distro.
|
||||||
|
|
||||||
|
> The Anbernic RG34XXSP has its own PortMaster-style pack
|
||||||
|
> (`gen1recomp-*-rg34xxsp-stockos64-mod.zip`, see
|
||||||
|
> [anbernic-rg34xxsp.md](anbernic-rg34xxsp.md)). That one bundles PortMaster's
|
||||||
|
> LÖVE runtime and expects the device's own SDL; this AppImage is the generic
|
||||||
|
> desktop-Linux artifact and shares nothing with it but the `game.love`.
|
||||||
|
|
||||||
|
## For players
|
||||||
|
|
||||||
|
```sh
|
||||||
|
chmod +x gen1recomp-*-linux-arm64.AppImage
|
||||||
|
./gen1recomp-*-linux-arm64.AppImage
|
||||||
|
```
|
||||||
|
|
||||||
|
Then use **Import ROM** in the launcher to point it at your own legal Red /
|
||||||
|
Blue / Yellow cartridge dump, exactly as on every other platform.
|
||||||
|
|
||||||
|
If your system has no FUSE (`dlopen(): error loading libfuse.so.2`), either
|
||||||
|
install it (`sudo apt install libfuse2`) or run without it:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./gen1recomp-*-linux-arm64.AppImage --appimage-extract-and-run
|
||||||
|
```
|
||||||
|
|
||||||
|
### What the host has to provide
|
||||||
|
|
||||||
|
Very little, and this is enforced by an assertion in the build rather than by
|
||||||
|
good intentions. The only libraries the AppImage requires at startup are:
|
||||||
|
|
||||||
|
```
|
||||||
|
glibc 2.29+ libstdc++ libfreetype6 zlib
|
||||||
|
```
|
||||||
|
|
||||||
|
Everything else — OpenGL/Mesa, X11, Wayland, KMSDRM, ALSA, PulseAudio — is
|
||||||
|
**dlopened**, so it is used when present and skipped when absent. That means
|
||||||
|
one image runs on a full desktop, on a Wayland-only session, on a
|
||||||
|
KMSDRM-only handheld with no X server, and on a box with ALSA but no
|
||||||
|
PulseAudio, without a different build for each.
|
||||||
|
|
||||||
|
That property does not come for free from Debian's packages, and getting it
|
||||||
|
is most of what the build below is doing; see
|
||||||
|
[Why five libraries are built from source](#why-five-libraries-are-built-from-source).
|
||||||
|
|
||||||
|
## For builders
|
||||||
|
|
||||||
|
```sh
|
||||||
|
scripts/build_linux_arm64.sh --version 0.1.0
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```
|
||||||
|
dist/linux-arm64/gen1recomp-<version>-linux-arm64.AppImage
|
||||||
|
dist/linux-arm64/gen1recomp-<version>-linux-arm64.AppImage.sha256
|
||||||
|
```
|
||||||
|
|
||||||
|
Useful flags: `--game-love PATH` reuses an already-packed payload (CI does
|
||||||
|
this so every platform ships identical bytes), `--rebuild-image` forces the
|
||||||
|
builder container to rebuild, `--clean-cache` throws away the pinned
|
||||||
|
downloads and the compiled LÖVE prefix.
|
||||||
|
|
||||||
|
### Requirements
|
||||||
|
|
||||||
|
An **aarch64 host** with **docker or podman**. A Raspberry Pi 5 is the
|
||||||
|
reference machine (a cold build takes about 10 minutes on one — six libraries
|
||||||
|
plus the engine; rebuilds reuse the cached prefix and take seconds). Apple Silicon with Docker
|
||||||
|
Desktop and GitHub's `ubuntu-24.04-arm` runner both work too.
|
||||||
|
|
||||||
|
The script refuses to run on x86_64 rather than falling back to qemu-user
|
||||||
|
emulation: that path takes hours and has produced miscompiled LuaJIT.
|
||||||
|
|
||||||
|
### Why this is not just another `scripts/build.sh` target
|
||||||
|
|
||||||
|
`scripts/build.sh linux` downloads LÖVE's official `love-11.5-x86_64.AppImage`,
|
||||||
|
unpacks its squashfs, drops `game.love` in, and glues it back together. That
|
||||||
|
trick is not available here — **LÖVE publishes no aarch64 binary at all.** The
|
||||||
|
11.5 release has win32, win64, macOS, Android, iOS and one x86_64 AppImage,
|
||||||
|
and that is the entire list.
|
||||||
|
|
||||||
|
So this build compiles LÖVE 11.5 from the official `linux-src` tarball and
|
||||||
|
assembles the AppImage from scratch. Every pinned input — the LÖVE source, the
|
||||||
|
five libraries built alongside it, and the AppImage type-2 runtime — is
|
||||||
|
SHA-256 verified on the host before the container ever sees it, and the
|
||||||
|
container itself runs with no network access.
|
||||||
|
|
||||||
|
### Why the build happens in a Debian bullseye container
|
||||||
|
|
||||||
|
glibc is backward compatible but not forward compatible: a binary linked
|
||||||
|
against glibc 2.41 will not start on a system with 2.31, and there is no way
|
||||||
|
to fix that after the fact. Compiling on the oldest base we support is
|
||||||
|
therefore the only thing that makes one artifact work everywhere.
|
||||||
|
|
||||||
|
Bullseye (glibc 2.31) is that base. The resulting binaries actually come out
|
||||||
|
needing only **glibc 2.29** and **GLIBCXX_3.4.21**, so the AppImage covers
|
||||||
|
everything from Ubuntu 20.04 and Raspberry Pi OS bullseye through current
|
||||||
|
trixie.
|
||||||
|
|
||||||
|
This is a statement about the *compile environment*, not about where the
|
||||||
|
artifact runs — building on your own newer distro would silently raise that
|
||||||
|
floor and strand every user on an older one, with no symptom until they
|
||||||
|
download it. CI enforces the floor: `linux-arm64-build` fails if the highest
|
||||||
|
required glibc symbol version climbs above 2.31.
|
||||||
|
|
||||||
|
### Why five libraries are built from source
|
||||||
|
|
||||||
|
SDL2, OpenAL, libtheora, libogg/libvorbis and libmpg123 are compiled rather
|
||||||
|
than installed from bullseye. In every case the reason is *correctness*, not
|
||||||
|
a newer version number — Debian builds these for a system where every
|
||||||
|
dependency is installed and co-versioned, which is the opposite of an
|
||||||
|
AppImage's situation. Each one broke the build in a different way, and all
|
||||||
|
three failure modes are now assertions that fail the build instead of
|
||||||
|
shipping.
|
||||||
|
|
||||||
|
**1. Hard-linked backends (SDL2, OpenAL).** Debian's `libSDL2` lists
|
||||||
|
`libpulse`, `libasound`, `libX11` and `libwayland-client` as `DT_NEEDED` —
|
||||||
|
resolved by the loader at startup, not dlopened. An AppImage bundling it
|
||||||
|
refuses to start unless the host has *all four*. It appeared to work in
|
||||||
|
testing only because a desktop Pi has all four; a headless CI runner is what
|
||||||
|
exposed it. Debian's OpenAL does the same via `libsndio`, which itself
|
||||||
|
hard-links `libasound`. Built from source with `--enable-*-shared` and
|
||||||
|
`ALSOFT_DLOPEN`, both dlopen their backends instead.
|
||||||
|
|
||||||
|
**2. A stray link (libtheora).** Debian's `libtheoradec.so.1` is linked
|
||||||
|
against `libcairo.so.2` — a packaging artifact, since a video decoder has no
|
||||||
|
business drawing vector graphics — and cairo drags in X11, xcb, fontconfig
|
||||||
|
and freetype. `--disable-examples` produces a `libtheoradec` needing only
|
||||||
|
`libogg`.
|
||||||
|
|
||||||
|
**3. SONAME collision with the host (ogg, vorbis, mpg123).** The subtle one.
|
||||||
|
OpenAL dlopens ALSA, ALSA's config loads its PulseAudio hook plugin, and that
|
||||||
|
plugin pulls the *host's* `libsndfile` into our process. `libsndfile` links
|
||||||
|
`libogg`, `libvorbis` and `libmpg123` — the same three we bundle. The loader
|
||||||
|
resolves a SONAME exactly once per process, so the host's `libsndfile` binds
|
||||||
|
to *our* copies:
|
||||||
|
|
||||||
|
```
|
||||||
|
openal -> libasound -> libasound_module_conf_pulse -> libsndfile (host, new)
|
||||||
|
`-> mpg123_info2 -> libmpg123 (ours, bullseye 1.26)
|
||||||
|
```
|
||||||
|
|
||||||
|
`mpg123_info2` arrived in mpg123 1.32, so the plugin failed to relocate, ALSA
|
||||||
|
config collapsed, and the game ran with **no audio device at all**. Not
|
||||||
|
bundling these instead would make `libogg`/`libvorbis`/`libmpg123` mandatory
|
||||||
|
host packages; building them current means our copies *satisfy* the host's
|
||||||
|
`libsndfile` rather than starving it.
|
||||||
|
|
||||||
|
The same collision is why the font stack — freetype, fontconfig, libpng,
|
||||||
|
brotli, zlib — is left to the host entirely. Bundling a bullseye freetype
|
||||||
|
2.10.4 meant a host `libcairo` could not find `FT_Get_Transform` (added in
|
||||||
|
2.11) and the game died at startup. Leaving the whole stack to the host keeps
|
||||||
|
it self-consistent, while `liblove` — compiled against 2.10.4 — only ever
|
||||||
|
asks for symbols every supported host already has.
|
||||||
|
|
||||||
|
The general rule this all reduces to: **never bundle a library the host's own
|
||||||
|
stack may also load, unless yours is at least as new as theirs.**
|
||||||
|
|
||||||
|
### CI
|
||||||
|
|
||||||
|
Three jobs, path-gated on `scripts/build_linux_arm64.sh`,
|
||||||
|
`scripts/linux-arm64/`, `scripts/pack_love.sh` and this document:
|
||||||
|
|
||||||
|
- **`linux-arm64-selftest`** (`ubuntu-latest`, x86_64) — offline gate. Checks
|
||||||
|
the pins are real digests on a dated tag rather than the moving
|
||||||
|
`continuous` one, that the Dockerfile still builds on bullseye, that the
|
||||||
|
exclude list still classifies known sonames correctly, that AppRun still
|
||||||
|
launches `game.love` with `--fused`, and that the host-arch guard actually
|
||||||
|
fires. Needs no container and no arm64 machine.
|
||||||
|
- **`linux-arm64-build`** (`ubuntu-24.04-arm`) — the real build, then extracts
|
||||||
|
the artifact and asserts the layout, that every bundled object resolves
|
||||||
|
under AppRun's `LD_LIBRARY_PATH`, and that the glibc floor is still ≤ 2.31.
|
||||||
|
Uploads the AppImage for 7 days.
|
||||||
|
- **release** — `linux-arm64` runs on `ubuntu-24.04-arm`, reuses the shared
|
||||||
|
`game.love` from the `love-payload` job, and the AppImage is staged and
|
||||||
|
published like every other release asset.
|
||||||
|
|
||||||
|
Unlike the Switch job, none of this needs secrets or self-hosted hardware, so
|
||||||
|
it runs on fork PRs too.
|
||||||
|
|
||||||
|
### Updating the pins
|
||||||
|
|
||||||
|
Both pins live in `scripts/linux-arm64/common.sh`:
|
||||||
|
|
||||||
|
- `LOVE_VERSION` / `LOVE_SRC_SHA256` — bumping any version invalidates the
|
||||||
|
cached prefix automatically (its name is keyed by every source version at
|
||||||
|
once, so a partial rebuild cannot mix vintages). Check that bullseye still
|
||||||
|
has `-dev` packages new enough for the new release; `build_appimage.sh`
|
||||||
|
asserts every optional module actually linked, because LÖVE's `configure`
|
||||||
|
exits 0 and silently drops a module when one is missing.
|
||||||
|
- `SDL2_*`, `OPENAL_*`, `THEORA_*`, `OGG_*`, `VORBIS_*`, `MPG123_*` — the
|
||||||
|
source-built libraries. Bumping these is usually safe and occasionally
|
||||||
|
necessary: `libmpg123` in particular must stay at least as new as what a
|
||||||
|
target host's `libsndfile` expects, which is asserted for `mpg123_info2`.
|
||||||
|
- `APPIMAGE_RUNTIME_TAG` / `APPIMAGE_RUNTIME_SHA256` — always a dated tag
|
||||||
|
from [AppImage/type2-runtime](https://github.com/AppImage/type2-runtime/releases).
|
||||||
|
The selftest fails the build if this ever points at `continuous`.
|
||||||
@@ -174,6 +174,32 @@ It runs immediately before queued button edges are promoted, so input added by
|
|||||||
the wrapper is visible during that same fixed step. The callback receives
|
the wrapper is visible during that same fixed step. The callback receives
|
||||||
`(next, game, dt)` and must call `next(game, dt)`.
|
`(next, game, dt)` and must call `next(game, dt)`.
|
||||||
|
|
||||||
|
`input.pointer` delivers uncaptured gameplay pointer events -- touches and
|
||||||
|
real mouse input alike. The callback receives `(next, game, ev)` where `ev`
|
||||||
|
is `{ phase, source, id, x, y, dx, dy, pressure, button }`: `phase` is
|
||||||
|
`"pressed"`, `"moved"`, `"released"` or `"cancelled"`; `source` is `"touch"`
|
||||||
|
or `"mouse"`; `id` is the LÖVE touch id or `"mouse"`; and the coordinates
|
||||||
|
are LOVE window units, the same space `render.hud`'s viewport and the touch
|
||||||
|
overlay lay out in. The on-screen touch controls keep first refusal: a
|
||||||
|
pointer that begins on a virtual control belongs to the pad for its whole
|
||||||
|
lifecycle and never reaches the hook, while one that begins outside stays
|
||||||
|
visible even if it later crosses a control. A real mouse reaches the hook
|
||||||
|
without `POKEPORT_TOUCH` (synthesized `istouch` mouse twins are dropped, so
|
||||||
|
a mobile touch fires once), and focus or visibility loss and input recovery
|
||||||
|
deliver a `"cancelled"` for every pointer the hook saw pressed but not yet
|
||||||
|
released. Return `true` without calling `next` to consume the event.
|
||||||
|
|
||||||
|
`mod.input` presses GB buttons source-safely. `mod.input:tap(game, btn)`
|
||||||
|
queues exactly one `wasPressed` edge for the next fixed step and holds
|
||||||
|
nothing; `local token = mod.input:press(game, btn)` holds the button until
|
||||||
|
`mod.input:release(token)`. Buttons are `up`, `down`, `left`, `right`, `a`,
|
||||||
|
`b`, `start` and `select`. Every press is its own input source inside the
|
||||||
|
engine's multi-source bookkeeping, so releasing a token never clears a hold
|
||||||
|
the keyboard, a controller, the touch overlay or another mod still owns;
|
||||||
|
`release` is idempotent and refuses tokens taken by another mod.
|
||||||
|
Outstanding tokens are released automatically on entry-chunk rollback, hot
|
||||||
|
reload and input recovery.
|
||||||
|
|
||||||
`ui.title_menu.items` receives `(next, game, items)` and follows the same
|
`ui.title_menu.items` receives `(next, game, items)` and follows the same
|
||||||
decorate-after-`next` convention as `ui.start_menu.items`. It is the safe place
|
decorate-after-`next` convention as `ui.start_menu.items`. It is the safe place
|
||||||
for a tool to offer a fresh-session action before gameplay begins.
|
for a tool to offer a fresh-session action before gameplay begins.
|
||||||
@@ -200,5 +226,12 @@ for driving a second physical display. This is what lets a mod lay the two
|
|||||||
passes out as two stacked Game Boy screens, or push one onto a second screen,
|
passes out as two stacked Game Boy screens, or push one onto a second screen,
|
||||||
without the engine knowing the layout.
|
without the engine knowing the layout.
|
||||||
|
|
||||||
|
`screen.render_visible` receives `(next, state)` while the main screen is being
|
||||||
|
composed. Return `false` to omit that state from drawing, opacity selection and
|
||||||
|
palette-zone ownership. The state remains on the stack and keeps its normal
|
||||||
|
update and input ownership, so a mod can mirror a native menu on another
|
||||||
|
display without reimplementing it. The default is `true`. Treat the wrapper as
|
||||||
|
a pure predicate: the renderer may ask it more than once per frame.
|
||||||
|
|
||||||
Developer mode also arms the mod loader's dev tripwire, which flags mods
|
Developer mode also arms the mod loader's dev tripwire, which flags mods
|
||||||
that reach outside their permission set.
|
that reach outside their permission set.
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
# New features (deliberate additions beyond the original)
|
# New features (deliberate additions beyond the original)
|
||||||
|
|
||||||
Intentional enhancements this port adds on top of faithful Pokémon Red
|
Intentional enhancements this port adds on top of faithful Pokémon Red, Blue,
|
||||||
behavior. They have no Game Boy equivalent and are kept by design.
|
and Yellow behavior. They have no Game Boy equivalent and are kept by design.
|
||||||
Genuine divergences from the original (things still missing, wrong, or
|
Genuine divergences from the original (things still missing, wrong, or
|
||||||
approximated) live in docs/known-differences.md; faithfully-ported
|
approximated) live in docs/known-differences.md; faithfully-ported behavior is
|
||||||
behavior is in docs/behavior-porting-notes.md.
|
in docs/behavior-porting-notes.md.
|
||||||
|
|
||||||
## Survey zoom
|
## Survey zoom
|
||||||
|
|
||||||
@@ -332,6 +332,37 @@ one used sideways. An `options.lua` from before this split keeps its single
|
|||||||
layout in both orientations until one of them is edited. In-game, Options →
|
layout in both orientations until one of them is edited. In-game, Options →
|
||||||
**TOUCH PAD** toggles the same on/off flag without leaving a play session.
|
**TOUCH PAD** toggles the same on/off flag without leaving a play session.
|
||||||
|
|
||||||
|
## Haptic feedback (mobile)
|
||||||
|
|
||||||
|
Options → **VIBRATION** (also in the launcher's gear menu) buzzes the device
|
||||||
|
the instant an on-screen control takes a button (#806). A glass pad has no
|
||||||
|
edges under a thumb, so the pulse is what tells you the press landed:
|
||||||
|
sliding the d-pad from one direction to the next buzzes again, a second
|
||||||
|
finger landing on a button that is already held does not, and releasing
|
||||||
|
never does.
|
||||||
|
|
||||||
|
Four levels: **OFF**, **LIGHT** (the default), **MEDIUM**, **HEAVY**.
|
||||||
|
"Intensity" is really a pulse length -- the platform call takes a duration
|
||||||
|
and nothing else -- so LIGHT is a 12 ms tick, MEDIUM 25 ms, HEAVY 45 ms.
|
||||||
|
Stepping the row fires one sample pulse at the level you land on, so the
|
||||||
|
three can be compared without leaving the menu. On iOS the system
|
||||||
|
vibration has one fixed length, so all three levels feel the same there and
|
||||||
|
the row is effectively on/off. The setting lives in `options.lua` and the
|
||||||
|
row only appears where the on-screen pad can (Android/iOS, or desktop with
|
||||||
|
`POKEPORT_TOUCH=1`, where it does nothing since desktop LOVE has no
|
||||||
|
vibrator).
|
||||||
|
|
||||||
|
## Screen orientation lock (Android)
|
||||||
|
|
||||||
|
Options → **ORIENTATION** (also in the launcher's gear menu) locks the
|
||||||
|
screen to **PORTRAIT**, **LANDSCAPE** (either landscape, following the
|
||||||
|
device), or **REVERSE LANDSCAPE**, or leaves it on **AUTO** (#592). AUTO
|
||||||
|
allows every orientation but defers to the system: with auto-rotate turned
|
||||||
|
off in Android's quick settings, the game stays put instead of following
|
||||||
|
the sensor (#716). Changes apply immediately -- the screen rotates as the
|
||||||
|
row is stepped -- and persist in `options.lua`. Android only: iOS follows
|
||||||
|
the app's fixed orientation list, and desktop windows rotate nothing.
|
||||||
|
|
||||||
## Translation support
|
## Translation support
|
||||||
|
|
||||||
Every string the player can read is now reachable from a mod, so a
|
Every string the player can read is now reachable from a mod, so a
|
||||||
@@ -365,6 +396,28 @@ plus a glyph-page and charmap stub, a naming-grid stub, and a
|
|||||||
be packed). `--refresh` re-harvests after an engine update, keeping
|
be packed). `--refresh` re-harvests after an engine update, keeping
|
||||||
existing translations and parking orphaned keys rather than dropping them.
|
existing translations and parking orphaned keys rather than dropping them.
|
||||||
|
|
||||||
|
A translation can also skip glyph pages entirely: scaffolding with
|
||||||
|
`--pixel-font` (or registering `mod.content.font:register("ttf", {})` in
|
||||||
|
an existing mod) renders text through a bundled TTF covering Latin with
|
||||||
|
diacritics, Cyrillic, kana and CJK, while box borders and `<PK>`-style
|
||||||
|
macro glyphs keep their tiles. The font is "Plain Pixel Font" by Douglas
|
||||||
|
Vautour (Burpy Fresh), licensed under CC-BY 4.0 (5x11 base characters,
|
||||||
|
11x11 double-width; see `assets/fonts/plainpixel/README.md`). Options on
|
||||||
|
the registry entry: `file` for a mod-shipped TTF, `size` (the font's
|
||||||
|
design em; Plain Pixel rasterizes cleanly only at multiples of 15),
|
||||||
|
`spacing` added to every advance, `yOffset` for vertical alignment
|
||||||
|
against the 8px cell grid, `bold`, which double-prints at a 1px
|
||||||
|
offset for fonts whose strokes read too light, and `tiles`, the
|
||||||
|
characters that keep their ROM tile instead of coming from the TTF.
|
||||||
|
|
||||||
|
`tiles` matters for a CJK translation. Sizing the font so a kana fills
|
||||||
|
the 8px cell leaves Latin narrower than the tile font it replaces, which
|
||||||
|
pulls the numeric columns out of line: the party menu's `:L12` stops
|
||||||
|
sitting over `34/ 34`. Naming `"0123456789/:"` keeps those on the
|
||||||
|
vanilla tiles, so numbers render exactly as they do in English while
|
||||||
|
kana still come from the font. It takes a string of characters, or a
|
||||||
|
list when a multi-character charmap sequence is meant.
|
||||||
|
|
||||||
See the wiki's Translations guide.
|
See the wiki's Translations guide.
|
||||||
|
|
||||||
## Save editor (bundled, reachable from the launcher)
|
## Save editor (bundled, reachable from the launcher)
|
||||||
@@ -447,6 +500,16 @@ automatically, so the tab opens on an "Add an index" prompt until you name
|
|||||||
one; paste an index URL or its `owner/repo` and it is remembered in
|
one; paste an index URL or its `owner/repo` and it is remembered in
|
||||||
`options.lua`. More than one index can be added, and the listings merge.
|
`options.lua`. More than one index can be added, and the listings merge.
|
||||||
|
|
||||||
|
A feed author can publish per-mod release stats by adding three optional
|
||||||
|
fields to an entry -- `downloads` (total across every release), and
|
||||||
|
`first_release` / `last_release` (ISO days) -- which the listing shows in
|
||||||
|
the same gold line the MODS tab uses. When a feed does not carry them,
|
||||||
|
the row fetches the mod's own GitHub releases instead -- the same cached
|
||||||
|
`ModUpdate` fetch the MODS tab uses, one entry per frame -- so the stats
|
||||||
|
appear for any mod with a `github` field regardless of feed maintenance.
|
||||||
|
The fields are additive: feeds that carry them stay readable by every
|
||||||
|
build that predates them, and feeds that do not render exactly as before.
|
||||||
|
|
||||||
## Soft reset (all versions)
|
## Soft reset (all versions)
|
||||||
|
|
||||||
Holding A, B, START and SELECT together restarts the game the way flicking
|
Holding A, B, START and SELECT together restarts the game the way flicking
|
||||||
@@ -508,3 +571,66 @@ file pickers are ordinary desktop dialogs and still appear normally, and a
|
|||||||
run started from a terminal (`lovec.exe`, what `scripts\run.ps1` prefers)
|
run started from a terminal (`lovec.exe`, what `scripts\run.ps1` prefers)
|
||||||
keeps its terminal and its printed output. Set `POKEPORT_CONSOLE=1` to opt
|
keeps its terminal and its printed output. Set `POKEPORT_CONSOLE=1` to opt
|
||||||
out.
|
out.
|
||||||
|
|
||||||
|
## A faster, higher-contrast launcher
|
||||||
|
|
||||||
|
The launcher and the save editor were rebuilt on one small immediate-mode UI
|
||||||
|
kit (`src/ui/kit/`), replacing the vendored FlexLove layout engine. The
|
||||||
|
visible result is that the launcher is quick: building and drawing a frame
|
||||||
|
went from about 9 ms to under 1 ms on the same machine and the same data, at
|
||||||
|
every window size, so the window keeps up with the pointer instead of
|
||||||
|
trailing it. Measure it yourself with `POKEPORT_LAUNCHER_PROF=200 love .`.
|
||||||
|
|
||||||
|
**Nothing blocks the window any more.** Fetching a mod index, checking a mod
|
||||||
|
for updates, listing versions, downloading an install and pulling thumbnails
|
||||||
|
all run on background threads. Opening FIND MODS on a cold cache used to
|
||||||
|
freeze the launcher for as long as the server took -- often minutes, with no
|
||||||
|
indication anything was happening. Mod indexes are now also fetched at boot,
|
||||||
|
so the tab is usually already populated by the time you reach it.
|
||||||
|
|
||||||
|
**Anything you wait on says so.** Every operation that takes time raises a
|
||||||
|
loading panel with a spinner or a progress bar that cannot be clicked around
|
||||||
|
or dismissed, so a half-finished install can never be interrupted by a stray
|
||||||
|
click. Work that only affects one row (a mod's update check) shows a small
|
||||||
|
spinner on that row instead and leaves the rest of the list usable.
|
||||||
|
|
||||||
|
**Lists page instead of scrolling.** Mods, Find Mods, save slots, settings,
|
||||||
|
release notes and the version list all show a fixed number of rows with a
|
||||||
|
pager underneath, and the number of rows comes from the window height -- a
|
||||||
|
tall window shows more, a phone shows fewer. A long list costs exactly what a
|
||||||
|
short one does. The mouse wheel turns pages.
|
||||||
|
|
||||||
|
**Updates live in the top right.** The in-app updater moved next to the
|
||||||
|
settings gear and pulses when an update is waiting, instead of sitting in a
|
||||||
|
banner at the bottom of the page that you had to scroll to notice. Checking
|
||||||
|
for updates from there shows a loader like everything else.
|
||||||
|
|
||||||
|
**A quit button.** An X sits to the right of the settings gear and closes
|
||||||
|
the app cleanly, the same shutdown path as the window's close button. Mostly
|
||||||
|
for platforms where reaching the window chrome is awkward (Android, Steam
|
||||||
|
Deck, fullscreen desktops).
|
||||||
|
|
||||||
|
**The look.** Black background, white outlines, no gradients or glows, and
|
||||||
|
buttons that are solid colour-coded keys: green commits, blue navigates, red
|
||||||
|
destroys, yellow wants attention. The three game tabs keep their red, blue
|
||||||
|
and gold cartridge colours. Everything is about a third larger than before.
|
||||||
|
The save editor follows the same theme, and adding an item there is now a
|
||||||
|
searchable pop-up like adding a Pokemon, rather than a cramped list wedged
|
||||||
|
into the tab.
|
||||||
|
|
||||||
|
**Reset rebinds.** Input rebinds are additive, so there was no in-game way to
|
||||||
|
undo one. A RESET REBINDS row in Settings, and a matching button under Touch
|
||||||
|
Controls on each game tab, restore the stock keyboard, gamepad and touch
|
||||||
|
layout. Both ask twice.
|
||||||
|
|
||||||
|
## Launch options: boot straight into a game
|
||||||
|
|
||||||
|
`love . --game=red` skips the launcher and starts that game; `--slot=<id or
|
||||||
|
number>` picks the save slot to load, and `--launcher` forces the launcher
|
||||||
|
anyway. Spell these with an `=`: LÖVE reads the command line first and takes a
|
||||||
|
bare word as a path to a game, so `--game red` fails looking for a folder
|
||||||
|
called `red`. `POKEPORT_GAME` / `POKEPORT_SLOT` do the same for shortcuts that
|
||||||
|
can only pass environment variables. This is for one-click entries: a desktop
|
||||||
|
shortcut per game, a Steam entry, or a handheld frontend. Asking for a game
|
||||||
|
whose ROM has not been imported opens the launcher on that game's tab rather
|
||||||
|
than failing.
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
# What This Port Requires
|
# What This Port Requires
|
||||||
|
|
||||||
The packaged desktop app requires one user-supplied input on first boot: a
|
The packaged desktop app requires one user-supplied input on first boot: a
|
||||||
canonical 1 MiB US Pokemon Red ROM.
|
canonical 1 MiB US Pokemon Red, Blue, or Yellow ROM.
|
||||||
|
|
||||||
The importer verifies SHA-1
|
The importer verifies the SHA-1 for the game (see `src/core/GameVersion.lua`
|
||||||
`ea9bcae617fdf159b045185467ae58b2e4a48b9a`. Other revisions, Virtual
|
for specific hashes). Other revisions and Virtual Console releases are rejected
|
||||||
Console releases, and Pokemon Blue are rejected rather than decoded with
|
rather than decoded with incorrect addresses.
|
||||||
incorrect addresses.
|
|
||||||
|
|
||||||
After verification, the app generates its private cache in the LÖVE save
|
After verification, the app generates its private cache in the LÖVE save
|
||||||
directory. It does not keep a copy of the ROM. Later boots use the cache.
|
directory. It does not keep a copy of the ROM. Later boots use the cache.
|
||||||
@@ -15,9 +14,11 @@ Python and Pillow are not required by the packaged app.
|
|||||||
## Bundled Metadata
|
## Bundled Metadata
|
||||||
|
|
||||||
Assembly removes high-level names and some relationships that the Lua port
|
Assembly removes high-level names and some relationships that the Lua port
|
||||||
needs. `tools/rom_manifest.json` therefore contains:
|
needs. The version-specific files `tools/rom_manifest.json`,
|
||||||
|
`tools/rom_manifest_blue.json`, and `tools/rom_manifest_yellow.json` therefore
|
||||||
|
contain:
|
||||||
|
|
||||||
- the 3,268 ROM symbol addresses actually read by the extractor
|
- the ROM symbol addresses actually read by the extractor
|
||||||
- symbolic IDs and ordering for maps, species, moves, items, and trainers
|
- symbolic IDs and ordering for maps, species, moves, items, and trainers
|
||||||
- source-erased dimensions, image names, and map object integration names
|
- source-erased dimensions, image names, and map object integration names
|
||||||
- hand-ported field/script integration tables
|
- hand-ported field/script integration tables
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
# RFC 0002 — Let mods hide an active screen state from the main render
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Proposed. Engine: `StateStack.lua`, `Game.lua`. Tests:
|
||||||
|
`screen_render_visible.lua`.
|
||||||
|
|
||||||
|
## Motivation
|
||||||
|
|
||||||
|
A mod can render a native menu on a companion display through
|
||||||
|
`render.compose`, but it cannot remove that menu from the main display without
|
||||||
|
also popping it. Popping transfers update and input ownership and forces the
|
||||||
|
mod to reimplement native menu behavior.
|
||||||
|
|
||||||
|
## The decision it extends
|
||||||
|
|
||||||
|
No prior D-number. Extends the render-hook plan in `docs/modding.md` and the
|
||||||
|
state-stack rendering contract in `docs/architecture.md`.
|
||||||
|
|
||||||
|
## The exact API delta
|
||||||
|
|
||||||
|
Backward-compatible, additive-only.
|
||||||
|
|
||||||
|
### `screen.render_visible`
|
||||||
|
|
||||||
|
New hook called with `(state) -> boolean` through the public wrapper signature
|
||||||
|
`(next, state)`. Its vanilla result is `true`.
|
||||||
|
|
||||||
|
Returning `false` excludes the state from the main draw, from opaque-base
|
||||||
|
selection and from palette-zone ownership. It does not remove the state or
|
||||||
|
change update, input, push or pop behavior. The call sites are
|
||||||
|
`StateStack:visibleBase`, `StateStack:draw` and the equivalent draw and palette
|
||||||
|
walks in `Game:draw`.
|
||||||
|
|
||||||
|
The hook is guarded by `Runtime.wantsHook`, so the no-subscriber path allocates
|
||||||
|
nothing. It is a pure render predicate and may be evaluated more than once per
|
||||||
|
frame.
|
||||||
|
|
||||||
|
## Migration note for existing mods
|
||||||
|
|
||||||
|
**Nothing.** With no subscriber every state remains visible, and the existing
|
||||||
|
state-stack, event and hook behavior is unchanged.
|
||||||
|
|
||||||
|
## Parity tests
|
||||||
|
|
||||||
|
- **No-mod:** the topmost opaque state still owns drawing and palette zones,
|
||||||
|
and `Runtime.wantsHook("screen.render_visible")` stays false.
|
||||||
|
- **Mod-API:** a fixture mod registers through `mod.hooks:wrap`, hides one
|
||||||
|
opaque state and proves the state beneath draws and owns the palette while
|
||||||
|
the hidden state remains topmost and continues updating.
|
||||||
|
|
||||||
|
## Deprecation etiquette
|
||||||
|
|
||||||
|
Nothing deprecated. This is one additive hook with a `true` vanilla default.
|
||||||
@@ -0,0 +1,204 @@
|
|||||||
|
# Build the Nintendo Switch NRO — contributor guide
|
||||||
|
|
||||||
|
Want to play a release build instead? Download the SD-ready zip and extract it
|
||||||
|
at your microSD root — see [switch-install.md](switch-install.md).
|
||||||
|
|
||||||
|
This guide is for contributors who build Gen1Recomp for Switch from source.
|
||||||
|
Hardware evidence, MTP operator loops, and deeper notes live in
|
||||||
|
[switch-development.md](switch-development.md).
|
||||||
|
|
||||||
|
> Releases ship `gen1recomp-*-switch.zip` (SD tree under `switch/gen1recomp/`;
|
||||||
|
> issue [#531](https://github.com/bryanthaboi/gen1recomp/issues/531)). Hardware
|
||||||
|
> evidence: **OLED** (author) and **V1 boot** (community). See
|
||||||
|
> [switch-development.md](switch-development.md) for known limitations.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prerequisites by OS
|
||||||
|
|
||||||
|
All packaging entrypoints are **bash**. On Windows, use Git Bash, MSYS2, or
|
||||||
|
WSL — not cmd.exe or PowerShell (AD-008).
|
||||||
|
|
||||||
|
### macOS / Linux
|
||||||
|
|
||||||
|
1. Install [devkitPro pacman](https://devkitpro.org/wiki/devkitPro_pacman).
|
||||||
|
2. Install Switch tools:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo dkp-pacman -S switch-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Ensure `nacptool` and `elf2nro` are on `PATH` (or under
|
||||||
|
`$DEVKITPRO/tools/bin` — the fused script prepends that when set).
|
||||||
|
|
||||||
|
**Optional:** Install [Docker](https://docs.docker.com/get-docker/) so fused
|
||||||
|
builds can fall back to the pinned image when native tools are missing.
|
||||||
|
|
||||||
|
### Windows (Git Bash / MSYS2 / WSL)
|
||||||
|
|
||||||
|
1. Use a bash environment:
|
||||||
|
- **MSYS2** with the [devkitPro](https://devkitpro.org/wiki/devkitPro_pacman)
|
||||||
|
packages (preferred for native `nacptool`/`elf2nro`), or
|
||||||
|
- **WSL** (Ubuntu/etc.) with the Linux pacman flow above, or
|
||||||
|
- **Git Bash** for `--fetch` / `--loose`; for `--fused` prefer MSYS2 or
|
||||||
|
WSL if Docker bind-mounts from Git Bash paths misbehave.
|
||||||
|
2. Install `switch-dev` (or rely on Docker fallback — see below).
|
||||||
|
3. Do **not** expect `scripts/build_switch.sh` to run under cmd/PowerShell.
|
||||||
|
|
||||||
|
### What you must install yourself
|
||||||
|
|
||||||
|
| You install | Script does **not** install |
|
||||||
|
| ----------- | --------------------------- |
|
||||||
|
| bash, git, zip tooling the repo already expects | — |
|
||||||
|
| `dkp-pacman` + `switch-dev` (native fused) | `dkp-pacman -S …` |
|
||||||
|
| Docker (optional fused fallback) | Docker Engine |
|
||||||
|
| A legal `.gb` ROM (to play) | Any ROM or game data |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Mode glossary
|
||||||
|
|
||||||
|
`scripts/build_switch.sh` supports three modes (combinable as noted):
|
||||||
|
|
||||||
|
| Mode | What it does |
|
||||||
|
| ---- | ------------ |
|
||||||
|
| `--fetch` | Downloads pinned **love.nro** + **love.elf** into `.bazinga/love-nx/11.5-nx1/` and verifies SHA-256 against `scripts/switch/love-nx-11.5-nx1.sha256`. |
|
||||||
|
| `--loose` | Packs `game.love`, copies pinned `love.nro` → `dist/switch/loose/` as `gen1recomp.nro` + `game.love` side by side. Needs the pin. |
|
||||||
|
| `--fused` | Builds `dist/switch/gen1recomp-<ver>-switch.nro` (game in romfs) via `nacptool` + `elf2nro`, then packs `dist/switch/gen1recomp-<ver>-switch.zip` (SD-ready tree). Needs the pin + toolchain (native or Docker). GitHub Releases publish the **zip only**. |
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
|
||||||
|
- `--fetch` alone is fine; combine as `--fetch --loose` or `--fetch --fused`.
|
||||||
|
- `--loose` and `--fused` are **XOR** — pick one packaging path per run.
|
||||||
|
- `--version X.Y.Z` sets the NACP / filename version (defaults to short git SHA).
|
||||||
|
|
||||||
|
### What `--fetch` downloads
|
||||||
|
|
||||||
|
Only the two pinned love-nx release assets (`love.nro`, `love.elf`). It does
|
||||||
|
**not** install:
|
||||||
|
|
||||||
|
- devkitPro / `dkp-pacman` / `switch-dev`
|
||||||
|
- Docker
|
||||||
|
- ROMs, saves, or mods
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Native tools, then Docker
|
||||||
|
|
||||||
|
Fused packaging (`scripts/switch/build_fused.sh`):
|
||||||
|
|
||||||
|
1. Prefer native `nacptool` + `elf2nro` on `PATH` (or `$DEVKITPRO/tools/bin`).
|
||||||
|
2. Else fall back to Docker using:
|
||||||
|
- `GEN1_DKP_IMAGE` if set, otherwise
|
||||||
|
- the image named in `scripts/switch/dkp-docker.image` (default
|
||||||
|
`devkitpro/devkita64:latest`).
|
||||||
|
|
||||||
|
If neither native tools nor Docker work, the script exits non-zero with
|
||||||
|
macOS / Linux / Windows / Docker hints and a pointer to this doc.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Example commands
|
||||||
|
|
||||||
|
From the repo root:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Download pinned love-nx only
|
||||||
|
scripts/build_switch.sh --fetch
|
||||||
|
|
||||||
|
# Loose pair for iteration (fetch + assemble)
|
||||||
|
scripts/build_switch.sh --fetch --loose
|
||||||
|
|
||||||
|
# Single fused NRO + SD-ready zip for a release-like artifact
|
||||||
|
scripts/build_switch.sh --fetch --fused --version 0.2.0
|
||||||
|
```
|
||||||
|
|
||||||
|
Outputs land under `dist/switch/` (and `dist/switch/loose/` for loose mode).
|
||||||
|
The fused path also writes `gen1recomp-<ver>-switch.nro.sha256` and
|
||||||
|
`gen1recomp-<ver>-switch.zip` (+ `.sha256` sidecar for the zip).
|
||||||
|
|
||||||
|
Offline packaging smoke (no network, no nacptool required):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
bash scripts/switch/selftest_build_switch.sh
|
||||||
|
bash scripts/switch/verify_payload.sh --self-test
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CI and release
|
||||||
|
|
||||||
|
Switch packaging has three automated surfaces (same policy as AD-010):
|
||||||
|
|
||||||
|
### Path-gated PR / push CI (`.github/workflows/ci.yml`)
|
||||||
|
|
||||||
|
When a change touches Switch packaging / Switch docs / NX runtime paths
|
||||||
|
(`scripts/build_switch.sh`, `scripts/switch/**`, `docs/switch-*.md`,
|
||||||
|
`tests/switch_ci_workflows_test.lua`, `tests/switch_transfer_docs_test.lua`,
|
||||||
|
the NX runtime modules `src/core/NxAssetOverlay.lua`, `src/core/Platform.lua`,
|
||||||
|
`src/core/GameVersion.lua`, `src/import/CacheFs.lua`, the NX engine suites
|
||||||
|
`tests/engine/assets_version_fallback_test.lua`,
|
||||||
|
`tests/engine/nx_generated_guard_test.lua`,
|
||||||
|
`tests/engine/nx_yellow_boot_test.lua`,
|
||||||
|
`tests/engine/switch_diagnostics_test.lua`, `tests/engine/platform_nx_*`,
|
||||||
|
or the Switch-related workflow YAML), CI runs:
|
||||||
|
|
||||||
|
1. **Offline selftest** on `ubuntu-latest` (forks **and** the canonical repo):
|
||||||
|
`scripts/switch/selftest_build_switch.sh`,
|
||||||
|
`scripts/switch/verify_payload.sh --self-test`,
|
||||||
|
`luajit tests/switch_ci_workflows_test.lua`,
|
||||||
|
`luajit tests/switch_transfer_docs_test.lua`, and the NX engine suites
|
||||||
|
headlessly (`luajit tests/engine/assets_version_fallback_test.lua`,
|
||||||
|
`luajit tests/engine/nx_generated_guard_test.lua`,
|
||||||
|
`luajit tests/engine/nx_yellow_boot_test.lua`).
|
||||||
|
2. **Fused NRO build** only on the **canonical** repository
|
||||||
|
(`bryanthaboi/gen1recomp`), on the self-hosted Mac runner
|
||||||
|
(`scripts/build_switch.sh --fetch --fused`), and only when the workflow
|
||||||
|
head is that repo (same-repo push/PR). **Fork repository** CI never runs
|
||||||
|
fused. **Fork → canonical PRs** 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
|
||||||
|
linking the Actions artifact named `gen1recomp-switch-nro`
|
||||||
|
(comment tag `switch-build-result`; see
|
||||||
|
`.github/workflows/switch-artifact-comment.yml`).
|
||||||
|
|
||||||
|
Unrelated PRs do not burn the self-hosted Mac on Switch packaging.
|
||||||
|
|
||||||
|
### Release hard-fail (`.github/workflows/release.yml`)
|
||||||
|
|
||||||
|
GitHub Releases always build Switch on the same self-hosted Mac runner as the
|
||||||
|
other platforms — this is a **hard gate** (no `continue-on-error`):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
scripts/build_switch.sh --fetch --fused --version "<release version>"
|
||||||
|
```
|
||||||
|
|
||||||
|
A Switch packaging failure fails the entire release job. The release asset is
|
||||||
|
`gen1recomp-<ver>-switch.zip` (SD-ready); the versioned `.nro` stays under
|
||||||
|
`dist/switch/` for the packer and for PR CI artifacts.
|
||||||
|
|
||||||
|
### Runner provisioning
|
||||||
|
|
||||||
|
The self-hosted Mac runner must have **native switch-tools** (`nacptool` /
|
||||||
|
`elf2nro`) **and/or Docker** available. CI and release do not silently run
|
||||||
|
`dkp-pacman -S`; keep the runner image/host provisioned per this guide.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Limitations / non-goals
|
||||||
|
|
||||||
|
These scripts and this guide do **not**:
|
||||||
|
|
||||||
|
- Push files to the console (no automated MTP / FTP / SD scripting)
|
||||||
|
- Bundle or download any Pokémon ROM
|
||||||
|
- Install `dkp-pacman` / `switch-dev` for you
|
||||||
|
- Provide `nxlink` / netloader deploy (deferred — see [switch-transfer.md](switch-transfer.md))
|
||||||
|
- Validate **Applet Mode** — use title override (hold **R**) for full memory
|
||||||
|
|
||||||
|
Player install steps: [switch-install.md](switch-install.md).
|
||||||
|
Manual transfer (MTP / SD / FTP, macOS / Linux / Windows):
|
||||||
|
[switch-transfer.md](switch-transfer.md).
|
||||||
|
Hardware depth and evidence: [switch-development.md](switch-development.md),
|
||||||
|
[switch-hardware-evidence.md](switch-hardware-evidence.md).
|
||||||
@@ -0,0 +1,528 @@
|
|||||||
|
# Nintendo Switch development (love-nx)
|
||||||
|
|
||||||
|
> Fused NRO support for issue [#531](https://github.com/bryanthaboi/gen1recomp/issues/531).
|
||||||
|
> Releases ship `gen1recomp-*-switch.zip` (SD-ready tree). Console copy is
|
||||||
|
> extract/merge at microSD root; title override required. See
|
||||||
|
> [Known limitations](#known-limitations-read-before-reviewing).
|
||||||
|
|
||||||
|
**Canonical install / build / transfer docs** (start here unless you need hardware depth):
|
||||||
|
|
||||||
|
- Players → [switch-install.md](switch-install.md)
|
||||||
|
- Builders → [switch-build.md](switch-build.md) (`scripts/build_switch.sh --fetch` downloads the pinned love-nx pair)
|
||||||
|
- Transfer (MTP / SD / FTP on macOS, Linux, Windows) → [switch-transfer.md](switch-transfer.md)
|
||||||
|
|
||||||
|
This document covers what landed, known limitations, how hardware was tested,
|
||||||
|
vendor layout, build/deploy, and the contributor transfer loop (detail lives in
|
||||||
|
the transfer runbook).
|
||||||
|
|
||||||
|
## Acknowledgments
|
||||||
|
|
||||||
|
- **Port / love-nx packaging:** [andrewqsantos](https://github.com/andrewqsantos)
|
||||||
|
- **Community hardware testing** (Switch V1 / Erista boot): [booshankles](https://github.com/booshankles)
|
||||||
|
- **Method guidance:** [Dusklight Switch port](https://github.com/HayatoG/dusklight/tree/main/platforms/switch) / love-nx
|
||||||
|
- **Upstream project:** [bryanthaboi](https://github.com/bryanthaboi) / Gen1Recomp
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
| Area | State |
|
||||||
|
| ---- | ----- |
|
||||||
|
| Feature | **Available** — playable fused NRO path (issue #531) |
|
||||||
|
| Runtime | Pinned love-nx **`11.5-nx1`** |
|
||||||
|
| Product artifact | Releases: SD-ready `gen1recomp-*-switch.zip`; local/PR: fused `.nro`; loose `nro`+`game.love` for iteration |
|
||||||
|
| Hardware | **OLED** validated (author, title override); **V1 / Erista** boot confirmed (community). Lite, docked soak, and Pro Controller matrices welcome |
|
||||||
|
| Deploy / install | Releases publish SD-ready zip; **extract/merge at microSD root** (MTP / SD / FTP — [switch-transfer.md](switch-transfer.md)); no `nxlink` path yet |
|
||||||
|
| Contributor transfer | Documented for **macOS, Linux, and Windows**; OpenMTP on Mac is one example, not the only contract |
|
||||||
|
| Network features on NX | Self-update / remote mod download **disabled** (`networkValidated == false`) |
|
||||||
|
| Community help | Welcome — especially HOS / love-nx packaging and broader hardware coverage |
|
||||||
|
|
||||||
|
### What landed
|
||||||
|
|
||||||
|
- Detect `NX` via `src/core/Platform.lua` without reusing Android flags
|
||||||
|
- Writable ROM inbox under `getSaveDirectory()/imports/` + per-tab “Scan again” (SHA-1 match for the open game)
|
||||||
|
- Joy-Con / gamepad mapping shared by launcher and gameplay (Nintendo A/B UX on NX)
|
||||||
|
- Launcher L/R tab switch; gameplay L/R game-speed cycle; Select+face display chords
|
||||||
|
- Focus loss / joystick reconnect recovery; opt-in `switch-debug.txt` diagnostics
|
||||||
|
- Loose assemble + fused NRO build scripts (`scripts/build_switch.sh`, `scripts/switch/*`)
|
||||||
|
- Payload gates so ROM / generated cache / saves never enter `game.love`
|
||||||
|
- Community mod zip inbox at `imports/mods/` (rescan installs; FIND MODS stays network-gated)
|
||||||
|
- Raw `.sav` inbox at `imports/saves/{red,blue,yellow}/` (**Import save** rescan) + export pull path `exports/{red,blue,yellow}/` (MTP hint; no openURL)
|
||||||
|
- Hardware evidence for Phase 0 probe, ROM import, naming A/B, save/suspend, fused NRO — see `docs/switch-hardware-evidence.md`
|
||||||
|
- Path-gated CI selftest + canonical fused PR artifact; release Switch hard-fail
|
||||||
|
- Save editor pad/touch input (virtual cursor, A click, B close) — see `tools/save-editor/README.md`
|
||||||
|
- Dynamic display size on NX only: handheld **1280×720**, docked/TV **1920×1080** (`src/core/NxDisplay.lua` + resizable conf so love-nx SDL can follow dock/undock at runtime)
|
||||||
|
|
||||||
|
### Known gaps / welcome contributions
|
||||||
|
|
||||||
|
- Docked vs handheld soak (≥30 min) and Lite coverage — resolution switch is implemented; long soak still welcome
|
||||||
|
- Switch Lite and fuller Pro Controller / third-party pad matrices
|
||||||
|
- Applet Mode remains unsupported by design (title override required)
|
||||||
|
- `nxlink` / netloader contrib fast-loop (deferred — see [switch-transfer.md](switch-transfer.md))
|
||||||
|
|
||||||
|
Transfer runbooks for Linux/Windows (and SD/FTP alternatives) are in
|
||||||
|
[switch-transfer.md](switch-transfer.md). Community mod zip install OLED smoke
|
||||||
|
is **pass** — see NXMOD-12 in [switch-hardware-evidence.md](switch-hardware-evidence.md).
|
||||||
|
|
||||||
|
## Design references (Dusklight)
|
||||||
|
|
||||||
|
This work borrowed method — not the native stack — from the [Dusklight Switch port](https://github.com/HayatoG/dusklight/tree/main/platforms/switch), especially [`LESSONS_AND_REUSE.md`](https://github.com/HayatoG/dusklight/blob/main/platforms/switch/LESSONS_AND_REUSE.md):
|
||||||
|
|
||||||
|
| Dusklight lesson | How Gen1Recomp applied it |
|
||||||
|
| ---------------- | ------------------------- |
|
||||||
|
| Emulators hide Tegra failures | Gate milestones on **real OLED hardware**, not Ryujinx/Yuzu alone |
|
||||||
|
| Prove the lower layer first | `tools/switch-probe` before full launcher |
|
||||||
|
| Know which binary ran | Embedded `build-info.json` (commit / love-nx tag) |
|
||||||
|
| Cap continuous logs | Opt-in diagnostics, ≤1 Hz flush; Lua error log rotation |
|
||||||
|
| Crash symbolization needs the exact ELF | Keep pinned `love.elf` with the NRO under test |
|
||||||
|
| Full memory matters | Title override; Applet Mode is not the validation path |
|
||||||
|
| Do not treat SD FS like desktop POSIX | Lua stays on `love.filesystem`; inbox + MTP for user files |
|
||||||
|
| Isolate platform code | Capability module instead of Android flag overload |
|
||||||
|
| NVK / WSI / `audren` stacks | **Not** copied — love-nx already supplies video/audio/input/FS |
|
||||||
|
|
||||||
|
The packaging goal matches Dusklight’s **single self-contained `.nro`**; contributor transfer stays multi-host (not Mac-only).
|
||||||
|
|
||||||
|
## Known limitations (read before reviewing)
|
||||||
|
|
||||||
|
1. **Transfer is manual and multi-method.** Runtime only needs files under the LÖVE save directory / NRO install folder. Use MTP, direct SD, or FTP per [switch-transfer.md](switch-transfer.md). macOS + OpenMTP is a documented example for OLED evidence — not “Switch requires a Mac.”
|
||||||
|
2. **Deploy is manual.** There is no automated push to the console and no `nxlink` path yet. Operators build locally, transfer files, then title-override launch.
|
||||||
|
3. **Hardware coverage.** Author P0/P1 pass rows were recorded on one Switch OLED; Switch V1 boot was confirmed independently. Treat Lite, docked soak, and other hosts as unknown until someone re-runs the checklist.
|
||||||
|
4. **No ROM/save/mod zip bytes in git.** Legal dumps and third-party mods stay on the console (or local untracked folders).
|
||||||
|
5. **AppleDouble sidecars** (`._*`) from some MTP clients can break zip/ROM/`.sav` scans — the launcher skips hidden `.*` names (including `._*.sav`); still prefer clean copies.
|
||||||
|
|
||||||
|
## How we tested
|
||||||
|
|
||||||
|
| Layer | What | Where |
|
||||||
|
| ----- | ---- | ----- |
|
||||||
|
| Unit / headless | Platform NX flags, RomImporter inbox, dual-path input, mod zip inbox, save `.sav` inbox, display chords, payload/self-tests | `tests/*`, `scripts/test.sh` |
|
||||||
|
| Switch CI / packaging | Path-gated offline selftest (`selftest_build_switch.sh`, `verify_payload.sh --self-test`, `switch_ci_workflows_test.lua`); canonical fused PR artifact | `.github/workflows/ci.yml`, [switch-build.md](switch-build.md) § CI and release |
|
||||||
|
| Probe on hardware | `getOS()==NX`, 1280×720, save path, Joy-Con events | `tools/switch-probe` → OLED |
|
||||||
|
| Integration on hardware | MTP inbox ROM import, Play Red/Blue, naming A/B, quit/reopen save, suspend×10, reboot, fused NRO alone + NRO-only update | `docs/switch-hardware-evidence.md` |
|
||||||
|
| Community hardware | Switch V1 / Erista boot with prebuilt NRO | [booshankles](https://github.com/booshankles) — see evidence log |
|
||||||
|
| Known gaps | Docked soak, ≥30 min long-play, Lite, automated/`nxlink` deploy | Matrix deferred / absent rows |
|
||||||
|
|
||||||
|
Operator evidence must stay in `docs/switch-hardware-evidence.md`. **Do not invent passes** for hardware not run.
|
||||||
|
|
||||||
|
## love-nx 11.5-nx1 (pinned)
|
||||||
|
|
||||||
|
**Tag:** [11.5-nx1](https://github.com/retronx-team/love-nx/releases/tag/11.5-nx1)
|
||||||
|
|
||||||
|
**Local layout (not committed):**
|
||||||
|
|
||||||
|
```text
|
||||||
|
.bazinga/love-nx/11.5-nx1/
|
||||||
|
├── love.nro # homebrew launcher binary (loose mode: copied to gen1recomp.nro)
|
||||||
|
└── love.elf # required for fused NRO builds (devkitPro nacptool/elf2nro)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Manifest:** `scripts/switch/love-nx-11.5-nx1.sha256` lists expected artifact names and SHA-256 checksums. Checksums are filled when binaries are fetched (`TBD_*` placeholders until then).
|
||||||
|
|
||||||
|
### Fetch instructions
|
||||||
|
|
||||||
|
Preferred (automated checksum verify):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scripts/build_switch.sh --fetch
|
||||||
|
```
|
||||||
|
|
||||||
|
That downloads pinned `love.nro` + `love.elf` into `.bazinga/love-nx/11.5-nx1/`
|
||||||
|
and checks them against `scripts/switch/love-nx-11.5-nx1.sha256`. See
|
||||||
|
[switch-build.md](switch-build.md) for the full mode glossary.
|
||||||
|
|
||||||
|
Manual fallback:
|
||||||
|
|
||||||
|
1. Open the [11.5-nx1 release](https://github.com/retronx-team/love-nx/releases/tag/11.5-nx1) and download `love.nro` and `love.elf`.
|
||||||
|
2. Create the directory: `mkdir -p .bazinga/love-nx/11.5-nx1`
|
||||||
|
3. Move both files into that directory.
|
||||||
|
4. Confirm checksums match the manifest:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
shasum -a 256 .bazinga/love-nx/11.5-nx1/love.nro \
|
||||||
|
.bazinga/love-nx/11.5-nx1/love.elf
|
||||||
|
```
|
||||||
|
|
||||||
|
**Never commit** love-nx binaries, ROM dumps, or generated cache into git. The repo `.gitignore` excludes `.bazinga/` (vendor cache) and `/dist/` (build output).
|
||||||
|
|
||||||
|
## Loose-mode dist layout
|
||||||
|
|
||||||
|
Development builds place `gen1recomp.nro` and `game.love` side by side:
|
||||||
|
|
||||||
|
```text
|
||||||
|
dist/switch/loose/
|
||||||
|
├── gen1recomp.nro
|
||||||
|
└── game.love
|
||||||
|
```
|
||||||
|
|
||||||
|
Assemble with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scripts/build_switch.sh --loose
|
||||||
|
```
|
||||||
|
|
||||||
|
(See `scripts/switch/assemble_loose.sh` for the underlying copy + checksum step.)
|
||||||
|
|
||||||
|
## Transfer & deploy (current contributor loop)
|
||||||
|
|
||||||
|
Detail for **macOS / Linux / Windows** and **MTP / SD / FTP** lives in
|
||||||
|
[switch-transfer.md](switch-transfer.md). Summary:
|
||||||
|
|
||||||
|
| Layer | Intent |
|
||||||
|
| ----- | ------ |
|
||||||
|
| **Runtime / players** | Extract the release zip at microSD root (`switch/gen1recomp/`) and land ROMs/mods under the save-dir inboxes. The game does not hard-depend on OpenMTP or macOS. |
|
||||||
|
| **Contributor loop** | Manual copy via MTP (DBI responder), direct SD (Hekate UMS / reader), or FTP. Fully manual — no CI deploy, no `nxlink` yet. |
|
||||||
|
|
||||||
|
The Mac + OpenMTP steps that remain below are the **OLED evidence reproduction** path; prefer the transfer runbook for day-to-day contrib on other hosts.
|
||||||
|
|
||||||
|
**Still avoided for routine evidence** (keeps SD handling honest):
|
||||||
|
|
||||||
|
- Treating `nxlink` / netloader as the release deploy story (deferred)
|
||||||
|
- DBI `MicroSD install` / `NAND install` / NSP-style virtual folders for the `.love`/`.nro` pair
|
||||||
|
|
||||||
|
If MTP fails: check cable, USB port, DBI state, and that only one MTP client holds the device — then retry or switch to SD/FTP. Do not silently rewrite evidence using an untested path and claim parity with recorded SHA-256 round-trips.
|
||||||
|
|
||||||
|
### Manual deploy checklist (today)
|
||||||
|
|
||||||
|
1. Build on the contributor host (`scripts/build_switch.sh --loose` or fused).
|
||||||
|
2. Close Gen1Recomp on the Switch; open DBI → `Run MTP responder`.
|
||||||
|
3. Copy artifacts with your MTP client into `1: SD Card/switch/gen1recomp/` (and ROMs/mods into the save-dir inboxes when needed).
|
||||||
|
4. Wait for the transfer queue; refresh; optionally round-trip SHA-256 on first artifacts of a type.
|
||||||
|
5. Exit MTP; launch via **title override** (hold **R** on a title → hbmenu, not Applet Mode).
|
||||||
|
|
||||||
|
## OpenMTP + DBI transfer (loose build, Mac evidence example)
|
||||||
|
|
||||||
|
Full multi-OS / multi-method steps: [switch-transfer.md](switch-transfer.md).
|
||||||
|
The numbered Mac loop below reproduces the OLED evidence path.
|
||||||
|
|
||||||
|
### On the Switch
|
||||||
|
|
||||||
|
1. Close Gen1Recomp if it is running.
|
||||||
|
2. Open **DBI** from hbmenu.
|
||||||
|
3. Select **`Run MTP responder`** (DBI documents `X` on the main screen).
|
||||||
|
4. Keep DBI on that screen for the entire transfer.
|
||||||
|
5. Connect the Switch to the Mac with a USB-C data cable.
|
||||||
|
|
||||||
|
### On the Mac
|
||||||
|
|
||||||
|
1. Close any other MTP clients.
|
||||||
|
2. Open **OpenMTP** and select the DBI device.
|
||||||
|
3. In the remote pane, open **`1: SD Card`**.
|
||||||
|
4. Navigate to **`switch/`** and create **`gen1recomp/`** if needed.
|
||||||
|
5. Enter **`1: SD Card/switch/gen1recomp/`**.
|
||||||
|
6. Drag from the local pane:
|
||||||
|
|
||||||
|
```text
|
||||||
|
dist/switch/loose/gen1recomp.nro
|
||||||
|
dist/switch/loose/game.love
|
||||||
|
```
|
||||||
|
|
||||||
|
7. Wait for the OpenMTP queue to finish completely.
|
||||||
|
8. Refresh the remote listing and confirm file sizes match the local files.
|
||||||
|
9. On the Switch, exit MTP responder normally in DBI before launching the app.
|
||||||
|
|
||||||
|
Expected layout on SD:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1: SD Card/
|
||||||
|
└── switch/
|
||||||
|
└── gen1recomp/
|
||||||
|
├── gen1recomp.nro
|
||||||
|
└── game.love
|
||||||
|
```
|
||||||
|
|
||||||
|
## Round-trip SHA-256 verification
|
||||||
|
|
||||||
|
For the **first deploy** of each artifact type (loose pair, later fused NRO), verify MTP integrity:
|
||||||
|
|
||||||
|
1. **Before send** — record local hashes:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
shasum -a 256 dist/switch/loose/gen1recomp.nro \
|
||||||
|
dist/switch/loose/game.love
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **After send** — in OpenMTP, copy the same files from `1: SD Card/switch/gen1recomp/` back to an empty local folder, e.g. `dist/switch/mtp-roundtrip/`.
|
||||||
|
|
||||||
|
3. **Compare** round-trip hashes:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
shasum -a 256 dist/switch/mtp-roundtrip/gen1recomp.nro \
|
||||||
|
dist/switch/mtp-roundtrip/game.love
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Local pre-send and round-trip hashes **must match**. Record results in the test report template below.
|
||||||
|
|
||||||
|
Repeat whenever a cable glitch or interrupted transfer is suspected.
|
||||||
|
|
||||||
|
## Title override launch (full memory)
|
||||||
|
|
||||||
|
Applet Mode is **not** the primary validation path. Use **title override** so hbmenu runs with full memory:
|
||||||
|
|
||||||
|
1. Confirm the OpenMTP transfer queue finished.
|
||||||
|
2. Exit MTP responder in DBI; disconnect USB if desired.
|
||||||
|
3. Hold **`R`** while launching any legitimately installed title.
|
||||||
|
4. Keep holding until **hbmenu** appears.
|
||||||
|
5. Confirm hbmenu does **not** show **Applet Mode**.
|
||||||
|
6. Launch **`gen1recomp`** (or the probe NRO during Phase 0).
|
||||||
|
|
||||||
|
Album / applet launches are only useful to document applet-specific limitations; P0/P1 gates use title override.
|
||||||
|
|
||||||
|
## Phase 0 hardware checklist
|
||||||
|
|
||||||
|
Complete **in order** on OLED hardware. Operator fills evidence fields — leave blank until tested.
|
||||||
|
|
||||||
|
| Step | Action | Pass | Evidence / notes |
|
||||||
|
| ---- | ------ | ---- | ---------------- |
|
||||||
|
| P0-0a | Fetch love-nx 11.5-nx1; record manifest SHA-256 | yes | See `scripts/switch/love-nx-11.5-nx1.sha256` |
|
||||||
|
| P0-0b | Build `switch-probe.love` per `tools/switch-probe/README.md` | yes | |
|
||||||
|
| P0-0c | Assemble loose probe (`game.love` = probe) to `dist/switch/loose/` | yes | |
|
||||||
|
| P0-0d | MTP deploy to `1: SD Card/switch/gen1recomp/`; round-trip SHA-256 | yes | nro `8290ac15…5918f5`; love `9f198637…fa2e34f` |
|
||||||
|
| P0-0e | Title override → probe boots; `getOS()` shows `NX` | yes | `getOS()`=`NX`, `love._os`=`NX` |
|
||||||
|
| P0-0f | Probe lists 1280×720 (or documented dims), save path, gamepad/touch log | yes | save `sdmc:/switch/gen1recomp/switch-probe`; Joy-Con Y→#3 X→#4 |
|
||||||
|
| P0-1a | Replace `game.love` with unpatched Gen1Recomp build | yes | feat/switch-nx inbox build |
|
||||||
|
| P0-1b | MTP replace `game.love` only; round-trip SHA-256 | yes | |
|
||||||
|
| P0-1c | Title override → launcher reaches import screen | yes | |
|
||||||
|
| P0-1d | Joy-Con: can navigate launcher (no touch-only) | yes | Full report: `docs/switch-hardware-evidence.md` |
|
||||||
|
|
||||||
|
**Operator:** Andrew **Date:** 2026-08-01 **Console:** Switch OLED only
|
||||||
|
**Deploy:** manual Mac + OpenMTP + DBI MTP (not automated)
|
||||||
|
**love-nx tag:** 11.5-nx1 **gen1recomp commit:** `df7cea4`
|
||||||
|
|
||||||
|
## Phase 0 test report template
|
||||||
|
|
||||||
|
Copy this block into your hardware notes or PR evidence. **Do not commit ROM files or ROM hashes of private dumps.**
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Switch Phase 0 — hardware report
|
||||||
|
|
||||||
|
- Operator:
|
||||||
|
- Date:
|
||||||
|
- Console model:
|
||||||
|
- Atmosphère / HOS version:
|
||||||
|
- gen1recomp commit:
|
||||||
|
- love-nx tag: 11.5-nx1
|
||||||
|
- love.nro SHA-256 (local):
|
||||||
|
- game.love SHA-256 (local, pre-send):
|
||||||
|
- MTP round-trip SHA-256 (gen1recomp.nro):
|
||||||
|
- MTP round-trip SHA-256 (game.love):
|
||||||
|
- Title override used: yes / no
|
||||||
|
- Applet Mode observed: yes / no (should be no for P0)
|
||||||
|
- Probe getOS():
|
||||||
|
- Probe dimensions:
|
||||||
|
- Probe save directory shown:
|
||||||
|
- Gamepad events logged: yes / no
|
||||||
|
- Touch events logged: yes / no
|
||||||
|
- Unpatched launcher boot: pass / fail
|
||||||
|
- Joy-Con launcher navigation: pass / fail / not tested
|
||||||
|
- Notes:
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fast dev loop (loose mode)
|
||||||
|
|
||||||
|
While iterating on Lua/assets:
|
||||||
|
|
||||||
|
1. Edit on Mac; run `scripts/test.sh --quick`.
|
||||||
|
2. Rebuild `.bazinga/work/game.love` (`scripts/build.sh mac --no-notarize` or project pack step).
|
||||||
|
3. Close Gen1Recomp on Switch.
|
||||||
|
4. DBI → `Run MTP responder`.
|
||||||
|
5. OpenMTP → `1: SD Card/switch/gen1recomp/`.
|
||||||
|
6. Replace **only** `game.love`; wait for queue + refresh listing.
|
||||||
|
7. Exit MTP responder; launch via title override.
|
||||||
|
8. Keep `gen1recomp.nro` unchanged until the love-nx pin changes.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scripts/test.sh --quick
|
||||||
|
scripts/build.sh mac --no-notarize
|
||||||
|
scripts/build_switch.sh --loose
|
||||||
|
shasum -a 256 .bazinga/work/game.love
|
||||||
|
```
|
||||||
|
|
||||||
|
## Controller input mapping (NX)
|
||||||
|
|
||||||
|
Measured on Switch OLED (`feat/switch-nx`, love-nx `11.5-nx1`, 1280×720). Both `joystickpressed` and `gamepadpressed` fire for Joy-Con; prefer the gamepad path when `joystick:isGamepad()` is true.
|
||||||
|
|
||||||
|
| Path | Control | Mapping |
|
||||||
|
| ---- | ------- | ------- |
|
||||||
|
| `gamepadpressed` | D-pad / left stick | move |
|
||||||
|
| `gamepadpressed` | SDL `a` / `b` on **NX** | swapped via `NX_GAMEPAD_BINDINGS`: physical **A** (east) = GB A confirm, physical **B** (south) = GB B cancel |
|
||||||
|
| `gamepadpressed` | SDL `a` / `b` on desktop | identity (SDL south = GB A) |
|
||||||
|
| `gamepadpressed` | `start` / `back` | Start / Select (+ / −) |
|
||||||
|
| `gamepadpressed` | Right / left shoulder (no Select) | Cycle game speed up / down (same as PC hotkey `1` / speed-down path) |
|
||||||
|
| `joystickpressed` (raw) | only if **not** `isGamepad()` | face/menu fallback |
|
||||||
|
| `joystickpressed` (raw) | `#1` / `#2` on NX | Nintendo B / A → GB B / A |
|
||||||
|
| `joystickpressed` (raw) | `#9` / `#10` | Select / Start (− / +) |
|
||||||
|
|
||||||
|
**Nintendo UX on Switch:** physical A confirms, physical B cancels (explicit NX remap of SDL face labels).
|
||||||
|
|
||||||
|
**Launcher extras** (`RomImporter`): physical **A** clicks at the virtual cursor; **L** / **R** switch tabs; **Start** / **Select** start Play when a ROM is ready (else open Choose ROM). D-pad / left stick move the virtual cursor.
|
||||||
|
|
||||||
|
**Dual-path rule:** love-nx emits both `gamepadpressed` and `joystickpressed` for Joy-Con. When `joystick:isGamepad()` is true, Input and RomImporter **ignore raw** face/menu so NamingScreen does not see A+B in one frame. `NamingScreen` also prefers A over B if both edges still fire.
|
||||||
|
|
||||||
|
Implementation: `src/core/GamepadMap.lua` (`NX_RAW_*`, `ignoreRawForJoystick`, `displayChordDigit`), `src/core/Game.lua` (shoulder speed), `src/import/RomImporter.lua` (launcher tabs). Launcher and gameplay share the same converter.
|
||||||
|
|
||||||
|
## ROM inbox (NX)
|
||||||
|
|
||||||
|
Legal dumps land in a shared MTP inbox; **Scan again** is tab-scoped:
|
||||||
|
|
||||||
|
| Item | Value |
|
||||||
|
| ---- | ----- |
|
||||||
|
| Save-relative path | `imports/` (also accepts loose `.gb`/`.gbc` at the save-dir root) |
|
||||||
|
| MTP destination | `1: SD Card/<save identity>/imports/` (see launcher notice for the live `getSaveDirectory()` path) |
|
||||||
|
| Candidates | `*.gb` / `*.gbc` (hidden `.*` AppleDouble names skipped) |
|
||||||
|
| Rescan | Game tab → **Scan again** — imports only the dump whose SHA-1 matches that tab (`GameVersion.forSha1`). Other known dumps stay for their own tabs |
|
||||||
|
| Already ready | Same SHA already imported → “No new ROM found.” |
|
||||||
|
|
||||||
|
Players may drop Red, Blue, and Yellow into the same folder. Opening Yellow and pressing **Scan again** must not start a Red import.
|
||||||
|
|
||||||
|
## Mod zip inbox (NX)
|
||||||
|
|
||||||
|
Community mods install from a **separate** MTP inbox (not mixed into the ROM `imports/` scan):
|
||||||
|
|
||||||
|
| Item | Value |
|
||||||
|
| ---- | ----- |
|
||||||
|
| Save-relative path | `imports/mods/` |
|
||||||
|
| MTP destination | `1: SD Card/<save identity>/imports/mods/` (see launcher notice for the live `getSaveDirectory()` path) |
|
||||||
|
| Candidates | `*.zip` only |
|
||||||
|
| Rescan | MODS tab → **Scan again** (installs each zip via `LauncherMods.installZip`; source zips are retained on success and failure) |
|
||||||
|
| FIND MODS | Remains network-gated / hidden on NX (`networkValidated == false`) |
|
||||||
|
|
||||||
|
Do **not** commit third-party mod zip bytes into git. Drop the zip over MTP, rescan, enable in MODS, then Play.
|
||||||
|
|
||||||
|
**MTP tip (esp. macOS clients):** OpenMTP/Finder often creates AppleDouble sidecars named `._Something.zip` / `._cart.gb` / `._foo.sav`. Those are not real archives, ROMs, or saves — the launcher ignores hidden `.*` names under `imports/`, `imports/mods/`, and `imports/saves/<game>/`. If install still fails with “could not be opened” / “not a zip file”, delete any `._*` under the inbox and confirm the real zip starts with the `PK` magic (re-copy the release asset if unsure). This is a host-side annoyance of the current manual MTP loop, not something players should need forever.
|
||||||
|
|
||||||
|
Drop any community release `.zip` into `imports/mods/`, rescan, enable.
|
||||||
|
Player-facing install steps: [switch-install.md](switch-install.md#community-mods).
|
||||||
|
Mods own their OPTIONS / rebinds — do not duplicate third-party control tables here.
|
||||||
|
|
||||||
|
## Save `.sav` inbox (NX)
|
||||||
|
|
||||||
|
Raw Gen1 battery images use a **separate** MTP inbox (not mixed into ROM `imports/` or mod `imports/mods/`):
|
||||||
|
|
||||||
|
| Item | Value |
|
||||||
|
| ---- | ----- |
|
||||||
|
| Save-relative path | `imports/saves/red/`, `imports/saves/blue/`, `imports/saves/yellow/` |
|
||||||
|
| MTP destination | `1: SD Card/<save identity>/imports/saves/<game>/` (see launcher notice for the live `getSaveDirectory()` path) |
|
||||||
|
| Candidates | non-hidden `*.sav` only in **that game’s** folder |
|
||||||
|
| Rescan | SAVE FILES → **Import save** on the matching game tab (scans only that folder) |
|
||||||
|
| After success | Retire to `*.sav.imported` + append content hash to `imports/saves/<game>/.imported-sha1` |
|
||||||
|
| Exports | **Export save** writes under `exports/<game>/gen1recomp-<game>-<slot>.sav`; NX shows an MTP path notice (no `openURL`) |
|
||||||
|
|
||||||
|
Do **not** commit `.sav` bytes into git. Drop the file into the matching game folder over MTP, press **Import save** on that tab, then play. Pull exports from `exports/<game>/`.
|
||||||
|
|
||||||
|
**MTP tip:** the same AppleDouble `._*.sav` rule applies — see the mod inbox tip above.
|
||||||
|
|
||||||
|
## Joy-Con display chords (Select + face)
|
||||||
|
|
||||||
|
PC digit hotkeys for COLORS / TILT / GBC FX / pipelines have Joy-Con equivalents. Hold **Select** (`back` / −) and press a face/shoulder button; the engine runs the same path as `Game:keypressed` for that digit (including `writeOptions` / Pipelines parity).
|
||||||
|
|
||||||
|
| Chord (Nintendo UX) | Engine key | Stock engine effect |
|
||||||
|
| ------------------- | ---------- | ------------------- |
|
||||||
|
| Select + **A** | `2` | COLORS cycle |
|
||||||
|
| Select + **B** | `3` | TILT / perspective |
|
||||||
|
| Select + **Y** | `5` | GBC FX |
|
||||||
|
| Select + **X** | `6` | Mod pipeline hotkey (if registered) |
|
||||||
|
| Select + **L** (left shoulder) | `7` | Mod pipeline hotkey (if registered) |
|
||||||
|
|
||||||
|
Keys `2` / `3` / `4` / `5` are claimed by the engine before mod pipeline hotkeys run, so a community mod cannot rebind those digits through `Pipelines.hotkey`. Mods that need their own controls should use OPTIONS rows or unclaimed hotkeys.
|
||||||
|
|
||||||
|
Without Select held, face buttons keep normal GB A/B gameplay mapping (no accidental color/tilt cycles). The **Options** menu remains available for the same settings — chords are optional shortcuts, not the only path.
|
||||||
|
|
||||||
|
On NX, A/B chords resolve through the Nintendo UX face remap so physical **A** → key `2` and physical **B** → key `3` match this table.
|
||||||
|
|
||||||
|
**OPTIONS → PERFORMANCE** clamps the port’s own extras (TILT / GBC FX / survey ZOOM) and can cap FPS — useful on weaker handheld budgets. Details: [new-features.md — Performance tier](new-features.md#performance-tier-low-end-devices).
|
||||||
|
|
||||||
|
Community mod zip install smoke (MODS inbox + Play): NXMOD-12 in [switch-hardware-evidence.md](switch-hardware-evidence.md).
|
||||||
|
|
||||||
|
**Opt-in diagnostics:** create an empty `switch-debug.txt` in the save directory; events flush to `switch.log` at ≤1 Hz with build identity (no ROM/save bytes).
|
||||||
|
|
||||||
|
**NX asset probe (always on Play):** every Switch Play writes `nx-asset-probe.log` in the save directory (`pokemon-love2d/`). It lists whether `assets/generated/…` vs `yellow|blue/assets/generated/…` exist, what `Assets.resolve` returns, and whether `newImage` / `newImageData` open — for Yellow/Blue blank-sprite triage. No ROM bytes.
|
||||||
|
|
||||||
|
**Blue/Yellow cache overlay (NX):** fused love-nx cannot reliably mount `yellow|blue/assets/generated` onto the un-prefixed path, so `src/core/NxAssetOverlay.lua` wraps EVERY read-side love API that accepts a filesystem path (`filesystem.read/load/lines/newFileData/getInfo`, `graphics.newImage/newFont`, `image.newImageData`, `audio.newSource`, `sound.newSoundData`, `font.newFontData`) once at boot — only when `Platform.isNX()`. Covering the whole read surface (not just the loaders the boot needs today) keeps future states and mods inside the fallback automatically; write-side functions stay stock. Core code must NOT call love loaders on literal `assets/generated` paths (enforced by `tests/engine/nx_generated_guard_test.lua`); the chip-audio worker is a separate Lua state and gets the prefix explicitly via `audio.programPrefix` from `ChipAudio.slimAudio`.
|
||||||
|
|
||||||
|
**Hardware re-test:** T16 **pass** @ `2699c9a` (naming A=confirm / B=cancel). T19 **pass** (quit/reopen, suspend×10, reboot) — operator 2026-08-01.
|
||||||
|
|
||||||
|
**Suspend/resume audio:** after resume, chip music is stopped to avoid duplicate streams; confirm on hardware during P0-09/10 (T19).
|
||||||
|
|
||||||
|
## Lua error log (save directory)
|
||||||
|
|
||||||
|
On any uncaught Lua error, Gen1Recomp appends a redacted trace to `lua-error.log` in the LÖVE save directory (`love.filesystem.getSaveDirectory()`). The on-screen error overlay includes a hint pointing at that file. Logs rotate to `lua-error.log.1` when the active file exceeds 32 KiB. ROM/save bytes and non-printable data are stripped — never commit or share logs that might contain private paths without reviewing them first.
|
||||||
|
|
||||||
|
## Native crash triage (love-nx / Atmosphère)
|
||||||
|
|
||||||
|
love-nx native faults land under the console’s `crash_reports/` folder on SD (reachable via the same manual MTP workflow used for game deploys).
|
||||||
|
|
||||||
|
1. **Collect** — DBI → `Run MTP responder`; copy `sdmc:/crash_reports/*.bin` (or the dated subfolder) to the contributor host. Prefer keeping the microSD in-console for routine pulls.
|
||||||
|
2. **Redact** — delete any attached screenshots or notes that mention ROM filenames, save paths, or private hashes before sharing logs publicly.
|
||||||
|
3. **Symbolize** — use the **pinned** `love.elf` from `.bazinga/love-nx/11.5-nx1/` that matches `build-info.json` / `scripts/switch/love-nx-11.5-nx1.sha256`. Never use a “latest” download.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Example: aarch64-none-elf-addr2line from devkitPro
|
||||||
|
aarch64-none-elf-addr2line -e .bazinga/love-nx/11.5-nx1/love.elf -f -C 0xADDRESS_FROM_CRASH_REPORT
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Correlate** — compare `gitCommit` / `loveNxTag` from embedded `build-info.json` with the operator’s hardware notes.
|
||||||
|
|
||||||
|
If `addr2line` cannot resolve an address, archive the crash `.bin` with the exact `love.elf` SHA-256 used for the build — addresses are only meaningful against that ELF.
|
||||||
|
|
||||||
|
## P0 / P1 hardware matrix (ADR §9)
|
||||||
|
|
||||||
|
Operator evidence lives in `docs/switch-hardware-evidence.md`. **Do not invent passes** for rows that require hardware not yet run.
|
||||||
|
|
||||||
|
| ID | Requirement | Status | Evidence |
|
||||||
|
| -- | ----------- | ------ | -------- |
|
||||||
|
| P0-0a–f | love-nx pin, probe, MTP, title override | **pass** | Phase 0 checklist above; T4 |
|
||||||
|
| P0-1a–d | Unpatched launcher boot + Joy-Con nav | **pass** | T4 / `docs/switch-hardware-evidence.md` |
|
||||||
|
| P0-02 | MTP inbox import path shown | **pass** | T12 |
|
||||||
|
| P0-03 | Rescan imports ROM | **pass** | T12 |
|
||||||
|
| P0-04 | Canonical hash routes version | **pass** | T12 |
|
||||||
|
| P0-05 | Source dump retained in inbox | **pass** | T12 |
|
||||||
|
| P0-06 | Play reaches game after import | **pass** | T12 |
|
||||||
|
| P0-07 | Joy-Con launcher navigation | **pass** | T16 @ `2699c9a` |
|
||||||
|
| P0-08 | Joy-Con gameplay (incl. naming A/B) | **pass** | T16 @ `2699c9a` |
|
||||||
|
| P0-09 | Save survives quit + reopen | **pass** | T19 |
|
||||||
|
| P0-10 | ≥10 suspend cycles, no stuck input/dup audio | **pass** | T19 (operator 2026-08-01) |
|
||||||
|
| P0-12 | Fused NRO boots without adjacent `game.love` | **pass** | T24 — `docs/switch-hardware-evidence.md` |
|
||||||
|
| P0-14 | Fused NRO MTP round-trip SHA-256 | **pass** | T24 — first artifact `b019e2e8…` @ `6fb5602` (redeploy after Blue fix) |
|
||||||
|
| P0-15 | Replace NRO only; saves persist | **pass** | T24 — operator NRO-only update keeps saves |
|
||||||
|
| P1-01 | Docked vs handheld spot-check | **deferred** | Code: `NxDisplay` 720p↔1080p; OLED dock soak not recorded yet |
|
||||||
|
| P1-02 | Applet Mode documented unsupported | **pass** | Title override required; Album path not validated |
|
||||||
|
| P1-03 | Long-play soak (≥30 min) | **deferred** | No soak session recorded |
|
||||||
|
| P1-04 | Reboot persistence | **pass** | T19 |
|
||||||
|
| P1-05 | Audio resume after suspend | **pass** | T19 (no dup audio reported) |
|
||||||
|
| — | Switch V1 / Erista boot | **pass** (boot) | Community — [booshankles](https://github.com/booshankles); see evidence log |
|
||||||
|
| — | Switch Lite / docked soak | **untested** / **deferred** | Welcome contributions |
|
||||||
|
| — | Automated / `nxlink` deploy | **absent** | Manual MTP / SD / FTP only (AD-009) |
|
||||||
|
| — | Multi-OS transfer runbooks | **pass** | [switch-transfer.md](switch-transfer.md) |
|
||||||
|
| — | Community mod zip OLED smoke (NXMOD-12) | **pass** | `docs/switch-hardware-evidence.md` |
|
||||||
|
|
||||||
|
## Review guidance
|
||||||
|
|
||||||
|
Maintainers may review as one PR or split later. Suggested slices (optional):
|
||||||
|
|
||||||
|
Each slice should declare: **no ROM/save bytes committed**, **love-nx pin with manifest checksums**, **hardware-tested rows listed with linked evidence**, **Applet Mode unsupported**, **network/updater disabled on NX**, **deploy still manual** (MTP / SD / FTP; no nxlink yet), **OpenMTP is one example not the sole contract**.
|
||||||
|
|
||||||
|
### Slice 1 — Platform + import (`platform/import`)
|
||||||
|
|
||||||
|
- `src/core/Platform.lua`, `conf.lua` NX branch
|
||||||
|
- `src/import/RomImporter.lua` (NX flags, inbox, scan, shell/updater gates)
|
||||||
|
- Tests: `tests/engine/platform_nx_*`, `tests/engine/rom_importer_nx_*` (ROM-free T2)
|
||||||
|
- Docs: inbox/MTP import sections only
|
||||||
|
|
||||||
|
### Slice 2 — Input + lifecycle (`input/lifecycle`)
|
||||||
|
|
||||||
|
- `src/core/GamepadMap.lua`, `Input.lua`, `main.lua` focus/joystick hooks
|
||||||
|
- `src/debug/SwitchDiagnostics.lua` (opt-in probe + error log)
|
||||||
|
- Tests: input/diagnostics suites
|
||||||
|
- Docs: controller mapping, suspend/audio notes
|
||||||
|
|
||||||
|
### Slice 3 — Build + docs (`build/docs`)
|
||||||
|
|
||||||
|
- `scripts/pack_love.sh`, `scripts/build_switch.sh`, `scripts/switch/*`
|
||||||
|
- `assets/switch/icon.jpg`, `docs/switch-development.md`, hardware evidence templates
|
||||||
|
- Gates: `pack_love.sh --dry-run`, `verify_payload.sh --self-test`, fused build script (devkitPro host)
|
||||||
|
|
||||||
|
**Pre-merge checklist:**
|
||||||
|
|
||||||
|
- [ ] Manifest `scripts/switch/love-nx-11.5-nx1.sha256` filled; binaries not in git
|
||||||
|
- [ ] `verify_payload.sh` rejects generated cache / ROM / `.sav` / `.bak`
|
||||||
|
- [ ] P0 matrix rows marked pass only with linked hardware evidence
|
||||||
|
- [x] Fused NRO P0-12/14/15 pass with T24 evidence (`docs/switch-hardware-evidence.md`)
|
||||||
|
- [ ] Updater / remote mod download hidden on NX (`networkValidated == false`)
|
||||||
|
|
||||||
@@ -0,0 +1,187 @@
|
|||||||
|
# Switch hardware evidence (Phase 0 + import + input)
|
||||||
|
|
||||||
|
> **Hardware evidence log.** Author passes below were recorded on **one
|
||||||
|
> Nintendo Switch OLED** with a **manual** Mac → DBI MTP deploy loop. A
|
||||||
|
> separate community row records Switch V1 / Erista boot. These rows do
|
||||||
|
> **not** claim Lite, docked soak, or automated install. See
|
||||||
|
> `docs/switch-development.md` for status and limitations.
|
||||||
|
|
||||||
|
**love-nx:** `11.5-nx1`
|
||||||
|
**Author console:** Switch OLED
|
||||||
|
**Deploy method (author):** manual OpenMTP + DBI `Run MTP responder` (no CI / no nxlink)
|
||||||
|
**Operator (author rows):** Andrew ([andrewqsantos](https://github.com/andrewqsantos))
|
||||||
|
**Date (author rows):** 2026-08-01
|
||||||
|
|
||||||
|
Do **not** commit ROM dumps or private dump hashes. Do **not** mark a row **pass** without hardware notes for that row.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Community — Switch V1 / Erista boot — pass (boot)
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
| ----- | ----- |
|
||||||
|
| Console | Nintendo Switch V1 (Erista) |
|
||||||
|
| Check | Prebuilt fused NRO boots under title override |
|
||||||
|
| Tester | [booshankles](https://github.com/booshankles) |
|
||||||
|
| Notes | Community confirmation only — not a full P0/P1 matrix re-run on V1 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 0 — probe (T4) — pass
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
| ----- | ----- |
|
||||||
|
| Commit (import era) | `df7cea4` |
|
||||||
|
| `getOS()` / `love._os` | `NX` |
|
||||||
|
| Dimensions | 1280×720 |
|
||||||
|
| Save (probe) | `sdmc:/switch/gen1recomp/switch-probe` |
|
||||||
|
| Joy-Con | `joystickpressed` + `gamepadpressed` (Y→`#3`, X→`#4`) |
|
||||||
|
|
||||||
|
| Artifact | SHA-256 |
|
||||||
|
| -------- | ------- |
|
||||||
|
| `gen1recomp.nro` | `8290ac153d4c630e48c9b26ef9123f5204ed8ee0cef3042511707b5b645918f5` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## T12 — Red import + Play — pass
|
||||||
|
|
||||||
|
Inbox MTP → “Scan again” → Play; Joy-Con launcher/gameplay (not touch-only).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# T16 — Joy-Con launcher + gameplay — pass (naming re-verify)
|
||||||
|
|
||||||
|
### Round 1 @ `7504753` — partial
|
||||||
|
|
||||||
|
| Check | Result |
|
||||||
|
| ----- | ------ |
|
||||||
|
| Launcher / overworld (Joy-Con only) | **pass** |
|
||||||
|
| Naming player/rival | **fail** (dual-path a+b; see below) |
|
||||||
|
| Touch required | **no** |
|
||||||
|
| `game.love` SHA-256 | `bd3a35461bf453c1f0465a5a289421aef3b5c72d3bf1f8d76e86231256829e0e` |
|
||||||
|
|
||||||
|
### Naming failure (root cause) — fixed in `efd81d8` + `2699c9a`
|
||||||
|
|
||||||
|
- love-nx fires **`gamepadpressed` + `joystickpressed` on the same physical press**.
|
||||||
|
- `NamingScreen` tested `wasPressed("b")` before `"a"` → if both true in one frame, always deletes.
|
||||||
|
- Dual-path fix: ignore raw when `isGamepad()` (`efd81d8`).
|
||||||
|
- SDL-only UX then had physical B confirm / A erase; NX face remap (`2699c9a`) restores Nintendo A=confirm / B=cancel.
|
||||||
|
|
||||||
|
### Round 2 @ `2699c9a` — pass (Nintendo UX)
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
| ----- | ----- |
|
||||||
|
| Commit tested | `2699c9a` |
|
||||||
|
| `game.love` SHA-256 | `a208b21e1f30b00e2e8c6fa6efe14f0e06d1db0ae1e50b810b16d9fb852926bc` |
|
||||||
|
| Touch required | **no** |
|
||||||
|
|
||||||
|
| Check | Result |
|
||||||
|
| ----- | ------ |
|
||||||
|
| Naming — player | **pass** — physical **A** confirms letter, **B** cancels/erases |
|
||||||
|
| Naming — rival | **pass** (same) |
|
||||||
|
| Launcher / overworld (prior round) | **pass** (unchanged mapping for d-pad/stick) |
|
||||||
|
|
||||||
|
T16 hardware gate: **closed**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## T19 — save / suspend — pass
|
||||||
|
|
||||||
|
| Check | Result |
|
||||||
|
| ----- | ------ |
|
||||||
|
| Save in-game → full quit → title-override reopen → load save | **pass** (@ `7504753` / retained) |
|
||||||
|
| Suspend/resume ×10 (launcher / gameplay / mixed) | **pass** (operator 2026-08-01) |
|
||||||
|
| Full console reboot persistence | **pass** (operator 2026-08-01) |
|
||||||
|
|
||||||
|
T19 hardware gate: **closed**. No stuck input, duplicate audio, or crash reported.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## T24 — fused NRO alone + NRO-only update — **pass**
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
| ----- | ----- |
|
||||||
|
| First fused attempt | `6fb5602` (Blue Play failed — mount) |
|
||||||
|
| Fix commits | `b1ad7c7` (logs/generated overlay), `ac6dfe7` (Blue/Yellow mount) |
|
||||||
|
| Deploy | isolated folder, no adjacent `game.love` |
|
||||||
|
| Boot fused | **pass** |
|
||||||
|
| ROM import | **pass** |
|
||||||
|
| Play **Red** | **pass** |
|
||||||
|
| Play **Blue** (after `ac6dfe7`) | **pass** (operator 2026-08-01) |
|
||||||
|
| NRO-only replace | **pass** — saves retained; app still boots/plays |
|
||||||
|
| Touch required | no |
|
||||||
|
|
||||||
|
T24 hardware gate: **closed**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## SWBLD — `build_switch.sh --fetch --fused` + install path — **pass**
|
||||||
|
|
||||||
|
Operator smoke for the switch-build-pipeline packaging CLI (closes matrix-deferred happy paths from validation).
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
| ----- | ----- |
|
||||||
|
| Command | `scripts/build_switch.sh --fetch --fused --version 0.0.0-test` |
|
||||||
|
| Host | macOS + native switch-tools (or Docker fallback if used) |
|
||||||
|
| Commit / build-info | `9147a64` (`gitCommit` in build-info) |
|
||||||
|
| love-nx | `11.5-nx1` (manifest checksums match) |
|
||||||
|
| Artifact | `dist/switch/gen1recomp-0.0.0-test-switch.nro` |
|
||||||
|
| NRO SHA-256 | `210efb884a8d27443dc1c64ed8f071b0f862d8d0c9b140ad8185093c4e4027db` |
|
||||||
|
| Install doc | `docs/switch-install.md` — at the time of this row: copy NRO under `sdmc:/switch/gen1recomp/` (releases now ship an SD-ready zip; same folder) |
|
||||||
|
| Console | Switch OLED |
|
||||||
|
| Operator | Andrew |
|
||||||
|
| Date | 2026-08-01 |
|
||||||
|
|
||||||
|
| Check | Result |
|
||||||
|
| ----- | ------ |
|
||||||
|
| `--fetch` + `--fused` produce NRO + `.sha256` | **pass** |
|
||||||
|
| Copy NRO to SD folder per install doc | **pass** (operator) |
|
||||||
|
| Title-override launch / play | treated as prior T24 path; this row records **packaging + deploy to folder** success |
|
||||||
|
|
||||||
|
SWBLD packaging smoke: **closed** for Mac fused build + file-to-SD install step.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## NXMOD-12 — Community mod zip OLED smoke — **pass**
|
||||||
|
|
||||||
|
Closed from existing OLED photo evidence on issue
|
||||||
|
[#531](https://github.com/bryanthaboi/gen1recomp/issues/531) (operator comment
|
||||||
|
with launcher MODS + overworld shots). Photos live on the orphan branch
|
||||||
|
[`switch-oled-photos`](https://github.com/andrewqsantos/gen1recomp/tree/switch-oled-photos)
|
||||||
|
of the operator fork — **not** committed to this repo. Do **not** commit
|
||||||
|
third-party mod `.zip` bytes. Community mods own their OPTIONS / rebinds;
|
||||||
|
this entry only proves the MODS inbox + Play path on OLED.
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
| ----- | ----- |
|
||||||
|
| Status | **pass** |
|
||||||
|
| gen1recomp commit | evidence era on `feat/switch-nx` (see #531); packaging pin love-nx `11.5-nx1` |
|
||||||
|
| love-nx tag | `11.5-nx1` |
|
||||||
|
| Console | Switch OLED |
|
||||||
|
| Mod | community release `.zip` (not vendored; not named here) |
|
||||||
|
| Zip committed to git? | **no** |
|
||||||
|
| Photo evidence | [#531 comment](https://github.com/bryanthaboi/gen1recomp/issues/531) — MODS tab + overworld |
|
||||||
|
| MODS tab photo | https://raw.githubusercontent.com/andrewqsantos/gen1recomp/switch-oled-photos/IMG_1766.jpg |
|
||||||
|
| Overworld photo | https://raw.githubusercontent.com/andrewqsantos/gen1recomp/switch-oled-photos/IMG_1771.jpg |
|
||||||
|
| Operator | Andrew |
|
||||||
|
| Date | 2026-08-01 |
|
||||||
|
|
||||||
|
### Checklist
|
||||||
|
|
||||||
|
| Step | Pass / fail / pending | Notes |
|
||||||
|
| ---- | --------------------- | ----- |
|
||||||
|
| MTP zip into save `imports/mods/` | **pass** | Photo evidence + prior inbox path |
|
||||||
|
| MODS → Scan again → mod listed | **pass** | IMG_1766 — community mod installed |
|
||||||
|
| Enable mod + Play Red boots without crash | **pass** | Overworld / Pallet / Oak lab photos on #531 |
|
||||||
|
| Overworld Select+A → visible colors change | **pass** | Stock COLORS chord path exercised |
|
||||||
|
| Overworld Select+B → visible tilt/perspective change | **pass** | Stock TILT chord path exercised (IMG_1771) |
|
||||||
|
|
||||||
|
### Evidence notes
|
||||||
|
|
||||||
|
```text
|
||||||
|
Operator: Andrew
|
||||||
|
Date: 2026-08-01
|
||||||
|
Commit tested: feat/switch-nx era documented on issue #531
|
||||||
|
Pass / fail summary: PASS — MODS zip install + Play on Switch OLED
|
||||||
|
Photo branch: andrewqsantos/gen1recomp@switch-oled-photos
|
||||||
|
```
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
# Install Gen1Recomp on Nintendo Switch
|
||||||
|
|
||||||
|
Every GitHub Release that includes Switch support ships an SD-ready zip:
|
||||||
|
`gen1recomp-*-switch.zip`. Extract it at the root of your microSD (install
|
||||||
|
**or** update — same steps), launch with **title override**, then import your
|
||||||
|
own legal `.gb` ROM.
|
||||||
|
|
||||||
|
> You need a console that can run Switch homebrew (custom firmware / hbmenu).
|
||||||
|
> This project does not help you set that up. Tracks issue
|
||||||
|
> [#531](https://github.com/bryanthaboi/gen1recomp/issues/531).
|
||||||
|
> Hardware: **OLED** validated by the porter; **V1 / Erista** boot confirmed
|
||||||
|
> by the community. Lite and other setups welcome more reports.
|
||||||
|
> See [switch-development.md](switch-development.md) for limitations.
|
||||||
|
|
||||||
|
Prefer building from source? See [switch-build.md](switch-build.md).
|
||||||
|
|
||||||
|
Port by [andrewqsantos](https://github.com/andrewqsantos). Community testing
|
||||||
|
help from [booshankles](https://github.com/booshankles).
|
||||||
|
|
||||||
|
## 1. Download the zip
|
||||||
|
|
||||||
|
1. Open
|
||||||
|
[Releases](https://github.com/bryanthaboi/gen1recomp/releases).
|
||||||
|
2. Download `gen1recomp-*-switch.zip` for the version you want.
|
||||||
|
(Optional: verify against `sha256sums.txt` in the same release.)
|
||||||
|
|
||||||
|
## 2. Extract onto the microSD
|
||||||
|
|
||||||
|
Extract the zip at the **root** of the microSD so you get:
|
||||||
|
|
||||||
|
```text
|
||||||
|
sdmc:/switch/gen1recomp/gen1recomp.nro
|
||||||
|
sdmc:/switch/gen1recomp/pokemon-love2d/imports/
|
||||||
|
sdmc:/switch/gen1recomp/pokemon-love2d/imports/mods/
|
||||||
|
sdmc:/switch/gen1recomp/pokemon-love2d/imports/saves/...
|
||||||
|
```
|
||||||
|
|
||||||
|
Merge folders if your OS asks. Any method works: **MTP** (DBI → Run MTP
|
||||||
|
responder + a client), **direct SD** (Hekate UMS or a card reader), or **FTP**.
|
||||||
|
Exit MTP / unmount / stop FTP cleanly before launching. Step-by-step for
|
||||||
|
macOS, Linux, and Windows: [switch-transfer.md](switch-transfer.md).
|
||||||
|
|
||||||
|
### Updating
|
||||||
|
|
||||||
|
Use the **same** extract/merge. It replaces `gen1recomp.nro` (and the small
|
||||||
|
help `README.txt` / `INSTALL.txt` files). Saves, imported ROMs, mods, and
|
||||||
|
options live under `pokemon-love2d/` — **do not delete that folder** when
|
||||||
|
updating, or you will lose progress.
|
||||||
|
|
||||||
|
## 3. Launch with title override
|
||||||
|
|
||||||
|
**Applet Mode is not supported** for this game (not enough memory).
|
||||||
|
|
||||||
|
1. On the Switch HOME menu, highlight any installed title.
|
||||||
|
2. Hold **R** and launch that title — this opens hbmenu with full memory
|
||||||
|
(title override).
|
||||||
|
3. From hbmenu, open `gen1recomp`.
|
||||||
|
|
||||||
|
Do **not** launch from the Album applet path for normal play.
|
||||||
|
|
||||||
|
## 4. Import your ROM
|
||||||
|
|
||||||
|
This project ships **no** game data. On first launch:
|
||||||
|
|
||||||
|
1. Put your own legally obtained Pokémon Red, Blue (`.gb`), or Yellow
|
||||||
|
(`.gbc`) dump into `switch/gen1recomp/pokemon-love2d/imports/` (the
|
||||||
|
launcher also shows the live save-dir path). All three can sit in the
|
||||||
|
same folder.
|
||||||
|
2. Use **Scan again** on that game’s tab (Red / Blue / Yellow). Rescan
|
||||||
|
matches by ROM SHA-1 for the open tab only — a Red dump never imports
|
||||||
|
from the Yellow tab (and vice versa).
|
||||||
|
|
||||||
|
## 5. Import / Export a raw `.sav`
|
||||||
|
|
||||||
|
Continue a cart or PC battery save (or pull a slot off-console) via MTP /
|
||||||
|
SD / FTP — same transfer methods as ROMs. Paths are **per game**:
|
||||||
|
|
||||||
|
| Game | Import inbox | Export folder |
|
||||||
|
| ---- | ------------ | ------------- |
|
||||||
|
| Red | `imports/saves/red/` | `exports/red/` |
|
||||||
|
| Blue | `imports/saves/blue/` | `exports/blue/` |
|
||||||
|
| Yellow | `imports/saves/yellow/` | `exports/yellow/` |
|
||||||
|
|
||||||
|
(Under the save dir `pokemon-love2d/` — the zip already creates these folders.)
|
||||||
|
|
||||||
|
1. Copy a Gen1 `.sav` (32 KB) into that game’s inbox under the save dir
|
||||||
|
([switch-transfer.md](switch-transfer.md)).
|
||||||
|
2. With the game’s ROM already imported, open **that game’s tab** →
|
||||||
|
**SAVE FILES** → **Import save**. Only that folder is scanned.
|
||||||
|
3. A successful import retires the file to `*.sav.imported` and records its
|
||||||
|
content hash so pressing **Import save** again does not clone slots.
|
||||||
|
Failed imports leave the original `.sav` in place.
|
||||||
|
4. To pull a slot off the console, use **Export save**, then copy the file
|
||||||
|
from that game’s **`exports/<game>/`** folder via MTP / SD / FTP.
|
||||||
|
|
||||||
|
Do not put `.sav` files into git. Prefer clean copies — some MTP clients
|
||||||
|
create `._*.sav` AppleDouble sidecars that are not real saves.
|
||||||
|
|
||||||
|
## Controls
|
||||||
|
|
||||||
|
### Gameplay
|
||||||
|
|
||||||
|
| Control | Action |
|
||||||
|
| ------- | ------ |
|
||||||
|
| D-pad / left stick | Move |
|
||||||
|
| **A** | Confirm |
|
||||||
|
| **B** | Cancel |
|
||||||
|
| **+** (Start) | Start |
|
||||||
|
| **−** (Select) | Select |
|
||||||
|
| **R** (no Select held) | Cycle game speed up |
|
||||||
|
| **L** (no Select held) | Cycle game speed down |
|
||||||
|
|
||||||
|
### Launcher
|
||||||
|
|
||||||
|
| Control | Action |
|
||||||
|
| ------- | ------ |
|
||||||
|
| D-pad / left stick | Move virtual cursor |
|
||||||
|
| **A** | Click at cursor |
|
||||||
|
| **L** / **R** | Previous / next tab |
|
||||||
|
| **Start** / **Select** | Play if a ROM is ready; otherwise Choose ROM |
|
||||||
|
|
||||||
|
### System
|
||||||
|
|
||||||
|
| Control | Action |
|
||||||
|
| ------- | ------ |
|
||||||
|
| Hold **R** on HOME, then open from hbmenu | Title override (full memory) |
|
||||||
|
|
||||||
|
## Community mods
|
||||||
|
|
||||||
|
Mods install from a zip inbox (same transfer methods as ROMs):
|
||||||
|
|
||||||
|
1. Copy a release `.zip` into the save-dir **`imports/mods/`** path the
|
||||||
|
launcher shows (MTP / SD / FTP — [switch-transfer.md](switch-transfer.md)).
|
||||||
|
2. In the launcher, open **MODS** → **Scan again** → enable the mod →
|
||||||
|
**Play**.
|
||||||
|
|
||||||
|
Remote **FIND MODS** / GitHub download stays **off** on Switch. Do not put
|
||||||
|
mod zips into git. Community mods ship their own OPTIONS / rebinds — this port
|
||||||
|
does not document third-party control tables.
|
||||||
|
|
||||||
|
### Joy-Con shortcuts (Select + face)
|
||||||
|
|
||||||
|
Hold **Select** (−) and press a face/shoulder button. Without Select, A/B stay
|
||||||
|
normal gameplay confirm/cancel. These chords are the stock engine display
|
||||||
|
hotkeys (`2`/`3`/`5` are claimed before any mod pipeline hotkey runs).
|
||||||
|
|
||||||
|
| Chord | Same as PC key | Stock engine effect |
|
||||||
|
| ----- | -------------- | ------------------- |
|
||||||
|
| Select + **A** | `2` | COLORS |
|
||||||
|
| Select + **B** | `3` | TILT |
|
||||||
|
| Select + **Y** | `5` | GBC FX |
|
||||||
|
| Select + **X** | `6` | Mod pipeline hotkey (if a mod registers `6`) |
|
||||||
|
| Select + **L** | `7` | Mod pipeline hotkey (if a mod registers `7`) |
|
||||||
|
|
||||||
|
If the handheld stutters with extras on, try **OPTIONS → PERFORMANCE** →
|
||||||
|
`LOW` or `BALANCED`. Full chord notes for contributors:
|
||||||
|
[switch-development.md](switch-development.md#joy-con-display-chords-select--face).
|
||||||
|
|
||||||
|
## Prefer building it yourself?
|
||||||
|
|
||||||
|
Building the fused NRO (and SD-ready zip) from source is covered in
|
||||||
|
[switch-build.md](switch-build.md). Copying artifacts and inbox files
|
||||||
|
(MTP / SD / FTP on macOS, Linux, Windows): [switch-transfer.md](switch-transfer.md).
|
||||||
|
Status, limitations, and how we tested: [switch-development.md](switch-development.md).
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
# Switch file transfer (MTP / SD / FTP)
|
||||||
|
|
||||||
|
Canonical ways to put Gen1Recomp artifacts and inbox files onto a Nintendo
|
||||||
|
Switch. **Any method is valid** if the bytes land in the destinations below.
|
||||||
|
|
||||||
|
This is the home runbook for contributors on **macOS, Linux, and Windows**.
|
||||||
|
Player install (what to download, title override) stays in
|
||||||
|
[switch-install.md](switch-install.md). Packaging stays in
|
||||||
|
[switch-build.md](switch-build.md). Hardware evidence lives in
|
||||||
|
[switch-hardware-evidence.md](switch-hardware-evidence.md).
|
||||||
|
|
||||||
|
> **Not supported yet:** `nxlink` / hbmenu netloader automation. Useful later
|
||||||
|
> for a fast contrib rebuild loop; deferred on purpose (AD-009). Do not treat
|
||||||
|
> netloader as the release or ROM/mod install path.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Destinations (shared by every method)
|
||||||
|
|
||||||
|
| What | Where on the console |
|
||||||
|
| ---- | -------------------- |
|
||||||
|
| SD-ready release zip | Extract at microSD **root** → `sdmc:/switch/gen1recomp/gen1recomp.nro` plus `pokemon-love2d/` inbox folders. Install and update use the same merge; do **not** delete `pokemon-love2d/` |
|
||||||
|
| Loose iteration pair | `sdmc:/switch/gen1recomp/gen1recomp.nro` **and** `game.love` beside it |
|
||||||
|
| ROM inbox | LÖVE save dir → `imports/` (launcher shows the live `getSaveDirectory()` path; under MTP often `1: SD Card/<save identity>/imports/`) |
|
||||||
|
| Mod zip inbox | Same save dir → `imports/mods/` then MODS → **Scan again** |
|
||||||
|
| Save `.sav` inbox | Same save dir → `imports/saves/red\|blue\|yellow/` then that game’s SAVE FILES → **Import save** |
|
||||||
|
| Save exports | Same save dir → `exports/red\|blue\|yellow/` (pull after **Export save**; MTP / SD / FTP) |
|
||||||
|
| Opt-in diagnostics | Empty `switch-debug.txt` in the save dir → `switch.log` |
|
||||||
|
| Lua error log | `lua-error.log` in the save dir |
|
||||||
|
|
||||||
|
Saves persist across zip re-extract / NRO replacements as long as
|
||||||
|
`pokemon-love2d/` is left in place. Never commit ROM dumps, `.sav`
|
||||||
|
files, or third-party mod zips to git.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Canonical methods
|
||||||
|
|
||||||
|
### 1. MTP (DBI responder + host client)
|
||||||
|
|
||||||
|
On the Switch: close Gen1Recomp → open **DBI** → **Run MTP responder** (often
|
||||||
|
**X** on the main screen) → keep that screen up → USB-C data cable to the host.
|
||||||
|
|
||||||
|
On the host: open **one** MTP client, navigate to **`1: SD Card`**, then the
|
||||||
|
paths above. Wait for the transfer queue; refresh; exit MTP on the Switch
|
||||||
|
before launching.
|
||||||
|
|
||||||
|
#### macOS (example: OpenMTP)
|
||||||
|
|
||||||
|
[OpenMTP](https://github.com/ganeshrvel/openmtp) is the loop used for OLED
|
||||||
|
hardware evidence — **one contributor example**, not a Mac-only product rule.
|
||||||
|
|
||||||
|
1. Quit other MTP clients.
|
||||||
|
2. Open OpenMTP → select the DBI device → **`1: SD Card`**.
|
||||||
|
3. Create `switch/gen1recomp/` if needed; extract the release zip at SD root
|
||||||
|
(or copy NRO / `game.love` for loose).
|
||||||
|
4. For ROMs/mods/saves, open the save-dir `imports/`, `imports/mods/`,
|
||||||
|
`imports/saves/<red|blue|yellow>/`, or `exports/<red|blue|yellow>/` path the
|
||||||
|
launcher prints.
|
||||||
|
5. Wait for the queue; refresh; exit MTP responder; title-override launch.
|
||||||
|
|
||||||
|
macOS clients often create AppleDouble sidecars (`._Something.zip`,
|
||||||
|
`._cart.gb`, `._foo.sav`). Those are not real archives or saves — the
|
||||||
|
launcher skips hidden `.*` names. Delete `._*` junk if a zip/ROM/`.sav`
|
||||||
|
fails to open.
|
||||||
|
|
||||||
|
#### Linux
|
||||||
|
|
||||||
|
1. Install desktop MTP support if needed (e.g. `gvfs-mtp` on GNOME/GTK
|
||||||
|
desktops, or your distro’s KDE MTP stack).
|
||||||
|
2. With DBI MTP active, open **Files** / **Dolphin** / **Thunar** and select
|
||||||
|
the Switch / DBI device → **`1: SD Card`**.
|
||||||
|
3. Extract the release zip at SD root (merge), or copy into `switch/gen1recomp/`
|
||||||
|
and the save-dir inboxes as above.
|
||||||
|
4. Use **only one** MTP accessor at a time. If `mtp-tools` / `mtpfs` reports
|
||||||
|
“device is busy”, close the file manager’s MTP mount (or the CLI mount)
|
||||||
|
and retry with a single client.
|
||||||
|
5. Eject/unmount cleanly; exit MTP on the Switch; title-override launch.
|
||||||
|
|
||||||
|
If MTP is unavailable or flaky on Linux, use **direct SD** (Hekate UMS or a
|
||||||
|
card reader) or **FTP** instead — same destinations in the table above.
|
||||||
|
|
||||||
|
#### Windows
|
||||||
|
|
||||||
|
1. With DBI MTP active, open **This PC** / **File Explorer** and look under
|
||||||
|
**Portable Devices** for the Switch / DBI MTP volume → **`1: SD Card`**.
|
||||||
|
2. Copy / extract into `switch\gen1recomp\` and the save-dir inboxes.
|
||||||
|
3. Optional: [OpenMTP](https://github.com/ganeshrvel/openmtp) on Windows if
|
||||||
|
Explorer is flaky.
|
||||||
|
4. If Windows does not show an MTP device: Device Manager → find DBI / Switch
|
||||||
|
→ Update driver → **MTP USB Device** (or Standard MTP Device). Prefer a
|
||||||
|
data-capable USB-C cable and a direct port.
|
||||||
|
5. Safely disconnect; exit MTP on the Switch; title-override launch.
|
||||||
|
|
||||||
|
If MTP is unavailable or flaky on Windows, use **direct SD** (Hekate UMS or a
|
||||||
|
card reader) or **FTP** instead — same destinations in the table above.
|
||||||
|
|
||||||
|
### 2. Direct SD (Hekate UMS or card reader)
|
||||||
|
|
||||||
|
Same destinations; no MTP client required.
|
||||||
|
|
||||||
|
- **Hekate UMS** (preferred when available): expose the microSD to the host
|
||||||
|
while the card stays in the console; mount the volume; copy files; **cleanly
|
||||||
|
unmount** before leaving UMS.
|
||||||
|
- **Physical reader**: power off / remove the microSD, copy on the host,
|
||||||
|
**eject safely**, reinsert, boot CFW, title-override launch.
|
||||||
|
|
||||||
|
Do not yank the card or unplug UMS mid-write.
|
||||||
|
|
||||||
|
### 3. FTP (any SD-exposing Switch FTP)
|
||||||
|
|
||||||
|
Any homebrew FTP server that can write the microSD is fine — for example
|
||||||
|
**DBI’s own FTP**, **sys-ftpd-light**, or **Sphaira** (names are illustrations
|
||||||
|
only; pick what your CFW setup already uses).
|
||||||
|
|
||||||
|
1. Start the FTP server on the Switch; note IP/port/credentials from that app.
|
||||||
|
2. From the host, connect with any FTP client and upload to the same
|
||||||
|
`switch/gen1recomp/`, `imports/`, `imports/mods/`, `imports/saves/<game>/`,
|
||||||
|
and `exports/<game>/` paths.
|
||||||
|
3. Stop the FTP server cleanly before launching Gen1Recomp.
|
||||||
|
|
||||||
|
If credentials or chroots differ by app, trust the **destination paths**, not
|
||||||
|
a single vendor tutorial.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## After every transfer
|
||||||
|
|
||||||
|
1. Exit MTP / unmount SD / stop FTP cleanly.
|
||||||
|
2. Launch via **title override** (hold **R** on a title → hbmenu). **Applet
|
||||||
|
Mode is not supported** (not enough memory).
|
||||||
|
3. For ROMs: open the matching game tab → **Scan again** if the file was
|
||||||
|
added after boot (SHA-1 must match that tab; other dumps in `imports/`
|
||||||
|
stay for their own tabs). For mods: MODS → **Scan again** → enable →
|
||||||
|
Play. For saves: SAVE FILES → **Import save** (rescans
|
||||||
|
`imports/saves/<game>/`). Pull exported `.sav` files from
|
||||||
|
`exports/<game>/`. Joy-Con display chords (stock engine):
|
||||||
|
[switch-install.md](switch-install.md#joy-con-shortcuts-select--face).
|
||||||
|
|
||||||
|
### Optional NRO integrity check
|
||||||
|
|
||||||
|
For the first deploy of a given artifact (or after a flaky cable):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
shasum -a 256 path/to/gen1recomp.nro # or sha256sum
|
||||||
|
```
|
||||||
|
|
||||||
|
Copy the file back from the SD and compare hashes. Round-trip must match.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Failure modes (quick)
|
||||||
|
|
||||||
|
| Symptom | What to try |
|
||||||
|
| ------- | ----------- |
|
||||||
|
| Device busy / no MTP volume | One client only; different cable/port; Windows MTP USB Device driver; alternate method (SD or FTP) |
|
||||||
|
| Zip/ROM/`.sav` “could not be opened” | Delete `._*` sidecars (including `._*.sav`); confirm real zip starts with `PK` |
|
||||||
|
| Half-copied NRO / crash on boot | Re-copy; verify SHA-256; exit transfer mode before launch |
|
||||||
|
| App opens in Applet Mode | Use title override (hold **R**), not Album |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Related
|
||||||
|
|
||||||
|
- Players: [switch-install.md](switch-install.md)
|
||||||
|
- Builders: [switch-build.md](switch-build.md)
|
||||||
|
- Status / hardware matrix: [switch-development.md](switch-development.md)
|
||||||
|
- Evidence log: [switch-hardware-evidence.md](switch-hardware-evidence.md)
|
||||||
@@ -10,8 +10,41 @@
|
|||||||
|
|
||||||
local editorMode = os.getenv("POKEPORT_EDITOR") == "1" or POKEPORT_EDITOR_MODE == true
|
local editorMode = os.getenv("POKEPORT_EDITOR") == "1" or POKEPORT_EDITOR_MODE == true
|
||||||
|
|
||||||
|
local SwitchDiagnostics = require("src.debug.SwitchDiagnostics")
|
||||||
|
local LaunchOptions = require("src.core.LaunchOptions")
|
||||||
|
local NxDisplay = require("src.core.NxDisplay")
|
||||||
|
|
||||||
|
-- Lua errors: persist a redacted trace in the save dir and surface a hint.
|
||||||
|
do
|
||||||
|
local defaultErrorHandler = love.errorhandler
|
||||||
|
function love.errorhandler(msg)
|
||||||
|
local hint = SwitchDiagnostics.logLuaError(msg)
|
||||||
|
if hint and type(msg) == "string" then
|
||||||
|
msg = msg .. "\n\n" .. hint
|
||||||
|
end
|
||||||
|
if defaultErrorHandler then
|
||||||
|
return defaultErrorHandler(msg)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local Game, EditorApp, Importer, TouchEditor
|
local Game, EditorApp, Importer, TouchEditor
|
||||||
|
|
||||||
|
-- #887: quit-to-launcher state, shared by love.load and love.quit (both need
|
||||||
|
-- it, so it is declared here rather than next to love.quit).
|
||||||
|
-- * launchedIntoGame -- a --game / POKEPORT_GAME shortcut booted this
|
||||||
|
-- session straight into a game, so there is no launcher behind it and a
|
||||||
|
-- window close must exit. Restarting instead re-read the same shortcut
|
||||||
|
-- and came right back into the game, and the next close did it again:
|
||||||
|
-- the app could not be closed at all (macOS feels this worst, where the
|
||||||
|
-- red X, Cmd+Q and the Dock's Quit are all the same quit event).
|
||||||
|
-- * RELAUNCH_MARKER -- written in the save dir just before the #785
|
||||||
|
-- restart, so the fresh boot ignores any boot-straight-into-a-game
|
||||||
|
-- option exactly once and keeps #785's promise of landing in the
|
||||||
|
-- launcher, whatever put the game on screen this time.
|
||||||
|
local launchedIntoGame = false
|
||||||
|
local RELAUNCH_MARKER = "relaunch_to_launcher.txt"
|
||||||
|
|
||||||
local autopilot -- optional scripted-input dev tool (tests/autopilot.lua)
|
local autopilot -- optional scripted-input dev tool (tests/autopilot.lua)
|
||||||
local driverCo -- optional frame-driver (POKEPORT_DRIVER=file.lua): a
|
local driverCo -- optional frame-driver (POKEPORT_DRIVER=file.lua): a
|
||||||
-- coroutine that receives `Game` and yields once per
|
-- coroutine that receives `Game` and yields once per
|
||||||
@@ -103,6 +136,11 @@ local function openEditor(version, slotId)
|
|||||||
require("src.import.CacheFs").mountVersion(version)
|
require("src.import.CacheFs").mountVersion(version)
|
||||||
editorVersion = version
|
editorVersion = version
|
||||||
editorHost = Importer
|
editorHost = Importer
|
||||||
|
-- Drop launcher pad/FlexLove so the save editor owns input (NX shim +
|
||||||
|
-- virtual cursor + system hand cursor). Desktop park is a light no-op.
|
||||||
|
if Importer and Importer.prepareOverlayHandoff then
|
||||||
|
Importer:prepareOverlayHandoff()
|
||||||
|
end
|
||||||
Importer = nil
|
Importer = nil
|
||||||
editorMode = true
|
editorMode = true
|
||||||
resizeForEditor()
|
resizeForEditor()
|
||||||
@@ -129,6 +167,9 @@ function closeEditor()
|
|||||||
restoreWindow()
|
restoreWindow()
|
||||||
Importer = editorHost
|
Importer = editorHost
|
||||||
editorHost = nil
|
editorHost = nil
|
||||||
|
if Importer and Importer.resumeAfterOverlay then
|
||||||
|
Importer:resumeAfterOverlay()
|
||||||
|
end
|
||||||
if Importer and version and Importer.savesChanged then
|
if Importer and version and Importer.savesChanged then
|
||||||
Importer:savesChanged(version)
|
Importer:savesChanged(version)
|
||||||
end
|
end
|
||||||
@@ -142,6 +183,9 @@ local closeTouchControlsEditor -- forward declaration
|
|||||||
|
|
||||||
local function openTouchControlsEditor()
|
local function openTouchControlsEditor()
|
||||||
touchEditorHost = Importer
|
touchEditorHost = Importer
|
||||||
|
if Importer and Importer.prepareOverlayHandoff then
|
||||||
|
Importer:prepareOverlayHandoff()
|
||||||
|
end
|
||||||
Importer = nil
|
Importer = nil
|
||||||
TouchEditor = require("src.ui.TouchControlsEditor")
|
TouchEditor = require("src.ui.TouchControlsEditor")
|
||||||
TouchEditor.load({ onClose = function() closeTouchControlsEditor() end })
|
TouchEditor.load({ onClose = function() closeTouchControlsEditor() end })
|
||||||
@@ -152,6 +196,9 @@ function closeTouchControlsEditor()
|
|||||||
TouchEditor = nil
|
TouchEditor = nil
|
||||||
Importer = touchEditorHost
|
Importer = touchEditorHost
|
||||||
touchEditorHost = nil
|
touchEditorHost = nil
|
||||||
|
if Importer and Importer.resumeAfterOverlay then
|
||||||
|
Importer:resumeAfterOverlay()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function bootGame(version)
|
local function bootGame(version)
|
||||||
@@ -161,7 +208,16 @@ local function bootGame(version)
|
|||||||
-- data, so data/generated + assets/generated resolve to that version's files.
|
-- data, so data/generated + assets/generated resolve to that version's files.
|
||||||
local GameVersion = require("src.core.GameVersion")
|
local GameVersion = require("src.core.GameVersion")
|
||||||
GameVersion.set(version or os.getenv("POKEPORT_VERSION") or "red")
|
GameVersion.set(version or os.getenv("POKEPORT_VERSION") or "red")
|
||||||
require("src.import.CacheFs").mountVersion(GameVersion.get())
|
local CacheFs = require("src.import.CacheFs")
|
||||||
|
-- Keep CacheFs.prefix aligned for any CacheFs.read fallback during Data:load
|
||||||
|
-- (Blue/Yellow caches live under blue/ / yellow/).
|
||||||
|
CacheFs.prefix = GameVersion.cachePrefix()
|
||||||
|
CacheFs.mountVersion(GameVersion.get())
|
||||||
|
-- NX: always write nx-asset-probe.log so Yellow/Blue art failures are
|
||||||
|
-- diagnosable from the SD without enabling switch-debug.txt.
|
||||||
|
pcall(function()
|
||||||
|
require("src.debug.SwitchDiagnostics").probeAssets(GameVersion.get())
|
||||||
|
end)
|
||||||
if love.window and love.window.setTitle then
|
if love.window and love.window.setTitle then
|
||||||
local Version = require("src.core.Version")
|
local Version = require("src.core.Version")
|
||||||
love.window.setTitle(Version.title(
|
love.window.setTitle(Version.title(
|
||||||
@@ -189,6 +245,13 @@ function love.load(args)
|
|||||||
-- of each flashing their own cmd.exe window (#606). No-op elsewhere.
|
-- of each flashing their own cmd.exe window (#606). No-op elsewhere.
|
||||||
require("src.core.HostShell").hideHostConsole()
|
require("src.core.HostShell").hideHostConsole()
|
||||||
|
|
||||||
|
-- NX fused mounts are unreliable for the blue|yellow cache overlay: wrap
|
||||||
|
-- the love loaders once so every generated-asset read falls back to the
|
||||||
|
-- versioned save-dir copy. Never installed on desktop/Android/iOS.
|
||||||
|
if require("src.core.Platform").isNX() then
|
||||||
|
require("src.core.NxAssetOverlay").install()
|
||||||
|
end
|
||||||
|
|
||||||
-- Self-updater boot shell: a fused build may mount and chainload a newer
|
-- Self-updater boot shell: a fused build may mount and chainload a newer
|
||||||
-- downloaded payload here. True means it took over, so we must stop. A
|
-- downloaded payload here. True means it took over, so we must stop. A
|
||||||
-- dev / source checkout no-ops (see src/update/Boot.lua).
|
-- dev / source checkout no-ops (see src/update/Boot.lua).
|
||||||
@@ -208,6 +271,16 @@ function love.load(args)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
love.graphics.setDefaultFilter("nearest", "nearest")
|
love.graphics.setDefaultFilter("nearest", "nearest")
|
||||||
|
-- NX: handheld 720p / docked 1080p. Runs for every boot path (launcher,
|
||||||
|
-- editor, scripted); no-op on desktop/mobile.
|
||||||
|
NxDisplay.sync()
|
||||||
|
|
||||||
|
-- Apply the persisted Android orientation lock (#592) before the launcher
|
||||||
|
-- shows: SDL created the window with no orientation hint, so without this
|
||||||
|
-- the launcher would rotate freely until Game:applyOptions runs at boot.
|
||||||
|
-- No-op on desktop / iOS / when options.lua does not exist yet.
|
||||||
|
require("src.core.Orientation").applyOptions(
|
||||||
|
require("src.core.SaveData").loadOptions())
|
||||||
|
|
||||||
-- Standalone editor. A bare `--editor` run has no launcher behind it, so
|
-- Standalone editor. A bare `--editor` run has no launcher behind it, so
|
||||||
-- Close quits; --save points it at a specific file, otherwise it opens the
|
-- Close quits; --save points it at a specific file, otherwise it opens the
|
||||||
@@ -256,6 +329,47 @@ function love.load(args)
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- The launcher draws before any game boots, so the mod loader has not run
|
||||||
|
-- and Strings has no catalog. Routing the launcher's text through Strings
|
||||||
|
-- (#767) only pays off if something fills that catalog this early, and no
|
||||||
|
-- restart could: the ordering is the same on every launch. Read the
|
||||||
|
-- enabled mods' string catalogs -- data only, no entry chunk -- so a
|
||||||
|
-- translation reaches the launcher too. Game:load replaces this with the
|
||||||
|
-- real merged catalog once a version boots.
|
||||||
|
do
|
||||||
|
local preload = require("src.mods.LauncherMods").translationStrings()
|
||||||
|
if preload then require("src.core.Strings").load({ strings = preload }) end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- LAUNCH OPTIONS: skip the launcher and boot a game directly.
|
||||||
|
-- --game red|blue|yellow (or POKEPORT_GAME / POKEPORT_LAUNCH)
|
||||||
|
-- --slot <id> optional; picks the save slot to load
|
||||||
|
-- --launcher force the launcher even if a game is set
|
||||||
|
-- This is what a desktop shortcut, a Steam entry, or a frontend like
|
||||||
|
-- EmulationStation needs: one click into the game the player wants, with no
|
||||||
|
-- menu in between. A game that is not imported falls through to the
|
||||||
|
-- launcher on its tab rather than booting into nothing.
|
||||||
|
-- A window close that restarted us into the launcher (#785) leaves the
|
||||||
|
-- marker behind: consume it and stay on the launcher, or the shortcut below
|
||||||
|
-- would boot the same game again and that close would restart again,
|
||||||
|
-- forever (#887). Consumed on read, so the very next launch is normal.
|
||||||
|
local relaunched = love.filesystem.getInfo(RELAUNCH_MARKER) ~= nil
|
||||||
|
if relaunched then pcall(love.filesystem.remove, RELAUNCH_MARKER) end
|
||||||
|
|
||||||
|
local launchGame, launchSlot = LaunchOptions.resolve(arg)
|
||||||
|
if launchGame and not relaunched and not LaunchOptions.forceLauncher(arg) then
|
||||||
|
if RomImporter.isReady(launchGame) then
|
||||||
|
if launchSlot then LaunchOptions.selectSlot(launchGame, launchSlot) end
|
||||||
|
-- No launcher behind this session: love.quit must exit, not restart.
|
||||||
|
launchedIntoGame = true
|
||||||
|
bootGame(launchGame)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
-- Not importable yet: open the launcher already showing that game, so the
|
||||||
|
-- shortcut still lands the player where they meant to go.
|
||||||
|
LaunchOptions.pendingTab = launchGame
|
||||||
|
end
|
||||||
|
|
||||||
-- Interactive: the launcher always runs. Red, Blue, and Yellow are each
|
-- Interactive: the launcher always runs. Red, Blue, and Yellow are each
|
||||||
-- live: a column shows Play when that game's ROM is already imported, or
|
-- live: a column shows Play when that game's ROM is already imported, or
|
||||||
-- Choose ROM / drag-drop when it is not. Any dropped .gb is routed by its
|
-- Choose ROM / drag-drop when it is not. Any dropped .gb is routed by its
|
||||||
@@ -273,6 +387,9 @@ function love.load(args)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function love.update(dt)
|
function love.update(dt)
|
||||||
|
SwitchDiagnostics.maybeFlush(false)
|
||||||
|
-- NX only (no-op elsewhere): follow dock/undock without waiting for SDL.
|
||||||
|
NxDisplay.sync()
|
||||||
if editorMode then return EditorApp.update(dt) end
|
if editorMode then return EditorApp.update(dt) end
|
||||||
if TouchEditor then return TouchEditor.update(dt) end
|
if TouchEditor then return TouchEditor.update(dt) end
|
||||||
if Importer then return Importer:update(dt) end
|
if Importer then return Importer:update(dt) end
|
||||||
@@ -347,48 +464,140 @@ function love.keyreleased(key)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function love.gamepadpressed(joystick, button)
|
function love.gamepadpressed(joystick, button)
|
||||||
if editorMode or TouchEditor then return end
|
SwitchDiagnostics.onJoystickEvent("gamepadpressed", joystick, button)
|
||||||
|
if editorMode then
|
||||||
|
if EditorApp and EditorApp.gamepadpressed then
|
||||||
|
return EditorApp.gamepadpressed(joystick, button)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if TouchEditor then
|
||||||
|
if TouchEditor.gamepadpressed then
|
||||||
|
return TouchEditor.gamepadpressed(joystick, button)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
if Importer then return Importer:gamepadpressed(joystick, button) end
|
if Importer then return Importer:gamepadpressed(joystick, button) end
|
||||||
Game:gamepadpressed(joystick, button)
|
Game:gamepadpressed(joystick, button)
|
||||||
end
|
end
|
||||||
|
|
||||||
function love.gamepadreleased(joystick, button)
|
function love.gamepadreleased(joystick, button)
|
||||||
if editorMode or TouchEditor then return end
|
SwitchDiagnostics.onJoystickEvent("gamepadreleased", joystick, button)
|
||||||
|
if editorMode then
|
||||||
|
if EditorApp and EditorApp.gamepadreleased then
|
||||||
|
return EditorApp.gamepadreleased(joystick, button)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if TouchEditor then
|
||||||
|
if TouchEditor.gamepadreleased then
|
||||||
|
return TouchEditor.gamepadreleased(joystick, button)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
if Importer then return Importer:gamepadreleased(joystick, button) end
|
if Importer then return Importer:gamepadreleased(joystick, button) end
|
||||||
Game:gamepadreleased(joystick, button)
|
Game:gamepadreleased(joystick, button)
|
||||||
end
|
end
|
||||||
|
|
||||||
function love.gamepadaxis(joystick, axis, value)
|
function love.gamepadaxis(joystick, axis, value)
|
||||||
if editorMode or TouchEditor then return end
|
SwitchDiagnostics.onJoystickEvent("gamepadaxis", joystick, axis, { value = value })
|
||||||
|
if editorMode then
|
||||||
|
if EditorApp and EditorApp.gamepadaxis then
|
||||||
|
return EditorApp.gamepadaxis(joystick, axis, value)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if TouchEditor then
|
||||||
|
if TouchEditor.gamepadaxis then
|
||||||
|
return TouchEditor.gamepadaxis(joystick, axis, value)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
if Importer then return Importer:gamepadaxis(joystick, axis, value) end
|
if Importer then return Importer:gamepadaxis(joystick, axis, value) end
|
||||||
Game:gamepadaxis(joystick, axis, value)
|
Game:gamepadaxis(joystick, axis, value)
|
||||||
end
|
end
|
||||||
|
|
||||||
function love.joystickpressed(joystick, button)
|
function love.joystickpressed(joystick, button)
|
||||||
if editorMode or TouchEditor then return end
|
SwitchDiagnostics.onJoystickEvent("joystickpressed", joystick, button)
|
||||||
|
if editorMode then
|
||||||
|
if EditorApp and EditorApp.joystickpressed then
|
||||||
|
return EditorApp.joystickpressed(joystick, button)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if TouchEditor then
|
||||||
|
if TouchEditor.joystickpressed then
|
||||||
|
return TouchEditor.joystickpressed(joystick, button)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
if Importer then return Importer:joystickpressed(joystick, button) end
|
if Importer then return Importer:joystickpressed(joystick, button) end
|
||||||
Game:joystickpressed(joystick, button)
|
Game:joystickpressed(joystick, button)
|
||||||
end
|
end
|
||||||
|
|
||||||
function love.joystickreleased(joystick, button)
|
function love.joystickreleased(joystick, button)
|
||||||
if editorMode or TouchEditor then return end
|
SwitchDiagnostics.onJoystickEvent("joystickreleased", joystick, button)
|
||||||
|
if editorMode then
|
||||||
|
if EditorApp and EditorApp.joystickreleased then
|
||||||
|
return EditorApp.joystickreleased(joystick, button)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if TouchEditor then
|
||||||
|
if TouchEditor.joystickreleased then
|
||||||
|
return TouchEditor.joystickreleased(joystick, button)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
if Importer then return Importer:joystickreleased(joystick, button) end
|
if Importer then return Importer:joystickreleased(joystick, button) end
|
||||||
Game:joystickreleased(joystick, button)
|
Game:joystickreleased(joystick, button)
|
||||||
end
|
end
|
||||||
|
|
||||||
function love.joystickaxis(joystick, axis, value)
|
function love.joystickaxis(joystick, axis, value)
|
||||||
if editorMode or TouchEditor then return end
|
SwitchDiagnostics.onJoystickEvent("joystickaxis", joystick, axis, { value = value })
|
||||||
|
if editorMode then
|
||||||
|
if EditorApp and EditorApp.joystickaxis then
|
||||||
|
return EditorApp.joystickaxis(joystick, axis, value)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if TouchEditor then
|
||||||
|
if TouchEditor.joystickaxis then
|
||||||
|
return TouchEditor.joystickaxis(joystick, axis, value)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
if Importer then return Importer:joystickaxis(joystick, axis, value) end
|
if Importer then return Importer:joystickaxis(joystick, axis, value) end
|
||||||
Game:joystickaxis(joystick, axis, value)
|
Game:joystickaxis(joystick, axis, value)
|
||||||
end
|
end
|
||||||
|
|
||||||
function love.joystickhat(joystick, hat, direction)
|
function love.joystickhat(joystick, hat, direction)
|
||||||
if editorMode or TouchEditor then return end
|
SwitchDiagnostics.onJoystickEvent("joystickhat", joystick, hat, { direction = direction })
|
||||||
|
if editorMode then
|
||||||
|
if EditorApp and EditorApp.joystickhat then
|
||||||
|
return EditorApp.joystickhat(joystick, hat, direction)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if TouchEditor then
|
||||||
|
if TouchEditor.joystickhat then
|
||||||
|
return TouchEditor.joystickhat(joystick, hat, direction)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
if Importer then return Importer:joystickhat(joystick, hat, direction) end
|
if Importer then return Importer:joystickhat(joystick, hat, direction) end
|
||||||
Game:joystickhat(joystick, hat, direction)
|
Game:joystickhat(joystick, hat, direction)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function love.joystickadded(joystick)
|
||||||
|
SwitchDiagnostics.onJoystickEvent("joystickadded", joystick)
|
||||||
|
if editorMode or TouchEditor then return end
|
||||||
|
if Importer then return end
|
||||||
|
Game:joystickadded(joystick)
|
||||||
|
end
|
||||||
|
|
||||||
function love.joystickremoved(joystick)
|
function love.joystickremoved(joystick)
|
||||||
|
SwitchDiagnostics.onJoystickEvent("joystickremoved", joystick)
|
||||||
if editorMode or TouchEditor then return end
|
if editorMode or TouchEditor then return end
|
||||||
if Importer then return end
|
if Importer then return end
|
||||||
Game:joystickremoved(joystick)
|
Game:joystickremoved(joystick)
|
||||||
@@ -400,6 +609,7 @@ end
|
|||||||
function love.focus(f)
|
function love.focus(f)
|
||||||
if editorMode or TouchEditor then return end
|
if editorMode or TouchEditor then return end
|
||||||
if Importer then
|
if Importer then
|
||||||
|
require("src.core.Input"):reset()
|
||||||
if Importer.focus then Importer:focus(f) end
|
if Importer.focus then Importer:focus(f) end
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@@ -409,12 +619,29 @@ end
|
|||||||
-- v is true when the window becomes visible again, false on minimize.
|
-- v is true when the window becomes visible again, false on minimize.
|
||||||
function love.visible(v)
|
function love.visible(v)
|
||||||
if editorMode or TouchEditor then return end
|
if editorMode or TouchEditor then return end
|
||||||
if Importer then return end
|
if Importer then
|
||||||
|
require("src.core.Input"):reset()
|
||||||
|
return
|
||||||
|
end
|
||||||
Game:visible(v)
|
Game:visible(v)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function love.lowmemory()
|
||||||
|
if editorMode or TouchEditor or Importer then return end
|
||||||
|
if Game then Game:onResume() end
|
||||||
|
end
|
||||||
|
|
||||||
function love.touchpressed(id, x, y, dx, dy, pressure)
|
function love.touchpressed(id, x, y, dx, dy, pressure)
|
||||||
if editorMode then return end
|
if editorMode then
|
||||||
|
-- iOS synthesizes mousepressed for the primary touch; forwarding here
|
||||||
|
-- would double-fire. Android / NX need the explicit touch → click path
|
||||||
|
-- (love-nx does not synthesize mouse for the editor the way desktop does).
|
||||||
|
if love.system.getOS() == "iOS" then return end
|
||||||
|
if EditorApp and EditorApp.mousepressed then
|
||||||
|
return EditorApp.mousepressed(x, y, 1)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
if TouchEditor then
|
if TouchEditor then
|
||||||
-- iOS synthesizes mousepressed for the primary touch (same as the
|
-- iOS synthesizes mousepressed for the primary touch (same as the
|
||||||
-- launcher); Android drives the editor through love.touch directly.
|
-- launcher); Android drives the editor through love.touch directly.
|
||||||
@@ -422,18 +649,12 @@ function love.touchpressed(id, x, y, dx, dy, pressure)
|
|||||||
return TouchEditor.touchpressed(id, x, y)
|
return TouchEditor.touchpressed(id, x, y)
|
||||||
end
|
end
|
||||||
if Importer then
|
if Importer then
|
||||||
-- iOS: LÖVE already synthesizes a mousepressed for the primary touch,
|
-- Both mobiles: FlexLove scroll needs the real touch stream. Clicks are
|
||||||
-- and love.mousepressed below forwards that to the Importer, so
|
-- polled inside the view; the istouch filter on mousepressed still drops
|
||||||
-- forwarding here too fires every launcher button twice per tap. The
|
-- Android's synthesized mouse twin so Import cannot double-fire (#553).
|
||||||
-- resulting double-present was fatal for the document picker: the
|
return Importer:touchpressed(id, x, y, dx, dy, pressure)
|
||||||
-- second sheet stole the first one's weakly-held delegate, so picking
|
|
||||||
-- a file silently did nothing. Android keeps the forward for upstream
|
|
||||||
-- parity (its SAF picker is a separate activity and tolerates the
|
|
||||||
-- re-launch).
|
|
||||||
if love.system.getOS() == "iOS" then return end
|
|
||||||
return Importer:mousepressed(x, y, 1)
|
|
||||||
end
|
end
|
||||||
Game:touchpressed(id, x, y)
|
Game:touchpressed(id, x, y, dx, dy, pressure)
|
||||||
end
|
end
|
||||||
|
|
||||||
function love.touchmoved(id, x, y, dx, dy, pressure)
|
function love.touchmoved(id, x, y, dx, dy, pressure)
|
||||||
@@ -442,8 +663,10 @@ function love.touchmoved(id, x, y, dx, dy, pressure)
|
|||||||
if love.system.getOS() == "iOS" then return end
|
if love.system.getOS() == "iOS" then return end
|
||||||
return TouchEditor.touchmoved(id, x, y)
|
return TouchEditor.touchmoved(id, x, y)
|
||||||
end
|
end
|
||||||
if Importer then return end
|
if Importer then
|
||||||
Game:touchmoved(id, x, y)
|
return Importer:touchmoved(id, x, y, dx, dy, pressure)
|
||||||
|
end
|
||||||
|
Game:touchmoved(id, x, y, dx, dy, pressure)
|
||||||
end
|
end
|
||||||
|
|
||||||
function love.touchreleased(id, x, y, dx, dy, pressure)
|
function love.touchreleased(id, x, y, dx, dy, pressure)
|
||||||
@@ -452,8 +675,10 @@ function love.touchreleased(id, x, y, dx, dy, pressure)
|
|||||||
if love.system.getOS() == "iOS" then return end
|
if love.system.getOS() == "iOS" then return end
|
||||||
return TouchEditor.touchreleased(id, x, y)
|
return TouchEditor.touchreleased(id, x, y)
|
||||||
end
|
end
|
||||||
if Importer then return end
|
if Importer then
|
||||||
Game:touchreleased(id, x, y)
|
return Importer:touchreleased(id, x, y, dx, dy, pressure)
|
||||||
|
end
|
||||||
|
Game:touchreleased(id, x, y, dx, dy, pressure)
|
||||||
end
|
end
|
||||||
|
|
||||||
function love.wheelmoved(x, y)
|
function love.wheelmoved(x, y)
|
||||||
@@ -466,7 +691,33 @@ function love.wheelmoved(x, y)
|
|||||||
Game:wheelmoved(x, y)
|
Game:wheelmoved(x, y)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- #781: Linux X11 multi-monitor with the primary display away from desktop
|
||||||
|
-- (0,0): SDL's polled mouse state can come back in desktop-virtual
|
||||||
|
-- coordinates while the event stream stays window-relative, which strands
|
||||||
|
-- every polled consumer (launcher Kit rising-edge clicks, the pad-cursor
|
||||||
|
-- motion yield, PadCursor) on coordinates no hit test can match. Sanitize
|
||||||
|
-- the poll once here: remember the last window-relative event coordinates
|
||||||
|
-- and substitute them whenever the polled value falls outside the window.
|
||||||
|
-- Linux only -- macOS / Windows / mobile keep the stock function, and the
|
||||||
|
-- NX launcher shim still composes because it captures whatever
|
||||||
|
-- love.mouse.getPosition is at bridge time (_ensureNxPointerBridge).
|
||||||
|
local eventMouseX, eventMouseY
|
||||||
|
if love.system and love.system.getOS() == "Linux"
|
||||||
|
and love.mouse and love.mouse.getPosition then
|
||||||
|
local polledGetPosition = love.mouse.getPosition
|
||||||
|
love.mouse.getPosition = function()
|
||||||
|
local x, y = polledGetPosition()
|
||||||
|
local w, h = love.graphics.getDimensions()
|
||||||
|
if x < 0 or y < 0 or x > w or y > h then
|
||||||
|
if eventMouseX then return eventMouseX, eventMouseY end
|
||||||
|
return math.max(0, math.min(x, w)), math.max(0, math.min(y, h))
|
||||||
|
end
|
||||||
|
return x, y
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function love.mousepressed(x, y, button, istouch)
|
function love.mousepressed(x, y, button, istouch)
|
||||||
|
if not istouch then eventMouseX, eventMouseY = x, y end
|
||||||
if TouchEditor then
|
if TouchEditor then
|
||||||
-- Android primary touch already arrived via love.touchpressed; a second
|
-- Android primary touch already arrived via love.touchpressed; a second
|
||||||
-- mouse path would double-fire Done / begin a second drag.
|
-- mouse path would double-fire Done / begin a second drag.
|
||||||
@@ -474,32 +725,35 @@ function love.mousepressed(x, y, button, istouch)
|
|||||||
return TouchEditor.mousepressed(x, y, button)
|
return TouchEditor.mousepressed(x, y, button)
|
||||||
end
|
end
|
||||||
if Importer then
|
if Importer then
|
||||||
-- The same double-fire TouchEditor guards against, which the launcher was
|
-- love.touchpressed already forwards the primary touch into FlexLove for
|
||||||
-- missing: love.touchpressed above forwards the primary touch to the
|
-- scroll. LÖVE ALSO synthesizes a mouse press for that same touch; if both
|
||||||
-- Importer on Android, and LÖVE ALSO synthesizes a mouse press for that
|
-- reached a press handler, one tap ran every launcher button twice and
|
||||||
-- same touch, so one tap ran every launcher button twice. On Import that
|
-- stacked two SAF pickers (#553). Clicks are polled inside FlexLove from
|
||||||
-- meant two choose() calls and two stacked SAF picker activities: the
|
-- love.touch / mouse.isDown, so dropping the synthesized istouch press is
|
||||||
-- player picked their ROM, the top picker closed, and the second was still
|
-- safe. A real mouse (DeX, Chromebook, USB) still reaches mousepressed.
|
||||||
-- underneath asking for it again, which is the "import the file twice"
|
if istouch and (love.system.getOS() == "Android"
|
||||||
-- in #553. Filtering on istouch keeps a real mouse (DeX, a Chromebook, a
|
or love.system.getOS() == "iOS") then return end
|
||||||
-- USB mouse) working, which an Android-wide return would have broken.
|
|
||||||
--
|
|
||||||
-- ANDROID ONLY, and the OS test is load bearing: love.touchpressed above
|
|
||||||
-- returns early on iOS and never forwards, so there the synthesized mouse
|
|
||||||
-- press is the ONLY event the launcher gets. Filtering istouch on both
|
|
||||||
-- killed every tap on iOS outright.
|
|
||||||
if istouch and love.system.getOS() == "Android" then return end
|
|
||||||
return Importer:mousepressed(x, y, button)
|
return Importer:mousepressed(x, y, button)
|
||||||
end
|
end
|
||||||
if editorMode and EditorApp.mousepressed then
|
if editorMode and EditorApp.mousepressed then
|
||||||
|
-- Same Android double-fire guard: touchpressed already clicked for the
|
||||||
|
-- save editor; a synthesized mouse press must not fire again.
|
||||||
|
if istouch and love.system.getOS() == "Android" then return end
|
||||||
return EditorApp.mousepressed(x, y, button)
|
return EditorApp.mousepressed(x, y, button)
|
||||||
end
|
end
|
||||||
if mouseTouch and Game and button == 1 then
|
if mouseTouch then
|
||||||
Game:touchpressed("mouse", x, y)
|
-- the mouse is standing in for a finger: the touch path owns it, and
|
||||||
|
-- feeding the same press back in as a mouse pointer would double it
|
||||||
|
if Game and button == 1 then Game:touchpressed("mouse", x, y) end
|
||||||
|
return
|
||||||
end
|
end
|
||||||
|
-- #807: a real mouse reaches gameplay as a pointer event for mods; Game
|
||||||
|
-- drops synthesized istouch twins so a mobile touch that already arrived
|
||||||
|
-- through love.touchpressed cannot fire twice
|
||||||
|
if Game then Game:mousepressed(x, y, button, istouch) end
|
||||||
end
|
end
|
||||||
|
|
||||||
function love.mousereleased(x, y, button)
|
function love.mousereleased(x, y, button, istouch)
|
||||||
if TouchEditor then
|
if TouchEditor then
|
||||||
if love.system.getOS() == "Android" then return end
|
if love.system.getOS() == "Android" then return end
|
||||||
return TouchEditor.mousereleased(x, y, button)
|
return TouchEditor.mousereleased(x, y, button)
|
||||||
@@ -508,20 +762,25 @@ function love.mousereleased(x, y, button)
|
|||||||
if editorMode and EditorApp.mousereleased then
|
if editorMode and EditorApp.mousereleased then
|
||||||
return EditorApp.mousereleased(x, y, button)
|
return EditorApp.mousereleased(x, y, button)
|
||||||
end
|
end
|
||||||
if mouseTouch and Game and button == 1 then
|
if mouseTouch then
|
||||||
Game:touchreleased("mouse", x, y)
|
if Game and button == 1 then Game:touchreleased("mouse", x, y) end
|
||||||
|
return
|
||||||
end
|
end
|
||||||
|
if Game then Game:mousereleased(x, y, button, istouch) end
|
||||||
end
|
end
|
||||||
|
|
||||||
function love.mousemoved(x, y)
|
function love.mousemoved(x, y, dx, dy, istouch)
|
||||||
|
if not istouch then eventMouseX, eventMouseY = x, y end
|
||||||
if TouchEditor then
|
if TouchEditor then
|
||||||
if love.system.getOS() == "Android" then return end
|
if love.system.getOS() == "Android" then return end
|
||||||
return TouchEditor.mousemoved(x, y)
|
return TouchEditor.mousemoved(x, y)
|
||||||
end
|
end
|
||||||
if editorMode or Importer then return end
|
if editorMode or Importer then return end
|
||||||
if mouseTouch and Game and love.mouse.isDown(1) then
|
if mouseTouch then
|
||||||
Game:touchmoved("mouse", x, y)
|
if Game and love.mouse.isDown(1) then Game:touchmoved("mouse", x, y) end
|
||||||
|
return
|
||||||
end
|
end
|
||||||
|
if Game then Game:mousemoved(x, y, dx, dy, istouch) end
|
||||||
end
|
end
|
||||||
|
|
||||||
function love.textinput(text)
|
function love.textinput(text)
|
||||||
@@ -532,9 +791,43 @@ function love.textinput(text)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- #785: set once love.quit has routed a window close into HostShell.restart,
|
||||||
|
-- so the follow-up quit event the restart itself raises (quit("restart") on
|
||||||
|
-- desktop; AppImage and Android relaunch the process instead, #575) falls
|
||||||
|
-- through to the normal shutdown below instead of restarting forever.
|
||||||
|
local quitToLauncher = false
|
||||||
|
|
||||||
function love.quit()
|
function love.quit()
|
||||||
if editorMode and EditorApp.quit then
|
if editorMode and EditorApp.quit then
|
||||||
return EditorApp.quit() -- return true to abort quit
|
-- true blocks the quit (unsaved-changes prompt). A quit that proceeds
|
||||||
|
-- must fall through to the worker shutdowns below instead of returning:
|
||||||
|
-- the bundled editor opens from a live launcher whose update-check and
|
||||||
|
-- fetch-pool workers are still parked in Channel:demand(), and returning
|
||||||
|
-- here skipped their "quit" push, so the process outlived the closed
|
||||||
|
-- window and kept the install folder locked on Windows (#727).
|
||||||
|
if EditorApp.quit() then return true end
|
||||||
|
end
|
||||||
|
-- Closing the window of a running game returns to the launcher instead of
|
||||||
|
-- exiting the app, so testing a mod does not need a relaunch every time
|
||||||
|
-- (#785). Game is only non-nil once bootGame ran; Importer non-nil means
|
||||||
|
-- the launcher (or its import) owns the window and its close still quits.
|
||||||
|
-- Scripted and headless runs (autopilot, frame driver, import-only, ROM
|
||||||
|
-- path import) keep the plain exit so they terminate as before. Nothing
|
||||||
|
-- is saved here on purpose: a window close never wrote the save, and the
|
||||||
|
-- restart path must be no worse than that, not quietly better.
|
||||||
|
local scripted = os.getenv("POKEPORT_AUTOPILOT") or os.getenv("POKEPORT_DRIVER")
|
||||||
|
or os.getenv("POKEPORT_IMPORT_ONLY") == "1" or os.getenv("POKEPORT_IMPORT_ROM")
|
||||||
|
-- #887: a shortcut session (--game / POKEPORT_GAME) has no launcher to go
|
||||||
|
-- back to and the restart would re-read the shortcut, so it exits instead.
|
||||||
|
if Game and not Importer and not quitToLauncher and not scripted
|
||||||
|
and not launchedIntoGame then
|
||||||
|
quitToLauncher = true
|
||||||
|
-- Tell the fresh boot to ignore any boot-straight-into-a-game option this
|
||||||
|
-- once, so the restart really does land in the launcher (#887). A failed
|
||||||
|
-- write only costs that suppression, so it must never block the restart.
|
||||||
|
pcall(love.filesystem.write, RELAUNCH_MARKER, "1")
|
||||||
|
require("src.core.HostShell").restart()
|
||||||
|
return true -- abort this quit; the restart lands back in the launcher
|
||||||
end
|
end
|
||||||
pcall(function()
|
pcall(function()
|
||||||
require("src.core.DiscordPresence").shutdown()
|
require("src.core.DiscordPresence").shutdown()
|
||||||
@@ -549,6 +842,12 @@ function love.quit()
|
|||||||
if package.loaded["src.update.Check"] then
|
if package.loaded["src.update.Check"] then
|
||||||
pcall(package.loaded["src.update.Check"].shutdown)
|
pcall(package.loaded["src.update.Check"].shutdown)
|
||||||
end
|
end
|
||||||
|
-- The launcher's fetch pool is the same story: its workers idle in
|
||||||
|
-- Channel:demand(), which never returns on its own, so a launcher that ever
|
||||||
|
-- touched the network would hang the process on exit (#339's shape again).
|
||||||
|
if package.loaded["src.net.Fetch"] then
|
||||||
|
pcall(package.loaded["src.net.Fetch"].shutdown)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function love.filedropped(file)
|
function love.filedropped(file)
|
||||||
|
|||||||
@@ -108,7 +108,8 @@ The APK lands under `app/build/outputs/apk/embedNoRecord/debug/`.
|
|||||||
### Payload path
|
### Payload path
|
||||||
|
|
||||||
`app/src/embed/assets/game.love` - zip of `main.lua`, `conf.lua`, `src/`,
|
`app/src/embed/assets/game.love` - zip of `main.lua`, `conf.lua`, `src/`,
|
||||||
`data/`, `assets/`, and the Red, Blue, and Yellow ROM manifests. The Android
|
`libs/` (the vendored FlexLove toolkit the launcher UI needs), `data/`,
|
||||||
|
`assets/`, and the Red, Blue, and Yellow ROM manifests. The Android
|
||||||
packer verifies the Yellow manifest before it packages; if a partial source
|
packer verifies the Yellow manifest before it packages; if a partial source
|
||||||
export omitted it, it restores the file from this checkout's Git data and then
|
export omitted it, it restores the file from this checkout's Git data and then
|
||||||
falls back to the project's GitHub copy. Generated game data,
|
falls back to the project's GitHub copy. Generated game data,
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 8.9 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 42 KiB |
@@ -37,6 +37,13 @@
|
|||||||
|
|
||||||
#include "filesystem/physfs/PhysfsIo.h"
|
#include "filesystem/physfs/PhysfsIo.h"
|
||||||
|
|
||||||
|
// #604 / #839: the SAF bridges below must hand GameActivity the exact
|
||||||
|
// directory physfs mounted as the save dir -- the same contract the iOS
|
||||||
|
// GRPickerBridge already gets (mobile/ios/patch_love_src.py,
|
||||||
|
// gr_saveDirectory) -- instead of letting Java recompute the root on its
|
||||||
|
// own, which can name a different volume on merged / adopted-SD storage.
|
||||||
|
#include "filesystem/Filesystem.h"
|
||||||
|
|
||||||
namespace love
|
namespace love
|
||||||
{
|
{
|
||||||
namespace android
|
namespace android
|
||||||
@@ -183,6 +190,19 @@ void vibrate(double seconds)
|
|||||||
env->DeleteLocalRef(activity);
|
env->DeleteLocalRef(activity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The directory physfs actually mounted as the save dir, or "" before the
|
||||||
|
// filesystem module is up. GameActivity must copy SAF picks HERE: its own
|
||||||
|
// getExternalFilesDir(null) recomputation can disagree with the mounted
|
||||||
|
// root on merged / adopted-SD storage (#604, #839).
|
||||||
|
static const char *bridgeSaveDirectory()
|
||||||
|
{
|
||||||
|
auto fs = Module::getInstance<love::filesystem::Filesystem>(Module::M_FILESYSTEM);
|
||||||
|
if (fs == nullptr)
|
||||||
|
return "";
|
||||||
|
const char *dir = fs->getSaveDirectory();
|
||||||
|
return dir != nullptr ? dir : "";
|
||||||
|
}
|
||||||
|
|
||||||
bool showFilePicker(const char *destFilename)
|
bool showFilePicker(const char *destFilename)
|
||||||
{
|
{
|
||||||
if (destFilename == nullptr || destFilename[0] == '\0')
|
if (destFilename == nullptr || destFilename[0] == '\0')
|
||||||
@@ -192,9 +212,11 @@ bool showFilePicker(const char *destFilename)
|
|||||||
jclass activity = env->FindClass("org/love2d/android/GameActivity");
|
jclass activity = env->FindClass("org/love2d/android/GameActivity");
|
||||||
|
|
||||||
jmethodID method = env->GetStaticMethodID(activity, "showFilePicker",
|
jmethodID method = env->GetStaticMethodID(activity, "showFilePicker",
|
||||||
"(Ljava/lang/String;)Z");
|
"(Ljava/lang/String;Ljava/lang/String;)Z");
|
||||||
jstring jname = env->NewStringUTF(destFilename);
|
jstring jname = env->NewStringUTF(destFilename);
|
||||||
jboolean result = env->CallStaticBooleanMethod(activity, method, jname);
|
jstring jsavedir = env->NewStringUTF(bridgeSaveDirectory());
|
||||||
|
jboolean result = env->CallStaticBooleanMethod(activity, method, jname, jsavedir);
|
||||||
|
env->DeleteLocalRef(jsavedir);
|
||||||
env->DeleteLocalRef(jname);
|
env->DeleteLocalRef(jname);
|
||||||
|
|
||||||
env->DeleteLocalRef(activity);
|
env->DeleteLocalRef(activity);
|
||||||
@@ -210,9 +232,11 @@ bool showCreateDocument(const char *suggestedName)
|
|||||||
jclass activity = env->FindClass("org/love2d/android/GameActivity");
|
jclass activity = env->FindClass("org/love2d/android/GameActivity");
|
||||||
|
|
||||||
jmethodID method = env->GetStaticMethodID(activity, "showCreateDocument",
|
jmethodID method = env->GetStaticMethodID(activity, "showCreateDocument",
|
||||||
"(Ljava/lang/String;)Z");
|
"(Ljava/lang/String;Ljava/lang/String;)Z");
|
||||||
jstring jname = env->NewStringUTF(suggestedName);
|
jstring jname = env->NewStringUTF(suggestedName);
|
||||||
jboolean result = env->CallStaticBooleanMethod(activity, method, jname);
|
jstring jsavedir = env->NewStringUTF(bridgeSaveDirectory());
|
||||||
|
jboolean result = env->CallStaticBooleanMethod(activity, method, jname, jsavedir);
|
||||||
|
env->DeleteLocalRef(jsavedir);
|
||||||
env->DeleteLocalRef(jname);
|
env->DeleteLocalRef(jname);
|
||||||
|
|
||||||
env->DeleteLocalRef(activity);
|
env->DeleteLocalRef(activity);
|
||||||
@@ -259,7 +283,19 @@ bool httpDownload(const char *url, const char *destPath, const char *userAgent,
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
|
JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
|
||||||
jclass activity = env->FindClass("org/love2d/android/GameActivity");
|
// NOT FindClass: this is the one bridge called off the main thread
|
||||||
|
// (love.thread workers in src/net/fetch_worker.lua and
|
||||||
|
// src/update/check_worker.lua). A worker is a raw pthread whose JNI
|
||||||
|
// class loader is the system one, which cannot see app classes, so
|
||||||
|
// FindClass("org/love2d/android/GameActivity") left a pending
|
||||||
|
// ClassNotFoundException and the next JNI call aborted the process --
|
||||||
|
// opening FIND MODS killed the app on the first stats fetch. Resolving
|
||||||
|
// through the live activity instance works from any attached thread.
|
||||||
|
jobject activityObj = (jobject) SDL_AndroidGetActivity();
|
||||||
|
if (activityObj == nullptr)
|
||||||
|
return false;
|
||||||
|
jclass activity = env->GetObjectClass(activityObj);
|
||||||
|
env->DeleteLocalRef(activityObj);
|
||||||
|
|
||||||
// Old APK / new liblove skew: report "no transport" the same way a
|
// Old APK / new liblove skew: report "no transport" the same way a
|
||||||
// missing curl does, instead of aborting on a missing method (#597).
|
// missing curl does, instead of aborting on a missing method (#597).
|
||||||
|
|||||||
@@ -116,6 +116,18 @@ public class GameActivity extends SDLActivity {
|
|||||||
// bad ROM instead of installing it (#553).
|
// bad ROM instead of installing it (#553).
|
||||||
private String pendingPickFilename = PICKED_ROM_FILENAME;
|
private String pendingPickFilename = PICKED_ROM_FILENAME;
|
||||||
private static final String STATE_PENDING_PICK = "pendingPickFilename";
|
private static final String STATE_PENDING_PICK = "pendingPickFilename";
|
||||||
|
// Absolute save directory physfs actually mounted, as reported by the
|
||||||
|
// native bridge call that opened the picker (love/src/common/android.cpp,
|
||||||
|
// bridgeSaveDirectory). This activity used to recompute
|
||||||
|
// getExternalFilesDir(null)/save/<identity> on its own at result time; on
|
||||||
|
// merged / adopted-SD storage that can name a different volume than the
|
||||||
|
// one LOVE mounted, so the copied pick (and pick_error.flag) landed where
|
||||||
|
// Lua never scans -- the launcher then "did nothing" after a pick (#604)
|
||||||
|
// and the folders a file manager can browse stayed empty while the game
|
||||||
|
// saved fine elsewhere (#839). Empty string means "not told yet": fall
|
||||||
|
// back to the historical computation.
|
||||||
|
private String pendingPickSaveDir = "";
|
||||||
|
private static final String STATE_PENDING_PICK_DIR = "pendingPickSaveDir";
|
||||||
private static final String STATE_PENDING_CREATE = "pendingCreateSuggestedName";
|
private static final String STATE_PENDING_CREATE = "pendingCreateSuggestedName";
|
||||||
// Suggested download name for the in-flight SAF create (set by showCreateDocument).
|
// Suggested download name for the in-flight SAF create (set by showCreateDocument).
|
||||||
private String pendingCreateSuggestedName = "export.sav";
|
private String pendingCreateSuggestedName = "export.sav";
|
||||||
@@ -186,6 +198,8 @@ public class GameActivity extends SDLActivity {
|
|||||||
// a recreated activity still lands under the basename it asked for.
|
// a recreated activity still lands under the basename it asked for.
|
||||||
String pick = savedInstanceState.getString(STATE_PENDING_PICK);
|
String pick = savedInstanceState.getString(STATE_PENDING_PICK);
|
||||||
if (pick != null) pendingPickFilename = pick;
|
if (pick != null) pendingPickFilename = pick;
|
||||||
|
String pickDir = savedInstanceState.getString(STATE_PENDING_PICK_DIR);
|
||||||
|
if (pickDir != null) pendingPickSaveDir = pickDir;
|
||||||
String create = savedInstanceState.getString(STATE_PENDING_CREATE);
|
String create = savedInstanceState.getString(STATE_PENDING_CREATE);
|
||||||
if (create != null) pendingCreateSuggestedName = create;
|
if (create != null) pendingCreateSuggestedName = create;
|
||||||
}
|
}
|
||||||
@@ -467,13 +481,23 @@ public class GameActivity extends SDLActivity {
|
|||||||
* @param destFilename basename under the app save identity (e.g.
|
* @param destFilename basename under the app save identity (e.g.
|
||||||
* picked_rom.gb, picked_mod.zip, picked_save.sav)
|
* picked_rom.gb, picked_mod.zip, picked_save.sav)
|
||||||
*/
|
*/
|
||||||
|
/** Legacy single-argument entry; resolves the save dir itself. */
|
||||||
@Keep
|
@Keep
|
||||||
public static boolean showFilePicker(String destFilename) {
|
public static boolean showFilePicker(String destFilename) {
|
||||||
|
return showFilePicker(destFilename, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
public static boolean showFilePicker(String destFilename, String saveDir) {
|
||||||
GameActivity self = (GameActivity) mSingleton;
|
GameActivity self = (GameActivity) mSingleton;
|
||||||
if (self == null) return false;
|
if (self == null) return false;
|
||||||
if (destFilename == null || destFilename.length() == 0) {
|
if (destFilename == null || destFilename.length() == 0) {
|
||||||
destFilename = PICKED_ROM_FILENAME;
|
destFilename = PICKED_ROM_FILENAME;
|
||||||
}
|
}
|
||||||
|
// Remember where LOVE's filesystem is really mounted so
|
||||||
|
// onActivityResult copies the pick there, not into a recomputed
|
||||||
|
// (possibly different-volume) root (#604, #839).
|
||||||
|
self.pendingPickSaveDir = (saveDir != null) ? saveDir : "";
|
||||||
// Reject path separators so a hostile JNI caller cannot escape the
|
// Reject path separators so a hostile JNI caller cannot escape the
|
||||||
// save identity directory.
|
// save identity directory.
|
||||||
if (destFilename.indexOf('/') >= 0 || destFilename.indexOf('\\') >= 0) {
|
if (destFilename.indexOf('/') >= 0 || destFilename.indexOf('\\') >= 0) {
|
||||||
@@ -644,8 +668,14 @@ public class GameActivity extends SDLActivity {
|
|||||||
* return degrades on the Lua side (RomImporter export) to "Exported
|
* return degrades on the Lua side (RomImporter export) to "Exported
|
||||||
* inside the app folder", which is the correct pre-KitKat behavior.
|
* inside the app folder", which is the correct pre-KitKat behavior.
|
||||||
*/
|
*/
|
||||||
|
/** Legacy single-argument entry; resolves the save dir itself. */
|
||||||
@Keep
|
@Keep
|
||||||
public static boolean showCreateDocument(String suggestedName) {
|
public static boolean showCreateDocument(String suggestedName) {
|
||||||
|
return showCreateDocument(suggestedName, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
public static boolean showCreateDocument(String suggestedName, String saveDir) {
|
||||||
if (android.os.Build.VERSION.SDK_INT < 19) return false;
|
if (android.os.Build.VERSION.SDK_INT < 19) return false;
|
||||||
// (see showFilePicker for why the import side got a pre-19 path)
|
// (see showFilePicker for why the import side got a pre-19 path)
|
||||||
GameActivity self = (GameActivity) mSingleton;
|
GameActivity self = (GameActivity) mSingleton;
|
||||||
@@ -657,9 +687,12 @@ public class GameActivity extends SDLActivity {
|
|||||||
Log.d("GameActivity", "refusing unsafe create name: " + suggestedName);
|
Log.d("GameActivity", "refusing unsafe create name: " + suggestedName);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
File source = new File(
|
// Route through the mounted save dir (#604, #839): Lua staged
|
||||||
new File(self.getExternalFilesDir(null), "save"),
|
// pending_export.sav where physfs writes, which is not necessarily
|
||||||
ROM_SAVE_IDENTITY + "/" + PENDING_EXPORT_FILENAME);
|
// where a fresh getExternalFilesDir(null) points on merged /
|
||||||
|
// adopted-SD storage.
|
||||||
|
self.pendingPickSaveDir = (saveDir != null) ? saveDir : "";
|
||||||
|
File source = new File(self.saveIdentityDir(), PENDING_EXPORT_FILENAME);
|
||||||
if (!source.isFile()) {
|
if (!source.isFile()) {
|
||||||
Log.d("GameActivity", "no pending export at " + source);
|
Log.d("GameActivity", "no pending export at " + source);
|
||||||
return false;
|
return false;
|
||||||
@@ -680,7 +713,21 @@ public class GameActivity extends SDLActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private File saveIdentityDir() {
|
private File saveIdentityDir() {
|
||||||
return new File(new File(getExternalFilesDir(null), "save"), ROM_SAVE_IDENTITY);
|
// Prefer the mounted save dir the last bridge call reported: the
|
||||||
|
// recomputation below can name a different volume than the one LOVE
|
||||||
|
// mounted on merged / adopted-SD storage (#604, #839).
|
||||||
|
if (pendingPickSaveDir != null && pendingPickSaveDir.length() > 0) {
|
||||||
|
return new File(pendingPickSaveDir);
|
||||||
|
}
|
||||||
|
File ext = getExternalFilesDir(null);
|
||||||
|
if (ext == null) {
|
||||||
|
// Shared storage unavailable (ejected / mid-adoption): without
|
||||||
|
// this guard File(null, "save") silently built the RELATIVE
|
||||||
|
// path save/<identity>, mkdirs() failed against "/", and the
|
||||||
|
// pick was dropped with no message at all (#604).
|
||||||
|
ext = getFilesDir();
|
||||||
|
}
|
||||||
|
return new File(new File(ext, "save"), ROM_SAVE_IDENTITY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Drops a small flag file in the save identity for Lua to consume on focus. */
|
/** Drops a small flag file in the save identity for Lua to consume on focus. */
|
||||||
@@ -875,6 +922,7 @@ public class GameActivity extends SDLActivity {
|
|||||||
protected void onSaveInstanceState(Bundle outState) {
|
protected void onSaveInstanceState(Bundle outState) {
|
||||||
super.onSaveInstanceState(outState);
|
super.onSaveInstanceState(outState);
|
||||||
outState.putString(STATE_PENDING_PICK, pendingPickFilename);
|
outState.putString(STATE_PENDING_PICK, pendingPickFilename);
|
||||||
|
outState.putString(STATE_PENDING_PICK_DIR, pendingPickSaveDir);
|
||||||
outState.putString(STATE_PENDING_CREATE, pendingCreateSuggestedName);
|
outState.putString(STATE_PENDING_CREATE, pendingCreateSuggestedName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,104 @@
|
|||||||
"tintColor": "3b5ca8",
|
"tintColor": "3b5ca8",
|
||||||
"category": "games",
|
"category": "games",
|
||||||
"versions": [
|
"versions": [
|
||||||
|
{
|
||||||
|
"version": "0.1.73",
|
||||||
|
"date": "2026-08-06",
|
||||||
|
"size": 9585729,
|
||||||
|
"downloadURL": "https://github.com/bryanthaboi/gen1recomp/releases/download/v0.1.73/gen1recomp-0.1.73-ios.ipa",
|
||||||
|
"localizedDescription": "Download the correct version for your computer below.\n\n## Issues closed\n\n- #878 Mod API: allow active screen states to be hidden from the main render\n\n## Contributors\n\n- @AverageConsumer\n- @bryanthaboi"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "0.1.72",
|
||||||
|
"date": "2026-08-05",
|
||||||
|
"size": 9586678,
|
||||||
|
"downloadURL": "https://github.com/bryanthaboi/gen1recomp/releases/download/v0.1.72/gen1recomp-0.1.72-ios.ipa",
|
||||||
|
"localizedDescription": "Download the correct version for your computer below.\n\n## Issues closed\n\n- #604 Android, retroid pocket 2+ Rom won't import\n- #666 Pikachu emotions are not working on Android\n- #716 Lock Auto Rotate Mobile\n- #727 [Bug] [Windows] Gen1 Recomp \"still in use\" after closing\n- #763 Some EVENTS are turned off\n- #781 Mouse cursor broken on Linux with multi-monitor X11 setup\n- #784 Leech Seed effect\n- #799 Held direction randomly stops player movement (requires re-input)\n- #801 Cannot update mods from the launcher (MacOS)\n- #810 Launcher menu cuts off in vertical mode iOS\n- #828 Closing the app causes settings in launcher to reset\n- #834 Mod import failing\n- #838 Exporting save file Pokemon Yellow\n- #839 AYN Thor Misplaced Data files\n- #849 Public folder support on iOS\n- #852 Cannot switch between saves states on smaller 4:3 screen or in vertical mode\n- #857 Mt. Moon Fossils Reappeared and Won’t Disappear.\n- #863 [Yellow] When you use stairs, Pikachu shouldn't be next to you in the new area\n- #864 Faithful Ratio\n- #867 Missing Dialogue after defeating Marowak in Pokemon Tower\n- #869 Giovanni moves up to the player too early\n- #870 Start Menu on Classic Color\n- #872 Missing text when finding an item with full inventory\n\n## Contributors\n\n- @bryanthaboi"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "0.1.71",
|
||||||
|
"date": "2026-08-05",
|
||||||
|
"size": 9569738,
|
||||||
|
"downloadURL": "https://github.com/bryanthaboi/gen1recomp/releases/download/v0.1.71/gen1recomp-0.1.71-ios.ipa",
|
||||||
|
"localizedDescription": "Download the correct version for your computer below.\n\n## Issues closed\n\n- #806 Haptic Feedback for On-screen controls (mobile)\n- #809 NPC Stuck in Rock when battle triggered and pushing rock towards it\n- #853 When selecting one of the two pokemon after defeating the Karate Master it should show the Pokedex entry\n- #854 Textbox disappears when the yes/no dialogue appears\n- #860 Disabled moves can still be used in the turn they were disabled\n- #862 Casino Poster Rocket Grunt walks into the poster and doesn't \"Dang!\"\n- #865 [Yellow] James doesn't move in multiple encounters\n- #866 [Yellow] Dialogue in wrong \"order\" after multiple encounters (J+J, Giovanni, probably more)\n\n## Contributors\n\n- @bryanthaboi"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "0.1.70",
|
||||||
|
"date": "2026-08-05",
|
||||||
|
"size": 9562731,
|
||||||
|
"downloadURL": "https://github.com/bryanthaboi/gen1recomp/releases/download/v0.1.70/gen1recomp-0.1.70-ios.ipa",
|
||||||
|
"localizedDescription": "Download the correct version for your computer below.\n\n## Issues closed\n\n- #590 Launcher/Save Editor/Dex\n- #788 Fly to the Pokemon Centers in the Routes is not available in original\n- #795 Fly logic is drastically different from the originals.\n- #796 Using Rare Candy from the menu closes it out.\n- #797 Gym Leaders giving items when your bag is full / Bypassing bag limit.\n- #805 Escape Rope Moltres Tower\n- #826 \"Super effective\" and \"not very effective\" SFX are reversed\n- #833 Cancelling nickname entry results in \"A\" as the nickname\n- #835 Restarting the launcher forgets the last rom used\n- #837 Wrong sound effect for Pikachu when entering battle\n- #844 Blizzard sound effect.\n- #845 Moderate issue: Fuchsia City binoculars.\n- #846 Surfing speed after using the bicycle.\n- #847 Minor issues related to the endgame.\n\n## Contributors\n\n- @bryanthaboi\n- @dburton95\n- @johnjohto\n- @KikiManjaro"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "0.1.69",
|
||||||
|
"date": "2026-08-04",
|
||||||
|
"size": 9552851,
|
||||||
|
"downloadURL": "https://github.com/bryanthaboi/gen1recomp/releases/download/v0.1.69/gen1recomp-0.1.69-ios.ipa",
|
||||||
|
"localizedDescription": "Download the correct version for your computer below.\n\n## Issues closed\n\n- #768 Menu behaviour for Pokemon and HM moves\n- #785 Back to launcher\n- #792 HM moves in the wrong position in the menu.\n- #807 Expose gameplay pointer events and source-safe mod input injection\n- #811 Untranslatables\n- #814 [minor thing] bold arrow on move swap (select)\n\n## Contributors\n\n- @bryanthaboi\n- @johnjohto"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "0.1.68",
|
||||||
|
"date": "2026-08-04",
|
||||||
|
"size": 9768011,
|
||||||
|
"downloadURL": "https://github.com/bryanthaboi/gen1recomp/releases/download/v0.1.68/gen1recomp-0.1.68-ios.ipa",
|
||||||
|
"localizedDescription": "Download the correct version for your computer below.\n\n## Contributors\n\n- @bryanthaboi\n- @caorthann-celt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "0.1.67",
|
||||||
|
"date": "2026-08-04",
|
||||||
|
"size": 9767619,
|
||||||
|
"downloadURL": "https://github.com/bryanthaboi/gen1recomp/releases/download/v0.1.67/gen1recomp-0.1.67-ios.ipa",
|
||||||
|
"localizedDescription": "Download the correct version for your computer below.\n\n## Issues closed\n\n- #373 Lines at Title Screen\n- #644 Substitute failing causes visual issues\n- #673 iOS: thin vertical seam below the START menu panel (v0.1.56, iPhone 15 Pro Max)\n- #703 Credits playing too fast (song ends after it should)\n- #726 Surfing Pikachu Minigame Broken\n- #737 Battle menu move cursor fails to reset to the first slot after switching Pokémon\n- #750 NPC (possibly player) trades still graphically broken\n- #752 Launcher exports save to AppData while in portable mode\n- #764 Trainer Fanfare doesn't play\n- #765 Text Advance broken in certain aspects\n- #768 Menu behaviour for Pokemon and HM moves\n- #773 Battle screen colours messed up when BG = World, battle in un-flashed Rock Tunnel\n- #774 bug(build): Desktop build can reject a valid game archive under pipefail\n- #775 TM42 Dream Eater dialog.\n- #777 Battle Screen is Very dark\n- #780 Do not delete save\n- #782 Giovanni battle at Silph Co plays wrong song\n\n## Contributors\n\n- @bryanthaboi\n- @luisgonzaleznf\n- @ShaneMcGovernIE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "0.1.66",
|
||||||
|
"date": "2026-08-04",
|
||||||
|
"size": 9749980,
|
||||||
|
"downloadURL": "https://github.com/bryanthaboi/gen1recomp/releases/download/v0.1.66/gen1recomp-0.1.66-ios.ipa",
|
||||||
|
"localizedDescription": "Download the correct version for your computer below.\n\n## Issues closed\n\n- #691 Save file transfer\n- #743 Cannot Scroll Main Menu/Mod Menu\n- #779 \"Enemy \" untranslateable\n\n## Contributors\n\n- @bryanthaboi\n- @jherediagu\n- @ShaneMcGovernIE\n- @vegerot"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "0.1.65",
|
||||||
|
"date": "2026-08-03",
|
||||||
|
"size": 9376618,
|
||||||
|
"downloadURL": "https://github.com/bryanthaboi/gen1recomp/releases/download/v0.1.65/gen1recomp-0.1.65-ios.ipa",
|
||||||
|
"localizedDescription": "Download the correct version for your computer below.\n\n## Issues closed\n\n- #592 Screen Orientation\n- #702 FLY overworld animation incorrect/incomplete\n- #748 Launcher Menu has overlap\n- #754 Can push boulders with STRENGTH through walls\n- #758 PC to Mac Online Multiplayer Disconnects Shortly After Starting Match\n\n## Contributors\n\n- @andrewqsantos\n- @Bortlesboat\n- @bryanthaboi\n- @castdrian\n- @johnjohto\n- @ShaneMcGovernIE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "0.1.64",
|
||||||
|
"date": "2026-08-03",
|
||||||
|
"size": 9317129,
|
||||||
|
"downloadURL": "https://github.com/bryanthaboi/gen1recomp/releases/download/v0.1.64/gen1recomp-0.1.64-ios.ipa",
|
||||||
|
"localizedDescription": "Download the correct version for your computer below.\n\n## Contributors\n\n- @bryanthaboi\n- @ShaneMcGovernIE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "0.1.63",
|
||||||
|
"date": "2026-08-03",
|
||||||
|
"size": 9316977,
|
||||||
|
"downloadURL": "https://github.com/bryanthaboi/gen1recomp/releases/download/v0.1.63/gen1recomp-0.1.63-ios.ipa",
|
||||||
|
"localizedDescription": "Download the correct version for your computer below.\n\n## Issues closed\n\n- #623 Game Corner Prize Exchange various issues\n- #624 Coins purschase menu missing your current money and coins\n- #636 Inaccurate Old Man tutorial in yellow + misc issues relating to him\n- #637 True color broken on Hall of Fame and Party Stats when using the wide battle option\n- #639 Pokédex can't show the correct stats when you catch 100 or more pokémon + overly simplified layout\n- #650 Rocket Hideout Progression Bug - Stuck with door closed after defeating Jessie and James in Yellow\n- #697 Missaligned HUD on Hall of Fame\n- #704 Player doesn't follow Prof. Oak to the Hall of Fame on the first screen\n- #722 Issues with Giovanni in Silph C.O.\n\n## Contributors\n\n- @bryanthaboi"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "0.1.62",
|
||||||
|
"date": "2026-08-03",
|
||||||
|
"size": 9309934,
|
||||||
|
"downloadURL": "https://github.com/bryanthaboi/gen1recomp/releases/download/v0.1.62/gen1recomp-0.1.62-ios.ipa",
|
||||||
|
"localizedDescription": "Download the correct version for your computer below.\n\n## Contributors\n\n- @bryanthaboi"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "0.1.61",
|
||||||
|
"date": "2026-08-03",
|
||||||
|
"size": 9052744,
|
||||||
|
"downloadURL": "https://github.com/bryanthaboi/gen1recomp/releases/download/v0.1.61/gen1recomp-0.1.61-ios.ipa",
|
||||||
|
"localizedDescription": "Download the correct version for your computer below.\n\n## Issues closed\n\n- #325 Launcher crashes importing a mod .zip with non-ASCII characters in the filename (Windows)\n- #646 Fuchsia City Pokemon not being added to Pokedex.\n- #651 Parasect trading NPC mute and deaf\n- #665 fix: file path sanitizing is needed\n- #706 Champion theme playing too late\n- #709 Fainted Pokemon plays its normal cry instead of the pitched-down, slower faint cry\n- #719 Silph C.O workers dialog bug.\n\n## Contributors\n\n- @bryanthaboi\n- @castdrian\n- @jherediagu\n- @johnjohto\n- @ShaneMcGovernIE\n- hernan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "0.1.60",
|
||||||
|
"date": "2026-08-02",
|
||||||
|
"size": 8337730,
|
||||||
|
"downloadURL": "https://github.com/bryanthaboi/gen1recomp/releases/download/v0.1.60/gen1recomp-0.1.60-ios.ipa",
|
||||||
|
"localizedDescription": "Download the correct version for your computer below.\n\n## Issues closed\n\n- #596 No rival theme\n- #641 PP UP UI\n- #648 The revive item does not return the divided experience to fallen Pokemon that participated in battle.\n- #677 Gamespeed 3x\n- #683 Rival theme not playing in Oak's Lab\n- #689 Corrupted color on table in Celadon City using Advanced Colors preset\n- #694 Missing sound effect when falling from boulder holes\n- #695 Pressing B on the PC should bring you back to the previous screen, not turn it off\n\n## Contributors\n\n- @bryanthaboi\n- @castdrian\n- @ShaneMcGovernIE\n- @spiritsnails\n- Shane McGovern"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"version": "0.1.59",
|
"version": "0.1.59",
|
||||||
"date": "2026-08-02",
|
"date": "2026-08-02",
|
||||||
|
|||||||
@@ -30,6 +30,45 @@ public final class GRPickerBridge: NSObject {
|
|||||||
// (<sandbox>/Library/Application Support/<identity>).
|
// (<sandbox>/Library/Application Support/<identity>).
|
||||||
private static let loveIdentity = "pokemon-love2d"
|
private static let loveIdentity = "pokemon-love2d"
|
||||||
|
|
||||||
|
@objc(httpDownloadWithUrl:destination:userAgent:accept:)
|
||||||
|
public static func httpDownload(url: UnsafePointer<CChar>?,
|
||||||
|
destination: UnsafePointer<CChar>?,
|
||||||
|
userAgent: UnsafePointer<CChar>?,
|
||||||
|
accept: UnsafePointer<CChar>?) -> Bool {
|
||||||
|
guard let url, let destination,
|
||||||
|
let requestURL = URL(string: String(cString: url)) else { return false }
|
||||||
|
var request = URLRequest(url: requestURL)
|
||||||
|
request.timeoutInterval = 300
|
||||||
|
if let userAgent, userAgent.pointee != 0 {
|
||||||
|
request.setValue(String(cString: userAgent), forHTTPHeaderField: "User-Agent")
|
||||||
|
}
|
||||||
|
if let accept, accept.pointee != 0 {
|
||||||
|
request.setValue(String(cString: accept), forHTTPHeaderField: "Accept")
|
||||||
|
}
|
||||||
|
let target = URL(fileURLWithPath: String(cString: destination))
|
||||||
|
let semaphore = DispatchSemaphore(value: 0)
|
||||||
|
var succeeded = false
|
||||||
|
let task = URLSession.shared.downloadTask(with: request) { temporary, response, error in
|
||||||
|
defer { semaphore.signal() }
|
||||||
|
guard error == nil, let temporary,
|
||||||
|
let http = response as? HTTPURLResponse,
|
||||||
|
(200..<300).contains(http.statusCode) else { return }
|
||||||
|
try? FileManager.default.removeItem(at: target)
|
||||||
|
do {
|
||||||
|
try FileManager.default.moveItem(at: temporary, to: target)
|
||||||
|
succeeded = true
|
||||||
|
} catch {
|
||||||
|
succeeded = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
task.resume()
|
||||||
|
guard semaphore.wait(timeout: .now() + 305) == .success else {
|
||||||
|
task.cancel()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return succeeded
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Entry points called from liblove (C strings on purpose)
|
// MARK: - Entry points called from liblove (C strings on purpose)
|
||||||
|
|
||||||
@objc(presentPickerWithKind:saveDir:)
|
@objc(presentPickerWithKind:saveDir:)
|
||||||
@@ -174,15 +213,27 @@ public final class GRPickerBridge: NSObject {
|
|||||||
// Without this the stacked present makes the picker auto-dismiss
|
// Without this the stacked present makes the picker auto-dismiss
|
||||||
// with zero documents (observed as didPickDocumentsAt 0 urls)
|
// with zero documents (observed as didPickDocumentsAt 0 urls)
|
||||||
// and the user's pick silently does nothing.
|
// and the user's pick silently does nothing.
|
||||||
|
guard var top = UIApplication.shared.windows
|
||||||
|
.first(where: { $0.isKeyWindow })?.rootViewController
|
||||||
|
else { return false }
|
||||||
|
// Self-heal before consulting the list. If UIKit is presenting
|
||||||
|
// nothing at all, any delegate still in it belongs to a sheet
|
||||||
|
// that is long gone, and treating it as live would lock the
|
||||||
|
// picker out for the rest of the session. Belt and braces with
|
||||||
|
// the dismissal callback above: that one closes the known hole,
|
||||||
|
// this one closes whatever hole iOS invents next.
|
||||||
|
if top.presentedViewController == nil, !liveDelegates.isEmpty {
|
||||||
|
NSLog("GRPickerBridge: clearing %d stale delegate(s)",
|
||||||
|
liveDelegates.count)
|
||||||
|
liveDelegates.removeAll()
|
||||||
|
}
|
||||||
guard liveDelegates.isEmpty else {
|
guard liveDelegates.isEmpty else {
|
||||||
NSLog("GRPickerBridge: picker already active; ignoring re-present")
|
NSLog("GRPickerBridge: picker already active; ignoring re-present")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
guard var top = UIApplication.shared.windows
|
|
||||||
.first(where: { $0.isKeyWindow })?.rootViewController
|
|
||||||
else { return false }
|
|
||||||
while let presented = top.presentedViewController { top = presented }
|
while let presented = top.presentedViewController { top = presented }
|
||||||
picker.delegate = delegate
|
picker.delegate = delegate
|
||||||
|
picker.presentationController?.delegate = delegate
|
||||||
liveDelegates.append(delegate)
|
liveDelegates.append(delegate)
|
||||||
delegate.onFinish = { [weak delegate] in
|
delegate.onFinish = { [weak delegate] in
|
||||||
liveDelegates.removeAll { $0 === delegate }
|
liveDelegates.removeAll { $0 === delegate }
|
||||||
@@ -197,7 +248,8 @@ public final class GRPickerBridge: NSObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class PickerDelegate: NSObject, UIDocumentPickerDelegate {
|
private final class PickerDelegate: NSObject, UIDocumentPickerDelegate,
|
||||||
|
UIAdaptivePresentationControllerDelegate {
|
||||||
private let onPick: ([URL]) -> Void
|
private let onPick: ([URL]) -> Void
|
||||||
var onFinish: (() -> Void)?
|
var onFinish: (() -> Void)?
|
||||||
init(onPick: @escaping ([URL]) -> Void) { self.onPick = onPick }
|
init(onPick: @escaping ([URL]) -> Void) { self.onPick = onPick }
|
||||||
@@ -215,4 +267,18 @@ private final class PickerDelegate: NSObject, UIDocumentPickerDelegate {
|
|||||||
NSLog("GRPickerBridge: picker cancelled")
|
NSLog("GRPickerBridge: picker cancelled")
|
||||||
onFinish?()
|
onFinish?()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Swiping the sheet down calls NEITHER of the two above: since iOS 13 an
|
||||||
|
// interactively dismissed picker reports only through the adaptive
|
||||||
|
// presentation delegate. Without this the delegate is never taken out of
|
||||||
|
// liveDelegates, the re-present guard below then swallows every later
|
||||||
|
// picker while still answering true -- so Lua arms its poll and waits for
|
||||||
|
// a file that no sheet is ever going to produce. That is the whole of the
|
||||||
|
// "Import ROM does nothing until you restart the app" report: the restart
|
||||||
|
// is not refreshing anything, it is clearing this array.
|
||||||
|
func presentationControllerDidDismiss(_ presentationController:
|
||||||
|
UIPresentationController) {
|
||||||
|
NSLog("GRPickerBridge: picker dismissed interactively")
|
||||||
|
onFinish?()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
<key>CFBundleInfoDictionaryVersion</key>
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
<string>6.0</string>
|
<string>6.0</string>
|
||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
<string>${PRODUCT_NAME}</string>
|
<string>gen1recomp</string>
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
@@ -44,9 +44,9 @@
|
|||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||||
<false/>
|
<true/>
|
||||||
<key>UIFileSharingEnabled</key>
|
<key>UIFileSharingEnabled</key>
|
||||||
<false/>
|
<true/>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<string>Launch Screen</string>
|
<string>Launch Screen</string>
|
||||||
<key>UIStatusBarHidden</key>
|
<key>UIStatusBarHidden</key>
|
||||||
@@ -96,13 +96,6 @@
|
|||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
<!-- gen1recomp iOS: expose the app's Documents folder in the Files app /
|
|
||||||
Finder so ROMs, mod .zips, and .sav files can be dropped in without
|
|
||||||
the picker; GRBootstrap sweeps them into the LÖVE save dir. -->
|
|
||||||
<key>UIFileSharingEnabled</key>
|
|
||||||
<true/>
|
|
||||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
|
||||||
<true/>
|
|
||||||
<!-- gen1recomp iOS: Pokéwalker mod reads step counts (opt-in, in the
|
<!-- gen1recomp iOS: Pokéwalker mod reads step counts (opt-in, in the
|
||||||
in-game mod manager) and converts them to Pokémon EXP. -->
|
in-game mod manager) and converts them to Pokémon EXP. -->
|
||||||
<key>NSHealthShareUsageDescription</key>
|
<key>NSHealthShareUsageDescription</key>
|
||||||
|
|||||||
@@ -103,6 +103,74 @@ WRAP_REGISTRATION = """#ifdef LOVE_IOS
|
|||||||
{ "pickFile", w_pickFile },
|
{ "pickFile", w_pickFile },
|
||||||
{ "createFile", w_createFile },
|
{ "createFile", w_createFile },
|
||||||
{ "syncHealthSteps", w_syncHealthSteps },
|
{ "syncHealthSteps", w_syncHealthSteps },
|
||||||
|
{ "httpDownload", w_httpDownload },
|
||||||
|
#endif
|
||||||
|
"""
|
||||||
|
|
||||||
|
WRAP_SYNC_FUNCS = """
|
||||||
|
#ifdef LOVE_IOS
|
||||||
|
static const char *gr_saveDirectory()
|
||||||
|
{
|
||||||
|
static std::string saveDirectory;
|
||||||
|
auto fs = Module::getInstance<love::filesystem::Filesystem>(Module::M_FILESYSTEM);
|
||||||
|
if (fs == nullptr)
|
||||||
|
return "";
|
||||||
|
saveDirectory = fs->getSaveDirectory();
|
||||||
|
return saveDirectory.c_str();
|
||||||
|
}
|
||||||
|
|
||||||
|
static int gr_callBridge(lua_State *L, const char *className,
|
||||||
|
const char *selector, const char *arg)
|
||||||
|
{
|
||||||
|
Class cls = objc_getClass(className);
|
||||||
|
if (cls == nullptr)
|
||||||
|
{
|
||||||
|
lua_pushboolean(L, 0);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
typedef signed char (*GRMsg)(Class, SEL, const char *, const char *);
|
||||||
|
signed char ok = ((GRMsg)objc_msgSend)(cls, sel_registerName(selector),
|
||||||
|
arg, gr_saveDirectory());
|
||||||
|
lua_pushboolean(L, ok != 0);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int w_syncHealthSteps(lua_State *L)
|
||||||
|
{
|
||||||
|
return gr_callBridge(L, "GRHealthBridge", "syncStepsWithCommand:saveDir:", "sync");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
WRAP_SYNC_REGISTRATION = """#ifdef LOVE_IOS
|
||||||
|
{ "syncHealthSteps", w_syncHealthSteps },
|
||||||
|
{ "httpDownload", w_httpDownload },
|
||||||
|
#endif
|
||||||
|
"""
|
||||||
|
|
||||||
|
BRIDGE_EXTRA_FUNCS = """
|
||||||
|
#ifdef LOVE_IOS
|
||||||
|
int w_httpDownload(lua_State *L)
|
||||||
|
{
|
||||||
|
const char *url = luaL_checkstring(L, 1);
|
||||||
|
const char *destination = luaL_checkstring(L, 2);
|
||||||
|
const char *userAgent = luaL_optstring(L, 3, "gen1recomp");
|
||||||
|
const char *accept = luaL_optstring(L, 4, "");
|
||||||
|
Class cls = objc_getClass("GRPickerBridge");
|
||||||
|
if (cls == nullptr)
|
||||||
|
{
|
||||||
|
lua_pushboolean(L, 0);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
typedef signed char (*GRDownload)(Class, SEL, const char *, const char *,
|
||||||
|
const char *, const char *);
|
||||||
|
signed char ok = ((GRDownload)objc_msgSend)(
|
||||||
|
cls, sel_registerName("httpDownloadWithUrl:destination:userAgent:accept:"),
|
||||||
|
url, destination, userAgent, accept);
|
||||||
|
lua_pushboolean(L, ok != 0);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -174,14 +242,17 @@ def patch_wrap_system():
|
|||||||
anchor = "static const luaL_Reg functions[] ="
|
anchor = "static const luaL_Reg functions[] ="
|
||||||
if anchor not in text:
|
if anchor not in text:
|
||||||
fail(f"anchor not found in {WRAP_SYSTEM}")
|
fail(f"anchor not found in {WRAP_SYSTEM}")
|
||||||
text = text.replace(anchor, WRAP_FUNCS + anchor, 1)
|
has_native_picker = re.search(r"\bint w_pickFile\s*\(", text) is not None
|
||||||
|
bridge_funcs = WRAP_SYNC_FUNCS if has_native_picker else WRAP_FUNCS
|
||||||
|
text = text.replace(anchor, bridge_funcs + BRIDGE_EXTRA_FUNCS + anchor, 1)
|
||||||
reg_anchor = '\t{ "vibrate", w_vibrate },\n'
|
reg_anchor = '\t{ "vibrate", w_vibrate },\n'
|
||||||
if reg_anchor not in text:
|
if reg_anchor not in text:
|
||||||
fail(f"registration anchor not found in {WRAP_SYSTEM}")
|
fail(f"registration anchor not found in {WRAP_SYSTEM}")
|
||||||
text = text.replace(reg_anchor, reg_anchor + WRAP_REGISTRATION, 1)
|
registration = WRAP_SYNC_REGISTRATION if has_native_picker else WRAP_REGISTRATION
|
||||||
|
text = text.replace(reg_anchor, reg_anchor + registration, 1)
|
||||||
WRAP_SYSTEM.write_text(text)
|
WRAP_SYSTEM.write_text(text)
|
||||||
print("patch_love_src: wrap_System.cpp patched "
|
print("patch_love_src: wrap_System.cpp patched "
|
||||||
"(pickFile/createFile/syncHealthSteps)")
|
"(pickFile/createFile/syncHealthSteps/httpDownload)")
|
||||||
|
|
||||||
|
|
||||||
def patch_pbxproj():
|
def patch_pbxproj():
|
||||||
|
|||||||
|
After Width: | Height: | Size: 153 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 7.2 KiB |
|
After Width: | Height: | Size: 26 KiB |
@@ -0,0 +1,119 @@
|
|||||||
|
# Gen1Recomp Xbox UWP build notes
|
||||||
|
|
||||||
|
This is the Xbox Dev Mode package for Gen1Recomp.
|
||||||
|
|
||||||
|
The rough shape is:
|
||||||
|
|
||||||
|
- `Gen1RecompUWP.exe` starts LÖVE through SDL's WinRT wrapper
|
||||||
|
- the bundled LÖVE 11.5 UWP backend provides LuaJIT and the Xbox file picker
|
||||||
|
- the bundled SDL2 runtime contains the Xbox controller mapping
|
||||||
|
- ANGLE provides OpenGL ES over D3D11
|
||||||
|
- the bundled runtime contains the audio, font, video, and compression libraries
|
||||||
|
|
||||||
|
## What You Need
|
||||||
|
|
||||||
|
The tested toolchain is:
|
||||||
|
|
||||||
|
- Visual Studio 2022 17.14
|
||||||
|
- MSVC v143 x64/x86 build tools
|
||||||
|
- C++ Universal Windows Platform tools
|
||||||
|
- Windows 11 SDK `10.0.26100.0`
|
||||||
|
- CMake 3.24 or newer
|
||||||
|
- Git for Windows
|
||||||
|
- Info-ZIP `zip` and `unzip`
|
||||||
|
|
||||||
|
Use Visual Studio Installer to add **Universal Windows Platform development**, the v143 C++ tools, CMake tools for Windows, and Windows SDK `10.0.26100.0`.
|
||||||
|
|
||||||
|
The x64 UWP dependencies are committed under `third_party`. Their versions,
|
||||||
|
source revisions, licences, and hashes are recorded in `third_party/manifest.json`.
|
||||||
|
No additional checkout or environment variable is required for a normal game
|
||||||
|
build.
|
||||||
|
|
||||||
|
## Rebuild the Dependencies
|
||||||
|
|
||||||
|
Run the dependency rebuild from the repository root:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\scripts\xbox-uwp\rebuild_dependencies.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
The script clones the pinned SDL2, LÖVE, LuaJIT, vcpkg, depot_tools, and ANGLE
|
||||||
|
sources when they are missing. It applies the Xbox SDL2 patch, builds the x64
|
||||||
|
UWP Release libraries, stages the required DLLs, import libraries, headers, and
|
||||||
|
licences under `third_party`, updates every SHA-256 entry in the manifest, then
|
||||||
|
builds the Release MSIX.
|
||||||
|
|
||||||
|
The generated source checkouts are ignored by Git. A fresh ANGLE sync is about
|
||||||
|
10 GB, so allow at least 20 GB of free disk space for all sources and build
|
||||||
|
outputs. Use `-SkipAngle` to retain the existing pinned ANGLE runtime while
|
||||||
|
rebuilding SDL2, LÖVE, LuaJIT, and the vcpkg libraries. Use `-SkipPackage` when
|
||||||
|
only the dependency bundle needs to be refreshed. The rebuild stops if a source
|
||||||
|
checkout has local changes. Remove that generated `source` directory to restore
|
||||||
|
the pinned revision.
|
||||||
|
|
||||||
|
## Build the MSIX
|
||||||
|
|
||||||
|
Run the Xbox build from Git Bash at the repository root:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scripts/build_xbox_uwp.sh --release --version 1.2.3
|
||||||
|
```
|
||||||
|
|
||||||
|
The build uses `scripts/pack_love.sh` to create and verify the same ROM-free
|
||||||
|
`game.love` payload used by the other release targets. It then links the UWP
|
||||||
|
host and stages LÖVE, LuaJIT, SDL2, ANGLE, and the vcpkg runtime DLLs.
|
||||||
|
|
||||||
|
Use `--relwithdebinfo` for a package with symbols. To package a `.love` produced
|
||||||
|
by another build or downloaded from CI, pass `--game-love path/to/game.love`.
|
||||||
|
The upstream `X.Y.Z` release becomes `X.Y.Z.0` in the generated MSIX manifest.
|
||||||
|
Neither the manifest template nor `src/core/Version.lua` is edited in place.
|
||||||
|
|
||||||
|
The manifest publisher must match the signing certificate subject. Pass it
|
||||||
|
when preparing a signed package:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scripts/build_xbox_uwp.sh --release --version 1.2.3 \
|
||||||
|
--publisher "CN=Gen1Recomp"
|
||||||
|
```
|
||||||
|
|
||||||
|
The normal build is unsigned. Release CI supplies the private PFX and password
|
||||||
|
from `XBOX_UWP_SIGNING_CERTIFICATE` and `XBOX_UWP_SIGNING_PASSWORD`; neither may
|
||||||
|
be committed. The public certificate is safe to include with the release.
|
||||||
|
|
||||||
|
Run the offline packaging checks from Git Bash:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash scripts/xbox-uwp/selftest_build_xbox_uwp.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build Output
|
||||||
|
|
||||||
|
Visual Studio package output lands under:
|
||||||
|
|
||||||
|
```text
|
||||||
|
ports\uwp\build\release\AppPackages\Gen1RecompUWP
|
||||||
|
```
|
||||||
|
|
||||||
|
The build also stages the distributable archive and checksum under:
|
||||||
|
|
||||||
|
```text
|
||||||
|
dist\xbox-uwp\gen1recomp-X.Y.Z-xbox-uwp.zip
|
||||||
|
dist\xbox-uwp\gen1recomp-X.Y.Z-xbox-uwp.zip.sha256
|
||||||
|
```
|
||||||
|
|
||||||
|
The archive contains the MSIX, framework dependencies, build provenance and,
|
||||||
|
for a signed release, the public certificate. The third-party notices are
|
||||||
|
packaged inside the MSIX. Install the MSIX and dependency packages through
|
||||||
|
Xbox Device Portal.
|
||||||
|
|
||||||
|
## Runtime Data
|
||||||
|
|
||||||
|
The package contains no ROM, generated cache, save or mod data. The Xbox file
|
||||||
|
picker copies user-selected files into LocalState and the launcher imports them
|
||||||
|
from there. Saves, ROM cache and installed mods remain under the LÖVE save
|
||||||
|
directory in LocalState.
|
||||||
|
|
||||||
|
LuaJIT requires the `codeGeneration` capability. `removableStorage` exposes
|
||||||
|
external media to the Xbox picker. The network capabilities support relay play
|
||||||
|
and direct hosting. The package does not request full trust or broad filesystem
|
||||||
|
access.
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.24)
|
||||||
|
project(Gen1RecompUWP LANGUAGES CXX)
|
||||||
|
|
||||||
|
if(NOT CMAKE_SYSTEM_NAME STREQUAL "WindowsStore")
|
||||||
|
message(FATAL_ERROR "Configure with a WindowsStore preset.")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
get_filename_component(GAME_ROOT "${CMAKE_CURRENT_LIST_DIR}/../.." ABSOLUTE)
|
||||||
|
set(THIRD_PARTY_ROOT "${CMAKE_CURRENT_LIST_DIR}/third_party")
|
||||||
|
set(LOVE_ROOT "${THIRD_PARTY_ROOT}/love")
|
||||||
|
set(SDL2_ROOT "${THIRD_PARTY_ROOT}/sdl2")
|
||||||
|
set(ANGLE_ROOT "${THIRD_PARTY_ROOT}/angle")
|
||||||
|
set(RUNTIME_ROOT "${THIRD_PARTY_ROOT}/runtime")
|
||||||
|
|
||||||
|
set(GEN1RECOMP_VERSION "0.0.0" CACHE STRING "Gen1Recomp release version")
|
||||||
|
if(NOT GEN1RECOMP_VERSION MATCHES "^[0-9]+\\.[0-9]+\\.[0-9]+$")
|
||||||
|
message(FATAL_ERROR "GEN1RECOMP_VERSION must use X.Y.Z format.")
|
||||||
|
endif()
|
||||||
|
string(REPLACE "." ";" VERSION_PARTS "${GEN1RECOMP_VERSION}")
|
||||||
|
foreach(part IN LISTS VERSION_PARTS)
|
||||||
|
if(part GREATER 65535)
|
||||||
|
message(FATAL_ERROR "MSIX version components cannot exceed 65535.")
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
set(GEN1RECOMP_UWP_PUBLISHER "CN=Gen1Recomp" CACHE STRING
|
||||||
|
"Publisher subject from the MSIX signing certificate")
|
||||||
|
set(UWP_PUBLISHER_XML "${GEN1RECOMP_UWP_PUBLISHER}")
|
||||||
|
string(REPLACE "&" "&" UWP_PUBLISHER_XML "${UWP_PUBLISHER_XML}")
|
||||||
|
string(REPLACE "\"" """ UWP_PUBLISHER_XML "${UWP_PUBLISHER_XML}")
|
||||||
|
string(REPLACE "<" "<" UWP_PUBLISHER_XML "${UWP_PUBLISHER_XML}")
|
||||||
|
string(REPLACE ">" ">" UWP_PUBLISHER_XML "${UWP_PUBLISHER_XML}")
|
||||||
|
set(UWP_PACKAGE_VERSION "${GEN1RECOMP_VERSION}.0")
|
||||||
|
set(PACKAGE_MANIFEST "${CMAKE_CURRENT_BINARY_DIR}/Package.appxmanifest")
|
||||||
|
configure_file(
|
||||||
|
"${CMAKE_CURRENT_LIST_DIR}/Package.appxmanifest.in"
|
||||||
|
"${PACKAGE_MANIFEST}"
|
||||||
|
@ONLY
|
||||||
|
)
|
||||||
|
|
||||||
|
set(REQUIRED_FILES
|
||||||
|
"${THIRD_PARTY_ROOT}/manifest.json"
|
||||||
|
"${LOVE_ROOT}/lib/lovestatic.lib"
|
||||||
|
"${LOVE_ROOT}/lib/liblove.lib"
|
||||||
|
"${LOVE_ROOT}/lib/lua51.lib"
|
||||||
|
"${LOVE_ROOT}/bin/love.dll"
|
||||||
|
"${LOVE_ROOT}/bin/lua51.dll"
|
||||||
|
"${SDL2_ROOT}/include/SDL2/SDL.h"
|
||||||
|
"${SDL2_ROOT}/lib/SDL2.lib"
|
||||||
|
"${SDL2_ROOT}/bin/SDL2.dll"
|
||||||
|
"${ANGLE_ROOT}/bin/libEGL.dll"
|
||||||
|
"${ANGLE_ROOT}/bin/libGLESv2.dll"
|
||||||
|
"${ANGLE_ROOT}/bin/d3dcompiler_47.dll"
|
||||||
|
)
|
||||||
|
foreach(path IN LISTS REQUIRED_FILES)
|
||||||
|
if(NOT EXISTS "${path}")
|
||||||
|
message(FATAL_ERROR "Missing UWP dependency: ${path}")
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
set(GEN1RECOMP_LOVE "${GAME_ROOT}/.bazinga/work/game.love" CACHE FILEPATH
|
||||||
|
"Path to the game.love payload produced by scripts/pack_love.sh")
|
||||||
|
if(NOT EXISTS "${GEN1RECOMP_LOVE}")
|
||||||
|
message(FATAL_ERROR
|
||||||
|
"Missing game.love payload: ${GEN1RECOMP_LOVE}\n"
|
||||||
|
"Build it with scripts/build_xbox_uwp.sh or scripts/pack_love.sh.")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(GAME_ARCHIVE "${CMAKE_CURRENT_BINARY_DIR}/gen1recomp.love")
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT "${GAME_ARCHIVE}"
|
||||||
|
COMMAND "${CMAKE_COMMAND}" -E copy_if_different
|
||||||
|
"${GEN1RECOMP_LOVE}" "${GAME_ARCHIVE}"
|
||||||
|
DEPENDS "${GEN1RECOMP_LOVE}"
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
add_custom_target(gen1recomp_love ALL DEPENDS "${GAME_ARCHIVE}")
|
||||||
|
set_source_files_properties("${GAME_ARCHIVE}" PROPERTIES GENERATED TRUE)
|
||||||
|
|
||||||
|
add_custom_target(verify_uwp_dependencies
|
||||||
|
COMMAND powershell -NoProfile -ExecutionPolicy Bypass -File
|
||||||
|
"${GAME_ROOT}/scripts/xbox-uwp/verify_dependencies.ps1"
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
|
||||||
|
add_executable(${PROJECT_NAME} WIN32 "app/main.cpp")
|
||||||
|
add_dependencies(${PROJECT_NAME} gen1recomp_love verify_uwp_dependencies)
|
||||||
|
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||||
|
CXX_STANDARD 17
|
||||||
|
CXX_STANDARD_REQUIRED YES
|
||||||
|
VS_GLOBAL_DefaultLanguage "en-US"
|
||||||
|
VS_SDK_REFERENCES "Microsoft.VCLibs, Version=14.0"
|
||||||
|
)
|
||||||
|
target_include_directories(${PROJECT_NAME} PRIVATE "${SDL2_ROOT}/include/SDL2")
|
||||||
|
target_link_libraries(${PROJECT_NAME} PRIVATE
|
||||||
|
"${LOVE_ROOT}/lib/lovestatic.lib"
|
||||||
|
"${LOVE_ROOT}/lib/liblove.lib"
|
||||||
|
"${SDL2_ROOT}/lib/SDL2.lib"
|
||||||
|
"${LOVE_ROOT}/lib/lua51.lib"
|
||||||
|
WindowsApp.lib
|
||||||
|
)
|
||||||
|
|
||||||
|
set(PACKAGE_ROOT_FILES
|
||||||
|
"${PACKAGE_MANIFEST}"
|
||||||
|
"${GAME_ARCHIVE}"
|
||||||
|
"${LOVE_ROOT}/bin/love.dll"
|
||||||
|
"${LOVE_ROOT}/bin/lua51.dll"
|
||||||
|
"${SDL2_ROOT}/bin/SDL2.dll"
|
||||||
|
"${ANGLE_ROOT}/bin/libEGL.dll"
|
||||||
|
"${ANGLE_ROOT}/bin/libGLESv2.dll"
|
||||||
|
"${ANGLE_ROOT}/bin/d3dcompiler_47.dll"
|
||||||
|
)
|
||||||
|
set_source_files_properties("${PACKAGE_MANIFEST}" PROPERTIES GENERATED TRUE)
|
||||||
|
|
||||||
|
set(RUNTIME_NAMES
|
||||||
|
brotlicommon.dll brotlidec.dll bz2.dll fmt.dll freetype.dll libpng16.dll
|
||||||
|
OpenAL32.dll theora.dll theoradec.dll vorbis.dll vorbisfile.dll ogg.dll z.dll
|
||||||
|
)
|
||||||
|
foreach(name IN LISTS RUNTIME_NAMES)
|
||||||
|
set(runtime "${RUNTIME_ROOT}/bin/${name}")
|
||||||
|
if(NOT EXISTS "${runtime}")
|
||||||
|
message(FATAL_ERROR "Missing UWP runtime DLL: ${name}")
|
||||||
|
endif()
|
||||||
|
list(APPEND PACKAGE_ROOT_FILES "${runtime}")
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
set_source_files_properties(${PACKAGE_ROOT_FILES} PROPERTIES
|
||||||
|
VS_COPY_TO_OUT_DIR Always
|
||||||
|
VS_DEPLOYMENT_CONTENT TRUE
|
||||||
|
VS_DEPLOYMENT_LOCATION "."
|
||||||
|
)
|
||||||
|
|
||||||
|
file(GLOB PACKAGE_ASSETS CONFIGURE_DEPENDS "${CMAKE_CURRENT_LIST_DIR}/Assets/*.png")
|
||||||
|
set_source_files_properties(${PACKAGE_ASSETS} PROPERTIES
|
||||||
|
VS_DEPLOYMENT_CONTENT TRUE
|
||||||
|
VS_DEPLOYMENT_LOCATION "Assets"
|
||||||
|
)
|
||||||
|
|
||||||
|
file(GLOB PACKAGE_LICENSES CONFIGURE_DEPENDS "${THIRD_PARTY_ROOT}/licenses/*.txt")
|
||||||
|
set_source_files_properties(${PACKAGE_LICENSES} PROPERTIES
|
||||||
|
VS_TOOL_OVERRIDE "Content"
|
||||||
|
VS_COPY_TO_OUT_DIR Always
|
||||||
|
VS_DEPLOYMENT_CONTENT TRUE
|
||||||
|
VS_DEPLOYMENT_LOCATION "licenses"
|
||||||
|
)
|
||||||
|
target_sources(${PROJECT_NAME} PRIVATE
|
||||||
|
${PACKAGE_ROOT_FILES}
|
||||||
|
${PACKAGE_ASSETS}
|
||||||
|
${PACKAGE_LICENSES}
|
||||||
|
)
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"version": 6,
|
||||||
|
"configurePresets": [
|
||||||
|
{
|
||||||
|
"name": "uwp-common",
|
||||||
|
"hidden": true,
|
||||||
|
"generator": "Visual Studio 17 2022",
|
||||||
|
"architecture": "x64",
|
||||||
|
"cacheVariables": {
|
||||||
|
"CMAKE_SYSTEM_NAME": "WindowsStore",
|
||||||
|
"CMAKE_SYSTEM_VERSION": "10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "uwp-relwithdebinfo",
|
||||||
|
"inherits": "uwp-common",
|
||||||
|
"displayName": "Gen1Recomp Xbox UWP (RelWithDebInfo)",
|
||||||
|
"binaryDir": "${sourceDir}/build/relwithdebinfo"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "uwp-release",
|
||||||
|
"inherits": "uwp-common",
|
||||||
|
"displayName": "Gen1Recomp Xbox UWP (Release)",
|
||||||
|
"binaryDir": "${sourceDir}/build/release"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"buildPresets": [
|
||||||
|
{
|
||||||
|
"name": "uwp-relwithdebinfo",
|
||||||
|
"configurePreset": "uwp-relwithdebinfo",
|
||||||
|
"configuration": "RelWithDebInfo",
|
||||||
|
"jobs": 8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "uwp-release",
|
||||||
|
"configurePreset": "uwp-release",
|
||||||
|
"configuration": "Release",
|
||||||
|
"jobs": 8
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Package
|
||||||
|
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||||
|
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
|
||||||
|
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||||
|
IgnorableNamespaces="uap mp">
|
||||||
|
<Identity Name="Gen1RecompUWP" Publisher="@UWP_PUBLISHER_XML@" Version="@UWP_PACKAGE_VERSION@" />
|
||||||
|
<mp:PhoneIdentity PhoneProductId="5074018d-a46b-4dd3-b7fa-93a1e39858ad" PhonePublisherId="00000000-0000-0000-0000-000000000000" />
|
||||||
|
<Properties>
|
||||||
|
<DisplayName>Gen1Recomp</DisplayName>
|
||||||
|
<PublisherDisplayName>Gen1Recomp</PublisherDisplayName>
|
||||||
|
<Logo>Assets\StoreLogo.png</Logo>
|
||||||
|
</Properties>
|
||||||
|
<Dependencies>
|
||||||
|
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.19041.0" MaxVersionTested="10.0.26100.0" />
|
||||||
|
</Dependencies>
|
||||||
|
<Resources>
|
||||||
|
<Resource Language="x-generate" />
|
||||||
|
</Resources>
|
||||||
|
<Applications>
|
||||||
|
<Application Id="App" Executable="$targetnametoken$.exe" EntryPoint="Gen1RecompUWP.App">
|
||||||
|
<uap:VisualElements
|
||||||
|
DisplayName="Gen1Recomp"
|
||||||
|
Description="Gen1Recomp for Xbox Dev Mode"
|
||||||
|
BackgroundColor="#101820"
|
||||||
|
Square150x150Logo="Assets\Square150x150Logo.png"
|
||||||
|
Square44x44Logo="Assets\Square44x44Logo.png">
|
||||||
|
<uap:DefaultTile
|
||||||
|
Square71x71Logo="Assets\SmallTile.png"
|
||||||
|
Wide310x150Logo="Assets\WideTile.png"
|
||||||
|
Square310x310Logo="Assets\LargeTile.png" />
|
||||||
|
<uap:SplashScreen Image="Assets\SplashScreen.png" BackgroundColor="#101820" />
|
||||||
|
</uap:VisualElements>
|
||||||
|
</Application>
|
||||||
|
</Applications>
|
||||||
|
<Capabilities>
|
||||||
|
<Capability Name="internetClient" />
|
||||||
|
<Capability Name="internetClientServer" />
|
||||||
|
<Capability Name="privateNetworkClientServer" />
|
||||||
|
<Capability Name="codeGeneration" />
|
||||||
|
<uap:Capability Name="removableStorage" />
|
||||||
|
</Capabilities>
|
||||||
|
</Package>
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
#include <Windows.h>
|
||||||
|
#include <SDL.h>
|
||||||
|
#include <string>
|
||||||
|
#include <winrt/Windows.ApplicationModel.h>
|
||||||
|
#include <winrt/Windows.Storage.h>
|
||||||
|
|
||||||
|
extern "C" int SDL_main(int argc, char **argv);
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
int runLove(int, char **)
|
||||||
|
{
|
||||||
|
std::wstring packagePath = winrt::Windows::ApplicationModel::Package::Current()
|
||||||
|
.InstalledLocation().Path().c_str();
|
||||||
|
std::string gamePath = winrt::to_string(packagePath + L"\\gen1recomp.love");
|
||||||
|
|
||||||
|
char executable[] = "Gen1RecompUWP";
|
||||||
|
char fused[] = "--fused";
|
||||||
|
char *loveArgv[] = {executable, gamePath.data(), fused, nullptr};
|
||||||
|
return SDL_main(3, loveArgv);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int CALLBACK WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
|
||||||
|
{
|
||||||
|
SDL_SetHint(SDL_HINT_WINRT_HANDLE_BACK_BUTTON, "1");
|
||||||
|
return SDL_WinRTRunApp(runLove, nullptr);
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# UWP dependencies
|
||||||
|
|
||||||
|
This directory contains the complete x64 UWP Release dependency bundle used by the package build:
|
||||||
|
|
||||||
|
- `love` contains the LÖVE 11.5 and LuaJIT binaries.
|
||||||
|
- `sdl2` contains the matching SDL headers, import library, and runtime.
|
||||||
|
- `angle` contains the EGL and GLES runtime.
|
||||||
|
- `runtime` contains the codec, font, compression, and audio DLLs used by LÖVE.
|
||||||
|
- `licenses` contains the corresponding third party notices.
|
||||||
|
|
||||||
|
`manifest.json` pins the source revisions and SHA-256 hashes. Run `scripts/xbox-uwp/verify_dependencies.ps1` from the repository root after updating any dependency.
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
# This is the official list of The ANGLE Project Authors
|
||||||
|
# for copyright purposes.
|
||||||
|
# This file is distinct from the CONTRIBUTORS files.
|
||||||
|
# See the latter for an explanation.
|
||||||
|
|
||||||
|
# Names should be added to this file as
|
||||||
|
# Name or Organization
|
||||||
|
# Email addresses for individuals are tracked elsewhere to avoid spam.
|
||||||
|
|
||||||
|
Google Inc.
|
||||||
|
TransGaming Inc.
|
||||||
|
3DLabs Inc. Ltd.
|
||||||
|
|
||||||
|
Adobe Systems Inc.
|
||||||
|
Autodesk, Inc.
|
||||||
|
BlackBerry Limited
|
||||||
|
Cable Television Laboratories, Inc.
|
||||||
|
Collabora, Ltd.
|
||||||
|
Cloud Party, Inc.
|
||||||
|
Igalia, S.L.
|
||||||
|
Imagination Technologies Ltd.
|
||||||
|
Intel Corporation
|
||||||
|
LunarG, Inc.
|
||||||
|
Mozilla Corporation
|
||||||
|
Turbulenz
|
||||||
|
Klarälvdalens Datakonsult AB
|
||||||
|
Microsoft Corporation
|
||||||
|
Microsoft Open Technologies, Inc.
|
||||||
|
NVIDIA Corporation
|
||||||
|
Opera Software ASA
|
||||||
|
The Qt Company Ltd.
|
||||||
|
Advanced Micro Devices, Inc.
|
||||||
|
LG Electronics, Inc.
|
||||||
|
IBM Inc.
|
||||||
|
AdaptVis GmbH
|
||||||
|
Samsung Electronics, Inc.
|
||||||
|
Arm Ltd.
|
||||||
|
Broadcom Inc.
|
||||||
|
Facebook, Inc.
|
||||||
|
The Khronos Group, Inc.
|
||||||
|
Numfum GmbH
|
||||||
|
Yandex LLC
|
||||||
|
Rive
|
||||||
|
Institute of Software, Chinese Academy of Sciences
|
||||||
|
Guangdong OPPO Mobile Telecommunications Corp., Ltd
|
||||||
|
Qualcomm Innovation Center, Inc.
|
||||||
|
|
||||||
|
Jacek Caban
|
||||||
|
Mark Callow
|
||||||
|
Ginn Chen
|
||||||
|
Tibor den Ouden
|
||||||
|
Régis Fénéon
|
||||||
|
James Hauxwell
|
||||||
|
Sam Hocevar
|
||||||
|
Pierre Leveille
|
||||||
|
Jonathan Liu
|
||||||
|
Boying Lu
|
||||||
|
Aitor Moreno
|
||||||
|
Yuri O'Donnell
|
||||||
|
Josh Soref
|
||||||
|
Ma Aiguo
|
||||||
|
Maks Naumov
|
||||||
|
Jinyoung Hur
|
||||||
|
Sebastian Bergstein
|
||||||
|
James Ross-Gowan
|
||||||
|
Nickolay Artamonov
|
||||||
|
Ihsan Akmal
|
||||||
|
Andrei Volykhin
|
||||||
|
Jérôme Duval
|
||||||
|
Руслан Ижбулатов
|
||||||
|
Thomas Miller
|
||||||
|
Till Rathmann
|
||||||
|
Nick Shaforostov
|
||||||
|
Jaime Bernardo
|
||||||
|
Le Hoang Quyen
|
||||||
|
Lu Yahan
|
||||||
|
Ethan Lee
|
||||||
|
Renaud Lepage
|
||||||
|
Artem Bolgar
|
||||||
|
Wander Lairson Costa
|
||||||
|
Stephan Hartmann
|
||||||
|
SeongHwan Park
|
||||||
|
Xiaopeng Li
|
||||||
|
Akihiko Odaki
|
||||||
|
Ho Cheung
|
||||||
|
Tao Wang
|
||||||
|
Phan Quang Minh
|
||||||
|
Hongchen Yan
|
||||||
|
Andrew Sumsion
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
// Copyright 2018 The ANGLE Project Authors.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions
|
||||||
|
// are met:
|
||||||
|
//
|
||||||
|
// Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
//
|
||||||
|
// Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following
|
||||||
|
// disclaimer in the documentation and/or other materials provided
|
||||||
|
// with the distribution.
|
||||||
|
//
|
||||||
|
// Neither the name of TransGaming Inc., Google Inc., 3DLabs Inc.
|
||||||
|
// Ltd., nor the names of their contributors may be used to endorse
|
||||||
|
// or promote products derived from this software without specific
|
||||||
|
// prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||||
|
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||||
|
// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||||
|
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||||
|
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
// POSSIBILITY OF SUCH DAMAGE.
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# ANGLE UWP runtime
|
||||||
|
|
||||||
|
`libEGL.dll` and `libGLESv2.dll` were built for x64 UWP from [SternXD/angle](https://github.com/SternXD/angle) commit `45b0b1e03400b7a10aaa9a077e196d1abcddafce`. ANGLE version `2.1.25011` and source hash `45b0b1e03400`.
|
||||||
|
|
||||||
|
`d3dcompiler_47.dll` is the x64 Direct3D HLSL compiler redistributable from Windows SDK `10.0.26100.7705`. Current hashes are recorded in `../manifest.json`.
|
||||||
|
|
||||||
|
ANGLE's upstream `LICENSE` and `AUTHORS` files are included beside its binaries.
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
// Copyright 2018 The ANGLE Project Authors.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions
|
||||||
|
// are met:
|
||||||
|
//
|
||||||
|
// Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
//
|
||||||
|
// Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following
|
||||||
|
// disclaimer in the documentation and/or other materials provided
|
||||||
|
// with the distribution.
|
||||||
|
//
|
||||||
|
// Neither the name of TransGaming Inc., Google Inc., 3DLabs Inc.
|
||||||
|
// Ltd., nor the names of their contributors may be used to endorse
|
||||||
|
// or promote products derived from this software without specific
|
||||||
|
// prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||||
|
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||||
|
// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||||
|
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||||
|
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
// POSSIBILITY OF SUCH DAMAGE.
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
|
||||||
|
--------------------------------------------------------------------------
|
||||||
|
|
||||||
|
This program, "bzip2", the associated library "libbzip2", and all
|
||||||
|
documentation, are copyright (C) 1996-2019 Julian R Seward. All
|
||||||
|
rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions
|
||||||
|
are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. The origin of this software must not be misrepresented; you must
|
||||||
|
not claim that you wrote the original software. If you use this
|
||||||
|
software in a product, an acknowledgment in the product
|
||||||
|
documentation would be appreciated but is not required.
|
||||||
|
|
||||||
|
3. Altered source versions must be plainly marked as such, and must
|
||||||
|
not be misrepresented as being the original software.
|
||||||
|
|
||||||
|
4. The name of the author may not be used to endorse or promote
|
||||||
|
products derived from this software without specific prior written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
|
||||||
|
OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||||
|
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||||
|
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||||
|
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
Julian Seward, jseward@acm.org
|
||||||
|
bzip2/libbzip2 version 1.0.8 of 13 July 2019
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
--- Optional exception to the license ---
|
||||||
|
|
||||||
|
As an exception, if, as a result of your compiling your source code, portions
|
||||||
|
of this Software are embedded into a machine-executable object form of such
|
||||||
|
source code, you may redistribute such embedded portions in such object form
|
||||||
|
without including the above copyright and permission notices.
|
||||||
@@ -0,0 +1,568 @@
|
|||||||
|
LICENSE.TXT:
|
||||||
|
|
||||||
|
FREETYPE LICENSES
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
The FreeType 2 font engine is copyrighted work and cannot be used
|
||||||
|
legally without a software license. In order to make this project
|
||||||
|
usable to a vast majority of developers, we distribute it under two
|
||||||
|
mutually exclusive open-source licenses.
|
||||||
|
|
||||||
|
This means that *you* must choose *one* of the two licenses described
|
||||||
|
below, then obey all its terms and conditions when using FreeType 2 in
|
||||||
|
any of your projects or products.
|
||||||
|
|
||||||
|
- The FreeType License, found in the file `docs/FTL.TXT`, which is
|
||||||
|
similar to the original BSD license *with* an advertising clause
|
||||||
|
that forces you to explicitly cite the FreeType project in your
|
||||||
|
product's documentation. All details are in the license file.
|
||||||
|
This license is suited to products which don't use the GNU General
|
||||||
|
Public License.
|
||||||
|
|
||||||
|
Note that this license is compatible to the GNU General Public
|
||||||
|
License version 3, but not version 2.
|
||||||
|
|
||||||
|
- The GNU General Public License version 2, found in
|
||||||
|
`docs/GPLv2.TXT` (any later version can be used also), for
|
||||||
|
programs which already use the GPL. Note that the FTL is
|
||||||
|
incompatible with GPLv2 due to its advertisement clause.
|
||||||
|
|
||||||
|
The contributed BDF and PCF drivers come with a license similar to
|
||||||
|
that of the X Window System. It is compatible to the above two
|
||||||
|
licenses (see files `src/bdf/README` and `src/pcf/README`). The same
|
||||||
|
holds for the source code files `src/base/fthash.c` and
|
||||||
|
`include/freetype/internal/fthash.h`; they were part of the BDF driver
|
||||||
|
in earlier FreeType versions.
|
||||||
|
|
||||||
|
The gzip module uses the zlib license (see `src/gzip/zlib.h`) which
|
||||||
|
too is compatible to the above two licenses.
|
||||||
|
|
||||||
|
The files `src/autofit/ft-hb-ft.c`, `src/autofit/ft-hb-decls.h`,
|
||||||
|
`src/autofit/ft-hb-types.h`, and `src/autofit/hb-script-list.h`
|
||||||
|
contain code taken (almost) verbatim from the HarfBuzz library, which
|
||||||
|
uses the 'Old MIT' license compatible to the above two licenses.
|
||||||
|
|
||||||
|
The MD5 checksum support (only used for debugging in development
|
||||||
|
builds) is in the public domain.
|
||||||
|
|
||||||
|
|
||||||
|
--- end of LICENSE.TXT ---
|
||||||
|
|
||||||
|
|
||||||
|
FTL.TXT:
|
||||||
|
|
||||||
|
The FreeType Project LICENSE
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
2006-Jan-27
|
||||||
|
|
||||||
|
Copyright 1996-2002, 2006 by
|
||||||
|
David Turner, Robert Wilhelm, and Werner Lemberg
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Introduction
|
||||||
|
============
|
||||||
|
|
||||||
|
The FreeType Project is distributed in several archive packages;
|
||||||
|
some of them may contain, in addition to the FreeType font engine,
|
||||||
|
various tools and contributions which rely on, or relate to, the
|
||||||
|
FreeType Project.
|
||||||
|
|
||||||
|
This license applies to all files found in such packages, and
|
||||||
|
which do not fall under their own explicit license. The license
|
||||||
|
affects thus the FreeType font engine, the test programs,
|
||||||
|
documentation and makefiles, at the very least.
|
||||||
|
|
||||||
|
This license was inspired by the BSD, Artistic, and IJG
|
||||||
|
(Independent JPEG Group) licenses, which all encourage inclusion
|
||||||
|
and use of free software in commercial and freeware products
|
||||||
|
alike. As a consequence, its main points are that:
|
||||||
|
|
||||||
|
o We don't promise that this software works. However, we will be
|
||||||
|
interested in any kind of bug reports. (`as is' distribution)
|
||||||
|
|
||||||
|
o You can use this software for whatever you want, in parts or
|
||||||
|
full form, without having to pay us. (`royalty-free' usage)
|
||||||
|
|
||||||
|
o You may not pretend that you wrote this software. If you use
|
||||||
|
it, or only parts of it, in a program, you must acknowledge
|
||||||
|
somewhere in your documentation that you have used the
|
||||||
|
FreeType code. (`credits')
|
||||||
|
|
||||||
|
We specifically permit and encourage the inclusion of this
|
||||||
|
software, with or without modifications, in commercial products.
|
||||||
|
We disclaim all warranties covering The FreeType Project and
|
||||||
|
assume no liability related to The FreeType Project.
|
||||||
|
|
||||||
|
|
||||||
|
Finally, many people asked us for a preferred form for a
|
||||||
|
credit/disclaimer to use in compliance with this license. We thus
|
||||||
|
encourage you to use the following text:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Portions of this software are copyright © <year> The FreeType
|
||||||
|
Project (https://freetype.org). All rights reserved.
|
||||||
|
"""
|
||||||
|
|
||||||
|
Please replace <year> with the value from the FreeType version you
|
||||||
|
actually use.
|
||||||
|
|
||||||
|
|
||||||
|
Legal Terms
|
||||||
|
===========
|
||||||
|
|
||||||
|
0. Definitions
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Throughout this license, the terms `package', `FreeType Project',
|
||||||
|
and `FreeType archive' refer to the set of files originally
|
||||||
|
distributed by the authors (David Turner, Robert Wilhelm, and
|
||||||
|
Werner Lemberg) as the `FreeType Project', be they named as alpha,
|
||||||
|
beta or final release.
|
||||||
|
|
||||||
|
`You' refers to the licensee, or person using the project, where
|
||||||
|
`using' is a generic term including compiling the project's source
|
||||||
|
code as well as linking it to form a `program' or `executable'.
|
||||||
|
This program is referred to as `a program using the FreeType
|
||||||
|
engine'.
|
||||||
|
|
||||||
|
This license applies to all files distributed in the original
|
||||||
|
FreeType Project, including all source code, binaries and
|
||||||
|
documentation, unless otherwise stated in the file in its
|
||||||
|
original, unmodified form as distributed in the original archive.
|
||||||
|
If you are unsure whether or not a particular file is covered by
|
||||||
|
this license, you must contact us to verify this.
|
||||||
|
|
||||||
|
The FreeType Project is copyright (C) 1996-2000 by David Turner,
|
||||||
|
Robert Wilhelm, and Werner Lemberg. All rights reserved except as
|
||||||
|
specified below.
|
||||||
|
|
||||||
|
1. No Warranty
|
||||||
|
--------------
|
||||||
|
|
||||||
|
THE FREETYPE PROJECT IS PROVIDED `AS IS' WITHOUT WARRANTY OF ANY
|
||||||
|
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE. IN NO EVENT WILL ANY OF THE AUTHORS OR COPYRIGHT HOLDERS
|
||||||
|
BE LIABLE FOR ANY DAMAGES CAUSED BY THE USE OR THE INABILITY TO
|
||||||
|
USE, OF THE FREETYPE PROJECT.
|
||||||
|
|
||||||
|
2. Redistribution
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
This license grants a worldwide, royalty-free, perpetual and
|
||||||
|
irrevocable right and license to use, execute, perform, compile,
|
||||||
|
display, copy, create derivative works of, distribute and
|
||||||
|
sublicense the FreeType Project (in both source and object code
|
||||||
|
forms) and derivative works thereof for any purpose; and to
|
||||||
|
authorize others to exercise some or all of the rights granted
|
||||||
|
herein, subject to the following conditions:
|
||||||
|
|
||||||
|
o Redistribution of source code must retain this license file
|
||||||
|
(`FTL.TXT') unaltered; any additions, deletions or changes to
|
||||||
|
the original files must be clearly indicated in accompanying
|
||||||
|
documentation. The copyright notices of the unaltered,
|
||||||
|
original files must be preserved in all copies of source
|
||||||
|
files.
|
||||||
|
|
||||||
|
o Redistribution in binary form must provide a disclaimer that
|
||||||
|
states that the software is based in part of the work of the
|
||||||
|
FreeType Team, in the distribution documentation. We also
|
||||||
|
encourage you to put an URL to the FreeType web page in your
|
||||||
|
documentation, though this isn't mandatory.
|
||||||
|
|
||||||
|
These conditions apply to any software derived from or based on
|
||||||
|
the FreeType Project, not just the unmodified files. If you use
|
||||||
|
our work, you must acknowledge us. However, no fee need be paid
|
||||||
|
to us.
|
||||||
|
|
||||||
|
3. Advertising
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Neither the FreeType authors and contributors nor you shall use
|
||||||
|
the name of the other for commercial, advertising, or promotional
|
||||||
|
purposes without specific prior written permission.
|
||||||
|
|
||||||
|
We suggest, but do not require, that you use one or more of the
|
||||||
|
following phrases to refer to this software in your documentation
|
||||||
|
or advertising materials: `FreeType Project', `FreeType Engine',
|
||||||
|
`FreeType library', or `FreeType Distribution'.
|
||||||
|
|
||||||
|
As you have not signed this license, you are not required to
|
||||||
|
accept it. However, as the FreeType Project is copyrighted
|
||||||
|
material, only this license, or another one contracted with the
|
||||||
|
authors, grants you the right to use, distribute, and modify it.
|
||||||
|
Therefore, by using, distributing, or modifying the FreeType
|
||||||
|
Project, you indicate that you understand and accept all the terms
|
||||||
|
of this license.
|
||||||
|
|
||||||
|
4. Contacts
|
||||||
|
-----------
|
||||||
|
|
||||||
|
There are two mailing lists related to FreeType:
|
||||||
|
|
||||||
|
o freetype@nongnu.org
|
||||||
|
|
||||||
|
Discusses general use and applications of FreeType, as well as
|
||||||
|
future and wanted additions to the library and distribution.
|
||||||
|
If you are looking for support, start in this list if you
|
||||||
|
haven't found anything to help you in the documentation.
|
||||||
|
|
||||||
|
o freetype-devel@nongnu.org
|
||||||
|
|
||||||
|
Discusses bugs, as well as engine internals, design issues,
|
||||||
|
specific licenses, porting, etc.
|
||||||
|
|
||||||
|
Our home page can be found at
|
||||||
|
|
||||||
|
https://freetype.org
|
||||||
|
|
||||||
|
|
||||||
|
--- end of FTL.TXT ---
|
||||||
|
|
||||||
|
|
||||||
|
GPLv2.TXT:
|
||||||
|
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 2, June 1991
|
||||||
|
|
||||||
|
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||||
|
51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The licenses for most software are designed to take away your
|
||||||
|
freedom to share and change it. By contrast, the GNU General Public
|
||||||
|
License is intended to guarantee your freedom to share and change free
|
||||||
|
software--to make sure the software is free for all its users. This
|
||||||
|
General Public License applies to most of the Free Software
|
||||||
|
Foundation's software and to any other program whose authors commit to
|
||||||
|
using it. (Some other Free Software Foundation software is covered by
|
||||||
|
the GNU Library General Public License instead.) You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
this service if you wish), that you receive source code or can get it
|
||||||
|
if you want it, that you can change the software or use pieces of it
|
||||||
|
in new free programs; and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to make restrictions that forbid
|
||||||
|
anyone to deny you these rights or to ask you to surrender the rights.
|
||||||
|
These restrictions translate to certain responsibilities for you if you
|
||||||
|
distribute copies of the software, or if you modify it.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must give the recipients all the rights that
|
||||||
|
you have. You must make sure that they, too, receive or can get the
|
||||||
|
source code. And you must show them these terms so they know their
|
||||||
|
rights.
|
||||||
|
|
||||||
|
We protect your rights with two steps: (1) copyright the software, and
|
||||||
|
(2) offer you this license which gives you legal permission to copy,
|
||||||
|
distribute and/or modify the software.
|
||||||
|
|
||||||
|
Also, for each author's protection and ours, we want to make certain
|
||||||
|
that everyone understands that there is no warranty for this free
|
||||||
|
software. If the software is modified by someone else and passed on, we
|
||||||
|
want its recipients to know that what they have is not the original, so
|
||||||
|
that any problems introduced by others will not reflect on the original
|
||||||
|
authors' reputations.
|
||||||
|
|
||||||
|
Finally, any free program is threatened constantly by software
|
||||||
|
patents. We wish to avoid the danger that redistributors of a free
|
||||||
|
program will individually obtain patent licenses, in effect making the
|
||||||
|
program proprietary. To prevent this, we have made it clear that any
|
||||||
|
patent must be licensed for everyone's free use or not licensed at all.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||||
|
|
||||||
|
0. This License applies to any program or other work which contains
|
||||||
|
a notice placed by the copyright holder saying it may be distributed
|
||||||
|
under the terms of this General Public License. The "Program", below,
|
||||||
|
refers to any such program or work, and a "work based on the Program"
|
||||||
|
means either the Program or any derivative work under copyright law:
|
||||||
|
that is to say, a work containing the Program or a portion of it,
|
||||||
|
either verbatim or with modifications and/or translated into another
|
||||||
|
language. (Hereinafter, translation is included without limitation in
|
||||||
|
the term "modification".) Each licensee is addressed as "you".
|
||||||
|
|
||||||
|
Activities other than copying, distribution and modification are not
|
||||||
|
covered by this License; they are outside its scope. The act of
|
||||||
|
running the Program is not restricted, and the output from the Program
|
||||||
|
is covered only if its contents constitute a work based on the
|
||||||
|
Program (independent of having been made by running the Program).
|
||||||
|
Whether that is true depends on what the Program does.
|
||||||
|
|
||||||
|
1. You may copy and distribute verbatim copies of the Program's
|
||||||
|
source code as you receive it, in any medium, provided that you
|
||||||
|
conspicuously and appropriately publish on each copy an appropriate
|
||||||
|
copyright notice and disclaimer of warranty; keep intact all the
|
||||||
|
notices that refer to this License and to the absence of any warranty;
|
||||||
|
and give any other recipients of the Program a copy of this License
|
||||||
|
along with the Program.
|
||||||
|
|
||||||
|
You may charge a fee for the physical act of transferring a copy, and
|
||||||
|
you may at your option offer warranty protection in exchange for a fee.
|
||||||
|
|
||||||
|
2. You may modify your copy or copies of the Program or any portion
|
||||||
|
of it, thus forming a work based on the Program, and copy and
|
||||||
|
distribute such modifications or work under the terms of Section 1
|
||||||
|
above, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) You must cause the modified files to carry prominent notices
|
||||||
|
stating that you changed the files and the date of any change.
|
||||||
|
|
||||||
|
b) You must cause any work that you distribute or publish, that in
|
||||||
|
whole or in part contains or is derived from the Program or any
|
||||||
|
part thereof, to be licensed as a whole at no charge to all third
|
||||||
|
parties under the terms of this License.
|
||||||
|
|
||||||
|
c) If the modified program normally reads commands interactively
|
||||||
|
when run, you must cause it, when started running for such
|
||||||
|
interactive use in the most ordinary way, to print or display an
|
||||||
|
announcement including an appropriate copyright notice and a
|
||||||
|
notice that there is no warranty (or else, saying that you provide
|
||||||
|
a warranty) and that users may redistribute the program under
|
||||||
|
these conditions, and telling the user how to view a copy of this
|
||||||
|
License. (Exception: if the Program itself is interactive but
|
||||||
|
does not normally print such an announcement, your work based on
|
||||||
|
the Program is not required to print an announcement.)
|
||||||
|
|
||||||
|
These requirements apply to the modified work as a whole. If
|
||||||
|
identifiable sections of that work are not derived from the Program,
|
||||||
|
and can be reasonably considered independent and separate works in
|
||||||
|
themselves, then this License, and its terms, do not apply to those
|
||||||
|
sections when you distribute them as separate works. But when you
|
||||||
|
distribute the same sections as part of a whole which is a work based
|
||||||
|
on the Program, the distribution of the whole must be on the terms of
|
||||||
|
this License, whose permissions for other licensees extend to the
|
||||||
|
entire whole, and thus to each and every part regardless of who wrote it.
|
||||||
|
|
||||||
|
Thus, it is not the intent of this section to claim rights or contest
|
||||||
|
your rights to work written entirely by you; rather, the intent is to
|
||||||
|
exercise the right to control the distribution of derivative or
|
||||||
|
collective works based on the Program.
|
||||||
|
|
||||||
|
In addition, mere aggregation of another work not based on the Program
|
||||||
|
with the Program (or with a work based on the Program) on a volume of
|
||||||
|
a storage or distribution medium does not bring the other work under
|
||||||
|
the scope of this License.
|
||||||
|
|
||||||
|
3. You may copy and distribute the Program (or a work based on it,
|
||||||
|
under Section 2) in object code or executable form under the terms of
|
||||||
|
Sections 1 and 2 above provided that you also do one of the following:
|
||||||
|
|
||||||
|
a) Accompany it with the complete corresponding machine-readable
|
||||||
|
source code, which must be distributed under the terms of Sections
|
||||||
|
1 and 2 above on a medium customarily used for software interchange; or,
|
||||||
|
|
||||||
|
b) Accompany it with a written offer, valid for at least three
|
||||||
|
years, to give any third party, for a charge no more than your
|
||||||
|
cost of physically performing source distribution, a complete
|
||||||
|
machine-readable copy of the corresponding source code, to be
|
||||||
|
distributed under the terms of Sections 1 and 2 above on a medium
|
||||||
|
customarily used for software interchange; or,
|
||||||
|
|
||||||
|
c) Accompany it with the information you received as to the offer
|
||||||
|
to distribute corresponding source code. (This alternative is
|
||||||
|
allowed only for noncommercial distribution and only if you
|
||||||
|
received the program in object code or executable form with such
|
||||||
|
an offer, in accord with Subsection b above.)
|
||||||
|
|
||||||
|
The source code for a work means the preferred form of the work for
|
||||||
|
making modifications to it. For an executable work, complete source
|
||||||
|
code means all the source code for all modules it contains, plus any
|
||||||
|
associated interface definition files, plus the scripts used to
|
||||||
|
control compilation and installation of the executable. However, as a
|
||||||
|
special exception, the source code distributed need not include
|
||||||
|
anything that is normally distributed (in either source or binary
|
||||||
|
form) with the major components (compiler, kernel, and so on) of the
|
||||||
|
operating system on which the executable runs, unless that component
|
||||||
|
itself accompanies the executable.
|
||||||
|
|
||||||
|
If distribution of executable or object code is made by offering
|
||||||
|
access to copy from a designated place, then offering equivalent
|
||||||
|
access to copy the source code from the same place counts as
|
||||||
|
distribution of the source code, even though third parties are not
|
||||||
|
compelled to copy the source along with the object code.
|
||||||
|
|
||||||
|
4. You may not copy, modify, sublicense, or distribute the Program
|
||||||
|
except as expressly provided under this License. Any attempt
|
||||||
|
otherwise to copy, modify, sublicense or distribute the Program is
|
||||||
|
void, and will automatically terminate your rights under this License.
|
||||||
|
However, parties who have received copies, or rights, from you under
|
||||||
|
this License will not have their licenses terminated so long as such
|
||||||
|
parties remain in full compliance.
|
||||||
|
|
||||||
|
5. You are not required to accept this License, since you have not
|
||||||
|
signed it. However, nothing else grants you permission to modify or
|
||||||
|
distribute the Program or its derivative works. These actions are
|
||||||
|
prohibited by law if you do not accept this License. Therefore, by
|
||||||
|
modifying or distributing the Program (or any work based on the
|
||||||
|
Program), you indicate your acceptance of this License to do so, and
|
||||||
|
all its terms and conditions for copying, distributing or modifying
|
||||||
|
the Program or works based on it.
|
||||||
|
|
||||||
|
6. Each time you redistribute the Program (or any work based on the
|
||||||
|
Program), the recipient automatically receives a license from the
|
||||||
|
original licensor to copy, distribute or modify the Program subject to
|
||||||
|
these terms and conditions. You may not impose any further
|
||||||
|
restrictions on the recipients' exercise of the rights granted herein.
|
||||||
|
You are not responsible for enforcing compliance by third parties to
|
||||||
|
this License.
|
||||||
|
|
||||||
|
7. If, as a consequence of a court judgment or allegation of patent
|
||||||
|
infringement or for any other reason (not limited to patent issues),
|
||||||
|
conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot
|
||||||
|
distribute so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you
|
||||||
|
may not distribute the Program at all. For example, if a patent
|
||||||
|
license would not permit royalty-free redistribution of the Program by
|
||||||
|
all those who receive copies directly or indirectly through you, then
|
||||||
|
the only way you could satisfy both it and this License would be to
|
||||||
|
refrain entirely from distribution of the Program.
|
||||||
|
|
||||||
|
If any portion of this section is held invalid or unenforceable under
|
||||||
|
any particular circumstance, the balance of the section is intended to
|
||||||
|
apply and the section as a whole is intended to apply in other
|
||||||
|
circumstances.
|
||||||
|
|
||||||
|
It is not the purpose of this section to induce you to infringe any
|
||||||
|
patents or other property right claims or to contest validity of any
|
||||||
|
such claims; this section has the sole purpose of protecting the
|
||||||
|
integrity of the free software distribution system, which is
|
||||||
|
implemented by public license practices. Many people have made
|
||||||
|
generous contributions to the wide range of software distributed
|
||||||
|
through that system in reliance on consistent application of that
|
||||||
|
system; it is up to the author/donor to decide if he or she is willing
|
||||||
|
to distribute software through any other system and a licensee cannot
|
||||||
|
impose that choice.
|
||||||
|
|
||||||
|
This section is intended to make thoroughly clear what is believed to
|
||||||
|
be a consequence of the rest of this License.
|
||||||
|
|
||||||
|
8. If the distribution and/or use of the Program is restricted in
|
||||||
|
certain countries either by patents or by copyrighted interfaces, the
|
||||||
|
original copyright holder who places the Program under this License
|
||||||
|
may add an explicit geographical distribution limitation excluding
|
||||||
|
those countries, so that distribution is permitted only in or among
|
||||||
|
countries not thus excluded. In such case, this License incorporates
|
||||||
|
the limitation as if written in the body of this License.
|
||||||
|
|
||||||
|
9. The Free Software Foundation may publish revised and/or new versions
|
||||||
|
of the General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the Program
|
||||||
|
specifies a version number of this License which applies to it and "any
|
||||||
|
later version", you have the option of following the terms and conditions
|
||||||
|
either of that version or of any later version published by the Free
|
||||||
|
Software Foundation. If the Program does not specify a version number of
|
||||||
|
this License, you may choose any version ever published by the Free Software
|
||||||
|
Foundation.
|
||||||
|
|
||||||
|
10. If you wish to incorporate parts of the Program into other free
|
||||||
|
programs whose distribution conditions are different, write to the author
|
||||||
|
to ask for permission. For software which is copyrighted by the Free
|
||||||
|
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||||
|
make exceptions for this. Our decision will be guided by the two goals
|
||||||
|
of preserving the free status of all derivatives of our free software and
|
||||||
|
of promoting the sharing and reuse of software generally.
|
||||||
|
|
||||||
|
NO WARRANTY
|
||||||
|
|
||||||
|
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||||
|
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||||
|
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||||
|
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||||
|
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||||
|
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||||
|
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||||
|
REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||||
|
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||||
|
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||||
|
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||||
|
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||||
|
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||||
|
POSSIBILITY OF SUCH DAMAGES.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
convey the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program is interactive, make it output a short notice like this
|
||||||
|
when it starts in an interactive mode:
|
||||||
|
|
||||||
|
Gnomovision version 69, Copyright (C) year name of author
|
||||||
|
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, the commands you use may
|
||||||
|
be called something other than `show w' and `show c'; they could even be
|
||||||
|
mouse-clicks or menu items--whatever suits your program.
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or your
|
||||||
|
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||||
|
necessary. Here is a sample; alter the names:
|
||||||
|
|
||||||
|
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||||
|
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||||
|
|
||||||
|
<signature of Ty Coon>, 1 April 1989
|
||||||
|
Ty Coon, President of Vice
|
||||||
|
|
||||||
|
This General Public License does not permit incorporating your program into
|
||||||
|
proprietary programs. If your program is a subroutine library, you may
|
||||||
|
consider it more useful to permit linking proprietary applications with the
|
||||||
|
library. If this is what you want to do, use the GNU Library General
|
||||||
|
Public License instead of this License.
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
COPYRIGHT NOTICE, DISCLAIMER, and LICENSE
|
||||||
|
=========================================
|
||||||
|
|
||||||
|
PNG Reference Library License version 2
|
||||||
|
---------------------------------------
|
||||||
|
|
||||||
|
* Copyright (c) 1995-2026 The PNG Reference Library Authors.
|
||||||
|
* Copyright (c) 2018-2026 Cosmin Truta.
|
||||||
|
* Copyright (c) 2000-2002, 2004, 2006-2018 Glenn Randers-Pehrson.
|
||||||
|
* Copyright (c) 1996-1997 Andreas Dilger.
|
||||||
|
* Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc.
|
||||||
|
|
||||||
|
The software is supplied "as is", without warranty of any kind,
|
||||||
|
express or implied, including, without limitation, the warranties
|
||||||
|
of merchantability, fitness for a particular purpose, title, and
|
||||||
|
non-infringement. In no event shall the Copyright owners, or
|
||||||
|
anyone distributing the software, be liable for any damages or
|
||||||
|
other liability, whether in contract, tort or otherwise, arising
|
||||||
|
from, out of, or in connection with the software, or the use or
|
||||||
|
other dealings in the software, even if advised of the possibility
|
||||||
|
of such damage.
|
||||||
|
|
||||||
|
Permission is hereby granted to use, copy, modify, and distribute
|
||||||
|
this software, or portions hereof, for any purpose, without fee,
|
||||||
|
subject to the following restrictions:
|
||||||
|
|
||||||
|
1. The origin of this software must not be misrepresented; you
|
||||||
|
must not claim that you wrote the original software. If you
|
||||||
|
use this software in a product, an acknowledgment in the product
|
||||||
|
documentation would be appreciated, but is not required.
|
||||||
|
|
||||||
|
2. Altered source versions must be plainly marked as such, and must
|
||||||
|
not be misrepresented as being the original software.
|
||||||
|
|
||||||
|
3. This Copyright notice may not be removed or altered from any
|
||||||
|
source or altered source distribution.
|
||||||
|
|
||||||
|
|
||||||
|
PNG Reference Library License version 1 (for libpng 0.5 through 1.6.35)
|
||||||
|
-----------------------------------------------------------------------
|
||||||
|
|
||||||
|
libpng versions 1.0.7, July 1, 2000, through 1.6.35, July 15, 2018 are
|
||||||
|
Copyright (c) 2000-2002, 2004, 2006-2018 Glenn Randers-Pehrson, are
|
||||||
|
derived from libpng-1.0.6, and are distributed according to the same
|
||||||
|
disclaimer and license as libpng-1.0.6 with the following individuals
|
||||||
|
added to the list of Contributing Authors:
|
||||||
|
|
||||||
|
Simon-Pierre Cadieux
|
||||||
|
Eric S. Raymond
|
||||||
|
Mans Rullgard
|
||||||
|
Cosmin Truta
|
||||||
|
Gilles Vollant
|
||||||
|
James Yu
|
||||||
|
Mandar Sahastrabuddhe
|
||||||
|
Google Inc.
|
||||||
|
Vadim Barkov
|
||||||
|
|
||||||
|
and with the following additions to the disclaimer:
|
||||||
|
|
||||||
|
There is no warranty against interference with your enjoyment of
|
||||||
|
the library or against infringement. There is no warranty that our
|
||||||
|
efforts or the library will fulfill any of your particular purposes
|
||||||
|
or needs. This library is provided with all faults, and the entire
|
||||||
|
risk of satisfactory quality, performance, accuracy, and effort is
|
||||||
|
with the user.
|
||||||
|
|
||||||
|
Some files in the "contrib" directory and some configure-generated
|
||||||
|
files that are distributed with libpng have other copyright owners, and
|
||||||
|
are released under other open source licenses.
|
||||||
|
|
||||||
|
libpng versions 0.97, January 1998, through 1.0.6, March 20, 2000, are
|
||||||
|
Copyright (c) 1998-2000 Glenn Randers-Pehrson, are derived from
|
||||||
|
libpng-0.96, and are distributed according to the same disclaimer and
|
||||||
|
license as libpng-0.96, with the following individuals added to the
|
||||||
|
list of Contributing Authors:
|
||||||
|
|
||||||
|
Tom Lane
|
||||||
|
Glenn Randers-Pehrson
|
||||||
|
Willem van Schaik
|
||||||
|
|
||||||
|
libpng versions 0.89, June 1996, through 0.96, May 1997, are
|
||||||
|
Copyright (c) 1996-1997 Andreas Dilger, are derived from libpng-0.88,
|
||||||
|
and are distributed according to the same disclaimer and license as
|
||||||
|
libpng-0.88, with the following individuals added to the list of
|
||||||
|
Contributing Authors:
|
||||||
|
|
||||||
|
John Bowler
|
||||||
|
Kevin Bracey
|
||||||
|
Sam Bushell
|
||||||
|
Magnus Holmgren
|
||||||
|
Greg Roelofs
|
||||||
|
Tom Tanner
|
||||||
|
|
||||||
|
Some files in the "scripts" directory have other copyright owners,
|
||||||
|
but are released under this license.
|
||||||
|
|
||||||
|
libpng versions 0.5, May 1995, through 0.88, January 1996, are
|
||||||
|
Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc.
|
||||||
|
|
||||||
|
For the purposes of this copyright and license, "Contributing Authors"
|
||||||
|
is defined as the following set of individuals:
|
||||||
|
|
||||||
|
Andreas Dilger
|
||||||
|
Dave Martindale
|
||||||
|
Guy Eric Schalnat
|
||||||
|
Paul Schmidt
|
||||||
|
Tim Wegner
|
||||||
|
|
||||||
|
The PNG Reference Library is supplied "AS IS". The Contributing
|
||||||
|
Authors and Group 42, Inc. disclaim all warranties, expressed or
|
||||||
|
implied, including, without limitation, the warranties of
|
||||||
|
merchantability and of fitness for any purpose. The Contributing
|
||||||
|
Authors and Group 42, Inc. assume no liability for direct, indirect,
|
||||||
|
incidental, special, exemplary, or consequential damages, which may
|
||||||
|
result from the use of the PNG Reference Library, even if advised of
|
||||||
|
the possibility of such damage.
|
||||||
|
|
||||||
|
Permission is hereby granted to use, copy, modify, and distribute this
|
||||||
|
source code, or portions hereof, for any purpose, without fee, subject
|
||||||
|
to the following restrictions:
|
||||||
|
|
||||||
|
1. The origin of this source code must not be misrepresented.
|
||||||
|
|
||||||
|
2. Altered versions must be plainly marked as such and must not
|
||||||
|
be misrepresented as being the original source.
|
||||||
|
|
||||||
|
3. This Copyright notice may not be removed or altered from any
|
||||||
|
source or altered source distribution.
|
||||||
|
|
||||||
|
The Contributing Authors and Group 42, Inc. specifically permit,
|
||||||
|
without fee, and encourage the use of this source code as a component
|
||||||
|
to supporting the PNG file format in commercial products. If you use
|
||||||
|
this source code in a product, acknowledgment is not required but would
|
||||||
|
be appreciated.
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
===============================================================================
|
||||||
|
LuaJIT -- a Just-In-Time Compiler for Lua. https://luajit.org/
|
||||||
|
|
||||||
|
Copyright (C) 2005-2026 Mike Pall. All rights reserved.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
||||||
|
[ MIT license: https://www.opensource.org/licenses/mit-license.php ]
|
||||||
|
|
||||||
|
===============================================================================
|
||||||
|
[ LuaJIT includes code from Lua 5.1/5.2, which has this license statement: ]
|
||||||
|
|
||||||
|
Copyright (C) 1994-2012 Lua.org, PUC-Rio.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
||||||
|
===============================================================================
|
||||||
|
[ LuaJIT includes code from dlmalloc, which has this license statement: ]
|
||||||
|
|
||||||
|
This is a version (aka dlmalloc) of malloc/free/realloc written by
|
||||||
|
Doug Lea and released to the public domain, as explained at
|
||||||
|
https://creativecommons.org/licenses/publicdomain
|
||||||
|
|
||||||
|
===============================================================================
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
Copyright (c) 2002, Xiph.org Foundation
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions
|
||||||
|
are met:
|
||||||
|
|
||||||
|
- Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
- Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
- Neither the name of the Xiph.org Foundation nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION
|
||||||
|
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
@@ -0,0 +1,501 @@
|
|||||||
|
COPYING:
|
||||||
|
|
||||||
|
GNU LIBRARY GENERAL PUBLIC LICENSE
|
||||||
|
Version 2, June 1991
|
||||||
|
|
||||||
|
Copyright (C) 1991 Free Software Foundation, Inc.
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
[This is the first released version of the library GPL. It is
|
||||||
|
numbered 2 because it goes with version 2 of the ordinary GPL.]
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The licenses for most software are designed to take away your
|
||||||
|
freedom to share and change it. By contrast, the GNU General Public
|
||||||
|
Licenses are intended to guarantee your freedom to share and change
|
||||||
|
free software--to make sure the software is free for all its users.
|
||||||
|
|
||||||
|
This license, the Library General Public License, applies to some
|
||||||
|
specially designated Free Software Foundation software, and to any
|
||||||
|
other libraries whose authors decide to use it. You can use it for
|
||||||
|
your libraries, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
this service if you wish), that you receive source code or can get it
|
||||||
|
if you want it, that you can change the software or use pieces of it
|
||||||
|
in new free programs; and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to make restrictions that forbid
|
||||||
|
anyone to deny you these rights or to ask you to surrender the rights.
|
||||||
|
These restrictions translate to certain responsibilities for you if
|
||||||
|
you distribute copies of the library, or if you modify it.
|
||||||
|
|
||||||
|
For example, if you distribute copies of the library, whether gratis
|
||||||
|
or for a fee, you must give the recipients all the rights that we gave
|
||||||
|
you. You must make sure that they, too, receive or can get the source
|
||||||
|
code. If you link a program with the library, you must provide
|
||||||
|
complete object files to the recipients so that they can relink them
|
||||||
|
with the library, after making changes to the library and recompiling
|
||||||
|
it. And you must show them these terms so they know their rights.
|
||||||
|
|
||||||
|
Our method of protecting your rights has two steps: (1) copyright
|
||||||
|
the library, and (2) offer you this license which gives you legal
|
||||||
|
permission to copy, distribute and/or modify the library.
|
||||||
|
|
||||||
|
Also, for each distributor's protection, we want to make certain
|
||||||
|
that everyone understands that there is no warranty for this free
|
||||||
|
library. If the library is modified by someone else and passed on, we
|
||||||
|
want its recipients to know that what they have is not the original
|
||||||
|
version, so that any problems introduced by others will not reflect on
|
||||||
|
the original authors' reputations.
|
||||||
|
|
||||||
|
Finally, any free program is threatened constantly by software
|
||||||
|
patents. We wish to avoid the danger that companies distributing free
|
||||||
|
software will individually obtain patent licenses, thus in effect
|
||||||
|
transforming the program into proprietary software. To prevent this,
|
||||||
|
we have made it clear that any patent must be licensed for everyone's
|
||||||
|
free use or not licensed at all.
|
||||||
|
|
||||||
|
Most GNU software, including some libraries, is covered by the ordinary
|
||||||
|
GNU General Public License, which was designed for utility programs. This
|
||||||
|
license, the GNU Library General Public License, applies to certain
|
||||||
|
designated libraries. This license is quite different from the ordinary
|
||||||
|
one; be sure to read it in full, and don't assume that anything in it is
|
||||||
|
the same as in the ordinary license.
|
||||||
|
|
||||||
|
The reason we have a separate public license for some libraries is that
|
||||||
|
they blur the distinction we usually make between modifying or adding to a
|
||||||
|
program and simply using it. Linking a program with a library, without
|
||||||
|
changing the library, is in some sense simply using the library, and is
|
||||||
|
analogous to running a utility program or application program. However, in
|
||||||
|
a textual and legal sense, the linked executable is a combined work, a
|
||||||
|
derivative of the original library, and the ordinary General Public License
|
||||||
|
treats it as such.
|
||||||
|
|
||||||
|
Because of this blurred distinction, using the ordinary General
|
||||||
|
Public License for libraries did not effectively promote software
|
||||||
|
sharing, because most developers did not use the libraries. We
|
||||||
|
concluded that weaker conditions might promote sharing better.
|
||||||
|
|
||||||
|
However, unrestricted linking of non-free programs would deprive the
|
||||||
|
users of those programs of all benefit from the free status of the
|
||||||
|
libraries themselves. This Library General Public License is intended to
|
||||||
|
permit developers of non-free programs to use free libraries, while
|
||||||
|
preserving your freedom as a user of such programs to change the free
|
||||||
|
libraries that are incorporated in them. (We have not seen how to achieve
|
||||||
|
this as regards changes in header files, but we have achieved it as regards
|
||||||
|
changes in the actual functions of the Library.) The hope is that this
|
||||||
|
will lead to faster development of free libraries.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow. Pay close attention to the difference between a
|
||||||
|
"work based on the library" and a "work that uses the library". The
|
||||||
|
former contains code derived from the library, while the latter only
|
||||||
|
works together with the library.
|
||||||
|
|
||||||
|
Note that it is possible for a library to be covered by the ordinary
|
||||||
|
General Public License rather than by this special one.
|
||||||
|
|
||||||
|
GNU LIBRARY GENERAL PUBLIC LICENSE
|
||||||
|
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||||
|
|
||||||
|
0. This License Agreement applies to any software library which
|
||||||
|
contains a notice placed by the copyright holder or other authorized
|
||||||
|
party saying it may be distributed under the terms of this Library
|
||||||
|
General Public License (also called "this License"). Each licensee is
|
||||||
|
addressed as "you".
|
||||||
|
|
||||||
|
A "library" means a collection of software functions and/or data
|
||||||
|
prepared so as to be conveniently linked with application programs
|
||||||
|
(which use some of those functions and data) to form executables.
|
||||||
|
|
||||||
|
The "Library", below, refers to any such software library or work
|
||||||
|
which has been distributed under these terms. A "work based on the
|
||||||
|
Library" means either the Library or any derivative work under
|
||||||
|
copyright law: that is to say, a work containing the Library or a
|
||||||
|
portion of it, either verbatim or with modifications and/or translated
|
||||||
|
straightforwardly into another language. (Hereinafter, translation is
|
||||||
|
included without limitation in the term "modification".)
|
||||||
|
|
||||||
|
"Source code" for a work means the preferred form of the work for
|
||||||
|
making modifications to it. For a library, complete source code means
|
||||||
|
all the source code for all modules it contains, plus any associated
|
||||||
|
interface definition files, plus the scripts used to control compilation
|
||||||
|
and installation of the library.
|
||||||
|
|
||||||
|
Activities other than copying, distribution and modification are not
|
||||||
|
covered by this License; they are outside its scope. The act of
|
||||||
|
running a program using the Library is not restricted, and output from
|
||||||
|
such a program is covered only if its contents constitute a work based
|
||||||
|
on the Library (independent of the use of the Library in a tool for
|
||||||
|
writing it). Whether that is true depends on what the Library does
|
||||||
|
and what the program that uses the Library does.
|
||||||
|
|
||||||
|
1. You may copy and distribute verbatim copies of the Library's
|
||||||
|
complete source code as you receive it, in any medium, provided that
|
||||||
|
you conspicuously and appropriately publish on each copy an
|
||||||
|
appropriate copyright notice and disclaimer of warranty; keep intact
|
||||||
|
all the notices that refer to this License and to the absence of any
|
||||||
|
warranty; and distribute a copy of this License along with the
|
||||||
|
Library.
|
||||||
|
|
||||||
|
You may charge a fee for the physical act of transferring a copy,
|
||||||
|
and you may at your option offer warranty protection in exchange for a
|
||||||
|
fee.
|
||||||
|
|
||||||
|
2. You may modify your copy or copies of the Library or any portion
|
||||||
|
of it, thus forming a work based on the Library, and copy and
|
||||||
|
distribute such modifications or work under the terms of Section 1
|
||||||
|
above, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The modified work must itself be a software library.
|
||||||
|
|
||||||
|
b) You must cause the files modified to carry prominent notices
|
||||||
|
stating that you changed the files and the date of any change.
|
||||||
|
|
||||||
|
c) You must cause the whole of the work to be licensed at no
|
||||||
|
charge to all third parties under the terms of this License.
|
||||||
|
|
||||||
|
d) If a facility in the modified Library refers to a function or a
|
||||||
|
table of data to be supplied by an application program that uses
|
||||||
|
the facility, other than as an argument passed when the facility
|
||||||
|
is invoked, then you must make a good faith effort to ensure that,
|
||||||
|
in the event an application does not supply such function or
|
||||||
|
table, the facility still operates, and performs whatever part of
|
||||||
|
its purpose remains meaningful.
|
||||||
|
|
||||||
|
(For example, a function in a library to compute square roots has
|
||||||
|
a purpose that is entirely well-defined independent of the
|
||||||
|
application. Therefore, Subsection 2d requires that any
|
||||||
|
application-supplied function or table used by this function must
|
||||||
|
be optional: if the application does not supply it, the square
|
||||||
|
root function must still compute square roots.)
|
||||||
|
|
||||||
|
These requirements apply to the modified work as a whole. If
|
||||||
|
identifiable sections of that work are not derived from the Library,
|
||||||
|
and can be reasonably considered independent and separate works in
|
||||||
|
themselves, then this License, and its terms, do not apply to those
|
||||||
|
sections when you distribute them as separate works. But when you
|
||||||
|
distribute the same sections as part of a whole which is a work based
|
||||||
|
on the Library, the distribution of the whole must be on the terms of
|
||||||
|
this License, whose permissions for other licensees extend to the
|
||||||
|
entire whole, and thus to each and every part regardless of who wrote
|
||||||
|
it.
|
||||||
|
|
||||||
|
Thus, it is not the intent of this section to claim rights or contest
|
||||||
|
your rights to work written entirely by you; rather, the intent is to
|
||||||
|
exercise the right to control the distribution of derivative or
|
||||||
|
collective works based on the Library.
|
||||||
|
|
||||||
|
In addition, mere aggregation of another work not based on the Library
|
||||||
|
with the Library (or with a work based on the Library) on a volume of
|
||||||
|
a storage or distribution medium does not bring the other work under
|
||||||
|
the scope of this License.
|
||||||
|
|
||||||
|
3. You may opt to apply the terms of the ordinary GNU General Public
|
||||||
|
License instead of this License to a given copy of the Library. To do
|
||||||
|
this, you must alter all the notices that refer to this License, so
|
||||||
|
that they refer to the ordinary GNU General Public License, version 2,
|
||||||
|
instead of to this License. (If a newer version than version 2 of the
|
||||||
|
ordinary GNU General Public License has appeared, then you can specify
|
||||||
|
that version instead if you wish.) Do not make any other change in
|
||||||
|
these notices.
|
||||||
|
|
||||||
|
Once this change is made in a given copy, it is irreversible for
|
||||||
|
that copy, so the ordinary GNU General Public License applies to all
|
||||||
|
subsequent copies and derivative works made from that copy.
|
||||||
|
|
||||||
|
This option is useful when you wish to copy part of the code of
|
||||||
|
the Library into a program that is not a library.
|
||||||
|
|
||||||
|
4. You may copy and distribute the Library (or a portion or
|
||||||
|
derivative of it, under Section 2) in object code or executable form
|
||||||
|
under the terms of Sections 1 and 2 above provided that you accompany
|
||||||
|
it with the complete corresponding machine-readable source code, which
|
||||||
|
must be distributed under the terms of Sections 1 and 2 above on a
|
||||||
|
medium customarily used for software interchange.
|
||||||
|
|
||||||
|
If distribution of object code is made by offering access to copy
|
||||||
|
from a designated place, then offering equivalent access to copy the
|
||||||
|
source code from the same place satisfies the requirement to
|
||||||
|
distribute the source code, even though third parties are not
|
||||||
|
compelled to copy the source along with the object code.
|
||||||
|
|
||||||
|
5. A program that contains no derivative of any portion of the
|
||||||
|
Library, but is designed to work with the Library by being compiled or
|
||||||
|
linked with it, is called a "work that uses the Library". Such a
|
||||||
|
work, in isolation, is not a derivative work of the Library, and
|
||||||
|
therefore falls outside the scope of this License.
|
||||||
|
|
||||||
|
However, linking a "work that uses the Library" with the Library
|
||||||
|
creates an executable that is a derivative of the Library (because it
|
||||||
|
contains portions of the Library), rather than a "work that uses the
|
||||||
|
library". The executable is therefore covered by this License.
|
||||||
|
Section 6 states terms for distribution of such executables.
|
||||||
|
|
||||||
|
When a "work that uses the Library" uses material from a header file
|
||||||
|
that is part of the Library, the object code for the work may be a
|
||||||
|
derivative work of the Library even though the source code is not.
|
||||||
|
Whether this is true is especially significant if the work can be
|
||||||
|
linked without the Library, or if the work is itself a library. The
|
||||||
|
threshold for this to be true is not precisely defined by law.
|
||||||
|
|
||||||
|
If such an object file uses only numerical parameters, data
|
||||||
|
structure layouts and accessors, and small macros and small inline
|
||||||
|
functions (ten lines or less in length), then the use of the object
|
||||||
|
file is unrestricted, regardless of whether it is legally a derivative
|
||||||
|
work. (Executables containing this object code plus portions of the
|
||||||
|
Library will still fall under Section 6.)
|
||||||
|
|
||||||
|
Otherwise, if the work is a derivative of the Library, you may
|
||||||
|
distribute the object code for the work under the terms of Section 6.
|
||||||
|
Any executables containing that work also fall under Section 6,
|
||||||
|
whether or not they are linked directly with the Library itself.
|
||||||
|
|
||||||
|
6. As an exception to the Sections above, you may also compile or
|
||||||
|
link a "work that uses the Library" with the Library to produce a
|
||||||
|
work containing portions of the Library, and distribute that work
|
||||||
|
under terms of your choice, provided that the terms permit
|
||||||
|
modification of the work for the customer's own use and reverse
|
||||||
|
engineering for debugging such modifications.
|
||||||
|
|
||||||
|
You must give prominent notice with each copy of the work that the
|
||||||
|
Library is used in it and that the Library and its use are covered by
|
||||||
|
this License. You must supply a copy of this License. If the work
|
||||||
|
during execution displays copyright notices, you must include the
|
||||||
|
copyright notice for the Library among them, as well as a reference
|
||||||
|
directing the user to the copy of this License. Also, you must do one
|
||||||
|
of these things:
|
||||||
|
|
||||||
|
a) Accompany the work with the complete corresponding
|
||||||
|
machine-readable source code for the Library including whatever
|
||||||
|
changes were used in the work (which must be distributed under
|
||||||
|
Sections 1 and 2 above); and, if the work is an executable linked
|
||||||
|
with the Library, with the complete machine-readable "work that
|
||||||
|
uses the Library", as object code and/or source code, so that the
|
||||||
|
user can modify the Library and then relink to produce a modified
|
||||||
|
executable containing the modified Library. (It is understood
|
||||||
|
that the user who changes the contents of definitions files in the
|
||||||
|
Library will not necessarily be able to recompile the application
|
||||||
|
to use the modified definitions.)
|
||||||
|
|
||||||
|
b) Accompany the work with a written offer, valid for at
|
||||||
|
least three years, to give the same user the materials
|
||||||
|
specified in Subsection 6a, above, for a charge no more
|
||||||
|
than the cost of performing this distribution.
|
||||||
|
|
||||||
|
c) If distribution of the work is made by offering access to copy
|
||||||
|
from a designated place, offer equivalent access to copy the above
|
||||||
|
specified materials from the same place.
|
||||||
|
|
||||||
|
d) Verify that the user has already received a copy of these
|
||||||
|
materials or that you have already sent this user a copy.
|
||||||
|
|
||||||
|
For an executable, the required form of the "work that uses the
|
||||||
|
Library" must include any data and utility programs needed for
|
||||||
|
reproducing the executable from it. However, as a special exception,
|
||||||
|
the source code distributed need not include anything that is normally
|
||||||
|
distributed (in either source or binary form) with the major
|
||||||
|
components (compiler, kernel, and so on) of the operating system on
|
||||||
|
which the executable runs, unless that component itself accompanies
|
||||||
|
the executable.
|
||||||
|
|
||||||
|
It may happen that this requirement contradicts the license
|
||||||
|
restrictions of other proprietary libraries that do not normally
|
||||||
|
accompany the operating system. Such a contradiction means you cannot
|
||||||
|
use both them and the Library together in an executable that you
|
||||||
|
distribute.
|
||||||
|
|
||||||
|
7. You may place library facilities that are a work based on the
|
||||||
|
Library side-by-side in a single library together with other library
|
||||||
|
facilities not covered by this License, and distribute such a combined
|
||||||
|
library, provided that the separate distribution of the work based on
|
||||||
|
the Library and of the other library facilities is otherwise
|
||||||
|
permitted, and provided that you do these two things:
|
||||||
|
|
||||||
|
a) Accompany the combined library with a copy of the same work
|
||||||
|
based on the Library, uncombined with any other library
|
||||||
|
facilities. This must be distributed under the terms of the
|
||||||
|
Sections above.
|
||||||
|
|
||||||
|
b) Give prominent notice with the combined library of the fact
|
||||||
|
that part of it is a work based on the Library, and explaining
|
||||||
|
where to find the accompanying uncombined form of the same work.
|
||||||
|
|
||||||
|
8. You may not copy, modify, sublicense, link with, or distribute
|
||||||
|
the Library except as expressly provided under this License. Any
|
||||||
|
attempt otherwise to copy, modify, sublicense, link with, or
|
||||||
|
distribute the Library is void, and will automatically terminate your
|
||||||
|
rights under this License. However, parties who have received copies,
|
||||||
|
or rights, from you under this License will not have their licenses
|
||||||
|
terminated so long as such parties remain in full compliance.
|
||||||
|
|
||||||
|
9. You are not required to accept this License, since you have not
|
||||||
|
signed it. However, nothing else grants you permission to modify or
|
||||||
|
distribute the Library or its derivative works. These actions are
|
||||||
|
prohibited by law if you do not accept this License. Therefore, by
|
||||||
|
modifying or distributing the Library (or any work based on the
|
||||||
|
Library), you indicate your acceptance of this License to do so, and
|
||||||
|
all its terms and conditions for copying, distributing or modifying
|
||||||
|
the Library or works based on it.
|
||||||
|
|
||||||
|
10. Each time you redistribute the Library (or any work based on the
|
||||||
|
Library), the recipient automatically receives a license from the
|
||||||
|
original licensor to copy, distribute, link with or modify the Library
|
||||||
|
subject to these terms and conditions. You may not impose any further
|
||||||
|
restrictions on the recipients' exercise of the rights granted herein.
|
||||||
|
You are not responsible for enforcing compliance by third parties to
|
||||||
|
this License.
|
||||||
|
|
||||||
|
11. If, as a consequence of a court judgment or allegation of patent
|
||||||
|
infringement or for any other reason (not limited to patent issues),
|
||||||
|
conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot
|
||||||
|
distribute so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you
|
||||||
|
may not distribute the Library at all. For example, if a patent
|
||||||
|
license would not permit royalty-free redistribution of the Library by
|
||||||
|
all those who receive copies directly or indirectly through you, then
|
||||||
|
the only way you could satisfy both it and this License would be to
|
||||||
|
refrain entirely from distribution of the Library.
|
||||||
|
|
||||||
|
If any portion of this section is held invalid or unenforceable under any
|
||||||
|
particular circumstance, the balance of the section is intended to apply,
|
||||||
|
and the section as a whole is intended to apply in other circumstances.
|
||||||
|
|
||||||
|
It is not the purpose of this section to induce you to infringe any
|
||||||
|
patents or other property right claims or to contest validity of any
|
||||||
|
such claims; this section has the sole purpose of protecting the
|
||||||
|
integrity of the free software distribution system which is
|
||||||
|
implemented by public license practices. Many people have made
|
||||||
|
generous contributions to the wide range of software distributed
|
||||||
|
through that system in reliance on consistent application of that
|
||||||
|
system; it is up to the author/donor to decide if he or she is willing
|
||||||
|
to distribute software through any other system and a licensee cannot
|
||||||
|
impose that choice.
|
||||||
|
|
||||||
|
This section is intended to make thoroughly clear what is believed to
|
||||||
|
be a consequence of the rest of this License.
|
||||||
|
|
||||||
|
12. If the distribution and/or use of the Library is restricted in
|
||||||
|
certain countries either by patents or by copyrighted interfaces, the
|
||||||
|
original copyright holder who places the Library under this License may add
|
||||||
|
an explicit geographical distribution limitation excluding those countries,
|
||||||
|
so that distribution is permitted only in or among countries not thus
|
||||||
|
excluded. In such case, this License incorporates the limitation as if
|
||||||
|
written in the body of this License.
|
||||||
|
|
||||||
|
13. The Free Software Foundation may publish revised and/or new
|
||||||
|
versions of the Library General Public License from time to time.
|
||||||
|
Such new versions will be similar in spirit to the present version,
|
||||||
|
but may differ in detail to address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the Library
|
||||||
|
specifies a version number of this License which applies to it and
|
||||||
|
"any later version", you have the option of following the terms and
|
||||||
|
conditions either of that version or of any later version published by
|
||||||
|
the Free Software Foundation. If the Library does not specify a
|
||||||
|
license version number, you may choose any version ever published by
|
||||||
|
the Free Software Foundation.
|
||||||
|
|
||||||
|
14. If you wish to incorporate parts of the Library into other free
|
||||||
|
programs whose distribution conditions are incompatible with these,
|
||||||
|
write to the author to ask for permission. For software which is
|
||||||
|
copyrighted by the Free Software Foundation, write to the Free
|
||||||
|
Software Foundation; we sometimes make exceptions for this. Our
|
||||||
|
decision will be guided by the two goals of preserving the free status
|
||||||
|
of all derivatives of our free software and of promoting the sharing
|
||||||
|
and reuse of software generally.
|
||||||
|
|
||||||
|
NO WARRANTY
|
||||||
|
|
||||||
|
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
||||||
|
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||||
|
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||||
|
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
|
||||||
|
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
|
||||||
|
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||||
|
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
|
||||||
|
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
||||||
|
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
|
||||||
|
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
|
||||||
|
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
|
||||||
|
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
|
||||||
|
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
||||||
|
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
||||||
|
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||||
|
DAMAGES.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
|
||||||
|
pffft Notice:
|
||||||
|
|
||||||
|
//$ nobt
|
||||||
|
|
||||||
|
/* Copyright (c) 2013 Julien Pommier ( pommier@modartt.com )
|
||||||
|
* Copyright (c) 2023 Christopher Robinson
|
||||||
|
*
|
||||||
|
* Based on original fortran 77 code from FFTPACKv4 from NETLIB
|
||||||
|
* (http://www.netlib.org/fftpack), authored by Dr Paul Swarztrauber
|
||||||
|
* of NCAR, in 1985.
|
||||||
|
*
|
||||||
|
* As confirmed by the NCAR fftpack software curators, the following
|
||||||
|
* FFTPACKv5 license applies to FFTPACKv4 sources. My changes are
|
||||||
|
* released under the same terms.
|
||||||
|
*
|
||||||
|
* FFTPACK license:
|
||||||
|
*
|
||||||
|
* http://www.cisl.ucar.edu/css/software/fftpack5/ftpk.html
|
||||||
|
*
|
||||||
|
* Copyright (c) 2004 the University Corporation for Atmospheric
|
||||||
|
* Research ("UCAR"). All rights reserved. Developed by NCAR's
|
||||||
|
* Computational and Information Systems Laboratory, UCAR,
|
||||||
|
* www.cisl.ucar.edu.
|
||||||
|
*
|
||||||
|
* Redistribution and use of the Software in source and binary forms,
|
||||||
|
* with or without modification, is permitted provided that the
|
||||||
|
* following conditions are met:
|
||||||
|
*
|
||||||
|
* - Neither the names of NCAR's Computational and Information Systems
|
||||||
|
* Laboratory, the University Corporation for Atmospheric Research,
|
||||||
|
* nor the names of its sponsors or contributors may be used to
|
||||||
|
* endorse or promote products derived from this Software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* - Redistributions of source code must retain the above copyright
|
||||||
|
* notices, this list of conditions, and the disclaimer below.
|
||||||
|
*
|
||||||
|
* - Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions, and the disclaimer below in the
|
||||||
|
* documentation and/or other materials provided with the
|
||||||
|
* distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT
|
||||||
|
* HOLDERS BE LIABLE FOR ANY CLAIM, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* PFFFT : a Pretty Fast FFT.
|
||||||
|
*
|
||||||
|
* This file is largerly based on the original FFTPACK implementation, modified
|
||||||
|
* in order to take advantage of SIMD instructions of modern CPUs.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Permission is granted to anyone to use this software for any purpose,
|
||||||
|
including commercial applications, and to alter it and redistribute it
|
||||||
|
freely, subject to the following restrictions:
|
||||||
|
|
||||||
|
1. The origin of this software must not be misrepresented; you must not
|
||||||
|
claim that you wrote the original software. If you use this software
|
||||||
|
in a product, an acknowledgment in the product documentation would be
|
||||||
|
appreciated but is not required.
|
||||||
|
2. Altered source versions must be plainly marked as such, and must not be
|
||||||
|
misrepresented as being the original software.
|
||||||
|
3. This notice may not be removed or altered from any source distribution.
|
||||||
|
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
COPYING:
|
||||||
|
|
||||||
|
Copyright (C) 2002-2009 Xiph.org Foundation
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions
|
||||||
|
are met:
|
||||||
|
|
||||||
|
- Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
- Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
- Neither the name of the Xiph.org Foundation nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION
|
||||||
|
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
|
||||||
|
LICENSE:
|
||||||
|
|
||||||
|
Please see the file COPYING for the copyright license for this software.
|
||||||
|
|
||||||
|
In addition to and irrespective of the copyright license associated
|
||||||
|
with this software, On2 Technologies, Inc. makes the following statement
|
||||||
|
regarding technology used in this software:
|
||||||
|
|
||||||
|
On2 represents and warrants that it shall not assert any rights
|
||||||
|
relating to infringement of On2's registered patents, nor initiate
|
||||||
|
any litigation asserting such rights, against any person who, or
|
||||||
|
entity which utilizes the On2 VP3 Codec Software, including any
|
||||||
|
use, distribution, and sale of said Software; which make changes,
|
||||||
|
modifications, and improvements in said Software; and to use,
|
||||||
|
distribute, and sell said changes as well as applications for other
|
||||||
|
fields of use.
|
||||||
|
|
||||||
|
This reference implementation is originally derived from the On2 VP3
|
||||||
|
Codec Software, and the Theora video format is essentially compatible
|
||||||
|
with the VP3 video format, consisting of a backward-compatible superset.
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
Copyright (c) 2002-2020 Xiph.org Foundation
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions
|
||||||
|
are met:
|
||||||
|
|
||||||
|
- Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
- Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
- Neither the name of the Xiph.org Foundation nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION
|
||||||
|
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
Copyright notice:
|
||||||
|
|
||||||
|
(C) 1995-2026 Jean-loup Gailly and Mark Adler
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Permission is granted to anyone to use this software for any purpose,
|
||||||
|
including commercial applications, and to alter it and redistribute it
|
||||||
|
freely, subject to the following restrictions:
|
||||||
|
|
||||||
|
1. The origin of this software must not be misrepresented; you must not
|
||||||
|
claim that you wrote the original software. If you use this software
|
||||||
|
in a product, an acknowledgment in the product documentation would be
|
||||||
|
appreciated but is not required.
|
||||||
|
2. Altered source versions must be plainly marked as such, and must not be
|
||||||
|
misrepresented as being the original software.
|
||||||
|
3. This notice may not be removed or altered from any source distribution.
|
||||||
|
|
||||||
|
Jean-loup Gailly Mark Adler
|
||||||
|
jloup@gzip.org madler@alumni.caltech.edu
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# LÖVE UWP binaries
|
||||||
|
|
||||||
|
These x64 UWP Release binaries were built from [`caorthann-celt/love-xbox-uwp`](https://github.com/caorthann-celt/love-xbox-uwp) at commit `62275ed10190afbec01f65754b3bcc04f244dd35`.
|
||||||
|
|
||||||
|
The build uses LÖVE 11.5, LuaJIT, SDL2, and ANGLE. Keep the DLLs and import libraries together; they are one binary interface.
|
||||||
|
|
||||||
|
The normal package build consumes these files directly. Rebuilding the backend is a separate dependency maintenance task.
|
||||||