Skip to content

Add multi-ROM base detection and EU1.1 bring-up infrastructure #72

Add multi-ROM base detection and EU1.1 bring-up infrastructure

Add multi-ROM base detection and EU1.1 bring-up infrastructure #72

name: MPH Multi-ROM Static Checks
on:
pull_request:
workflow_dispatch:
permissions:
contents: read
jobs:
static-multirom:
runs-on: ubuntu-latest
steps:
- name: Checkout project
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Check Python syntax
run: |
python -m py_compile \
tools/mph_profile.py \
tools/prepare_mph.py \
tools/check_mph_multirom_profiles.py \
tools/check_melonprime_detector.py \
tools/patch_ndsrecomp_mph_runtime.py \
tools/promote_mph_static_coverage.py \
tools/promote_mph_runtime_coverage.py \
tools/benchmark_mph_fmv.py \
tools/fuzz_mph_gameplay.py \
tools/capture_mph_checkpoints.py
- name: Check shell syntax
run: bash -n tools/build-linux.sh
- name: Check PowerShell syntax
shell: pwsh
run: |
$failed = $false
foreach ($path in @('tools/build-windows.ps1', 'tools/make_release.ps1')) {
$tokens = $null
$errors = $null
[System.Management.Automation.Language.Parser]::ParseFile(
(Resolve-Path $path), [ref]$tokens, [ref]$errors) | Out-Null
if ($errors.Count -ne 0) {
$failed = $true
Write-Error "$path has PowerShell parser errors:`n$($errors | Out-String)"
}
}
if ($failed) { exit 1 }
- name: Cross-check runtime detector against melonPrimeDS develop_hud
run: |
curl -fsSL --retry 3 \
https://raw.githubusercontent.com/ag-advania/melonPrimeDS/develop_hud/src/frontend/qt_sdl/MelonPrimeGameRomAddrTable.h \
-o /tmp/MelonPrimeGameRomAddrTable.h
curl -fsSL --retry 3 \
https://raw.githubusercontent.com/ag-advania/melonPrimeDS/develop_hud/src/frontend/qt_sdl/MelonPrimeGameRomDetect.cpp \
-o /tmp/MelonPrimeGameRomDetect.cpp
python tools/check_mph_multirom_profiles.py \
--melonprime-table /tmp/MelonPrimeGameRomAddrTable.h
python tools/check_melonprime_detector.py \
--profiles config/mph_rom_profiles.json \
--detector /tmp/MelonPrimeGameRomDetect.cpp
- name: Test profile-aware coverage promotion
run: |
mkdir -p /tmp/eu-inputs
cat > /tmp/eu-inputs/arm9.toml <<'EOF'
[program]
id = "mph_amhp1_arm9"
load_address = 0x02004000
size = 0x00001000
entry_pc = 0x02004000
EOF
cat > /tmp/eu-inputs/arm7.toml <<'EOF'
[program]
id = "mph_amhp1_arm7"
load_address = 0x02380000
size = 0x00001000
entry_pc = 0x02380000
EOF
cat > /tmp/eu-trace.json <<'EOF'
{
"mph_profile": "EU1_1",
"rom_sha1": "bdcd1dea293e24c98d4c481430e90d21198985a5",
"scenario": "scenarios/adventure_start.json",
"static_coverage": {"tier3_entries9": 3},
"tier3_coverage": {"entries": [
{"cpu": 9, "pc": 33570816, "thumb": 0, "kind": 2, "hits": 99},
{"cpu": 9, "pc": 33570848, "thumb": 0, "kind": 2, "hits": 7},
{"cpu": 9, "pc": 33579008, "thumb": 0, "kind": 2, "hits": 5},
{"cpu": 7, "pc": 37224480, "thumb": 0, "kind": 3, "hits": 4}
]}
}
EOF
python tools/promote_mph_static_coverage.py \
--version EU1_1 \
--inputs /tmp/eu-inputs \
--trace /tmp/eu-trace.json \
--out /tmp/eu-coverage.json \
--runner-commit 0123456789abcdef0123456789abcdef01234567
python - <<'PY'
import json
p = json.load(open('/tmp/eu-coverage.json', encoding='utf-8'))
assert p['profile'] == 'EU1_1'
assert p['game_sha1'] == 'bdcd1dea293e24c98d4c481430e90d21198985a5'
assert p['scenario'] == 'scenarios/adventure_start.json'
assert p['main_image']['arm9']['end'] == '0x02005000'
assert [e['addr'] for e in p['entry_points']['arm9']] == ['0x02004020']
assert [e['addr'] for e in p['entry_points']['arm7']] == ['0x02380020']
PY
truncate -s $((0x00408000)) /tmp/eu-runtime.bin
cat > /tmp/eu-before.json <<'EOF'
{
"mph_profile": "EU1_1",
"rom_sha1": "bdcd1dea293e24c98d4c481430e90d21198985a5",
"tier3_coverage": {"entries": [
{"cpu": 9, "pc": 33525760, "thumb": 0, "kind": 2, "caller": 1, "hits": 1}
]}
}
EOF
cat > /tmp/eu-after.json <<'EOF'
{
"mph_profile": "EU1_1",
"rom_sha1": "bdcd1dea293e24c98d4c481430e90d21198985a5",
"runtime_capture": {
"sha1": "2dacada9962037d4e1fa3099d9de8cdf616c318c",
"bytes": 4227072
},
"tier3_coverage": {"entries": [
{"cpu": 9, "pc": 33525760, "thumb": 0, "kind": 2, "caller": 1, "hits": 2},
{"cpu": 9, "pc": 33525792, "thumb": 0, "kind": 3, "caller": 2, "hits": 3}
]}
}
EOF
python tools/promote_mph_runtime_coverage.py \
--version EU1_1 \
--before-benchmark /tmp/eu-before.json \
--benchmark /tmp/eu-after.json \
--image /tmp/eu-runtime.bin \
--out /tmp/eu-runtime.toml
grep -q 'Profile: EU1_1' /tmp/eu-runtime.toml
grep -q 'Metroid Prime Hunters (Europe rev 1) ARM9 FMV runtime' /tmp/eu-runtime.toml
grep -q 'id = "mph_amhp1_arm9_fmv_runtime"' /tmp/eu-runtime.toml
! grep -q 'Metroid Prime Hunters (USA) ARM9 FMV runtime' /tmp/eu-runtime.toml
- name: Verify registry-driven profile and FMV routing
run: |
grep -q 'MPH_PROFILE_FMV_RUNTIME_BANK' CMakeLists.txt
grep -q 'config/${MPH_PROFILE_FMV_RUNTIME_BANK}.toml' CMakeLists.txt
grep -q 'capture/${MPH_PROFILE_FMV_RUNTIME_BANK}.bin' CMakeLists.txt
! grep -q -- '--bank mph_arm9_fmv_runtime' CMakeLists.txt
grep -q 'MPH_PROFILE_KEYS' CMakeLists.txt
! grep -q 'PROPERTY STRINGS US1_0 EU1_1' CMakeLists.txt
! grep -q "ValidateSet('US1_0', 'EU1_1')" tools/build-windows.ps1
grep -q 'FMV_RUNTIME_BANK' tools/build-linux.sh
grep -q 'FmvRuntimeBank' tools/build-windows.ps1
grep -q 'FmvRuntimeBank' tools/make_release.ps1
grep -q 'rom_bytes\[0x1E\]' tools/prepare_mph.py
! grep -q 'rom_bytes\[0x1C\]' tools/prepare_mph.py
- name: Fetch exact pinned ndsrecomp revision
run: |
pin="$(tr -d '\r\n' < ndsrecomp.pin)"
test "${#pin}" -eq 40
git init /tmp/ndsrecomp
git -C /tmp/ndsrecomp remote add origin https://github.com/mstan/ndsrecomp.git
git -C /tmp/ndsrecomp fetch --depth 1 origin "$pin"
git -C /tmp/ndsrecomp checkout --detach FETCH_HEAD
test "$(git -C /tmp/ndsrecomp rev-parse HEAD)" = "$pin"
- name: Render US1.0 and EU1.1 launcher profiles
run: |
mkdir -p /tmp/fake-prefix/lib/cmake/SDL2 /tmp/fake-recomp-ui
cat > /tmp/fake-prefix/lib/cmake/SDL2/SDL2Config.cmake <<'EOF'
if(NOT TARGET SDL2::SDL2)
add_library(SDL2::SDL2 INTERFACE IMPORTED)
endif()
EOF
cat > /tmp/fake-recomp-ui/recomp_ui.cmake <<'EOF'
function(recomp_target_launcher_ui)
endfunction()
EOF
cmake -S launcher/recomp-ui -B /tmp/mph-launcher-us10 \
-DNDSRECOMP_ROOT=/tmp/ndsrecomp \
-DRECOMP_UI_ROOT=/tmp/fake-recomp-ui \
-DCMAKE_PREFIX_PATH=/tmp/fake-prefix
cmake -S launcher/recomp-ui -B /tmp/mph-launcher-eu11 \
-DNDSRECOMP_ROOT=/tmp/ndsrecomp \
-DRECOMP_UI_ROOT=/tmp/fake-recomp-ui \
-DCMAKE_PREFIX_PATH=/tmp/fake-prefix \
-DMPH_LAUNCHER_ROM_SHA1=bdcd1dea293e24c98d4c481430e90d21198985a5 \
-DMPH_LAUNCHER_REGION=Europe \
'-DMPH_LAUNCHER_DEFAULT_ROM=Metroid Prime Hunters (Europe Rev 1).nds' \
-DMPH_LAUNCHER_ADAPTIVE_WIDESCREEN=OFF
us=/tmp/mph-launcher-us10/launcher_main_profile.cpp
eu=/tmp/mph-launcher-eu11/launcher_main_profile.cpp
test -f "$us" -a -f "$eu"
grep -q '90164d1ac127ee5f9815ea4ae7de798c7b5fc629' "$us"
grep -q 'game.region = "USA";' "$us"
grep -q 'game.known_sha1_hex = nullptr;' "$us"
grep -q 'game.num_known_sha1 = 0;' "$us"
grep -q 'bool adaptive_widescreen = true;' "$us"
grep -A1 'int mod_feature_count' "$us" | grep -q 'return 2;'
grep -q 'adaptive = adaptive && true;' "$us"
grep -q 'bdcd1dea293e24c98d4c481430e90d21198985a5' "$eu"
grep -q 'game.region = "Europe";' "$eu"
grep -q 'game.known_sha1_hex = nullptr;' "$eu"
grep -q 'game.num_known_sha1 = 0;' "$eu"
grep -q 'exe / "Metroid Prime Hunters (Europe Rev 1).nds";' "$eu"
grep -q 'bool adaptive_widescreen = false;' "$eu"
grep -A1 'int mod_feature_count' "$eu" | grep -q 'return 1;'
grep -q 'if (!false) ++index;' "$eu"
grep -q 'adaptive = adaptive && false;' "$eu"
- name: Verify Linux profile-owned launch policy
run: |
grep -q 'launcher_default_rom' tools/build-linux.sh
grep -q 'fmv_runtime_bank' tools/build-linux.sh
grep -q 'cp "$GAME_CONFIG" "$APPDIR/usr/share/mph-recomp/game.toml"' \
tools/build-linux.sh
grep -q -- '--config "$HERE/usr/share/mph-recomp/game.toml"' \
tools/build-linux.sh
! grep -q -- '--adaptive-widescreen' tools/build-linux.sh
- name: Verify ndsrecomp runtime patch is idempotent
run: |
python tools/patch_ndsrecomp_mph_runtime.py \
--framework-root /tmp/ndsrecomp \
--profiles config/mph_rom_profiles.json
sha256sum \
/tmp/ndsrecomp/runner/src/mph_runtime_profiles.generated.h \
/tmp/ndsrecomp/runner/src/title_patches.h \
/tmp/ndsrecomp/runner/src/title_patches.cpp \
/tmp/ndsrecomp/runner/src/frontend.cpp \
/tmp/ndsrecomp/runner/src/main.cpp \
> /tmp/first.sha256
python tools/patch_ndsrecomp_mph_runtime.py \
--framework-root /tmp/ndsrecomp \
--profiles config/mph_rom_profiles.json
sha256sum \
/tmp/ndsrecomp/runner/src/mph_runtime_profiles.generated.h \
/tmp/ndsrecomp/runner/src/title_patches.h \
/tmp/ndsrecomp/runner/src/title_patches.cpp \
/tmp/ndsrecomp/runner/src/frontend.cpp \
/tmp/ndsrecomp/runner/src/main.cpp \
> /tmp/second.sha256
diff -u /tmp/first.sha256 /tmp/second.sha256
grep -q '0x020DB138u, 0x020DEE46u, 0x020DEE4Eu' \
/tmp/ndsrecomp/runner/src/mph_runtime_profiles.generated.h
grep -q 'bdcd1dea293e24c98d4c481430e90d21198985a5' \
/tmp/ndsrecomp/runner/src/mph_runtime_profiles.generated.h
grep -q 'nds_title_patches_select_mph_runtime_profile' \
/tmp/ndsrecomp/runner/src/main.cpp
grep -q 'nds_title_patches_mph_in_ball' \
/tmp/ndsrecomp/runner/src/frontend.cpp
! grep -q 'kMphUs10MorphState' /tmp/ndsrecomp/runner/src/frontend.cpp
! grep -q 'kMphUs10AimX' /tmp/ndsrecomp/runner/src/title_patches.cpp
! grep -q 'kMphUs10AimY' /tmp/ndsrecomp/runner/src/title_patches.cpp
- name: Compile patched runner translation units
run: |
mkdir -p /tmp/ndsrecomp/generated
for source in \
arm9_bios.c arm9_bios_dispatch.c \
arm7_bios.c arm7_bios_dispatch.c \
freebios_arm9.c freebios_arm9_dispatch.c \
freebios_arm7.c freebios_arm7_dispatch.c; do
: > "/tmp/ndsrecomp/generated/$source"
done
cmake -S /tmp/ndsrecomp/runner -B /tmp/nds-runner-profile-check \
-DCMAKE_BUILD_TYPE=Release \
-DNDS_BOOTSTRAP_FIRMWARE=ON \
-DNDS_ENABLE_COMPUTE_RENDERER=OFF \
-DNDS_ENABLE_PCAP_BACKEND=OFF
cmake --build /tmp/nds-runner-profile-check --target \
src/title_patches.o -j2
cmake --build /tmp/nds-runner-profile-check --target \
src/frontend.o -j2
cmake --build /tmp/nds-runner-profile-check --target \
src/main.o -j2
- name: Test runtime base-profile and compatibility dispatch
run: |
c++ -std=c++20 -Wall -Wextra -Wno-unused-parameter \
-I/tmp/ndsrecomp/runner/src \
-I/tmp/ndsrecomp/recompiler/armv4t \
tools/tests/mph_runtime_profile_test.cpp \
/tmp/ndsrecomp/runner/src/title_patches.cpp \
-o /tmp/mph-runtime-profile-test
/tmp/mph-runtime-profile-test
- name: Compile US1.0 and EU1.1 ROM checkers
run: |
for profile in US1_0 EU1_1; do
build="/tmp/mph-romcheck-$profile"
cmake -S . -B "$build" \
-DCMAKE_BUILD_TYPE=Release \
-DNDSRECOMP_ROOT=/tmp/ndsrecomp \
-DMPH_VERSION="$profile" \
-DMPH_ROM="/tmp/nonexistent-$profile.nds"
cmake --build "$build" --target mph_romcheck -j2
test -x "$build/MetroidPrimeHuntersRecomp"
done
strings /tmp/mph-romcheck-EU1_1/MetroidPrimeHuntersRecomp | \
grep -q bdcd1dea293e24c98d4c481430e90d21198985a5
strings /tmp/mph-romcheck-EU1_1/MetroidPrimeHuntersRecomp | \
grep -q EU1_1
- name: Diff sanity
run: git diff --check