mirror of
https://github.com/quer3q/ps5-dump-forge.git
synced 2026-10-21 21:12:17 -05:00
- Edit tab (replaces Inspect): rename, delete and add files in a game folder or image, applied in place behind an undo journal or by a verified rebuild; Extract copies files out of any source; additional content (DLC, updates) is recognised and packaged as fpkg. - Storage tab (replaces Library): the Library plus a File explorer that renames, moves and deletes at once and opens a folder or image in Edit or Convert. - PS5: ShadowMount+'s mounted games are hidden and locked in every write path; Forge's own files are protected; L2/R2 switch tabs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
255 lines
9.6 KiB
YAML
255 lines
9.6 KiB
YAML
name: CI
|
|
|
|
on:
|
|
push:
|
|
pull_request:
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
concurrency:
|
|
group: ci-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
env:
|
|
CARGO_TERM_COLOR: always
|
|
|
|
# Unpinned and latest on purpose: major action tags, *-latest runners, the latest stable Rust.
|
|
# rust-toolchain.toml selects `stable`, but that does not update the runner's preinstalled
|
|
# stable, so every job runs `rustup update stable` first, then `rustup toolchain install` adds
|
|
# that file's components and targets. No cargo cache.
|
|
defaults:
|
|
run:
|
|
shell: bash
|
|
|
|
jobs:
|
|
test:
|
|
name: test (${{ matrix.os }})
|
|
runs-on: ${{ matrix.os }}
|
|
timeout-minutes: 60
|
|
# The tests run optimized but without the shipped profile's fat LTO and single codegen
|
|
# unit, which made every test binary a slow whole-program link (worst with MSVC's linker).
|
|
# The release jobs, fsck and ps5 still build the shipped profile.
|
|
env:
|
|
CARGO_PROFILE_RELEASE_LTO: "false"
|
|
CARGO_PROFILE_RELEASE_CODEGEN_UNITS: "16"
|
|
strategy:
|
|
fail-fast: false
|
|
# Every platform that ships builds and tests the whole workspace, the Tauri app included
|
|
# (on WebKitGTK on Linux, with its WebView2 startup code on Windows).
|
|
matrix:
|
|
include:
|
|
- os: macos-15
|
|
- os: ubuntu-latest
|
|
fmt: true
|
|
- os: windows-latest
|
|
- os: windows-11-arm
|
|
- os: ubuntu-22.04-arm
|
|
steps:
|
|
# Defender scans every file the build and the tests write; the runner is throwaway.
|
|
- if: runner.os == 'Windows'
|
|
shell: pwsh
|
|
continue-on-error: true
|
|
run: Set-MpPreference -DisableRealtimeMonitoring $true
|
|
- uses: actions/checkout@v7
|
|
with:
|
|
persist-credentials: false
|
|
- run: |
|
|
rustup update --no-self-update stable
|
|
rustup toolchain install --no-self-update
|
|
rustc --version
|
|
- if: matrix.fmt
|
|
run: cargo fmt --all --check
|
|
# Tauri v2's Linux build deps, as the release's linux job installs them.
|
|
- if: runner.os == 'Linux'
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y --no-install-recommends libwebkit2gtk-4.1-dev libgtk-3-dev \
|
|
librsvg2-dev libayatana-appindicator3-dev patchelf file
|
|
# tauri-build needs the built frontend (app/dist) to compile the app crate.
|
|
- uses: actions/setup-node@v7
|
|
with:
|
|
node-version: lts/*
|
|
- working-directory: app
|
|
run: npm ci && npm run build
|
|
- run: cargo clippy --workspace --all-targets --locked -- -D warnings
|
|
- run: cargo test --workspace --locked --release
|
|
# Every target onto real exFAT and FAT32 volumes (hdiutil, no root): macOS's drivers
|
|
# renumber a file's inode at its first write and exFAT has no RENAME_EXCL.
|
|
- if: runner.os == 'macOS'
|
|
run: cargo test -p ps5-dump-forge-core --test fat_volumes --locked --release -- --ignored
|
|
|
|
fsck:
|
|
# Independent checks: the writers' own read-back can share their bugs,
|
|
# so exfatprogs' fsck.exfat looks at the .exfat here, fsck-macos at the same image and
|
|
# FreeBSD's fsck_ufs (fsck-ufs) at the .ffpkg.
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 60
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
with:
|
|
persist-credentials: false
|
|
- run: |
|
|
rustup update --no-self-update stable
|
|
rustup toolchain install --no-self-update
|
|
rustc --version
|
|
- run: cargo build --release --locked -p ps5-dump-forge-cli
|
|
- name: Fixture game folder
|
|
run: |
|
|
g=fixture/game
|
|
mkdir -p "$g/sce_sys" "$g/data/levels/a/b/c" "$g/data/empty" "$g/data/many"
|
|
printf '\x4f\x15\x3d\x1d\x00\x01\x01\x12' > "$g/eboot.bin"
|
|
echo '{"titleId":"PPSA01234","contentId":"UP0000-PPSA01234_00-FORGECIFIXTURE00"}' > "$g/sce_sys/param.json"
|
|
# 5 MB: past UFS2's 12 direct blocks (single indirect) and many exFAT clusters.
|
|
head -c 5000000 /dev/urandom > "$g/data/levels/big.bin"
|
|
echo deep > "$g/data/levels/a/b/c/deep.txt"
|
|
echo composed > "$g/data/levels/Ünïcødé.txt"
|
|
: > "$g/data/zero.bin"
|
|
# 1000 entries: multi-block UFS2 and multi-cluster exFAT directories.
|
|
for i in $(seq -w 1 1000); do echo "$i" > "$g/data/many/f$i.txt"; done
|
|
- name: Convert to .exfat and .ffpkg
|
|
run: |
|
|
mkdir images
|
|
for f in exfat ffpkg; do
|
|
target/release/ps5-dump-forge convert fixture/game --to "$f" -o "images/game.$f" > "images/$f.jsonl"
|
|
done
|
|
ls -ls images
|
|
target/release/ps5-dump-forge inspect images/game.exfat
|
|
target/release/ps5-dump-forge inspect images/game.ffpkg
|
|
- name: fsck.exfat (exfatprogs)
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y --no-install-recommends exfatprogs
|
|
fsck.exfat -V || true
|
|
fsck.exfat -n images/game.exfat
|
|
# tar keeps the empty directory and the images' holes; an artifact keeps neither.
|
|
- run: tar -cSzf fsck-images.tar.gz fixture images
|
|
- uses: actions/upload-artifact@v7
|
|
with:
|
|
name: fsck-images
|
|
path: fsck-images.tar.gz
|
|
if-no-files-found: error
|
|
retention-days: 3
|
|
|
|
fsck-macos:
|
|
# macOS's own fsck_exfat, plus a read-only mount compared file by file with the source.
|
|
needs: fsck
|
|
runs-on: macos-15
|
|
timeout-minutes: 60
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
with:
|
|
persist-credentials: false
|
|
- uses: actions/download-artifact@v8
|
|
with:
|
|
name: fsck-images
|
|
- run: tar -xzf fsck-images.tar.gz
|
|
- run: scripts/check-exfat.sh images/game.exfat fixture/game
|
|
|
|
fsck-ufs:
|
|
# FreeBSD's fsck_ufs -n on the .ffpkg, in a FreeBSD VM. fsck_ufs -n exits 0 even when it
|
|
# reports (and declines) a repair, so clean means what scripts/fsck-ufs.sh requires: exit
|
|
# status 0, the "N files, ..." summary, and no line besides the phase headers and summary.
|
|
needs: fsck
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 60
|
|
steps:
|
|
- uses: actions/download-artifact@v8
|
|
with:
|
|
name: fsck-images
|
|
- name: Keep only the .ffpkg (the workspace is copied into the VM)
|
|
run: |
|
|
tar -xzf fsck-images.tar.gz images/game.ffpkg
|
|
rm fsck-images.tar.gz
|
|
gzip images/game.ffpkg
|
|
ls -l images
|
|
- uses: vmactions/freebsd-vm@v1
|
|
with:
|
|
usesh: true
|
|
copyback: false
|
|
run: |
|
|
set -eu
|
|
freebsd-version
|
|
gunzip images/game.ffpkg.gz
|
|
md=$(mdconfig -a -t vnode -o readonly -f images/game.ffpkg)
|
|
rc=0
|
|
fsck_ufs -n "/dev/$md" > fsck.log 2>&1 || rc=$?
|
|
cat fsck.log
|
|
echo "FSCK_RC=$rc"
|
|
dumpfs "/dev/$md" | head -40
|
|
mdconfig -d -u "$md"
|
|
if [ "$rc" = 0 ] && grep -qE '^[0-9]+ files, ' fsck.log \
|
|
&& ! grep -vqE '^\*\* (/dev/|Last Mounted|Phase [1-5] )|^[0-9]+ files, ' fsck.log
|
|
then
|
|
echo "VERDICT /dev/$md IS CLEAN"
|
|
else
|
|
echo "VERDICT /dev/$md NOT CLEAN"
|
|
exit 1
|
|
fi
|
|
|
|
ps5:
|
|
# The PS5 payload, built and checked as the release builds it (scripts/release-ps5.sh), and a
|
|
# host check of its entry.c. The FreeBSD VM tests (scripts/test-freebsd.sh) are left out: under
|
|
# TCG they take 80+ minutes on a runner.
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 60
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
with:
|
|
persist-credentials: false
|
|
- uses: actions/setup-node@v7
|
|
with:
|
|
node-version: lts/*
|
|
- run: scripts/release-ps5.sh
|
|
- run: ps5/test-entry.sh
|
|
|
|
fuzz:
|
|
# Every reader of untrusted images, 60 s each from the seed corpora.
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 60
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
with:
|
|
persist-credentials: false
|
|
- run: |
|
|
rustup update --no-self-update stable
|
|
rustup toolchain install --no-self-update
|
|
rustup toolchain install --no-self-update --profile minimal nightly
|
|
rustc +nightly --version
|
|
# The prebuilt cargo-fuzz is a musl binary and defaults to its own target
|
|
# (x86_64-unknown-linux-musl), where the sanitizer cannot run; build for the host.
|
|
echo "FUZZ_TARGET=$(rustc +nightly -vV | sed -n 's/^host: //p')" >> "$GITHUB_ENV"
|
|
- uses: taiki-e/install-action@v2
|
|
with:
|
|
tool: cargo-fuzz
|
|
- run: cargo fmt --all --check
|
|
working-directory: fuzz
|
|
- run: fuzz/seed.sh
|
|
- run: cargo +nightly fuzz build --target "$FUZZ_TARGET"
|
|
working-directory: fuzz
|
|
# One libFuzzer process per core (each is single-threaded), each target its full 60 s;
|
|
# the logs follow, one group per target.
|
|
- name: Run each target for 60 s
|
|
working-directory: fuzz
|
|
run: |
|
|
mkdir -p logs
|
|
: > failed.txt
|
|
cargo +nightly fuzz list | xargs -P "$(nproc)" -I{} sh -c \
|
|
'cargo +nightly fuzz run --target "$FUZZ_TARGET" {} -- -max_total_time=60 > logs/{}.log 2>&1 || echo {} >> failed.txt'
|
|
for t in $(cargo +nightly fuzz list); do
|
|
echo "::group::$t"
|
|
cat "logs/$t.log"
|
|
echo "::endgroup::"
|
|
done
|
|
if [ -s failed.txt ]; then
|
|
echo "::error::fuzz targets with findings: $(tr '\n' ' ' < failed.txt)(inputs in the fuzz-artifacts artifact)"
|
|
exit 1
|
|
fi
|
|
- if: failure()
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: fuzz-artifacts
|
|
path: fuzz/artifacts
|
|
if-no-files-found: ignore
|
|
retention-days: 3
|