Initialise native Rust port of Ratchet & Clank: Going Commando
Sets up the foundation for reimplementing the PS2 game in Rust rather than emulating it. Nothing is implemented yet; this is the scaffold, the verified target and the working agreements that keep a long project coherent. Target disc, verified against a real image: SCUS_972.68 v1.01, USA Black Label, NTSC 60 Hz boot ELF 2,618,684 bytes, SHA-1 bacda860...d098a77, entry 0x00131AE8 The entry point independently matches the value recorded by the rac2-gc-decomp community reference, which cross-confirms the identification. This is the exact build the upstream research pins; Greatest Hits v2.00 is a different binary and is not the target. NTSC matters beyond label compatibility: PAL discs run 50 Hz, which would make chargeboots and other speed-sensitive movement run at 5/6 speed. Verifying the disc before writing movement code avoids discovering that in Phase 5. Contents: - Five-crate workspace (formats, extract, game, video, engine) on Bevy 0.19.1, matching the version ReRAC uses so its Graphics Synthesizer and VU work stays portable. `cargo check --workspace` is clean. - tools/identify-disc.py, which reproduces the identification against any disc image and distinguishes a truncated dump from a non-PS2 image. - .githooks/pre-commit, refusing game data from entering git history: disc image extensions, asset directories, container signatures at offset 0, and files over 50 MiB. Verified against seven cases including a disc image renamed to look like source. Now that this pushes to a remote, this matters more than it would have locally. - AGENTS.md, the operating agreements: no game data ever, reproduce the PS2 float model rather than approximating it, bound every read, verify against the real disc. - PROVENANCE.md, recording that this follows ReRAC (ISC) and learns from rac2-gc-decomp (MIT). Wrench is used as a written specification only and no GPLv3 code is copied, since that would relicense everything derived from it. RAC2Decomp is explicitly not used: its provenance is disputed in the upstream community reference, and it targets the wrong region. - docs/roadmap.md, phased so the riskiest unknowns are retired early. The two that matter: overlay relocation transfers from ReRAC and makes 27 level overlays tractable, while VU1 microcode is binary and is the hardest single item. Phase 5 is a deliberately scoped Veldin vertical slice as the go/no-go.
This commit is contained in:
Executable
+100
@@ -0,0 +1,100 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# Pre-commit guard: refuse to commit game data, disc images, or anything
|
||||||
|
# unusually large into this repository.
|
||||||
|
#
|
||||||
|
# This repository is a from-scratch Rust reimplementation. It contains NO
|
||||||
|
# game assets, NO disc images, NO BIOS dumps and NO proprietary SDK binaries.
|
||||||
|
# The build takes your own legally obtained copy of the disc as input.
|
||||||
|
#
|
||||||
|
# Hooks live in .githooks/ and are activated by `git config core.hooksPath`.
|
||||||
|
# To bypass in an emergency: git commit --no-verify
|
||||||
|
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
MAX_BYTES=$((50 * 1024 * 1024)) # 50 MiB
|
||||||
|
|
||||||
|
RED=$'\033[31m'; YEL=$'\033[33m'; RST=$'\033[0m'
|
||||||
|
errors=0
|
||||||
|
|
||||||
|
# Collect staged files (new/modified/renamed copies), NUL-separated so we
|
||||||
|
# survive spaces and odd characters in paths.
|
||||||
|
mapfile -d '' staged < <(
|
||||||
|
git diff --cached --name-only --diff-filter=ACMR -z 2>/dev/null
|
||||||
|
)
|
||||||
|
|
||||||
|
if [ ${#staged[@]} -eq 0 ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
for path in "${staged[@]}"; do
|
||||||
|
# Blobs that were removed from the worktree still exist in the object DB.
|
||||||
|
if ! [ -f "$path" ]; then
|
||||||
|
size=$(git cat-file -s ":$path" 2>/dev/null || echo 0)
|
||||||
|
else
|
||||||
|
size=$(stat -c%s "$path" 2>/dev/null || echo 0)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 1) Disc-image / asset extensions.
|
||||||
|
case "$path" in
|
||||||
|
*.iso|*.bin|*.img|*.cue|*.mdf|*.rom|*.rom1|*.rom2)
|
||||||
|
printf '%sBLOCKED%s %s\n' "$RED" "$RST" "$path"
|
||||||
|
printf ' looks like a disc image, BIOS dump or raw asset blob.\n'
|
||||||
|
errors=1
|
||||||
|
continue
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# 2) Known extracted-asset directories.
|
||||||
|
case "/$path/" in
|
||||||
|
*/extracted/*|*/assets/*|*/iso/*|*/disc/*)
|
||||||
|
printf '%sBLOCKED%s %s\n' "$RED" "$RST" "$path"
|
||||||
|
printf ' lives in an asset directory that must stay untracked.\n'
|
||||||
|
errors=1
|
||||||
|
continue
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# 3) Oversized files (catches archives and anything else we did not think of).
|
||||||
|
if [ "$size" -gt "$MAX_BYTES" ]; then
|
||||||
|
printf '%sBLOCKED%s %s (%s MiB, limit 50)\n' \
|
||||||
|
"$RED" "$RST" "$path" "$(( size / 1024 / 1024 ))"
|
||||||
|
errors=1
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 4) Content signature: ISO 9660 / WAD / CRI ADX container magic at offset 0.
|
||||||
|
# Catches disc images renamed to look like source. Compare the first five
|
||||||
|
# bytes as hex so binary bytes (NUL, 0x10, ...) cannot perturb the match.
|
||||||
|
# 2048 bytes is one PS2 logical sector: the smallest plausible asset.
|
||||||
|
if [ -f "$path" ] && [ "$size" -ge 2048 ]; then
|
||||||
|
sig=$(head -c 5 "$path" 2>/dev/null | xxd -p | tr -d '\n')
|
||||||
|
case "$sig" in
|
||||||
|
4344303031*|574144*|414458*)
|
||||||
|
printf '%sBLOCKED%s %s\n' "$RED" "$RST" "$path"
|
||||||
|
printf ' begins with a disc/asset container signature (%s).\n' "${sig:0:10}"
|
||||||
|
errors=1
|
||||||
|
continue
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$errors" -ne 0 ]; then
|
||||||
|
cat <<EOF
|
||||||
|
|
||||||
|
${RED}Commit aborted.${RST} This repository must never contain game data.
|
||||||
|
|
||||||
|
These files belong outside git (they are covered by .gitignore). Unstage them:
|
||||||
|
${YEL} git reset${RST}
|
||||||
|
and confirm with:
|
||||||
|
${YEL} git status${RST}
|
||||||
|
|
||||||
|
If something here is genuinely source code and larger than 50 MiB, that is
|
||||||
|
itself worth investigating before committing it.
|
||||||
|
|
||||||
|
EOF
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
+38
@@ -0,0 +1,38 @@
|
|||||||
|
# ── Game data: NEVER commit. This is copyrighted material and it is huge. ──
|
||||||
|
# Your legally obtained disc image and anything extracted from it.
|
||||||
|
*.iso
|
||||||
|
*.bin
|
||||||
|
*.img
|
||||||
|
*.cue
|
||||||
|
*.mdf
|
||||||
|
*.raw
|
||||||
|
extracted/
|
||||||
|
assets/
|
||||||
|
iso/
|
||||||
|
disc/
|
||||||
|
data/iso/
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# ReRAC's own output layout, in case we adopt it.
|
||||||
|
extracted/
|
||||||
|
gs_ram/
|
||||||
|
|
||||||
|
# BIOS dumps (licensed separately, not ours to redistribute).
|
||||||
|
*.rom
|
||||||
|
*.rom1
|
||||||
|
*.rom2
|
||||||
|
bios/
|
||||||
|
|
||||||
|
# ── Vendored upstream (pinned by PROVENANCE.md, not tracked) ──
|
||||||
|
vendor/
|
||||||
|
|
||||||
|
# ── Rust build output ──
|
||||||
|
/target/
|
||||||
|
**/*.rs.bk
|
||||||
|
*.pdb
|
||||||
|
|
||||||
|
# ── Editor / OS noise ──
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
# AGENTS.md — working agreements for this repository
|
||||||
|
|
||||||
|
Guidance for coding agents and for anyone picking this up cold. Read this
|
||||||
|
before changing anything.
|
||||||
|
|
||||||
|
## What this project is
|
||||||
|
|
||||||
|
A from-scratch native PC reimplementation of **Ratchet & Clank: Going Commando**
|
||||||
|
(PlayStation 2, 2003), written in Rust. The game is **reimplemented**, not
|
||||||
|
emulated and not translated: there is no MIPS interpreter and no CPU
|
||||||
|
emulator anywhere in this repository.
|
||||||
|
|
||||||
|
The goal is a build that plays the game the way the original does. "Close
|
||||||
|
enough" is not the bar — the point is that it feels like the game you remember.
|
||||||
|
|
||||||
|
## Hard rules
|
||||||
|
|
||||||
|
### 1. No game data in this repository. Ever.
|
||||||
|
|
||||||
|
Not the ISO, not extracted assets, not textures, not models, not audio, not
|
||||||
|
BIOS dumps, not SDK binaries. Not in a branch, not in a stash, not "just for a
|
||||||
|
test".
|
||||||
|
|
||||||
|
- Everything is covered by `.gitignore`.
|
||||||
|
- `.githooks/pre-commit` blocks disc images, asset directories, container
|
||||||
|
signatures and files over 50 MiB. It is activated via
|
||||||
|
`git config core.hooksPath .githooks` — **check that this is still set** in a
|
||||||
|
fresh clone, or run `git config core.hooksPath .githooks`.
|
||||||
|
- To undo the hook for a legitimate source change: `git commit --no-verify`.
|
||||||
|
Legitimate source changes should not need it.
|
||||||
|
- Game data is an **input**, supplied by the user from their own legally
|
||||||
|
obtained disc. Repositories that need the disc state its serial and hashes.
|
||||||
|
|
||||||
|
### 2. Reproduce the PS2 float model.
|
||||||
|
|
||||||
|
The Emotion Engine's FPU is not IEEE-754. It has **no denormals**, and rounding
|
||||||
|
and exception behaviour differ from x86 in ways that are visible in gameplay.
|
||||||
|
|
||||||
|
Gameplay and animation maths must model this deliberately rather than using
|
||||||
|
Rust's native `f32` and hoping. When porting a routine, find out how the
|
||||||
|
original computes it and reproduce that, including the odd cases — ReRAC's
|
||||||
|
reference implementation deliberately reproduces a "post-scale list bug" in the
|
||||||
|
moby animation evaluator because doing so is what makes the result match.
|
||||||
|
|
||||||
|
Do not "fix" something that looks like a bug until you have confirmed the
|
||||||
|
original does the same thing. Fidelity beats correctness.
|
||||||
|
|
||||||
|
### 3. Verify against the real disc, not against a guess.
|
||||||
|
|
||||||
|
A parser is correct when it round-trips your own disc's bytes, checked by hash.
|
||||||
|
Commit size-and-hash tables (never content) so a test can assert "this matches
|
||||||
|
retail" rather than "this matches whatever was on my disk".
|
||||||
|
|
||||||
|
### 4. Bound every read.
|
||||||
|
|
||||||
|
Asset data is untrusted input from a 2003 disc format. Parsers return typed
|
||||||
|
errors; they never panic and never read past the end of a buffer.
|
||||||
|
|
||||||
|
## Orientation
|
||||||
|
|
||||||
|
Read these before working in an unfamiliar area:
|
||||||
|
|
||||||
|
| Path | What it is |
|
||||||
|
|---|---|
|
||||||
|
| `docs/disc-identification.md` | Which disc we build against, verified |
|
||||||
|
| `docs/roadmap.md` | Phases, gates, and what is done |
|
||||||
|
| `docs/plan/` | One file per subsystem, written **as work proceeds** |
|
||||||
|
| `docs/formats/` | Format specs, written **as formats are reversed** |
|
||||||
|
| `PROVENANCE.md` | Upstream projects we build on, and their licences |
|
||||||
|
| `vendor/reRAC/` | Pinned reference checkout (not tracked) |
|
||||||
|
|
||||||
|
## Crates
|
||||||
|
|
||||||
|
| Crate | Responsibility |
|
||||||
|
|---|---|
|
||||||
|
| `gc-formats` | Parse asset formats into plain Rust values. No I/O beyond the caller's buffer. |
|
||||||
|
| `gc-extract` | Disc pipeline: identify, unpack, verify. |
|
||||||
|
| `gc-game` | Game logic: hero, weapons, classes, progression, saves. |
|
||||||
|
| `gc-video` | IPU/MPEG-2 decode. |
|
||||||
|
| `gc-engine` | Bevy app: GS, VU0/VU1, renderer, frame loop. |
|
||||||
|
|
||||||
|
The dependency direction is `gc-formats` ← `gc-extract`; `gc-formats` ←
|
||||||
|
`gc-game` ← `gc-engine`; `gc-video` ← `gc-game` ← `gc-engine`. `gc-formats`
|
||||||
|
depends on nothing of ours. Do not invert these — a format parser that can
|
||||||
|
reach into the engine cannot be tested in isolation.
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
|
||||||
|
- Rust 2021 edition, workspace inheritance for all shared config. Do not
|
||||||
|
hardcode a version in a member crate.
|
||||||
|
- `rustfmt` defaults. Clippy warnings are errors in spirit; do not silence them
|
||||||
|
without a comment explaining why.
|
||||||
|
- Error types: `thiserror` for library errors in `gc-formats` and `gc-game`,
|
||||||
|
`anyhow` at the binary/`xtask` boundary.
|
||||||
|
- Comments explain **why**, especially for anything that looks like a bug. The
|
||||||
|
`.githooks` and crate docs deliberately have few inline comments; match the
|
||||||
|
surrounding density rather than adding noise.
|
||||||
|
- Tests live beside the code (`#[cfg(test)] mod tests`) for unit-level checks,
|
||||||
|
and in `tests/` for golden-file checks against real disc data.
|
||||||
|
|
||||||
|
## Working rhythm
|
||||||
|
|
||||||
|
This is a long project. Keep it navigable:
|
||||||
|
|
||||||
|
1. Work in small, coherent commits with a substantive body explaining what was
|
||||||
|
non-obvious. Match the existing style in `git log`.
|
||||||
|
2. **Update `docs/roadmap.md`** when a phase advances or a gate is met.
|
||||||
|
3. **Write the `docs/plan/*.md` file for a subsystem when you start it**, and
|
||||||
|
update it as you learn. The reasoning is more valuable than the code; the
|
||||||
|
code is in git.
|
||||||
|
4. **Update this file** if you change a convention.
|
||||||
|
|
||||||
|
## When you get stuck
|
||||||
|
|
||||||
|
Reverse engineering means long stretches of not knowing. That is normal, not a
|
||||||
|
failure. Write down what you tried and what it ruled out in the relevant
|
||||||
|
`docs/plan/` file before moving on — the next attempt (which may be you, in
|
||||||
|
three months) needs the negative results as much as the positive ones.
|
||||||
|
|
||||||
|
If something is genuinely blocked, say so plainly and say what would unblock
|
||||||
|
it. Do not paper over a gap with an approximation that looks like it works.
|
||||||
|
|
||||||
|
## Legal
|
||||||
|
|
||||||
|
Ratchet & Clank is a trademark of Sony Interactive Entertainment, developed by
|
||||||
|
Insomniac Games. This is an unofficial fan project, not affiliated with or
|
||||||
|
endorsed by either. We redistribute no game content. Upstream projects we learn
|
||||||
|
from keep their own licences — see `PROVENANCE.md`, and keep those notices
|
||||||
|
intact when porting their code.
|
||||||
Generated
+5563
File diff suppressed because it is too large
Load Diff
+82
@@ -0,0 +1,82 @@
|
|||||||
|
[workspace]
|
||||||
|
resolver = "2"
|
||||||
|
members = [
|
||||||
|
"crates/gc-formats",
|
||||||
|
"crates/gc-extract",
|
||||||
|
"crates/gc-engine",
|
||||||
|
"crates/gc-game",
|
||||||
|
"crates/gc-video",
|
||||||
|
]
|
||||||
|
|
||||||
|
[workspace.package]
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
rust-version = "1.85"
|
||||||
|
license = "MIT OR ISC"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
# The PS2's Emotion Engine is a MIPS R5900 with a 128-bit vector unit and a
|
||||||
|
# float model that differs from x86 in ways that matter for this game (no
|
||||||
|
# denormals, specific rounding). Every ported routine must reproduce that model
|
||||||
|
# rather than Rust's native f32, or the game will subtly not feel right.
|
||||||
|
# See docs/plan/float-model.md.
|
||||||
|
[workspace.dependencies]
|
||||||
|
gc-formats = { path = "crates/gc-formats" }
|
||||||
|
gc-extract = { path = "crates/gc-extract" }
|
||||||
|
gc-game = { path = "crates/gc-game" }
|
||||||
|
gc-video = { path = "crates/gc-video" }
|
||||||
|
|
||||||
|
anyhow = "1"
|
||||||
|
thiserror = "2"
|
||||||
|
bytemuck = { version = "1", features = ["derive", "min_const_generics"] }
|
||||||
|
sha1 = "0.10"
|
||||||
|
|
||||||
|
# Bevy 0.19.1 is the engine ReRAC uses; matching it keeps the GS/VU work portable.
|
||||||
|
#
|
||||||
|
# default-features = false avoids the cpal audio backend, which needs system
|
||||||
|
# ALSA development headers on Fedora (alsa-lib-devel). Audio decode lives in
|
||||||
|
# gc-video and is the last phase of the port, so the host audio backend is not
|
||||||
|
# needed to build. Re-enable "audio" and install alsa-lib-devel when it is.
|
||||||
|
bevy = { version = "0.19.1", default-features = false, features = [
|
||||||
|
"std",
|
||||||
|
"bevy_asset",
|
||||||
|
"bevy_color",
|
||||||
|
"bevy_core_pipeline",
|
||||||
|
"bevy_gizmos",
|
||||||
|
"bevy_log",
|
||||||
|
"bevy_mesh",
|
||||||
|
"bevy_pbr",
|
||||||
|
"bevy_render",
|
||||||
|
"bevy_shader",
|
||||||
|
"bevy_sprite",
|
||||||
|
"bevy_state",
|
||||||
|
"bevy_text",
|
||||||
|
"bevy_ui",
|
||||||
|
"bevy_window",
|
||||||
|
"bevy_winit",
|
||||||
|
"bevy_animation",
|
||||||
|
"bevy_gltf",
|
||||||
|
"png",
|
||||||
|
"ktx2",
|
||||||
|
"zlib",
|
||||||
|
"zstd_rust",
|
||||||
|
"tonemapping_luts",
|
||||||
|
"multi_threaded",
|
||||||
|
"x11",
|
||||||
|
"wayland",
|
||||||
|
"keyboard",
|
||||||
|
"mouse",
|
||||||
|
"gamepad",
|
||||||
|
] }
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
# Iterate fast on our own code; do not rebuild dependencies unoptimised.
|
||||||
|
opt-level = 1
|
||||||
|
|
||||||
|
[profile.dev.package."*"]
|
||||||
|
opt-level = 3
|
||||||
|
debug = false
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
lto = "thin"
|
||||||
|
codegen-units = 1
|
||||||
+120
@@ -0,0 +1,120 @@
|
|||||||
|
# PROVENANCE
|
||||||
|
|
||||||
|
This project is built on the work of other people. Their licences apply to their
|
||||||
|
code, and those notices travel with anything derived from it.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
| Project | Role | Licence | Code copied into this repo? |
|
||||||
|
|---|---|---|---|
|
||||||
|
| [ReRAC](https://github.com/re-rac/rerac) | Native Rust/Bevy port of Ratchet & Clank (2002) — the architecture this follows | ISC | Not yet; derived from, attribute when used |
|
||||||
|
| [OpenRAC / rac2-gc-decomp](https://github.com/OpenRAC/rac2-gc-decomp) | Matching decompilation research for this game | MIT | No — consulted for documentation |
|
||||||
|
| [rac2-gc-decomp community reference](https://github.com/OpenRAC/rac2-gc-decomp/blob/RAC2/docs/COMMUNITY-ENGINE-REFERENCE.md) | Engine architecture notes, moby struct layout | MIT | Documentation only |
|
||||||
|
| [Wrench](https://github.com/chaoticgd/wrench) | Insomniac container format documentation | GPLv3 | **No.** Specification reference only |
|
||||||
|
| [RAC2Decomp](https://github.com/platypet2217-star/RAC2Decomp) | Functional decompilation, PAL | MIT | **Not used** — see below |
|
||||||
|
|
||||||
|
## Game content: none
|
||||||
|
|
||||||
|
No game assets, disc images, extracted data, BIOS dumps or proprietary
|
||||||
|
toolchains are or may be committed to this repository. The build takes the
|
||||||
|
user's own legally obtained disc as input. This is enforced by
|
||||||
|
`.githooks/pre-commit`.
|
||||||
|
|
||||||
|
Ratchet & Clank is a trademark of Sony Interactive Entertainment. This is an
|
||||||
|
unofficial fan project, not affiliated with or endorsed by Sony Interactive
|
||||||
|
Entertainment or Insomniac Games.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ReRAC — ISC
|
||||||
|
|
||||||
|
The direct model for this project. ReRAC re-implements the *first* Ratchet &
|
||||||
|
Clank as a native Rust application on Bevy, reading assets from the player's own
|
||||||
|
disc. This project follows the same architecture for the second game.
|
||||||
|
|
||||||
|
What we take from ReRAC:
|
||||||
|
|
||||||
|
- **The architecture.** Crate split (`formats` / `extract` / `game` / `engine` /
|
||||||
|
`video`), Bevy 0.19.1, the PS2 float-model discipline, and the milestone
|
||||||
|
gating style.
|
||||||
|
- **Overlay relocation.** `rc-formats::level_overlay` implements
|
||||||
|
relocation-tolerant code-identity matching so one level's function can be
|
||||||
|
found on every other level. R&C2's 27 overlays have the same seven-section
|
||||||
|
layout (`.lit`, `.bss`, `.data`, `lvl.vtbl`, `lvl.camvtbl`, `lvl.sndvtbl`,
|
||||||
|
`.text`), so this machinery transfers directly. It is the single most
|
||||||
|
valuable thing we inherit.
|
||||||
|
- **Verified format specs.** `docs/formats/disc_layout.md` documents all four
|
||||||
|
R&C games' container layouts, derived from behavioural analysis.
|
||||||
|
- **Discipline worth copying.** Golden tests against committed snapshot hashes;
|
||||||
|
bit-exact CPU/GPU identity checks; documenting negative results.
|
||||||
|
|
||||||
|
ISC permits use, modification and redistribution with the notice intact. When
|
||||||
|
code is derived from ReRAC it must carry:
|
||||||
|
|
||||||
|
```
|
||||||
|
Copyright (c) 2026 ReRAC contributors
|
||||||
|
Licensed under the ISC License.
|
||||||
|
```
|
||||||
|
|
||||||
|
Pinned checkout: `vendor/rerac` at commit
|
||||||
|
`d00cb00ad4018f776556e72640dcba438d098a77` (2026-10-09). Not tracked by git —
|
||||||
|
clone it yourself; the commit is recorded so everyone uses the same one.
|
||||||
|
|
||||||
|
Note that ReRAC targets a *different game*. Its per-level documentation is a
|
||||||
|
useful hint about Insomniac's engine in general, but R&C2's spherical worlds
|
||||||
|
changed roughly 50,000 lines of gameplay code, so nothing about R&C1's
|
||||||
|
movement, camera or level geometry should be treated as directly applicable.
|
||||||
|
|
||||||
|
## OpenRAC / rac2-gc-decomp — MIT
|
||||||
|
|
||||||
|
Matching decompilation research for **this** game, targeting exactly our disc
|
||||||
|
(`SCUS_972.68` v1.01). It reconstructs the boot ELF and all 27 level overlays
|
||||||
|
byte-for-byte, and publishes verified struct layouts, symbol names and engine
|
||||||
|
documentation.
|
||||||
|
|
||||||
|
We use it as a **reference**, not as source. Its value is that it tells us what
|
||||||
|
the structures *are*, which saves a great deal of guessing.
|
||||||
|
|
||||||
|
Its matching progress is low (~1.6% of code in C) and byte-matching is not a
|
||||||
|
prerequisite for this port anyway — it is a verification technique. We are not
|
||||||
|
pursuing a byte-identical rebuild.
|
||||||
|
|
||||||
|
## Wrench — GPLv3, specification reference only
|
||||||
|
|
||||||
|
Community documentation and tooling for Insomniac's container formats. We use
|
||||||
|
the written format *specifications* as a reference for how the data is laid
|
||||||
|
out.
|
||||||
|
|
||||||
|
**No Wrench code is copied into this repository.** That is deliberate and
|
||||||
|
load-bearing: copying GPLv3 code would impose GPLv3 terms on everything derived
|
||||||
|
from it. Clean-room reimplementation from a written specification does not. If
|
||||||
|
this boundary is ever crossed, the licensing of this project changes and
|
||||||
|
`AGENTS.md` must be updated to say so.
|
||||||
|
|
||||||
|
## RAC2Decomp — not used
|
||||||
|
|
||||||
|
A functional (non-byte-matching) decompilation of the PAL release. It is
|
||||||
|
recorded here only so its absence is a decision rather than an oversight.
|
||||||
|
|
||||||
|
The `rac2-gc-decomp` community reference explicitly flags it as *"criticized by
|
||||||
|
community for questionable provenance and non-clean-room C code. Avoid
|
||||||
|
copying."* Given that we are building a reimplementation and its provenance is
|
||||||
|
disputed, we do not use it. It is also the wrong region: PAL, 50 Hz.
|
||||||
|
|
||||||
|
## Third-party libraries
|
||||||
|
|
||||||
|
Rust dependencies keep their own licences; see `Cargo.lock` and each crate's
|
||||||
|
`LICENSE` file. Notable ones:
|
||||||
|
|
||||||
|
- **Bevy** (MIT / Apache-2.0) — application framework and wgpu renderer.
|
||||||
|
- **wgpu / wgpu-hal / wgpu-types** (MIT / Apache-2.0) — GPU abstraction used for
|
||||||
|
the Graphics Synthesizer reimplementation.
|
||||||
|
- **bytemuck** (Zlib OR Apache-2.0 OR MIT) — zero-copy casts over asset
|
||||||
|
structures.
|
||||||
|
- **thiserror**, **anyhow** (MIT OR Apache-2.0) — error handling.
|
||||||
|
|
||||||
|
## When you add a dependency or port code
|
||||||
|
|
||||||
|
1. Record it in this file, with its licence and what you took.
|
||||||
|
2. Keep the upstream notice intact in any file containing derived code.
|
||||||
|
3. If it would change this project's licensing terms, stop and say so first.
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
# Going Commando
|
||||||
|
|
||||||
|
A native PC reimplementation of **Ratchet & Clank: Going Commando** (PlayStation
|
||||||
|
2, 2003), written in Rust.
|
||||||
|
|
||||||
|
The game is reimplemented, not emulated. There is no MIPS interpreter here and
|
||||||
|
no CPU emulator: the systems are ported to Rust so they run as ordinary native
|
||||||
|
code.
|
||||||
|
|
||||||
|
**Status: initial scaffold.** The disc is identified and verified, the
|
||||||
|
workspace builds, and nothing else is implemented yet. See
|
||||||
|
[`docs/roadmap.md`](docs/roadmap.md) for where things stand and what comes next.
|
||||||
|
|
||||||
|
## The target
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| Game | Ratchet & Clank: Going Commando |
|
||||||
|
| Platform | PlayStation 2 |
|
||||||
|
| Disc | `SCUS_972.68` v1.01, USA Black Label |
|
||||||
|
| Video | NTSC, 60 Hz |
|
||||||
|
| Boot ELF SHA-1 | `bacda860cbd6723142e86db56f197e47e728970f` |
|
||||||
|
|
||||||
|
Verified against a real disc — see [`docs/disc-identification.md`](docs/disc-identification.md).
|
||||||
|
This is the build the reference projects pin; Greatest Hits v2.00 is a
|
||||||
|
different binary and is not the target.
|
||||||
|
|
||||||
|
## No game content
|
||||||
|
|
||||||
|
This repository contains **no game assets, no disc images, no BIOS dumps and no
|
||||||
|
proprietary toolchains**. You supply your own legally obtained copy of the disc
|
||||||
|
as an input, the way the upstream reference projects do.
|
||||||
|
|
||||||
|
A pre-commit hook enforces this, blocking disc images, asset directories,
|
||||||
|
container signatures and files over 50 MiB. If you clone elsewhere, enable it:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git config core.hooksPath .githooks
|
||||||
|
```
|
||||||
|
|
||||||
|
## Layout
|
||||||
|
|
||||||
|
```
|
||||||
|
crates/
|
||||||
|
gc-formats/ asset format parsers (pure, no I/O beyond the caller's buffer)
|
||||||
|
gc-extract/ disc pipeline: identify, unpack, verify
|
||||||
|
gc-game/ game logic: hero, weapons, moby classes, progression, saves
|
||||||
|
gc-video/ IPU / MPEG-2 decode
|
||||||
|
gc-engine/ Bevy app: Graphics Synthesizer, VU0/VU1, renderer, frame loop
|
||||||
|
docs/
|
||||||
|
disc-identification.md which disc we build against, verified
|
||||||
|
roadmap.md phases, gates, current status
|
||||||
|
plan/ one file per subsystem, written as work proceeds
|
||||||
|
formats/ format specs, written as formats are reversed
|
||||||
|
tools/
|
||||||
|
identify-disc.py identify any Going Commando disc image
|
||||||
|
vendor/
|
||||||
|
rerac/ pinned upstream reference checkout (not tracked)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
Requires a Rust toolchain via [rustup](https://rustup.rs).
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo check --workspace
|
||||||
|
cargo test --workspace
|
||||||
|
```
|
||||||
|
|
||||||
|
The host audio backend is disabled in `Cargo.toml` (see the comment there) so
|
||||||
|
that the workspace builds without system ALSA development headers. Audio decode
|
||||||
|
is the last phase of the port and lives in `gc-video`; re-enable it and install
|
||||||
|
`alsa-lib-devel` when that work starts.
|
||||||
|
|
||||||
|
## Identifying a disc
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python3 tools/identify-disc.py <your-disc.iso>
|
||||||
|
```
|
||||||
|
|
||||||
|
Reports the serial, version and video mode from `SYSTEM.CNF`, hashes the boot
|
||||||
|
executable, and warns when the disc is not the supported build. Reads only a
|
||||||
|
few megabytes, so it is near-instant.
|
||||||
|
|
||||||
|
## How it will be verified
|
||||||
|
|
||||||
|
Visual and behavioural comparison against PCSX2, replaying recorded input and
|
||||||
|
comparing frames, level by level. The PS2 float model is reproduced rather than
|
||||||
|
approximated — see [`AGENTS.md`](AGENTS.md#2-reproduce-the-ps2-float-model).
|
||||||
|
|
||||||
|
## Credits and licensing
|
||||||
|
|
||||||
|
This project learns from and is built on the work of others. Licences are
|
||||||
|
recorded in [`PROVENANCE.md`](PROVENANCE.md) and notices must travel with any
|
||||||
|
code derived from them.
|
||||||
|
|
||||||
|
- **ReRAC** — a native Rust/Bevy port of *Ratchet & Clank* (2002), the first
|
||||||
|
game. ISC. The architecture this project follows.
|
||||||
|
- **OpenRAC / rac2-gc-decomp** — matching decompilation research for this
|
||||||
|
game. MIT. Source of verified struct layouts and engine documentation.
|
||||||
|
- **Wrench** — community documentation of Insomniac's container formats. GPL,
|
||||||
|
used as a specification reference only; no code is copied.
|
||||||
|
|
||||||
|
Ratchet & Clank is a trademark of Sony Interactive Entertainment. This is an
|
||||||
|
unofficial fan project, not affiliated with or endorsed by Sony Interactive
|
||||||
|
Entertainment or Insomniac Games.
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
[package]
|
||||||
|
name = "gc-engine"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
publish.workspace = true
|
||||||
|
description = "Bevy application: Graphics Synthesizer, VU0/VU1 and the renderer"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
|
gc-formats.workspace = true
|
||||||
|
gc-game.workspace = true
|
||||||
|
bevy.workspace = true
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
//! The application: renderer, hardware models, and the frame loop.
|
||||||
|
//!
|
||||||
|
//! Reimplements the pieces of the PlayStation 2 that the game talks to, so the
|
||||||
|
//! game logic in `gc-game` can be written as ordinary Rust that draws and
|
||||||
|
//! sounds like the original.
|
||||||
|
//!
|
||||||
|
//! # Hardware being modelled
|
||||||
|
//!
|
||||||
|
//! * **Graphics Synthesizer (GS)** - fixed-function rasteriser. No shaders on
|
||||||
|
//! the real hardware: the GS does texture mapping, alpha and blend rules,
|
||||||
|
//! and z/stencil. Here that becomes wgpu plus explicit handling of the GS's
|
||||||
|
//! own rules (its `round(log2(z/n) + K)` mip selection, its blend and alpha
|
||||||
|
//! test behaviour, its alpha-0x80-is-1.0 convention).
|
||||||
|
//! * **VU0 / VU1** - vector units running microcode uploaded from the disc.
|
||||||
|
//! Skinning, lighting and transform setup live here. The microcode is
|
||||||
|
//! **binary** in the game's `.vutext` section, so it is reverse engineered
|
||||||
|
//! rather than parsed: this is the single hardest item in the project.
|
||||||
|
//! * **DMA and GIF/VIF packets** - the game streams geometry to the GS through
|
||||||
|
//! tag-split packet lists. Those packet formats are the input to rendering.
|
||||||
|
//! * **SPU2 / IOP** - audio and the disc interface, in `gc-video` and
|
||||||
|
//! `gc-extract` respectively.
|
||||||
|
//!
|
||||||
|
//! # Divergence from the first game
|
||||||
|
//!
|
||||||
|
//! R&C2's levels are spherical worlds, so camera, culling, fog and the sky are
|
||||||
|
//! all effectively rewritten compared with Ratchet & Clank (2002). Geometry
|
||||||
|
//! subsystems carry over in shape - `tfrag`, `tie`, `shrub`, `moby` are the
|
||||||
|
//! same four - but their behaviour on a sphere is new work.
|
||||||
|
//!
|
||||||
|
//! # Reference frame
|
||||||
|
//!
|
||||||
|
//! The port is validated against PCSX2 by replaying recorded input and
|
||||||
|
//! comparing frames. Without that loop, "looks close" and "is correct" drift
|
||||||
|
//! apart, and this game's difficulty and movement make that drift very
|
||||||
|
//! noticeable.
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
[package]
|
||||||
|
name = "gc-extract"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
publish.workspace = true
|
||||||
|
description = "Disc pipeline: identify, unpack and verify game data from a Going Commando disc image"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
|
gc-formats.workspace = true
|
||||||
|
sha1.workspace = true
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
//! Disc pipeline for *Ratchet & Clank: Going Commando* (PS2).
|
||||||
|
//!
|
||||||
|
//! Turns a legally obtained disc image into verified, unpacked asset files that
|
||||||
|
//! the engine can read directly, and does nothing else.
|
||||||
|
//!
|
||||||
|
//! # Stages
|
||||||
|
//!
|
||||||
|
//! 1. **identify** - read `SYSTEM.CNF`, confirm serial/version/video mode and
|
||||||
|
//! hash the boot ELF. Reports clearly when a disc is not the supported
|
||||||
|
//! build. A Python reference implementation of this stage lives in
|
||||||
|
//! `tools/identify-disc.py`.
|
||||||
|
//! 2. **unpack** - read `RC2.HDR`, walk the table of contents, decompress WAD
|
||||||
|
//! streams and write per-level and global asset directories.
|
||||||
|
//! 3. **verify** - check every extracted file against a recorded size and
|
||||||
|
//! SHA-1 table, so a corrupted or mismatched dump cannot quietly produce a
|
||||||
|
//! subtly wrong game.
|
||||||
|
//!
|
||||||
|
//! # Output layout
|
||||||
|
//!
|
||||||
|
//! Extracted data is written outside the repository (see `.gitignore`). The
|
||||||
|
//! pre-commit hook in `.githooks/` refuses disc images, asset directories and
|
||||||
|
//! oversized files, so nothing here can end up in git history by accident.
|
||||||
|
//!
|
||||||
|
//! # Verification is not optional
|
||||||
|
//!
|
||||||
|
//! Every parser downstream is only as trustworthy as the bytes it was handed.
|
||||||
|
//! A hash table committed to the repo (sizes and hashes only, never content)
|
||||||
|
//! is what lets a test say "this parse matches the retail disc" rather than
|
||||||
|
//! "this parse matches whatever happened to be on my disk".
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
[package]
|
||||||
|
name = "gc-formats"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
publish.workspace = true
|
||||||
|
description = "Parsers for Ratchet & Clank: Going Commando (PS2) asset formats"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
|
bytemuck.workspace = true
|
||||||
|
sha1.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
//! Asset format parsers for *Ratchet & Clank: Going Commando* (PS2).
|
||||||
|
//!
|
||||||
|
//! Target: `SCUS_972.68` v1.01, USA Black Label, NTSC 60 Hz. See
|
||||||
|
//! `docs/disc-identification.md`.
|
||||||
|
//!
|
||||||
|
//! This crate reads data structures out of the game and produces plain Rust
|
||||||
|
//! values. It performs no rendering, no simulation and no I/O beyond what the
|
||||||
|
//! caller hands it, which keeps every parser testable against a byte slice.
|
||||||
|
//!
|
||||||
|
//! # What belongs here
|
||||||
|
//!
|
||||||
|
//! * `iso9660` - the disc filesystem. R&C2 lists its assets as real files,
|
||||||
|
//! unlike R&C1, so enumeration works.
|
||||||
|
//! * `hdr` - `RC2.HDR`, the table of contents (408,444 bytes at the
|
||||||
|
//! disc root). The engine addresses assets by sector, and
|
||||||
|
//! this is where those sector numbers come from.
|
||||||
|
//! * `wad` - Insomniac's LZ77 container (`WAD` magic, 16-byte header).
|
||||||
|
//! Carries level core data, audio and video streams.
|
||||||
|
//! * `tfrag` - terrain fragments: LOD chains, strips, morph targets.
|
||||||
|
//! * `tie` - instanced environment geometry ("ties"), with fat vertices.
|
||||||
|
//! * `shrub` - instanced foliage and decorative meshes.
|
||||||
|
//! * `moby` - dynamic actors, including Ratchet, enemies and crates.
|
||||||
|
//! * `moby_anim` - skeletal animation: sequences, frames, blending.
|
||||||
|
//! * `collision` - the collision world: cells, packed vertices, faces.
|
||||||
|
//! * `occlusion` - the PVS grid used for visibility.
|
||||||
|
//! * `sky`, `volumes`, `textures` - sky shells, fog volumes, CSM1 textures.
|
||||||
|
//!
|
||||||
|
//! # Rules
|
||||||
|
//!
|
||||||
|
//! **Bound every read.** Malformed data must produce a typed error, never a
|
||||||
|
//! panic and never a read past the end of the buffer.
|
||||||
|
//!
|
||||||
|
//! **No game data in this repository.** Parsers are written against a copy of
|
||||||
|
//! your own legally obtained disc, and the extracted output stays outside git.
|
||||||
|
//!
|
||||||
|
//! **Verify against the real thing.** Every parser gets a test that loads
|
||||||
|
//! known-good bytes from your disc and checks the parsed result. See
|
||||||
|
//! `docs/formats/` for the specs being implemented.
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
[package]
|
||||||
|
name = "gc-game"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
publish.workspace = true
|
||||||
|
description = "Game logic: hero controller, weapons, moby classes, progression and saves"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
|
gc-formats.workspace = true
|
||||||
|
gc-video.workspace = true
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
//! Game logic for *Ratchet & Clank: Going Commando* (PS2).
|
||||||
|
//!
|
||||||
|
//! The rules, systems and behaviour of the game, reimplemented from the
|
||||||
|
//! original code rather than wrapped around it. No emulation, no interpreter:
|
||||||
|
//! this is a Rust implementation that is meant to behave the way the game does.
|
||||||
|
//!
|
||||||
|
//! # What belongs here
|
||||||
|
//!
|
||||||
|
//! * `hero` - Ratchet's controller. Movement on a **spherical** planet,
|
||||||
|
//! which is what distinguishes this game: reaching for the
|
||||||
|
//! camera and the collision code changes gravity handling
|
||||||
|
//! across the whole world, and is the main reason the first
|
||||||
|
//! game's movement code does not transfer.
|
||||||
|
//! * `chargeboots`- hold and release timing, chained jumps and the dash.
|
||||||
|
//! * `weapons` - the wrench and the weapon set, plus the weapon modification
|
||||||
|
//! system (per-weapon bitmasks: lock-on, shock, acid, ...).
|
||||||
|
//! * `gadgets` - the Pyrocitor, Bomb Glove, Glove of Doom and friends.
|
||||||
|
//! * `moby_update`- the per-class update dispatch, driven by each level's
|
||||||
|
//! `lvl.vtbl` class table. Classes that are identical across
|
||||||
|
//! levels are shared; the rest are resolved through the
|
||||||
|
//! overlay relocator in `gc-formats`.
|
||||||
|
//! * `collision` - queries against the collision world.
|
||||||
|
//! * `progression`- bolts, the Gadgetron vendor, RPG upgrades and unlocks.
|
||||||
|
//! * `save_game` - the IFF chunk save format, and importing a Ratchet & Clank
|
||||||
|
//! (2002) save to unlock the legacy weapons for free.
|
||||||
|
//! * `memcard` - the 25-state memory-card finite state machine and its UI.
|
||||||
|
//!
|
||||||
|
//! # The float model
|
||||||
|
//!
|
||||||
|
//! The Emotion Engine's FPU is not IEEE-754 in every respect: it has no
|
||||||
|
//! denormals, and rounding differs in places. Gameplay code here reproduces
|
||||||
|
//! that model rather than using Rust's native `f32`, because the difference is
|
||||||
|
//! visible in the movement and the camera. See `docs/plan/float-model.md`.
|
||||||
|
//!
|
||||||
|
//! # Timing
|
||||||
|
//!
|
||||||
|
//! The game is 60 Hz (this disc is NTSC). Constants are expressed per tick at
|
||||||
|
//! that rate. PAL discs run 50 Hz and would need different timing throughout.
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
[package]
|
||||||
|
name = "gc-video"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
publish.workspace = true
|
||||||
|
description = "IPU/MPEG-2 video stream decoding for Ratchet & Clank: Going Commando"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
//! Video decoding for *Ratchet & Clank: Going Commando* (PS2).
|
||||||
|
//!
|
||||||
|
//! R&C2 uses the Emotion Engine's **IPU** to stream MPEG-2 video, most visibly
|
||||||
|
//! on the title screen where Ratchet watches television. This crate decodes
|
||||||
|
//! those streams to raw frames for the host GPU to upload.
|
||||||
|
//!
|
||||||
|
//! # Note on scale
|
||||||
|
//!
|
||||||
|
//! The MPEG WADs on this disc total roughly 2.4 GB (about 1.0 GB, 1.3 GB and
|
||||||
|
//! 115 MB). Video is a data problem before it is a decoding problem, so the
|
||||||
|
//! pipeline needs to stream rather than hold a whole movie in memory.
|
||||||
|
//!
|
||||||
|
//! # Pipeline
|
||||||
|
//!
|
||||||
|
//! `bits.rs` -> `vlc.rs` -> `idct.rs` -> `mpeg2.rs`
|
||||||
|
//!
|
||||||
|
//! MPEG-2 decoding is a solved, well-specified problem, and a general-purpose
|
||||||
|
//! decoder will produce correct output. The PS2-specific part is only the
|
||||||
|
//! container: how the streams are stored and addressed on the disc.
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
# Disc identification — VERIFIED 2026-10-09
|
||||||
|
|
||||||
|
Your disc was identified while initialising this repository. It is the **best
|
||||||
|
case target**: the exact build that `OpenRAC/rac2-gc-decomp` pins.
|
||||||
|
|
||||||
|
## Result
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
|---|---|
|
||||||
|
| Title | Ratchet & Clank: Going Commando |
|
||||||
|
| Serial | `SCUS_972.68` |
|
||||||
|
| Version | **1.01** (Black Label) |
|
||||||
|
| Video mode | **NTSC** (60 Hz) |
|
||||||
|
| Region | USA |
|
||||||
|
| ISO 9660 volume | `RATCHETANDCLANK2` |
|
||||||
|
| Image size | 3,828,350,976 bytes |
|
||||||
|
| Publisher / Preparer | SONY / INSOMNIAC |
|
||||||
|
|
||||||
|
### Boot executable (`SCUS_972.68`)
|
||||||
|
|
||||||
|
| Property | Value |
|
||||||
|
|---|---|
|
||||||
|
| Size | 2,618,684 bytes |
|
||||||
|
| SHA-1 | `bacda860cbd6723142e86db56f197e47e728970f` |
|
||||||
|
| CRC-32 | `262705496` |
|
||||||
|
| Format | ELF32, little-endian, MIPS (EM_MIPS = 8) |
|
||||||
|
| Entry point | `0x00131AE8` |
|
||||||
|
|
||||||
|
The entry point `0x00131AE8` matches the value independently recorded by the
|
||||||
|
`rac2-gc-decomp` community reference for this target, which cross-confirms the
|
||||||
|
identification.
|
||||||
|
|
||||||
|
## Why this matters
|
||||||
|
|
||||||
|
**`VER = 1.01` + `VMODE = NTSC` is exactly what we want.**
|
||||||
|
|
||||||
|
- **v1.01 is the pinned target.** `rac2-gc-decomp` states plainly that
|
||||||
|
*Greatest Hits v2.00 is a different target — do not point a build at it.*
|
||||||
|
You have the Black Label original.
|
||||||
|
- **NTSC means 60 Hz.** This is the detail that decides the movement constants.
|
||||||
|
PAL discs report `VMODE = PAL` and run the game at 50 Hz, which makes the
|
||||||
|
chargeboots and other speed-sensitive movement run at 5/6 of their intended
|
||||||
|
speed. Building against NTSC timing constants is correct for this disc.
|
||||||
|
|
||||||
|
There is no ambiguity to resolve, so no disc-conversion work is required.
|
||||||
|
|
||||||
|
## Not used for identification
|
||||||
|
|
||||||
|
Redump's catalogue records the **first track** hash for this disc as
|
||||||
|
`f2cfaf0eb12b3c0d8fd6db7aa49ce032e8a6020b` (CRC-32 `3828350976`). The values
|
||||||
|
computed above are the **boot executable's** hash, which is a different thing:
|
||||||
|
a single file rather than a track. Both are stable identifiers, but they must
|
||||||
|
not be compared against each other. We identify by serial + `VER` + `VMODE` +
|
||||||
|
boot-ELF SHA-1, which is unambiguous.
|
||||||
|
|
||||||
|
## Disc layout (observed)
|
||||||
|
|
||||||
|
R&C2 lists its assets in the ISO 9660 filesystem, unlike R&C1. Confirmed
|
||||||
|
entries at the disc root:
|
||||||
|
|
||||||
|
```
|
||||||
|
SYSTEM.CNF 60 bytes
|
||||||
|
RC2.HDR 408,444 <- the table of contents, as a named file
|
||||||
|
SCUS_972.68 2,618,684 <- the boot ELF
|
||||||
|
IOPRP255.IMG 264,449
|
||||||
|
Z6TAIL.DUP 0
|
||||||
|
G/... <- global WADs (LEVEL*, AUDIO*, SCENE*, MPEG*, HUD, …)
|
||||||
|
```
|
||||||
|
|
||||||
|
`RC2.HDR` being present as a real file is convenient: the extractor can read
|
||||||
|
the table of contents directly instead of hunting for it at a fixed sector as
|
||||||
|
with R&C1. The `G/` WADs are listed in the filesystem, so enumeration works
|
||||||
|
too.
|
||||||
|
|
||||||
|
### Observation on MPEG WADs
|
||||||
|
|
||||||
|
The three MPEG WADs are large — roughly 1.0 GB, 1.3 GB and 115 MB. That is a
|
||||||
|
substantial fraction of the 3.8 GB disc and will matter for the video phase
|
||||||
|
(R&C2 uses IPU/FMV streams, notably the title screen). Worth factoring into the
|
||||||
|
plan: video is a large *data* problem before it is a decoding problem.
|
||||||
|
|
||||||
|
## Reproducing this check
|
||||||
|
|
||||||
|
`tools/identify-disc.py` performs the same identification against any
|
||||||
|
R&C2 disc image and prints the table above. It reads only the ISO 9660
|
||||||
|
structures, `SYSTEM.CNF` and the boot ELF (a few MB of seeks), so it runs in
|
||||||
|
well under a second on a local image.
|
||||||
+194
@@ -0,0 +1,194 @@
|
|||||||
|
# Roadmap
|
||||||
|
|
||||||
|
Status as of **2026-10-09**.
|
||||||
|
|
||||||
|
A from-scratch native Rust reimplementation of Ratchet & Clank: Going Commando
|
||||||
|
(PS2). Each phase ends in a **gate** — something checkable, not a feeling that
|
||||||
|
it is going well. Phases are ordered so that the riskiest unknowns are retired
|
||||||
|
early, while they are still cheap to retire.
|
||||||
|
|
||||||
|
## Where we are
|
||||||
|
|
||||||
|
- [x] Target disc identified and verified (`SCUS_972.68` v1.01, NTSC 60 Hz)
|
||||||
|
- [x] Workspace builds (`cargo check --workspace`)
|
||||||
|
- [x] Repository hygiene in place (pre-commit guard, `.gitignore`, licence
|
||||||
|
record)
|
||||||
|
- [ ] Nothing else implemented yet
|
||||||
|
|
||||||
|
The disc being the exact build the reference projects pin is the single
|
||||||
|
biggest favourable fact so far: no disc conversion, no regional timing work,
|
||||||
|
and no re-derivation of the reference baseline.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 0 — Foundation ✅
|
||||||
|
|
||||||
|
**Done.**
|
||||||
|
|
||||||
|
- Disc identified: `SCUS_972.68` v1.01, USA Black Label, NTSC 60 Hz. Boot ELF
|
||||||
|
SHA-1 `bacda860cbd6723142e86db56f197e47e728970f`, entry `0x00131AE8` — the
|
||||||
|
entry point independently matches the value recorded by `rac2-gc-decomp`,
|
||||||
|
which cross-confirms the identification.
|
||||||
|
- `tools/identify-disc.py` reproduces the identification against any disc image
|
||||||
|
and distinguishes a truncated dump from a non-PS2 image.
|
||||||
|
- Five-crate workspace scaffolding, Bevy 0.19.1 pinned, builds clean.
|
||||||
|
- `.githooks/pre-commit` blocks game data. Verified against seven cases:
|
||||||
|
`.iso`, renamed disc image, WAD container, ADX container, `extracted/`,
|
||||||
|
a 60 MiB file, and legitimate source.
|
||||||
|
- `AGENTS.md` and `PROVENANCE.md` record conventions and licences.
|
||||||
|
|
||||||
|
### Open from this phase
|
||||||
|
|
||||||
|
- `alsa-lib-devel` is not installed, so Bevy's host audio backend is disabled in
|
||||||
|
`Cargo.toml`. Audio is the last phase; revisit then.
|
||||||
|
- Bevy feature set is deliberately minimal. Re-add features as needed rather
|
||||||
|
than switching `default-features` back on wholesale.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 1 — Disc pipeline
|
||||||
|
|
||||||
|
**Goal:** turn the disc image into verified, unpacked asset files.
|
||||||
|
|
||||||
|
- [ ] `iso9660` reader (R&C2 lists assets as real files, unlike R&C1)
|
||||||
|
- [ ] `RC2.HDR` table of contents parser (408,444 bytes at disc root)
|
||||||
|
- [ ] WAD LZ77 decompression
|
||||||
|
- [ ] Unpack global WADs (`G/*.WAD`) and per-level WADs
|
||||||
|
- [ ] Commit a size+SHA-1 table per target file (hashes only, never content)
|
||||||
|
- [ ] `verify` stage
|
||||||
|
|
||||||
|
**Gate:** every extracted file matches the committed hash table, and the
|
||||||
|
decompressed size of every WAD stream matches its header.
|
||||||
|
|
||||||
|
**Note:** the three MPEG WADs total roughly 2.4 GB of the 3.8 GB disc. Video is
|
||||||
|
a data problem before it is a decoding problem — plan to stream.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2 — Format parsers
|
||||||
|
|
||||||
|
**Goal:** every asset type parses into plain Rust values, tested against real
|
||||||
|
disc data.
|
||||||
|
|
||||||
|
- [ ] `tfrag` — terrain: LOD chains, strips, morph targets
|
||||||
|
- [ ] `tie` — instanced environment geometry, fat vertices
|
||||||
|
- [ ] `shrub` — foliage and decorative meshes
|
||||||
|
- [ ] `moby` — dynamic actors; `moby_anim` — skeletal animation
|
||||||
|
- [ ] `collision` — cells, packed vertices, faces
|
||||||
|
- [ ] `occlusion` — the PVS grid
|
||||||
|
- [ ] `sky`, `volumes`, `textures` (CSM1 CLUT + PS2 alpha)
|
||||||
|
- [ ] `level_overlay` — the seven-section parser and relocation from Phase 3
|
||||||
|
|
||||||
|
**Gate:** every parser has a golden test against bytes from the real disc, and
|
||||||
|
reports mismatched header counts as failures rather than warnings.
|
||||||
|
|
||||||
|
**Discipline:** bound every read. Typed errors, no panics, no reads past the
|
||||||
|
buffer.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3 — Overlay system
|
||||||
|
|
||||||
|
**Goal:** make 27 level overlays tractable.
|
||||||
|
|
||||||
|
Every overlay carries the same seven sections (`.lit`, `.bss`, `.data`,
|
||||||
|
`lvl.vtbl`, `lvl.camvtbl`, `lvl.sndvtbl`, `.text`) and the same engine code,
|
||||||
|
linked at different addresses. ReRAC solves this by masking relocations — `j`/
|
||||||
|
`jal` targets, `lui` immediates of main-RAM addresses, the `%lo` that completes
|
||||||
|
them, and `$gp`-relative operands — then matching the masked words to find a
|
||||||
|
reference function's copy on another overlay.
|
||||||
|
|
||||||
|
- [ ] Port the masking and code-identity logic
|
||||||
|
- [ ] `Relocation::func` and `Relocation::data`
|
||||||
|
- [ ] `$gp` for this game (`0x001AEFF0`, against R&C1's `0x166c00`)
|
||||||
|
- [ ] Extend the level table from 19 to 27
|
||||||
|
|
||||||
|
**Gate:** every level-01 function's relocated copy is found on all 27 overlays;
|
||||||
|
cross-checked against an independent cluster hash.
|
||||||
|
|
||||||
|
**Why this is high-value:** it turns "27 overlays of unknown code" into a table
|
||||||
|
lookup, and it is the main thing inherited from ReRAC.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 4 — Engine core
|
||||||
|
|
||||||
|
**Goal:** draw a frame.
|
||||||
|
|
||||||
|
- [ ] Graphics Synthesizer in wgpu, with the GS's own rules: its
|
||||||
|
`round(log2(z/n) + K)` mip selection, blend and alpha-test behaviour,
|
||||||
|
alpha `0x80` meaning 1.0, CLAMP wrap
|
||||||
|
- [ ] VU0/VU1 skinning and lighting on the PS2 float model
|
||||||
|
- [ ] LOD/morph/cull per `TfragProc` and `TieProc`
|
||||||
|
- [ ] Occlusion culling, fog, shadows
|
||||||
|
- [ ] GIF/VIF packet stream interpretation
|
||||||
|
|
||||||
|
**Gate:** CPU and GPU paths agree on every vertex of a reference level — the
|
||||||
|
same identity check ReRAC runs.
|
||||||
|
|
||||||
|
**Risk:** VU1 microcode is **binary** in the game's `.vutext` section, so it is
|
||||||
|
reverse engineered rather than parsed. This is the hardest single item in the
|
||||||
|
project and the main threat to the timeline. Note that ReRAC's own moby VU1
|
||||||
|
analysis trips their disassembler on two opcodes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 5 — Veldin vertical slice
|
||||||
|
|
||||||
|
**Goal:** one planet, playable start to finish.
|
||||||
|
|
||||||
|
- [ ] Ratchet's controller on a **spherical** planet
|
||||||
|
- [ ] Chargeboots: hold/release timing, chained jumps, dash
|
||||||
|
- [ ] Wrench and the camera
|
||||||
|
- [ ] Collision and the HUD
|
||||||
|
|
||||||
|
**Gate:** Veldin is completable and visually matches PCSX2 frame-for-frame under
|
||||||
|
replayed input.
|
||||||
|
|
||||||
|
**This is the go/no-go for the project.** If Veldin works, the other 26 levels
|
||||||
|
are content. If it does not, the reason is discovered while it is still cheap.
|
||||||
|
Spherical gravity is the thing most likely to break, because it invalidates
|
||||||
|
R&C1's movement and camera assumptions wholesale.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 6 — Remaining overlays and the shell
|
||||||
|
|
||||||
|
- [ ] Per-level moby class dispatch via `lvl.vtbl`
|
||||||
|
- [ ] Remaining 26 levels
|
||||||
|
- [ ] Planets, travel, the Gadgetron vendor, RPG upgrades
|
||||||
|
- [ ] Weapon modification bitmasks (lock-on, shock, acid, …)
|
||||||
|
- [ ] Minigames and challenges
|
||||||
|
- [ ] The 25-state memory-card FSM and its UI
|
||||||
|
- [ ] IFF chunk saves, including import of a R&C1 save to unlock legacy weapons
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 7 — Audio and video
|
||||||
|
|
||||||
|
- [ ] 989snd v2 audio decode and playback (not CRI ADX — a different codec)
|
||||||
|
- [ ] SPU2 mixing, reverb zones, level sounds
|
||||||
|
- [ ] IPU/MPEG-2 video for the title screen and cutscenes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 8 — Fidelity pass
|
||||||
|
|
||||||
|
Differential testing against PCSX2 across every level:
|
||||||
|
|
||||||
|
- [ ] Recorded-input frame comparison, per planet
|
||||||
|
- [ ] Geometry, fog, blend, alpha, texture sampling
|
||||||
|
- [ ] Audio sync and 60 Hz timing
|
||||||
|
- [ ] Behavioural comparison: traversal routes, boss timing, RNG
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Principles
|
||||||
|
|
||||||
|
- **A gate is pass/fail, not a percentage.** "Looks fine" is not progress.
|
||||||
|
- **Negative results are written down.** A ruled-out approach saves the next
|
||||||
|
attempt as much as a working one.
|
||||||
|
- **The float model is not negotiable.** Approximate is not the same as
|
||||||
|
faithful, and the difference is visible in how the game feels.
|
||||||
|
- **Fidelity over tidiness.** Where the original does something that looks like
|
||||||
|
a bug, reproduce it and say why in a comment. See `AGENTS.md`.
|
||||||
Executable
+231
@@ -0,0 +1,231 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Identify a Ratchet & Clank: Going Commando disc image.
|
||||||
|
|
||||||
|
Reads only the ISO 9660 structures, SYSTEM.CNF and the boot ELF, so it needs a
|
||||||
|
few megabytes of seeks rather than a full read. Reports the serial, version and
|
||||||
|
video mode, and warns when the disc is not the build our references target.
|
||||||
|
|
||||||
|
python3 tools/identify-disc.py <disc.iso>
|
||||||
|
python3 tools/identify-disc.py --verbose <disc.iso>
|
||||||
|
|
||||||
|
Only your own legally obtained disc is read. Nothing is written anywhere.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import hashlib
|
||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
import zlib
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
SECTOR = 2048
|
||||||
|
|
||||||
|
# The build OpenRAC/rac2-gc-decomp pins, and the one our reference material
|
||||||
|
# describes. Boot-ELF SHA-1 below is measured from a retail v1.01 image.
|
||||||
|
TARGET_SERIAL = "SCUS_972.68"
|
||||||
|
TARGET_VERSION = "1.01"
|
||||||
|
TARGET_ELF_SHA1 = "bacda860cbd6723142e86db56f197e47e728970f"
|
||||||
|
|
||||||
|
# Serial -> (game, edition) for the R&C discs we can recognise.
|
||||||
|
KNOWN_TITLES = {
|
||||||
|
"SCUS_972.68": ("Going Commando (USA)", "Black Label v1.01"),
|
||||||
|
"SCUS_972.68GH": ("Going Commando (USA)", "Greatest Hits v2.00 - DIFFERENT TARGET"),
|
||||||
|
"SCES_516.07": ("Going Commando (PAL)", "Locked and Loaded v1.00 - 50 Hz"),
|
||||||
|
"SCPS_150.56": ("Going Commando (JP)", "Gagaga! Ginga no Commando-ssu"),
|
||||||
|
"SCKA_200.11": ("Going Commando (KR)", ""),
|
||||||
|
"SCUS_971.99": ("Ratchet & Clank (USA)", "the first game, not Going Commando"),
|
||||||
|
"SCES_509.16": ("Ratchet & Clank (PAL)", "the first game, not Going Commando"),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class NotAnIso(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Iso:
|
||||||
|
"""Minimal read-only ISO 9660 view: enough to find the root directory."""
|
||||||
|
|
||||||
|
path: Path
|
||||||
|
volume_id: str = ""
|
||||||
|
root_lba: int = 0
|
||||||
|
root_size: int = 0
|
||||||
|
files: dict[str, tuple[int, int]] = field(default_factory=dict)
|
||||||
|
|
||||||
|
def read(self, lba: int, size: int) -> bytes:
|
||||||
|
with open(self.path, "rb") as fh:
|
||||||
|
fh.seek(lba * SECTOR)
|
||||||
|
data = fh.read(size)
|
||||||
|
if len(data) < size:
|
||||||
|
raise NotAnIso(
|
||||||
|
f"image is truncated: wanted {size} bytes at LBA {lba}, got {len(data)}"
|
||||||
|
)
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def open_iso(path: Path) -> Iso:
|
||||||
|
size = path.stat().st_size
|
||||||
|
with open(path, "rb") as fh:
|
||||||
|
magic = fh.read(SECTOR * 16 + 6)
|
||||||
|
|
||||||
|
# The CD001 signature sits at offset 0x8001 in the Primary Volume Descriptor.
|
||||||
|
if magic[0x8001:0x8006] != b"CD001":
|
||||||
|
# A 2352-byte raw dump has a different layout; we only support .iso.
|
||||||
|
head = magic[:5]
|
||||||
|
raise NotAnIso(
|
||||||
|
"not an ISO 9660 image (no CD001 signature at 0x8001); "
|
||||||
|
"only plain 2048-byte-sector .iso dumps are supported"
|
||||||
|
)
|
||||||
|
|
||||||
|
iso = Iso(path=path)
|
||||||
|
with open(path, "rb") as fh:
|
||||||
|
fh.seek(16 * SECTOR)
|
||||||
|
pvd = fh.read(SECTOR)
|
||||||
|
iso.volume_id = pvd[40:72].decode("latin1").strip()
|
||||||
|
|
||||||
|
root = pvd[156:190]
|
||||||
|
iso.root_lba = struct.unpack_from("<I", root, 2)[0]
|
||||||
|
iso.root_size = struct.unpack_from("<I", root, 10)[0]
|
||||||
|
|
||||||
|
fh.seek(iso.root_lba * SECTOR)
|
||||||
|
data = fh.read(iso.root_size)
|
||||||
|
|
||||||
|
off = 0
|
||||||
|
while off < len(data):
|
||||||
|
rec_len = data[off]
|
||||||
|
if rec_len == 0:
|
||||||
|
# Records never span a logical block; skip to the next one.
|
||||||
|
off = (off // SECTOR + 1) * SECTOR
|
||||||
|
continue
|
||||||
|
name_len = data[off + 32]
|
||||||
|
lba = struct.unpack_from("<I", data, off + 2)[0]
|
||||||
|
fsize = struct.unpack_from("<I", data, off + 10)[0]
|
||||||
|
name = data[off + 33:off + 33 + name_len].split(b";")[0].split(b"\x00")[0]
|
||||||
|
iso.files[name.decode("latin1")] = (lba, fsize)
|
||||||
|
off += rec_len
|
||||||
|
return iso
|
||||||
|
|
||||||
|
|
||||||
|
def cnf_value(cnf: str, key: str) -> str:
|
||||||
|
for line in cnf.splitlines():
|
||||||
|
if "=" not in line:
|
||||||
|
continue
|
||||||
|
k, v = line.split("=", 1)
|
||||||
|
if k.strip() == key:
|
||||||
|
return v.split(";")[0].strip()
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def elf_summary(blob: bytes) -> str:
|
||||||
|
if len(blob) < 52 or blob[:4] != b"\x7fELF":
|
||||||
|
return "not an ELF"
|
||||||
|
bits = {1: "ELF32", 2: "ELF64"}.get(blob[4], "?")
|
||||||
|
endian = {1: "little", 2: "big"}.get(blob[5], "?")
|
||||||
|
machine = struct.unpack_from("<H", blob, 18)[0]
|
||||||
|
entry = struct.unpack_from("<I", blob, 24)[0]
|
||||||
|
machine_name = {8: "MIPS (EM_MIPS)", 40: "PowerPC", 20: "PPC"}.get(machine, str(machine))
|
||||||
|
return f"{bits} {endian}-endian, {machine_name}, entry 0x{entry:08X}"
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
ap = argparse.ArgumentParser(description=__doc__)
|
||||||
|
ap.add_argument("iso", type=Path, help="path to your own disc image (.iso)")
|
||||||
|
ap.add_argument("--verbose", "-v", action="store_true", help="list root directory")
|
||||||
|
args = ap.parse_args()
|
||||||
|
|
||||||
|
if not args.iso.is_file():
|
||||||
|
print(f"error: {args.iso} is not a file", file=sys.stderr)
|
||||||
|
return 2
|
||||||
|
|
||||||
|
try:
|
||||||
|
iso = open_iso(args.iso)
|
||||||
|
except NotAnIso as exc:
|
||||||
|
print(f"error: {exc}", file=sys.stderr)
|
||||||
|
return 2
|
||||||
|
|
||||||
|
print("Disc identification")
|
||||||
|
print("=" * 62)
|
||||||
|
print(f" file {args.iso.name}")
|
||||||
|
print(f" size {args.iso.stat().st_size:,} bytes")
|
||||||
|
print(f" volume id {iso.volume_id or '(none)'}")
|
||||||
|
|
||||||
|
if "SYSTEM.CNF" not in iso.files:
|
||||||
|
# A truncated dump still has a valid PVD, so distinguish "damaged"
|
||||||
|
# from "genuinely not a PS2 disc".
|
||||||
|
if iso.volume_id:
|
||||||
|
print(
|
||||||
|
f"\n SYSTEM.CNF is missing but the image declares volume id "
|
||||||
|
f"{iso.volume_id!r}, which means the dump is truncated or damaged.",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
return 4
|
||||||
|
print("\n no SYSTEM.CNF: not a PlayStation 2 game disc", file=sys.stderr)
|
||||||
|
return 3
|
||||||
|
|
||||||
|
lba, size = iso.files["SYSTEM.CNF"]
|
||||||
|
cnf = iso.read(lba, size).decode("latin1")
|
||||||
|
|
||||||
|
boot = cnf_value(cnf, "BOOT2").replace("cdrom0:\\", "").replace("\\", "/")
|
||||||
|
serial = boot.rsplit("/", 1)[-1]
|
||||||
|
version = cnf_value(cnf, "VER")
|
||||||
|
vmode = cnf_value(cnf, "VMODE")
|
||||||
|
|
||||||
|
print(f" BOOT2 {boot}")
|
||||||
|
print(f" serial {serial}")
|
||||||
|
print(f" version {version or '(absent)'}")
|
||||||
|
print(f" video mode {vmode or '(absent)'}")
|
||||||
|
|
||||||
|
if serial in KNOWN_TITLES:
|
||||||
|
title, edition = KNOWN_TITLES[serial]
|
||||||
|
print(f" title {title}")
|
||||||
|
if edition:
|
||||||
|
print(f" edition {edition}")
|
||||||
|
|
||||||
|
if boot in iso.files:
|
||||||
|
blba, bsize = iso.files[boot]
|
||||||
|
elf = iso.read(blba, bsize)
|
||||||
|
sha1 = hashlib.sha1(elf).hexdigest()
|
||||||
|
print()
|
||||||
|
print("Boot executable")
|
||||||
|
print("-" * 62)
|
||||||
|
print(f" size {bsize:,} bytes")
|
||||||
|
print(f" SHA-1 {sha1}")
|
||||||
|
print(f" CRC-32 {zlib.crc32(elf)}")
|
||||||
|
print(f" format {elf_summary(elf)}")
|
||||||
|
else:
|
||||||
|
print("\n the boot executable named by SYSTEM.CNF is not in the root directory")
|
||||||
|
sha1 = None
|
||||||
|
|
||||||
|
if args.verbose:
|
||||||
|
print()
|
||||||
|
print("Root directory")
|
||||||
|
print("-" * 62)
|
||||||
|
for name in sorted(iso.files):
|
||||||
|
l, s = iso.files[name]
|
||||||
|
print(f" {name:24s} LBA {l:>8,} {s:>12,} bytes")
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("=" * 62)
|
||||||
|
is_target = serial == TARGET_SERIAL and version == TARGET_VERSION
|
||||||
|
if is_target and sha1 == TARGET_ELF_SHA1:
|
||||||
|
print(" This is the build we target: USA Black Label v1.01, NTSC 60 Hz.")
|
||||||
|
print(" Expected SHA-1 of the boot executable matches.")
|
||||||
|
elif serial == TARGET_SERIAL:
|
||||||
|
print(f" Serial matches, but version is {version or 'unknown'}"
|
||||||
|
f" and we target {TARGET_VERSION}.")
|
||||||
|
print(" Greatest Hits v2.00 is a different build and will not match")
|
||||||
|
print(" the reference material. Everything else should still work.")
|
||||||
|
else:
|
||||||
|
print(f" Not the target build. We target {TARGET_SERIAL} v{TARGET_VERSION}.")
|
||||||
|
if vmode == "PAL":
|
||||||
|
print(" PAL disc: the game runs at 50 Hz, so movement runs at 5/6")
|
||||||
|
print(" speed. Plan timing constants accordingly.")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
Reference in New Issue
Block a user