Initial LegendOfZeldaALinkToThePastSNESRecomp scaffold
This commit is contained in:
commit
0cd0e5555c
24 files changed
+1253
No files matched your search
@@ -0,0 +1,375 @@
|
|||||||
|
# Build the Legend of Zelda: A Link to the Past SETUP PACK for Linux, Windows, macOS
|
||||||
|
# arm64 and macOS Intel, tag the commit, and publish the four zips as a
|
||||||
|
# GitHub Release.
|
||||||
|
#
|
||||||
|
# HUMAN-TRIGGERED ONLY. There is no push, pull_request or schedule trigger and
|
||||||
|
# there must never be one: a run is a release decision, made by a person on
|
||||||
|
# the Actions page. Releases across the port set are driven from the studio
|
||||||
|
# bulk tool, and a per-push run would compete with it for minutes and for the
|
||||||
|
# meaning of "did CI pass".
|
||||||
|
#
|
||||||
|
# WHAT A RUN DOES, with the defaults:
|
||||||
|
# 1. Picks the next version: the latest v* tag with its patch number bumped
|
||||||
|
# (or VERSION when there is no tag yet, or whatever `version` says).
|
||||||
|
# 2. Builds the setup host for all four platforms with that version as the
|
||||||
|
# lobby pin, and packages it.
|
||||||
|
# 3. Tags the commit the run was started from as vX.Y.Z and publishes a
|
||||||
|
# public GitHub Release with the four zips. Uncheck publish_release for a
|
||||||
|
# dry run that only uploads workflow artifacts.
|
||||||
|
#
|
||||||
|
# WHAT THIS BUILDS. Not the game. src/gen/*.c is recompiler output derived
|
||||||
|
# from ROM bytes: never committed, never in CI, never in a zip. What CI can
|
||||||
|
# honestly produce is the SETUP HOST -- the same executable built with
|
||||||
|
# -DSNESRECOMP_SETUP_HOST=ON, i.e. without recompiled code -- packaged with
|
||||||
|
# the recompiler and this source tree. On the player's machine the launcher
|
||||||
|
# takes their own ROM, generates, rebuilds, and relaunches into the real game.
|
||||||
|
#
|
||||||
|
# THE TOOLCHAIN. Each pack is built with the retcomm-toolchains cmake-clang-v1
|
||||||
|
# pack, because that is what the player's own rebuild uses: if it cannot build
|
||||||
|
# this game, CI is where that must fail. By default the pack is embedded in
|
||||||
|
# the zip (embed_toolchain) so first run downloads nothing; turn it off for a
|
||||||
|
# lean zip that fetches the pack on demand (Linux pack ~800 MB, Windows
|
||||||
|
# ~200 MB, macOS ~90 MB).
|
||||||
|
|
||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
publish_release:
|
||||||
|
description: 'Tag this commit and publish a public GitHub Release'
|
||||||
|
type: boolean
|
||||||
|
default: true
|
||||||
|
version:
|
||||||
|
description: 'Version to release, e.g. 0.2.0 (empty = bump the latest v* tag)'
|
||||||
|
type: string
|
||||||
|
default: ''
|
||||||
|
bump:
|
||||||
|
description: 'When version is empty, which part of the latest tag to bump'
|
||||||
|
type: choice
|
||||||
|
options: [patch, minor, major]
|
||||||
|
default: patch
|
||||||
|
embed_toolchain:
|
||||||
|
description: 'Embed the cmake-clang-v1 pack in each zip (else the launcher downloads it)'
|
||||||
|
type: boolean
|
||||||
|
default: true
|
||||||
|
toolchain_tag:
|
||||||
|
description: 'retcomm-toolchains release to build with (empty = latest)'
|
||||||
|
type: string
|
||||||
|
default: ''
|
||||||
|
verify_pins:
|
||||||
|
description: 'Fail if the submodules disagree with framework_pins.txt'
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
env:
|
||||||
|
EXE_NAME: LegendOfZeldaALinkToThePastSNESRecomp
|
||||||
|
ZIP_PREFIX: legendofzeldaalinktothepast
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
preflight:
|
||||||
|
name: Preflight
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
outputs:
|
||||||
|
version: ${{ steps.meta.outputs.version }}
|
||||||
|
tag: ${{ steps.meta.outputs.tag }}
|
||||||
|
publish: ${{ steps.meta.outputs.publish }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
fetch-depth: 0
|
||||||
|
fetch-tags: true
|
||||||
|
|
||||||
|
- name: Record framework pins
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
ARGS=()
|
||||||
|
if [[ "${{ inputs.verify_pins }}" == "true" ]]; then
|
||||||
|
ARGS+=(--check framework_pins.txt)
|
||||||
|
fi
|
||||||
|
bash snesrecomp/tools/ci/record_pins.sh "${ARGS[@]}"
|
||||||
|
|
||||||
|
- name: Resolve release version
|
||||||
|
id: meta
|
||||||
|
env:
|
||||||
|
IN_VERSION: ${{ inputs.version }}
|
||||||
|
IN_BUMP: ${{ inputs.bump || 'patch' }}
|
||||||
|
IN_PUBLISH: ${{ inputs.publish_release }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
PUBLISH=true
|
||||||
|
[[ "${IN_PUBLISH}" == "false" ]] && PUBLISH=false
|
||||||
|
FILE_VER="$(tr -d '[:space:]' < VERSION)"
|
||||||
|
RAW="$(printf '%s' "${IN_VERSION}" | tr -d '[:space:]')"
|
||||||
|
LATEST="$(git tag -l 'v[0-9]*.[0-9]*.[0-9]*' --sort=-v:refname | head -n1 || true)"
|
||||||
|
|
||||||
|
if [[ -n "${RAW}" ]]; then
|
||||||
|
VER="${RAW#v}"
|
||||||
|
echo "version given: ${VER}"
|
||||||
|
elif [[ "${GITHUB_REF}" == refs/tags/v* ]]; then
|
||||||
|
# Started from an existing tag: release exactly that.
|
||||||
|
VER="${GITHUB_REF_NAME#v}"
|
||||||
|
echo "running on tag ${GITHUB_REF_NAME}: releasing ${VER}"
|
||||||
|
elif [[ -z "${LATEST}" ]]; then
|
||||||
|
# First release: VERSION says what it is.
|
||||||
|
VER="${FILE_VER}"
|
||||||
|
echo "no v* tag yet: first release is VERSION = ${VER}"
|
||||||
|
else
|
||||||
|
BASE="${LATEST#v}"; BASE="${BASE%%[-+]*}"
|
||||||
|
IFS=. read -r MA MI PA <<<"${BASE}"
|
||||||
|
case "${IN_BUMP}" in
|
||||||
|
major) MA=$((MA + 1)); MI=0; PA=0 ;;
|
||||||
|
minor) MI=$((MI + 1)); PA=0 ;;
|
||||||
|
*) PA=$((PA + 1)) ;;
|
||||||
|
esac
|
||||||
|
VER="${MA}.${MI}.${PA}"
|
||||||
|
# A deliberate bump of VERSION in the tree outranks the automatic
|
||||||
|
# patch step, so a maintainer can move to 0.2.0 by editing the file.
|
||||||
|
if [[ "$(printf '%s\n%s\n' "${FILE_VER}" "${VER}" | sort -V | tail -n1)" == "${FILE_VER}" \
|
||||||
|
&& "${FILE_VER}" != "${BASE}" ]]; then
|
||||||
|
echo "VERSION (${FILE_VER}) is ahead of ${LATEST} bumped (${VER}); using VERSION"
|
||||||
|
VER="${FILE_VER}"
|
||||||
|
else
|
||||||
|
echo "auto-bump ${IN_BUMP}: ${LATEST} -> ${VER}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! "${VER}" =~ ^[0-9]+\.[0-9]+\.[0-9]+([.+-][A-Za-z0-9.+-]*)?$ ]]; then
|
||||||
|
echo "::error::'${VER}' is not a version (want X.Y.Z)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
TAG="v${VER}"
|
||||||
|
if [[ "${PUBLISH}" == "true" && "${GITHUB_REF}" != "refs/tags/${TAG}" ]] \
|
||||||
|
&& git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then
|
||||||
|
echo "::error::tag ${TAG} already exists at $(git rev-parse --short "${TAG}"). Pass a new version, or start the run from that tag to re-release it."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "version=${VER}" >>"$GITHUB_OUTPUT"
|
||||||
|
echo "tag=${TAG}" >>"$GITHUB_OUTPUT"
|
||||||
|
echo "publish=${PUBLISH}" >>"$GITHUB_OUTPUT"
|
||||||
|
echo "release ${TAG} from ${GITHUB_SHA::12}; publish=${PUBLISH}"
|
||||||
|
|
||||||
|
build:
|
||||||
|
name: ${{ matrix.artifact }}
|
||||||
|
needs: preflight
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: ubuntu-24.04
|
||||||
|
artifact: linux-x64
|
||||||
|
- os: windows-2022
|
||||||
|
artifact: windows-x64
|
||||||
|
- os: macos-15
|
||||||
|
artifact: macos-arm64
|
||||||
|
- os: macos-15-intel
|
||||||
|
artifact: macos-x64
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
# The lobby pin compiled into the binary and written into the pack's
|
||||||
|
# VERSION, so the tag, the binary and the player's own rebuild agree.
|
||||||
|
RELEASE_VERSION: ${{ needs.preflight.outputs.version }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
fetch-depth: 1
|
||||||
|
|
||||||
|
- name: Record framework pins
|
||||||
|
run: bash snesrecomp/tools/ci/record_pins.sh
|
||||||
|
|
||||||
|
# A setup host is defined by what it lacks. The checkout must carry no
|
||||||
|
# generated C; if it ever does, that is committed ROM-derived output and
|
||||||
|
# the build must stop here rather than package it.
|
||||||
|
- name: Assert no generated C in the tree
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
if compgen -G 'src/gen/*.c' >/dev/null || [[ -f recomp/funcs.h ]]; then
|
||||||
|
echo "::error::src/gen/*.c or recomp/funcs.h is present in the checkout."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "src/gen is empty — building the setup host."
|
||||||
|
|
||||||
|
- name: Cache toolchain pack
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: .cache/toolchain-pack
|
||||||
|
key: cmake-clang-v1-${{ matrix.artifact }}-${{ inputs.toolchain_tag || 'latest' }}
|
||||||
|
|
||||||
|
- name: Fetch toolchain pack
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
ARGS=(--artifact "${{ matrix.artifact }}")
|
||||||
|
if [[ -n "${{ inputs.toolchain_tag }}" ]]; then
|
||||||
|
ARGS+=(--tag "${{ inputs.toolchain_tag }}")
|
||||||
|
fi
|
||||||
|
bash snesrecomp/tools/ci/fetch_toolchain.sh "${ARGS[@]}"
|
||||||
|
|
||||||
|
# The macOS pack is cmake + ninja + python; SDL3 comes from
|
||||||
|
# runner.cmake's pinned source fallback. Prefetch the tarball with curl
|
||||||
|
# (HTTP/1.1) and cache it, so configure does not download.
|
||||||
|
- name: Cache SDL3 source (macOS)
|
||||||
|
if: runner.os == 'macOS'
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: .cache/sdl3-src
|
||||||
|
key: sdl3-src-3.4.10
|
||||||
|
|
||||||
|
- name: Prefetch SDL3 source (macOS)
|
||||||
|
if: runner.os == 'macOS'
|
||||||
|
run: bash snesrecomp/tools/ci/prefetch_sdl3.sh --out-dir .cache/sdl3-src
|
||||||
|
|
||||||
|
- name: Configure and build the setup host
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
# The pack's env.sh is its own statement of how to use it (PATH,
|
||||||
|
# CC/CXX, sysroot, SDL3_DIR). Source it rather than restate it.
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
source "${TOOLCHAIN_DIR}/env.sh"
|
||||||
|
echo "cmake: $(command -v cmake) ($(cmake --version | head -n1))"
|
||||||
|
echo "cc: ${CC:-$(command -v cc)}"
|
||||||
|
EXTRA=()
|
||||||
|
if [[ -n "${SNESRECOMP_SDL3_SOURCE_DIR:-}" ]]; then
|
||||||
|
EXTRA+=("-DSNESRECOMP_SDL3_SOURCE_DIR=${SNESRECOMP_SDL3_SOURCE_DIR}")
|
||||||
|
fi
|
||||||
|
if [[ "${{ runner.os }}" == "macOS" ]]; then
|
||||||
|
EXTRA+=("-DCMAKE_OSX_DEPLOYMENT_TARGET=11.0")
|
||||||
|
fi
|
||||||
|
JOBS="$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)"
|
||||||
|
cmake -S . -B build-ci -G Ninja \
|
||||||
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
|
-DSNESRECOMP_SETUP_HOST=ON \
|
||||||
|
-DSNESRECOMP_ENABLE_TRACE=OFF \
|
||||||
|
-DSNESRECOMP_BUILD_VERSION="${RELEASE_VERSION}" \
|
||||||
|
"${EXTRA[@]+"${EXTRA[@]}"}"
|
||||||
|
cmake --build build-ci -j"${JOBS}" --target "${EXE_NAME}"
|
||||||
|
|
||||||
|
- name: Package setup pack
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
# The packager inspects the executable's imports and zips the
|
||||||
|
# stage; Git Bash on Windows has neither tool, the pack does.
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
source "${TOOLCHAIN_DIR}/env.sh"
|
||||||
|
ARGS=()
|
||||||
|
if [[ "${{ inputs.embed_toolchain }}" != "false" ]]; then
|
||||||
|
ARGS+=(--embed-toolchain)
|
||||||
|
fi
|
||||||
|
bash scripts/package_release.sh build-ci "${{ matrix.artifact }}" "${ARGS[@]+"${ARGS[@]}"}"
|
||||||
|
ls -la dist/
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ${{ env.ZIP_PREFIX }}-${{ matrix.artifact }}
|
||||||
|
path: dist/${{ env.ZIP_PREFIX }}-*-${{ matrix.artifact }}.zip
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 7
|
||||||
|
compression-level: 0
|
||||||
|
|
||||||
|
release:
|
||||||
|
name: Tag and publish
|
||||||
|
needs: [preflight, build]
|
||||||
|
if: needs.preflight.outputs.publish == 'true'
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
fetch-depth: 0
|
||||||
|
fetch-tags: true
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
path: artifacts
|
||||||
|
|
||||||
|
- name: Collect zips
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
mkdir -p dist
|
||||||
|
find artifacts -type f -name "${ZIP_PREFIX}-*.zip" -exec cp -v {} dist/ \;
|
||||||
|
# Four platforms in, four zips out. A release that silently carries
|
||||||
|
# three is the failure this counts for.
|
||||||
|
count="$(find dist -name "${ZIP_PREFIX}-*.zip" | wc -l | tr -d ' ')"
|
||||||
|
if [[ "${count}" -ne 4 ]]; then
|
||||||
|
echo "::error::expected 4 platform zips, found ${count}"
|
||||||
|
ls -la dist
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
( cd dist && sha256sum "${ZIP_PREFIX}"-*.zip > SHA256SUMS )
|
||||||
|
cat dist/SHA256SUMS
|
||||||
|
|
||||||
|
# The tag is created here, after all four builds succeeded, so a failed
|
||||||
|
# build never leaves a version number claimed by nothing.
|
||||||
|
- name: Tag the commit
|
||||||
|
env:
|
||||||
|
TAG: ${{ needs.preflight.outputs.tag }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
git config user.name "github-actions[bot]"
|
||||||
|
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||||
|
if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then
|
||||||
|
echo "tag ${TAG} already exists at $(git rev-parse --short "${TAG}") (re-release from that tag)"
|
||||||
|
else
|
||||||
|
git tag -a "${TAG}" -m "Legend of Zelda: A Link to the Past ${TAG}" "${GITHUB_SHA}"
|
||||||
|
git push origin "refs/tags/${TAG}"
|
||||||
|
echo "tagged ${GITHUB_SHA::12} as ${TAG}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Release notes
|
||||||
|
env:
|
||||||
|
TAG: ${{ needs.preflight.outputs.tag }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
# From rom_identity.txt, not a copy baked into this workflow.
|
||||||
|
ROM_FILE="$(python3 snesrecomp/tools/rom_identity.py rom_identity.txt --get rom_file)"
|
||||||
|
ROM_SHA256="$(python3 snesrecomp/tools/rom_identity.py rom_identity.txt --get expected_sha256)"
|
||||||
|
{
|
||||||
|
echo "Legend of Zelda: A Link to the Past ${TAG} — setup packs"
|
||||||
|
echo
|
||||||
|
echo "These packs do not contain the game. Each holds a setup program, the"
|
||||||
|
echo "recompiler and the source; on first run it takes your own copy of"
|
||||||
|
echo "${ROM_FILE} (SHA-256 ${ROM_SHA256}), recompiles it locally, and builds"
|
||||||
|
echo "the playable game. No ROM data is included or downloaded."
|
||||||
|
echo
|
||||||
|
if [[ "${{ inputs.embed_toolchain }}" != "false" ]]; then
|
||||||
|
echo "The portable build tools (cmake-clang-v1) are embedded in every pack."
|
||||||
|
else
|
||||||
|
echo "The portable build tools (cmake-clang-v1) are downloaded on first run."
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
echo '### Framework revisions'
|
||||||
|
echo
|
||||||
|
echo '```'
|
||||||
|
bash snesrecomp/tools/ci/record_pins.sh
|
||||||
|
echo '```'
|
||||||
|
} > notes.md
|
||||||
|
cat notes.md
|
||||||
|
|
||||||
|
- name: Publish release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
tag_name: ${{ needs.preflight.outputs.tag }}
|
||||||
|
target_commitish: ${{ github.sha }}
|
||||||
|
name: "Legend of Zelda: A Link to the Past ${{ needs.preflight.outputs.tag }}"
|
||||||
|
body_path: notes.md
|
||||||
|
files: |
|
||||||
|
dist/${{ env.ZIP_PREFIX }}-*.zip
|
||||||
|
dist/SHA256SUMS
|
||||||
|
fail_on_unmatched_files: true
|
||||||
|
make_latest: true
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
# Generated recompiler output — derived from a copyrighted ROM. Never commit.
|
||||||
|
/src/gen/
|
||||||
|
/recomp/funcs.h
|
||||||
|
|
||||||
|
# The ROM itself, in every form. Players supply their own.
|
||||||
|
*.sfc
|
||||||
|
*.smc
|
||||||
|
*.srm
|
||||||
|
|
||||||
|
# Build trees and packaged output
|
||||||
|
/build/
|
||||||
|
/build-*/
|
||||||
|
/dist/
|
||||||
|
/saves/
|
||||||
|
|
||||||
|
# Local probe output that names paths on one machine
|
||||||
|
/rom_probe.json
|
||||||
|
|
||||||
|
# Launcher/runtime state written beside the executable (and in the repo root
|
||||||
|
# when the game is run from there): the cached ROM path, keybinds, settings.
|
||||||
|
# All of it is one machine's, none of it is the project's.
|
||||||
|
/rom.cfg
|
||||||
|
/keybinds.ini
|
||||||
|
/config.ini
|
||||||
|
/input.ini
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
[submodule "snesrecomp"]
|
||||||
|
path = snesrecomp
|
||||||
|
url = https://github.com/RetroPortingToolKit/snesrecomp.git
|
||||||
|
branch = main
|
||||||
|
[submodule "recomp-ui"]
|
||||||
|
path = recomp-ui
|
||||||
|
url = https://github.com/mstan/recomp-ui.git
|
||||||
|
branch = master
|
||||||
+136
@@ -0,0 +1,136 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.20)
|
||||||
|
project(LegendOfZeldaALinkToThePastSNESRecomp C CXX)
|
||||||
|
|
||||||
|
set(CMAKE_C_STANDARD 11)
|
||||||
|
|
||||||
|
# Release pin for lobby matching. Release builds stamp the VERSION file so two
|
||||||
|
# peers can prove they are the same build; everything else stamps "dev".
|
||||||
|
file(READ "${CMAKE_CURRENT_SOURCE_DIR}/VERSION" _GAME_VER_RAW)
|
||||||
|
string(STRIP "${_GAME_VER_RAW}" GAME_RELEASE_VERSION)
|
||||||
|
set(SNESRECOMP_BUILD_VERSION "" CACHE STRING
|
||||||
|
"Lobby release pin (empty = Release uses VERSION, else dev)")
|
||||||
|
if(SNESRECOMP_BUILD_VERSION STREQUAL "")
|
||||||
|
get_property(_is_multi GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
|
||||||
|
if(_is_multi)
|
||||||
|
set(SNESRECOMP_BUILD_VERSION "$<IF:$<CONFIG:Release>,${GAME_RELEASE_VERSION},dev>")
|
||||||
|
elseif(CMAKE_BUILD_TYPE STREQUAL "Release")
|
||||||
|
set(SNESRECOMP_BUILD_VERSION "${GAME_RELEASE_VERSION}")
|
||||||
|
else()
|
||||||
|
set(SNESRECOMP_BUILD_VERSION "dev")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(SNESRECOMP_ROOT "${CMAKE_SOURCE_DIR}/snesrecomp" CACHE PATH
|
||||||
|
"Path to the snesrecomp checkout used by this game")
|
||||||
|
if(NOT EXISTS "${SNESRECOMP_ROOT}/runner/runner.cmake")
|
||||||
|
message(FATAL_ERROR
|
||||||
|
"snesrecomp submodule missing at ${SNESRECOMP_ROOT}.\n"
|
||||||
|
"Run: git submodule update --init --recursive")
|
||||||
|
endif()
|
||||||
|
# Mod packages are part of every project, not an option. The launcher's Mods
|
||||||
|
# page, the .snesmod loader, the trusted-plugin registry and netplay's mod-set
|
||||||
|
# gate all hang off this one switch; a build without it cannot exchange mods
|
||||||
|
# with a peer that has them, and the framework says so at every lobby init.
|
||||||
|
# Must precede the include: runner.cmake reads it to add the loader sources.
|
||||||
|
set(SNESRECOMP_ENABLE_MODS ON CACHE BOOL
|
||||||
|
"Mod package loader + trusted plugins (required for every title)" FORCE)
|
||||||
|
include(${SNESRECOMP_ROOT}/runner/runner.cmake)
|
||||||
|
|
||||||
|
add_executable(LegendOfZeldaALinkToThePastSNESRecomp
|
||||||
|
${SNESRECOMP_RUNNER_SOURCES}
|
||||||
|
src/main.c
|
||||||
|
src/host_contract.c
|
||||||
|
src/game_rtl.c
|
||||||
|
src/gen_stubs.c
|
||||||
|
)
|
||||||
|
|
||||||
|
# ROM identity. rom_identity.txt is the one place the digests live: the build
|
||||||
|
# turns it into snesrecomp_rom_identity.h (SNESRECOMP_ROM_EXPECTED_SHA256 and
|
||||||
|
# friends) for src/main.c, and tools/regen.sh, scripts/package_release.sh and
|
||||||
|
# the release workflow read the same file. A revision bump is one edit there.
|
||||||
|
snesrecomp_rom_identity(LegendOfZeldaALinkToThePastSNESRecomp ${CMAKE_SOURCE_DIR}/rom_identity.txt)
|
||||||
|
|
||||||
|
# Generated recompiler output. Not committed: it is derived from a copyrighted
|
||||||
|
# ROM, so every developer regenerates it locally from their own copy -- and a
|
||||||
|
# release does not ship it either. With -DSNESRECOMP_SETUP_HOST=ON and an empty
|
||||||
|
# src/gen/, this builds the SETUP HOST instead: the same executable minus the
|
||||||
|
# recompiled code, whose only path is the launcher's Generate & rebuild wizard.
|
||||||
|
# That is what release zips contain; the player's machine produces the
|
||||||
|
# playable binary from their own ROM. Without the option an empty src/gen/ is
|
||||||
|
# still a hard error (the framework owns the message).
|
||||||
|
snesrecomp_target_generated_code(LegendOfZeldaALinkToThePastSNESRecomp ${CMAKE_SOURCE_DIR}/src/gen)
|
||||||
|
|
||||||
|
# The mod catalog. This names WHERE the packages are; the framework owns the
|
||||||
|
# layout they are staged into beside the executable, and stages them on every
|
||||||
|
# build OF THE EXECUTABLE -- including the launcher's own Generate & rebuild on
|
||||||
|
# a player's machine, which builds only this target and would skip a sibling
|
||||||
|
# ALL target. mods/preloaded/packages/ holds reviewed, default-disabled
|
||||||
|
# packages; players install .snesmod archives through the launcher.
|
||||||
|
# snesrecomp/docs/MOD_PACKAGES.md.
|
||||||
|
#
|
||||||
|
# It used to be `snesrecomp_target_stage_dir(... ${CMAKE_SOURCE_DIR}/mods mods)`
|
||||||
|
# -- the destination spelled per title, which is how three ports in the same
|
||||||
|
# workspace ended up shipping an empty Mods page and nothing said so. Declaring
|
||||||
|
# it is now also enforced: a project with packages that never calls this fails
|
||||||
|
# to configure.
|
||||||
|
snesrecomp_target_mod_catalog(LegendOfZeldaALinkToThePastSNESRecomp ${CMAKE_SOURCE_DIR}/mods/preloaded)
|
||||||
|
# Any other directory the game reads exe-relative (a translations/ table, ...)
|
||||||
|
# is staged with snesrecomp_target_stage_dir, never with a separate ALL target.
|
||||||
|
|
||||||
|
target_include_directories(LegendOfZeldaALinkToThePastSNESRecomp PRIVATE
|
||||||
|
${SNESRECOMP_RUNNER_INCLUDE_DIRS}
|
||||||
|
# common_rtl.c includes "config.h": the desktop key-map / Config contract
|
||||||
|
# the framework host supplies (snesrecomp_target_desktop_host below).
|
||||||
|
${SNESRECOMP_ROOT}/runner/src/desktop
|
||||||
|
${CMAKE_SOURCE_DIR}/src
|
||||||
|
${CMAKE_SOURCE_DIR}/recomp
|
||||||
|
)
|
||||||
|
target_link_libraries(LegendOfZeldaALinkToThePastSNESRecomp PRIVATE ${SNESRECOMP_RUNNER_LIBRARIES})
|
||||||
|
target_compile_definitions(LegendOfZeldaALinkToThePastSNESRecomp PRIVATE
|
||||||
|
SNES_GAME_VERSION="${SNESRECOMP_BUILD_VERSION}"
|
||||||
|
SNES_GAME_TITLE="Legend of Zelda: A Link to the Past"
|
||||||
|
)
|
||||||
|
snesrecomp_target_sdl(LegendOfZeldaALinkToThePastSNESRecomp)
|
||||||
|
|
||||||
|
# The desktop host: launcher flow, ROM resolution, config.ini + [KeyMap],
|
||||||
|
# window + SDL/OpenGL presenters, audio, gamepads, the save-state browser and
|
||||||
|
# rewind filmstrip, the pacing clock, crash handlers and the post-mortem
|
||||||
|
# report. src/main.c is the shim that names this title and supplies its
|
||||||
|
# hooks; the host is the framework's and improves on a submodule pull.
|
||||||
|
# Add TIER2 once this port reads the interpreter-gap manifest after each run.
|
||||||
|
snesrecomp_target_desktop_host(LegendOfZeldaALinkToThePastSNESRecomp)
|
||||||
|
# rom.cfg (the ROM the wizard set this project up from; git-ignored) is
|
||||||
|
# staged beside the executable when none is there yet, so the first launch
|
||||||
|
# opens on that dump rather than asking for one.
|
||||||
|
snesrecomp_target_rom_cache(LegendOfZeldaALinkToThePastSNESRecomp)
|
||||||
|
|
||||||
|
# Shared Dear ImGui pre-boot launcher (ROM picker, verification, display /
|
||||||
|
# audio / input settings). Self-contained: it brings its own ImGui, GL link and
|
||||||
|
# staged assets, and defines RECOMP_LAUNCHER so src/main.c compiles its GUI path.
|
||||||
|
set(RECOMP_UI_ROOT "${CMAKE_SOURCE_DIR}/recomp-ui" CACHE PATH
|
||||||
|
"Root directory of the recomp-ui launcher repo" FORCE)
|
||||||
|
if(NOT EXISTS "${RECOMP_UI_ROOT}/recomp_ui.cmake")
|
||||||
|
message(FATAL_ERROR
|
||||||
|
"recomp-ui missing at ${RECOMP_UI_ROOT}.\n"
|
||||||
|
"Run: git submodule update --init --recursive recomp-ui")
|
||||||
|
endif()
|
||||||
|
include(${RECOMP_UI_ROOT}/recomp_ui.cmake)
|
||||||
|
# BOXART stages launcher_assets/img/boxart.tga beside the exe; the launcher
|
||||||
|
# shows assets/img/boxart.tga by default. EXISTS-guarded in recomp_ui.cmake,
|
||||||
|
# so this configures cleanly before any art is fetched.
|
||||||
|
recomp_target_launcher_ui(LegendOfZeldaALinkToThePastSNESRecomp CONSOLE snes
|
||||||
|
BOXART "${CMAKE_SOURCE_DIR}/launcher_assets/img/boxart.tga")
|
||||||
|
|
||||||
|
# Generate & rebuild from inside the launcher: pick ROM -> recompile locally
|
||||||
|
# -> cmake --build -> relaunch. The implementation is the framework's; this
|
||||||
|
# project contributes no configuration (snesrecomp_codegen_host_autowire in
|
||||||
|
# src/main.c). recomp-ui/src is on the include path because the host speaks
|
||||||
|
# RecompLauncherCGameInfo.
|
||||||
|
target_sources(LegendOfZeldaALinkToThePastSNESRecomp PRIVATE
|
||||||
|
${SNESRECOMP_ROOT}/host/snesrecomp_codegen_host.c)
|
||||||
|
target_include_directories(LegendOfZeldaALinkToThePastSNESRecomp PRIVATE
|
||||||
|
${SNESRECOMP_ROOT}/host
|
||||||
|
${RECOMP_UI_ROOT}/src)
|
||||||
|
|
||||||
|
# Netplay is not built into this target.
|
||||||
|
# No multitap: two seats, one controller per port.
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
PolyForm Noncommercial License 1.0.0
|
||||||
|
|
||||||
|
<https://polyformproject.org/licenses/noncommercial/1.0.0>
|
||||||
|
|
||||||
|
Copyright (c) 2026 Matthew Stanley
|
||||||
|
|
||||||
|
## Acceptance
|
||||||
|
|
||||||
|
In order to get any license under these terms, you must agree to them as
|
||||||
|
both strict obligations and conditions to all your licenses.
|
||||||
|
|
||||||
|
## Copyright License
|
||||||
|
|
||||||
|
The licensor grants you a copyright license for the software to do
|
||||||
|
everything you might otherwise need the licensor's permission to do. Your
|
||||||
|
license covers distributing the software, making changes and new works
|
||||||
|
based on it, and all other uses of the software, subject to the
|
||||||
|
limitations and conditions in these terms.
|
||||||
|
|
||||||
|
## Noncommercial Purposes
|
||||||
|
|
||||||
|
Any noncommercial purpose is a permitted purpose.
|
||||||
|
|
||||||
|
## Personal Uses
|
||||||
|
|
||||||
|
Personal use for research, experiment, and testing for the benefit of
|
||||||
|
public knowledge, personal study, private entertainment, hobby projects,
|
||||||
|
amateur pursuits, or religious observance, without any anticipated
|
||||||
|
commercial application, counts as use for a permitted purpose.
|
||||||
|
|
||||||
|
## Noncommercial Organizations
|
||||||
|
|
||||||
|
Use by any charitable organization, educational institution, public
|
||||||
|
research organization, public safety or health organization, environmental
|
||||||
|
protection organization, or government institution counts as use for a
|
||||||
|
permitted purpose regardless of the source of funding or obligations
|
||||||
|
resulting from the funding.
|
||||||
|
|
||||||
|
## Fair Use
|
||||||
|
|
||||||
|
You may have "fair use" rights for the software under the law. These
|
||||||
|
terms do not limit them.
|
||||||
|
|
||||||
|
## No Other Rights
|
||||||
|
|
||||||
|
These terms do not allow you to sublicense or transfer any of your
|
||||||
|
licenses to anyone else, or prevent the licensor from granting licenses to
|
||||||
|
anyone else. These terms do not imply any other licenses.
|
||||||
|
|
||||||
|
## Patent Defense
|
||||||
|
|
||||||
|
If you make any written claim that the software infringes or contributes
|
||||||
|
to infringement of any patent, your patent license for the software granted
|
||||||
|
under these terms ends immediately. If your company makes such a claim,
|
||||||
|
your patent license ends immediately for work on behalf of your company.
|
||||||
|
|
||||||
|
## Violations
|
||||||
|
|
||||||
|
The first time you are notified in writing that you have violated any of
|
||||||
|
these terms, or done anything with the software not covered by your
|
||||||
|
licenses, your licenses can nonetheless continue if you come into full
|
||||||
|
compliance with these terms, and take practical steps to correct past
|
||||||
|
violations, within 32 days of receiving notice. Otherwise, all your
|
||||||
|
licenses end immediately.
|
||||||
|
|
||||||
|
## No Liability
|
||||||
|
|
||||||
|
As far as the law allows, the software comes as is, without any warranty
|
||||||
|
or condition, and the licensor will not be liable to you for any damages
|
||||||
|
arising out of these terms or the use or nature of the software, under any
|
||||||
|
kind of legal claim.
|
||||||
|
|
||||||
|
## Definitions
|
||||||
|
|
||||||
|
The **licensor** is the individual or entity offering these terms, and the
|
||||||
|
**software** is the software the licensor makes available under these
|
||||||
|
terms.
|
||||||
|
|
||||||
|
**You** refers to the individual or entity agreeing to these terms.
|
||||||
|
|
||||||
|
**Your company** is any legal entity, sole proprietorship, or other kind
|
||||||
|
of organization that you work for, plus all organizations that have control
|
||||||
|
over, are under the control of, or are under common control with that
|
||||||
|
organization. **Control** means ownership of substantially all the assets
|
||||||
|
of an entity, or the power to direct its management and policies by vote,
|
||||||
|
contract, or otherwise. Control can be direct or indirect.
|
||||||
|
|
||||||
|
**Your licenses** are all the licenses granted to you for the software
|
||||||
|
under these terms.
|
||||||
|
|
||||||
|
**Use** means anything you do with the software requiring one of your
|
||||||
|
licenses.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
For the avoidance of doubt, the licensor's intent is to restrict uses where
|
||||||
|
profit is derived from this software. Non-profit personal, educational,
|
||||||
|
or community use is welcome regardless of organizational context.
|
||||||
|
|
||||||
|
For commercial licensing inquiries, contact the licensor, Matthew Stanley,
|
||||||
|
at <https://1379.tech>.
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
# Legend of Zelda: A Link to the Past Recomp
|
||||||
|
|
||||||
|
A native recompilation of **Legend of Zelda: A Link to the Past** (USA), built on
|
||||||
|
[snesrecomp](https://github.com/RetroPortingToolKit/snesrecomp).
|
||||||
|
|
||||||
|
_Add a short description here._
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
> **You must legally own a copy of the game.** No ROM data is distributed with
|
||||||
|
> this project, in the repository or in any release. The recompiled C is
|
||||||
|
> generated locally from your own copy and is never committed.
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Scaffolded on 2026-10-30 — **not yet a working port.** The layout, build,
|
||||||
|
regeneration pipeline, CI, and packaging are wired up; the game does not run
|
||||||
|
until the host work in `src/game_rtl.c` is done. See
|
||||||
|
[Porting from here](#porting-from-here).
|
||||||
|
|
||||||
|
## ROM identity
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| File | `Legend of Zelda, The - A Link to the Past (USA).sfc` |
|
||||||
|
| Publisher | — |
|
||||||
|
| Developer | — |
|
||||||
|
| Year | — |
|
||||||
|
| Mapping | lorom |
|
||||||
|
| Region | USA (North America) |
|
||||||
|
| Coprocessor | none |
|
||||||
|
| CRC32 | `777aac2f` |
|
||||||
|
| SHA-256 | `66871d66be19ad2c34c927d6b14cd8eb6fc3181965b6e517cb361f7316009cfb` |
|
||||||
|
|
||||||
|
`tools/regen.sh` refuses to run against anything else, so a mismatched
|
||||||
|
revision fails immediately instead of producing subtly wrong output.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git submodule update --init --recursive
|
||||||
|
bash tools/regen.sh --rom /path/to/Legend of Zelda, The - A Link to the Past (USA).sfc
|
||||||
|
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
|
||||||
|
cmake --build build -j
|
||||||
|
```
|
||||||
|
|
||||||
|
The ROM does not have to live in the repository — keeping it on your own
|
||||||
|
drive is the better habit, and `SNESRECOMP_ROM` sets the path once for a
|
||||||
|
shell. Without either, `tools/regen.sh` finds `Legend of Zelda, The - A Link to the Past (USA).sfc` at the repo root,
|
||||||
|
then the path the setup wizard recorded in `rom.cfg`; `.gitignore` blocks
|
||||||
|
both from ever being committed.
|
||||||
|
|
||||||
|
`tools/regen.sh` verifies the ROM, generates `src/gen/*.c`, and re-syncs
|
||||||
|
`recomp/funcs.h`. Re-run it whenever you change anything under `recomp/`.
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./build/LegendOfZeldaALinkToThePastSNESRecomp # launcher picks the ROM
|
||||||
|
./build/LegendOfZeldaALinkToThePastSNESRecomp /path/to/Legend of Zelda, The - A Link to the Past (USA).sfc # or name it and skip the launcher
|
||||||
|
```
|
||||||
|
|
||||||
|
With no ROM on the command line the build opens the recomp-ui launcher: a ROM
|
||||||
|
picker with a verification badge, plus display / audio / input settings. Your
|
||||||
|
choice is cached in `rom.cfg` beside the executable, so the next launch opens
|
||||||
|
on it and "Skip launcher on boot" makes it immediate. If the launcher is not
|
||||||
|
built in (`--no-recomp-ui`), the host falls back to a native file picker.
|
||||||
|
|
||||||
|
Either way the ROM is checked against the digests above before anything boots
|
||||||
|
— a dump that could not have produced this build is refused at the door rather
|
||||||
|
than mis-executing ten frames in.
|
||||||
|
|
||||||
|
## Layout
|
||||||
|
|
||||||
|
| Path | What lives there |
|
||||||
|
|---|---|
|
||||||
|
| `recomp/` | Analysis input: `bank*.cfg`, `symbols.toml`, generated `funcs.h` |
|
||||||
|
| `rom_identity.txt` | ROM digests — read by the build, `tools/regen.sh` and CI |
|
||||||
|
| `src/` | Host code you own: `main.c`, `game_rtl.c` |
|
||||||
|
| `src/gen/` | Generated C. Never committed — regenerate locally |
|
||||||
|
| `snesrecomp/` | Framework submodule (owns `lib/recomp-net`, `lib/retcomm-rbengine`) |
|
||||||
|
| `tools/` | `regen.sh` — the ROM → C pipeline |
|
||||||
|
| `scripts/` | `package_release.sh` — player-facing zip |
|
||||||
|
| `framework_pins.txt` | Exact framework commits this project was scaffolded against |
|
||||||
|
|
||||||
|
## Porting from here
|
||||||
|
|
||||||
|
The scaffold stops where the game-specific work starts. In rough order:
|
||||||
|
|
||||||
|
1. **Make it boot.** `src/game_rtl.c` holds the frame driver. It starts on
|
||||||
|
the framework's beam-aligned driver (`snesrecomp/runner/src/beam_frame_driver.h`):
|
||||||
|
one frame per PPU field, interrupts taken where the beam latches them,
|
||||||
|
the field rasterized from the raster journal. A title whose main loop it
|
||||||
|
does not fit replaces those calls with its own driver.
|
||||||
|
2. **Name things.** Add entries to `recomp/symbols.toml` as you identify
|
||||||
|
routines, then re-run `tools/regen.sh`. Set `emit = true` to promote one
|
||||||
|
into ahead-of-time analysis; leave it false to keep it interpreted.
|
||||||
|
Regen synchronizes the marked blocks in each bank cfg, creating missing
|
||||||
|
configs. Only proven variants become AOT code. See `recomp/README.md`.
|
||||||
|
3. **Resolve dispatch misses.** After every run, deal with unresolved
|
||||||
|
indirect targets before anything else — they are the reason a port
|
||||||
|
diverges, and they are cheap to fix early.
|
||||||
|
4. **Never synthesise a result** to get past uncovered code, and never edit
|
||||||
|
`src/gen/` by hand. Fix the config or the framework and regenerate.
|
||||||
|
|
||||||
|
## Multiplayer
|
||||||
|
|
||||||
|
Single player.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project's own source is under the license in `LICENSE`. The framework
|
||||||
|
carries its own terms — see `snesrecomp/LICENSE` and
|
||||||
|
`snesrecomp/THIRD_PARTY_ATTRIBUTION.md`. Neither covers the game data, which
|
||||||
|
is not distributed here.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
snesrecomp=ef0b3ff7d6965ffcd96d8fd917cb182539b05d76
|
||||||
|
recomp-ui=3f1d6f122a58823614abce14f5b86f4d165a629c
|
||||||
|
recomp-net=8c76fb643339144a3a84d5ba8f5defe6250dcd26
|
||||||
|
retcomm-rbengine=2a03e73693acee0fb78076ea058642c931bef12e
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# Preloaded mods
|
||||||
|
|
||||||
|
Ship reviewed, default-disabled packages here:
|
||||||
|
|
||||||
|
```text
|
||||||
|
packages/<package-id>/<version>/
|
||||||
|
manifest.toml
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
A manifest's `[[target]]` names this title as `game_id = "legendofzeldaalinktothepast-usa"` with the
|
||||||
|
ROM's SHA-256 (both live in `rom_identity.txt`). CMakeLists.txt declares this
|
||||||
|
directory with `snesrecomp_target_mod_catalog` and the FRAMEWORK stages
|
||||||
|
`packages/` beside the executable on every build -- do not add a copy step of
|
||||||
|
your own, and do not spell the destination: it belongs to snesrecomp, so a
|
||||||
|
future change to the layout touches one file instead of every port. Nothing
|
||||||
|
placed beside the executable by hand survives a build.
|
||||||
|
Players install `.snesmod` archives through the launcher's Mods page, which
|
||||||
|
the runtime keeps under its own state beside the executable.
|
||||||
|
|
||||||
|
See `snesrecomp/docs/MOD_PACKAGES.md` for the manifest format and the
|
||||||
|
trusted-plugin registration a package can activate.
|
||||||
Whitespace-only changes.
Submodule
+1
Submodule recomp-ui added at 3f1d6f122a.
@@ -0,0 +1,35 @@
|
|||||||
|
# recomp/
|
||||||
|
|
||||||
|
Analysis input for Legend of Zelda: A Link to the Past. `tools/regen.sh` reads this directory and
|
||||||
|
writes `src/gen/`.
|
||||||
|
|
||||||
|
| File | Owner | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| `bank*.cfg` | you, plus generated blocks | Per-bank analysis directives |
|
||||||
|
| `symbols.toml` | you | Progressive name map; the source of truth |
|
||||||
|
| `funcs.h` | generated | Re-synced by `tools/regen.sh` — do not hand-edit |
|
||||||
|
|
||||||
|
The block between `>>> BEGIN symbols.toml` and `<<< END symbols.toml` in
|
||||||
|
each bank cfg is rewritten from `symbols.toml` on every regen. Edit
|
||||||
|
`symbols.toml`, not the block.
|
||||||
|
|
||||||
|
Python 3.11+ includes the TOML reader. On older Python, install it with
|
||||||
|
`python -m pip install tomli`. Projects without `symbols.toml` do not need it.
|
||||||
|
|
||||||
|
Each `[[func]]` has a C identifier `name`, a 16-bit hexadecimal `addr`
|
||||||
|
(for example, `"8000"`), and an integer `bank` (for example, `0xc0`).
|
||||||
|
Missing bank configs are created automatically; hand-written directives outside
|
||||||
|
the generated blocks are preserved. Removing an entry also removes its generated
|
||||||
|
directives on the next regen.
|
||||||
|
|
||||||
|
- `emit = false` (the default) writes a `symbol` label and `force_lle` boundary:
|
||||||
|
it stays interpreted, with no generated `func` declaration in `funcs.h`.
|
||||||
|
- `emit = true` writes a `func` declaration and requests analysis from that
|
||||||
|
entry, even without `--cfg-roots`. Only variants the analyzer proves safe
|
||||||
|
become AOT code; others still use the interpreter. Optional `entry_m` and
|
||||||
|
`entry_x` select the initial register-width flags (0 or 1, both default to 1).
|
||||||
|
|
||||||
|
An unchanged cfg file is not by itself evidence of failed code generation:
|
||||||
|
`src/gen/bank*_v2.c` contains code discovered from the ROM, while `bank*.cfg`
|
||||||
|
contains analysis inputs. Discovery does not copy every reached function back
|
||||||
|
into the configs.
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Legend of Zelda: A Link to the Past (USA) — LLE-first seed
|
||||||
|
# Regenerate with tools/regen.sh. Promote functions by editing recomp/symbols.toml
|
||||||
|
# and re-running the regen script, which rewrites the block below.
|
||||||
|
bank = 0
|
||||||
|
auto_vectors
|
||||||
|
tier_down_stubs
|
||||||
|
|
||||||
|
# Reset vector $8000, native NMI $80c9 (from the cartridge header).
|
||||||
|
|
||||||
|
# >>> BEGIN symbols.toml (generated — do not edit)
|
||||||
|
# <<< END symbols.toml
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# Progressive symbol map for Legend of Zelda: A Link to the Past.
|
||||||
|
#
|
||||||
|
# Each [[func]] names an address the analyzer should treat as a function.
|
||||||
|
# Set emit = true to promote one into ahead-of-time codegen; leave it false
|
||||||
|
# to keep it interpreted. tools/regen.sh syncs labels and tier choices into
|
||||||
|
# each recomp/bankNN.cfg; only emit=true adds a func/funcs.h declaration.
|
||||||
|
# Do not hand-edit the generated blocks in the bank configs.
|
||||||
|
|
||||||
|
[[func]]
|
||||||
|
name = "I_RESET"
|
||||||
|
addr = "8000"
|
||||||
|
bank = 0
|
||||||
|
emit = false
|
||||||
|
note = "Emulation RESET vector ($FFFC)"
|
||||||
|
|
||||||
|
[[func]]
|
||||||
|
name = "I_NMI"
|
||||||
|
addr = "80c9"
|
||||||
|
bank = 0
|
||||||
|
emit = false
|
||||||
|
note = "Native NMI vector ($FFEA)"
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# ROM identity for Legend of Zelda: A Link to the Past — the single source of truth.
|
||||||
|
#
|
||||||
|
# Read by three consumers, so a revision change is made once, here:
|
||||||
|
# - the build (CMake generates snesrecomp_rom_identity.h from this file)
|
||||||
|
# - tools/regen.sh
|
||||||
|
# - .github/workflows/release.yml
|
||||||
|
#
|
||||||
|
# Format is `key = value`, one per line; `#` starts a comment. Values may be
|
||||||
|
# quoted. Leaving a digest empty means the build cannot verify a ROM it is
|
||||||
|
# handed — it will say so rather than accept it.
|
||||||
|
|
||||||
|
display_name = Legend of Zelda: A Link to the Past
|
||||||
|
rom_file = Legend of Zelda, The - A Link to the Past (USA).sfc
|
||||||
|
# Every digest of the bare image (copier header stripped), so the Retro
|
||||||
|
# catalog can take a submission's identity from this file rather than ask
|
||||||
|
# the submitter to hash the ROM again. The build verifies on crc32/sha256.
|
||||||
|
expected_crc32 = 777aac2f
|
||||||
|
expected_md5 = 608c22b8ff930c62dc2de54bcd6eba72
|
||||||
|
expected_sha1 = 6d4f10a8b10e10dbe624cb23cf03b88bb8252973
|
||||||
|
expected_sha256 = 66871d66be19ad2c34c927d6b14cd8eb6fc3181965b6e517cb361f7316009cfb
|
||||||
|
rom_size = 1048576
|
||||||
|
mapping = lorom
|
||||||
|
region = USA
|
||||||
|
# What a mod package's [[target]] game_id must name to apply to this title
|
||||||
|
# (snesrecomp/docs/MOD_PACKAGES.md). Stable across revisions on purpose.
|
||||||
|
game_id = legendofzeldaalinktothepast-usa
|
||||||
Executable
+71
@@ -0,0 +1,71 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Package a Legend of Zelda: A Link to the Past release: a SETUP PACK.
|
||||||
|
#
|
||||||
|
# The zip does not contain the game. It contains a setup program (the host
|
||||||
|
# built with -DSNESRECOMP_SETUP_HOST=ON, i.e. without recompiled code), the
|
||||||
|
# recompiler and this source tree. On first run the player picks their own
|
||||||
|
# ROM and the launcher recompiles and rebuilds locally. No ROM, no src/gen,
|
||||||
|
# no recomp/funcs.h ever leaves this tree in a zip -- the framework's packager
|
||||||
|
# enforces that mechanically.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# scripts/package_release.sh [<build-dir>] [<platform-tag>] [--embed-toolchain]
|
||||||
|
#
|
||||||
|
# Both positionals are optional; BUILD_DIR and PLATFORM still work as env vars.
|
||||||
|
# Defaults: build, and the host's own <os>-<arch>. --embed-toolchain copies
|
||||||
|
# the cmake-clang-v1 pack named by RETCOMM_TOOLCHAIN_DIR into the zip so the
|
||||||
|
# player downloads nothing.
|
||||||
|
#
|
||||||
|
# Writes: dist/legendofzeldaalinktothepast-<VERSION>-<platform-tag>.zip
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# A silent exit is the worst outcome a packaging step can have: the first
|
||||||
|
# Windows CI run died here with exit 1 and not one line of output. Name the
|
||||||
|
# command and line instead of leaving the reader to bisect a shell script.
|
||||||
|
trap 'echo "::error::$(basename "${BASH_SOURCE[0]}") failed at line ${LINENO}: ${BASH_COMMAND}" >&2' ERR
|
||||||
|
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
cd "$ROOT"
|
||||||
|
|
||||||
|
EXTRA=()
|
||||||
|
POS=()
|
||||||
|
for a in "$@"; do
|
||||||
|
case "$a" in
|
||||||
|
--embed-toolchain|--no-embed-toolchain) EXTRA+=("$a") ;;
|
||||||
|
*) POS+=("$a") ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
BUILD_DIR="${POS[0]:-${BUILD_DIR:-build}}"
|
||||||
|
PLATFORM="${POS[1]:-${PLATFORM:-$(uname -s | tr '[:upper:]' '[:lower:]')-$(uname -m)}}"
|
||||||
|
|
||||||
|
IMPL="snesrecomp/tools/ci/stage_setup_host.sh"
|
||||||
|
if [ ! -f "$IMPL" ]; then
|
||||||
|
echo "package_release.sh: $IMPL missing — the snesrecomp pin predates setup packs." >&2
|
||||||
|
echo " Update the submodule: git submodule update --init --recursive" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The ROM hint and digests come from rom_identity.txt -- the same file the
|
||||||
|
# build compiles in and tools/regen.sh verifies against -- so a revision bump
|
||||||
|
# is one edit. stage_setup_host.sh already needs python3, so this does too.
|
||||||
|
IDENTITY_TOOL="snesrecomp/tools/rom_identity.py"
|
||||||
|
ROM_FILE="$(python3 "$IDENTITY_TOOL" rom_identity.txt --get rom_file)"
|
||||||
|
ROM_SHA256="$(python3 "$IDENTITY_TOOL" rom_identity.txt --get expected_sha256)"
|
||||||
|
ROM_CRC32="$(python3 "$IDENTITY_TOOL" rom_identity.txt --get expected_crc32)"
|
||||||
|
|
||||||
|
# mods/ is the catalog the build stages beside the executable, and the package
|
||||||
|
# a netplay host transfers to a guest. Add --runtime-dir <name> for any other
|
||||||
|
# directory the game loads exe-relative (translations, ...).
|
||||||
|
exec bash "$IMPL" \
|
||||||
|
--runtime-dir mods \
|
||||||
|
--build-dir "$BUILD_DIR" \
|
||||||
|
--artifact "$PLATFORM" \
|
||||||
|
--exe-name LegendOfZeldaALinkToThePastSNESRecomp \
|
||||||
|
--zip-prefix legendofzeldaalinktothepast \
|
||||||
|
--display-name "Legend of Zelda: A Link to the Past" \
|
||||||
|
--rom-hint "$ROM_FILE" \
|
||||||
|
--rom-sha256 "$ROM_SHA256" \
|
||||||
|
--rom-crc32 "$ROM_CRC32" \
|
||||||
|
"${EXTRA[@]+"${EXTRA[@]}"}"
|
||||||
Submodule
+1
Submodule snesrecomp added at ef0b3ff7d6.
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* Legend of Zelda: A Link to the Past — per-game runtime glue.
|
||||||
|
*
|
||||||
|
* THIS FILE IS THE PORT. Everything else the scaffold produced is layout,
|
||||||
|
* build wiring, and packaging; this is where the actual work happens.
|
||||||
|
*
|
||||||
|
* The framework does not, and cannot, drive an arbitrary SNES game on its
|
||||||
|
* own. Two responsibilities always land on the game host:
|
||||||
|
*
|
||||||
|
* 1. Deciding what "one frame" means for THIS title, and returning from
|
||||||
|
* run_frame() at that boundary. A real ROM's reset vector never
|
||||||
|
* returns — it enters a main loop that waits on vblank — so somebody
|
||||||
|
* has to choose the yield point.
|
||||||
|
* 2. Delivering NMI/IRQ at the hardware edge (see
|
||||||
|
* snesrecomp/docs/FRAME_MODEL_HOSTS.md).
|
||||||
|
*
|
||||||
|
* A new port starts on the framework's beam-aligned driver
|
||||||
|
* (runner/src/beam_frame_driver.h): one frame is one PPU field, cut at the
|
||||||
|
* beam's vblank edge; interrupts -- raster splits and a coprocessor's IRQ
|
||||||
|
* line -- are taken where the beam latches them; parked time still runs the
|
||||||
|
* beam, APU and coprocessors; and the field is rasterized from the raster
|
||||||
|
* journal with real HDMA. It is the framework's, so a fix to it reaches this
|
||||||
|
* port on a submodule pull.
|
||||||
|
*
|
||||||
|
* This file used to hold its own copy of a simpler driver: frames measured
|
||||||
|
* from wherever the CPU last stopped, NMI delivered at an arbitrary beam
|
||||||
|
* position, the field drawn with end-of-frame register state. A game that
|
||||||
|
* uploads in vblank between INIDISP=$80 and $0F flickered black, and one
|
||||||
|
* that chains raster splits fell apart. Replace these calls only for what
|
||||||
|
* the driver does not do; MetalWarriorsSNESRecomp's src/mw_rtl.c and
|
||||||
|
* GundamWingEndlessDuelSNESRecomp's src/game_rtl.c are developed examples of
|
||||||
|
* title-specific drivers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "game_rtl.h"
|
||||||
|
|
||||||
|
#include "beam_frame_driver.h"
|
||||||
|
|
||||||
|
void GameRunOneFrame(void)
|
||||||
|
{
|
||||||
|
snes_beam_frame_driver_run_frame();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameDrawPpuFrame(void)
|
||||||
|
{
|
||||||
|
snes_beam_frame_driver_draw_ppu_frame();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameSessionReset(void)
|
||||||
|
{
|
||||||
|
/* Rematch / soft-return: clear anything that must not survive a new
|
||||||
|
* session. recomp-ai-rules/NETPLAY.md §3 — sticky state that "has always
|
||||||
|
* been fine" is the usual desync culprit, because single-player never
|
||||||
|
* re-enters the boot path twice in one process. */
|
||||||
|
snes_beam_frame_driver_reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
const RtlGameInfo kGameInfo = {
|
||||||
|
.title = "legendofzeldaalinktothepast",
|
||||||
|
.initialize = NULL,
|
||||||
|
.run_frame = &GameRunOneFrame,
|
||||||
|
.draw_ppu_frame = &GameDrawPpuFrame,
|
||||||
|
.save_name_prefix = "save",
|
||||||
|
.session_reset = &GameSessionReset,
|
||||||
|
};
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
#ifndef GAME_RTL_H
|
||||||
|
#define GAME_RTL_H
|
||||||
|
|
||||||
|
#include "common_cpu_infra.h"
|
||||||
|
|
||||||
|
/* One frame of Legend of Zelda: A Link to the Past. See game_rtl.c — this is the port. */
|
||||||
|
void GameRunOneFrame(void);
|
||||||
|
void GameDrawPpuFrame(void);
|
||||||
|
void GameSessionReset(void);
|
||||||
|
|
||||||
|
extern const RtlGameInfo kGameInfo;
|
||||||
|
|
||||||
|
#endif /* GAME_RTL_H */
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
/* Hand-written bodies for code the recompiler could not translate.
|
||||||
|
*
|
||||||
|
* Empty at scaffold time. Fill this in as regeneration reports unresolved
|
||||||
|
* targets — and fill it in with real behaviour, never with a value invented
|
||||||
|
* to get past the gap. An invented result turns a loud failure into a silent
|
||||||
|
* wrong one (recomp-ai-rules/PRINCIPLES.md, "No stubs"). If a routine cannot
|
||||||
|
* be translated yet, leave it to the interpreter tier instead.
|
||||||
|
*/
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* The host contract for Legend of Zelda: A Link to the Past.
|
||||||
|
*
|
||||||
|
* The runner is a library, not an application: a handful of symbols are
|
||||||
|
* deliberately left for the host to define. The framework's desktop host
|
||||||
|
* (snesrecomp_target_desktop_host in CMakeLists.txt) defines the ones that
|
||||||
|
* depend on the frontend -- Die, RtlApuLock/RtlApuUnlock, g_spc_player,
|
||||||
|
* RtlDrawPpuFrame, MkDir, ChangeWindowScale and the widescreen globals.
|
||||||
|
*
|
||||||
|
* What is left is the trace contract: the debug_on_* hooks the generated code
|
||||||
|
* calls on every block entry and WRAM write. They belong to the trace build
|
||||||
|
* (-DSNESRECOMP_ENABLE_TRACE=ON supplies real ones through the debug server)
|
||||||
|
* and are empty here so a production build links.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#if !defined(SNESRECOMP_TRACE) || !SNESRECOMP_TRACE
|
||||||
|
void debug_on_block_enter(uint32_t pc, uint32_t a, uint32_t x, uint32_t y)
|
||||||
|
{
|
||||||
|
(void)pc; (void)a; (void)x; (void)y;
|
||||||
|
}
|
||||||
|
|
||||||
|
void debug_on_wram_write_byte(uint32_t addr, uint8_t old_val, uint8_t new_val)
|
||||||
|
{
|
||||||
|
(void)addr; (void)old_val; (void)new_val;
|
||||||
|
}
|
||||||
|
|
||||||
|
void debug_on_wram_write_word(uint32_t addr, uint16_t old_val, uint16_t new_val)
|
||||||
|
{
|
||||||
|
(void)addr; (void)old_val; (void)new_val;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
+67
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* Legend of Zelda: A Link to the Past — desktop host shim.
|
||||||
|
*
|
||||||
|
* Identity and hooks, nothing else. The host itself is the framework's
|
||||||
|
* (snesrecomp/runner/src/desktop/host_main.h): the pre-boot launcher, ROM
|
||||||
|
* resolution and digest checks, config.ini and keybinds.ini, the window and
|
||||||
|
* the SDL / OpenGL presenters, audio, gamepads, the in-game save-state
|
||||||
|
* browser and rewind filmstrip, the OSD, the pacing clock, crash handlers and
|
||||||
|
* the post-mortem report, mod packages, Generate & rebuild, and the netplay
|
||||||
|
* barrier when the project is built with it. A fix there reaches this
|
||||||
|
* project on a submodule pull; nothing in this file needs to change for it.
|
||||||
|
*
|
||||||
|
* This file used to BE the host -- a ~700-line copy made at scaffold time --
|
||||||
|
* so every project scaffolded before a fix kept the bug. Two ports of the
|
||||||
|
* same game, a week apart, had different hosts.
|
||||||
|
*
|
||||||
|
* Nothing here ever ships a ROM. The player supplies one; the host makes
|
||||||
|
* that easy (launcher, then positional argument, then a copy beside the
|
||||||
|
* executable, then the rom.cfg cache, then a file picker) and checks what it
|
||||||
|
* is handed against the digests in rom_identity.txt.
|
||||||
|
*
|
||||||
|
* Grow this file only with what is specific to THIS title: a custom
|
||||||
|
* presenter (prepare_frame / draw_frame), an SPC player, a pacing rule
|
||||||
|
* (keep_pacing_debt), a Mods provider. See SnesDesktopHostGame for the
|
||||||
|
* complete list of hooks and what each is for.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "host_main.h"
|
||||||
|
#include "game_rtl.h"
|
||||||
|
#include "snesrecomp_rom_identity.h" /* generated from rom_identity.txt */
|
||||||
|
|
||||||
|
#ifndef __ANDROID__
|
||||||
|
#define SDL_MAIN_HANDLED 1
|
||||||
|
#endif
|
||||||
|
#include "desktop/sdl_compat.h"
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
/* SDLActivity loads libmain.so and dlsyms its SDL_main. */
|
||||||
|
#define main SDL_main
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef SNES_GAME_VERSION
|
||||||
|
#define SNES_GAME_VERSION "dev"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static const SnesDesktopHostGame kGameHost = {
|
||||||
|
.display_name = "Legend of Zelda: A Link to the Past",
|
||||||
|
.window_title = "Legend of Zelda: A Link to the Past",
|
||||||
|
.region = SNESRECOMP_ROM_REGION,
|
||||||
|
.rom_file = SNESRECOMP_ROM_FILE,
|
||||||
|
.expected_sha256_hex = SNESRECOMP_ROM_EXPECTED_SHA256,
|
||||||
|
.expected_crc32_hex = SNESRECOMP_ROM_EXPECTED_CRC32,
|
||||||
|
.game_id = SNESRECOMP_ROM_GAME_ID,
|
||||||
|
.build_version = SNES_GAME_VERSION,
|
||||||
|
.game_info = &kGameInfo,
|
||||||
|
.num_players = 1,
|
||||||
|
/* Battery-backed SRAM shows the launcher's SAVES panel. Leave NULL for a
|
||||||
|
* title without one. The path is exe-relative. */
|
||||||
|
.sram_path = "saves/save.srm",
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifndef __ANDROID__
|
||||||
|
#undef main /* desktop: keep plain main() even if SDL_main.h remapped it */
|
||||||
|
#endif
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
return snesrecomp_desktop_main(&kGameHost, argc, argv);
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
/* Game-specific RAM variable declarations for Legend of Zelda: A Link to the Past.
|
||||||
|
*
|
||||||
|
* The framework includes this by name (snes/cpu.c), so every project must
|
||||||
|
* provide one even when it is empty. Populate it as WRAM locations are
|
||||||
|
* identified, so generated code and hand-written host code refer to the same
|
||||||
|
* address by the same name.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef VARIABLES_H
|
||||||
|
#define VARIABLES_H
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
#endif /* VARIABLES_H */
|
||||||
Executable
+98
@@ -0,0 +1,98 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Regenerate src/gen/*.c for Legend of Zelda: A Link to the Past from a verified ROM.
|
||||||
|
#
|
||||||
|
# Generated C is derived from copyrighted ROM data and is never committed;
|
||||||
|
# every developer regenerates from their own copy. The ROM digests come from
|
||||||
|
# rom_identity.txt, which the build and the release workflow read too, so
|
||||||
|
# there is one place to change when a revision changes.
|
||||||
|
#
|
||||||
|
# Flags:
|
||||||
|
# --rom <path> ROM to generate from. Defaults to SNESRECOMP_ROM, a known
|
||||||
|
# filename at the repo root, then the path in rom.cfg (which
|
||||||
|
# the setup wizard writes). The ROM does not have to live in
|
||||||
|
# the repo — keeping it on your own drive is the better
|
||||||
|
# habit, and SNESRECOMP_ROM sets it once for a shell.
|
||||||
|
# --no-verify skip the ROM digest check (for a revision this project has
|
||||||
|
# not been pinned to yet — expect the generated C to differ)
|
||||||
|
# --cfg-roots seed analysis from every func declaration in recomp/*.cfg
|
||||||
|
# -h|--help this message
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
cd "$ROOT"
|
||||||
|
|
||||||
|
VERIFY=1
|
||||||
|
CFG_ROOTS=0
|
||||||
|
ROM=""
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--rom) ROM=$2; shift 2 ;;
|
||||||
|
--no-verify) VERIFY=0; shift ;;
|
||||||
|
--cfg-roots) CFG_ROOTS=1; shift ;;
|
||||||
|
-h|--help) sed -n '2,/^set -euo/p' "$0" | sed -n '/^# /p' | sed 's/^# //'; exit 0 ;;
|
||||||
|
*) echo "regen.sh: unknown flag: $1 (try --help)" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
SNESRECOMP_ROOT="${SNESRECOMP_ROOT:-snesrecomp}"
|
||||||
|
CLI="$SNESRECOMP_ROOT/snesrecomp_cli.py"
|
||||||
|
if [ ! -f "$CLI" ]; then
|
||||||
|
echo "regen.sh: $CLI missing — run: git submodule update --init --recursive" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
PYTHON=${PYTHON:-}
|
||||||
|
if [ -z "$PYTHON" ]; then
|
||||||
|
# Windows App Execution Aliases can exist on PATH without running Python.
|
||||||
|
for _snes_python in python3 python; do
|
||||||
|
if "$_snes_python" -c 'import sys; sys.exit(sys.version_info[0] != 3)' >/dev/null 2>&1; then
|
||||||
|
PYTHON=$_snes_python
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
if [ -z "$PYTHON" ]; then
|
||||||
|
echo "regen.sh: no python3 on PATH" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The framework parses rom_identity.txt (the same parser CI uses), so this
|
||||||
|
# script carries no copy of a digest to forget on a revision bump.
|
||||||
|
IDENTITY="$SNESRECOMP_ROOT/tools/rom_identity.py"
|
||||||
|
EXPECTED_CRC32="${SNESRECOMP_EXPECTED_CRC32:-$("$PYTHON" "$IDENTITY" "$ROOT/rom_identity.txt" --get expected_crc32)}"
|
||||||
|
EXPECTED_SHA256="${SNESRECOMP_EXPECTED_SHA256:-$("$PYTHON" "$IDENTITY" "$ROOT/rom_identity.txt" --get expected_sha256)}"
|
||||||
|
|
||||||
|
# The framework owns the search: --rom, then SNESRECOMP_ROM, then a known
|
||||||
|
# name at the root, then rom.cfg (the path the setup wizard recorded).
|
||||||
|
RESOLVE=(resolve-rom --project-root "$ROOT"
|
||||||
|
--name "Legend of Zelda, The - A Link to the Past (USA).sfc" --name "legendofzeldaalinktothepast.sfc" --name "legendofzeldaalinktothepast.smc")
|
||||||
|
if [ -n "$ROM" ]; then RESOLVE+=(--rom "$ROM"); fi
|
||||||
|
if ! ROM=$("$PYTHON" "$CLI" "${RESOLVE[@]}"); then
|
||||||
|
echo "regen.sh: pass --rom /path/to/Legend of Zelda, The - A Link to the Past (USA).sfc, set SNESRECOMP_ROM, put it" >&2
|
||||||
|
echo " at the repo root, or record its path in rom.cfg. You must" >&2
|
||||||
|
echo " legally own a copy of Legend of Zelda: A Link to the Past." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
ROM=${ROM%$'\r'} # Windows Python ends lines with CRLF
|
||||||
|
|
||||||
|
VERIFY_ARGS=()
|
||||||
|
if [ "$VERIFY" -eq 1 ]; then
|
||||||
|
VERIFY_ARGS=(--expected-crc32 "$EXPECTED_CRC32" --expected-sha256 "$EXPECTED_SHA256")
|
||||||
|
echo "== Verifying $ROM =="
|
||||||
|
"$PYTHON" "$CLI" verify-rom --rom "$ROM" "${VERIFY_ARGS[@]}"
|
||||||
|
else
|
||||||
|
echo "== Skipping ROM verification (--no-verify) =="
|
||||||
|
fi
|
||||||
|
|
||||||
|
GEN_ARGS=(--rom "$ROM" --cfg-dir recomp --out-dir src/gen
|
||||||
|
--funcs-h recomp/funcs.h --project-root "$ROOT")
|
||||||
|
if [ "$CFG_ROOTS" -eq 1 ]; then GEN_ARGS+=(--cfg-roots); fi
|
||||||
|
if [ "$VERIFY" -eq 1 ]; then GEN_ARGS+=("${VERIFY_ARGS[@]}"); fi
|
||||||
|
|
||||||
|
echo "== Generating src/gen =="
|
||||||
|
"$PYTHON" "$CLI" generate "${GEN_ARGS[@]}"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "Done. Build with:"
|
||||||
|
echo " cmake -S . -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build -j"
|
||||||
Reference in new issue
Block a user