Nothing here changes what a tool computes; it changes where tools look. - tools/re-capture: 33 censuses globbed /work/sylph_extract, a path that has existed nowhere since /work became a clone, so they matched nothing and printed empty results. They now resolve the disc through a new disc.py from $SYLPHEED_DISC and exit loudly without it (the #44 fix, generalised). Nine scripts that imported siblings from the retired Reborn checkout or an old session scratchpad now import from their own directory. unitgroup.py only needs the variable when --pak is not given. - sylpheed-xex: the loader only ever uses the XEX2 retail key. The dead devkit key and a doc comment claiming a devkit fallback that does not exist are gone; Project Sylpheed is a retail XEX2, so no XEX1 key either. - sylpheed-viewer: real_font_rasterizes looked for /tmp/sylph_extract and so always skipped. It reads $SYLPHEED_DISC now, and passes against the disc. - Comments and docs that named xenia-rs, the Reborn repository or /work/*.pe as places to look now name sylpheed.db, Canary's ppc_context.h and the flat .pe; docs/re/README.md no longer says the native Canary build does not run. Historical records keep their original paths: findings that were measured against /work/xenia-rs/sylpheed.db still say so. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
146 lines
4.3 KiB
Rust
146 lines
4.3 KiB
Rust
//! Analysis-side goldens: every row in the sylpheed-ppc fixtures must
|
|
//! round-trip cleanly through the [`sylpheed_xexdb::ppc`] shim. This
|
|
//! pins the shim's behaviour to the canonical `sylpheed_ppc::disasm::format`
|
|
//! output so that any future refactor of the shim layer surfaces here.
|
|
//!
|
|
//! Loads the same JSON fixtures committed under
|
|
//! `crates/sylpheed-ppc/tests/golden/`. No separate analysis-side fixture
|
|
//! files — the cpu canon is the source of truth.
|
|
|
|
use std::path::PathBuf;
|
|
|
|
use serde::Deserialize;
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct GoldenRow {
|
|
label: String,
|
|
raw: String,
|
|
addr: String,
|
|
mnemonic: String,
|
|
operands: String,
|
|
#[serde(default)]
|
|
ext_mnemonic: Option<String>,
|
|
#[serde(default)]
|
|
ext_operands: Option<String>,
|
|
#[serde(default)]
|
|
branch_target: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct GoldenFile {
|
|
rows: Vec<GoldenRow>,
|
|
}
|
|
|
|
fn cpu_fixture(name: &str) -> PathBuf {
|
|
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
|
.join("..")
|
|
.join("sylpheed-ppc")
|
|
.join("tests")
|
|
.join("golden")
|
|
.join(name)
|
|
}
|
|
|
|
fn parse_hex(s: &str) -> u32 {
|
|
let trimmed = s
|
|
.strip_prefix("0x")
|
|
.or_else(|| s.strip_prefix("0X"))
|
|
.unwrap_or(s);
|
|
u32::from_str_radix(trimmed, 16).expect("hex u32")
|
|
}
|
|
|
|
/// Verify the shim's `Decoded { base, ext }` mirrors the canonical fields
|
|
/// from `sylpheed_ppc::disasm::format` for every fixture row.
|
|
fn check_fixture(fixture_name: &str) {
|
|
let path = cpu_fixture(fixture_name);
|
|
assert!(
|
|
path.exists(),
|
|
"missing fixture {} — run `cargo test -p sylpheed-ppc --test disasm_goldens` to (re)generate it",
|
|
path.display()
|
|
);
|
|
let src = std::fs::read_to_string(&path).unwrap();
|
|
let golden: GoldenFile = serde_json::from_str(&src).unwrap();
|
|
|
|
for row in &golden.rows {
|
|
let raw = parse_hex(&row.raw);
|
|
let addr = parse_hex(&row.addr);
|
|
|
|
let canonical = sylpheed_ppc::disasm::format(&sylpheed_ppc::decode(raw, addr));
|
|
let shim = sylpheed_xexdb::ppc::disasm(raw, addr);
|
|
|
|
assert_eq!(
|
|
shim.base, canonical.disasm,
|
|
"shim.base drifted for {} (raw={})",
|
|
row.label, row.raw,
|
|
);
|
|
assert_eq!(
|
|
shim.ext, canonical.ext_disasm,
|
|
"shim.ext drifted for {} (raw={})",
|
|
row.label, row.raw,
|
|
);
|
|
|
|
// Also pin against the fixture's structured fields — guards against
|
|
// someone changing the cpu canon without regenerating the fixture.
|
|
assert_eq!(
|
|
canonical.mnemonic, row.mnemonic,
|
|
"mnemonic drift: {}",
|
|
row.label
|
|
);
|
|
assert_eq!(
|
|
canonical.operands, row.operands,
|
|
"operands drift: {}",
|
|
row.label
|
|
);
|
|
assert_eq!(
|
|
canonical.ext_mnemonic, row.ext_mnemonic,
|
|
"ext_mnemonic drift: {}",
|
|
row.label
|
|
);
|
|
assert_eq!(
|
|
canonical.ext_operands, row.ext_operands,
|
|
"ext_operands drift: {}",
|
|
row.label
|
|
);
|
|
|
|
let target_str = canonical.branch_target.map(|t| format!("0x{t:08X}"));
|
|
assert_eq!(
|
|
target_str, row.branch_target,
|
|
"branch_target drift: {}",
|
|
row.label
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn analysis_shim_matches_base_mnemonics() {
|
|
check_fixture("base_mnemonics.json");
|
|
}
|
|
|
|
#[test]
|
|
fn analysis_shim_matches_extended_mnemonics() {
|
|
check_fixture("extended_mnemonics.json");
|
|
}
|
|
|
|
#[test]
|
|
fn analysis_shim_matches_vmx128_registers() {
|
|
check_fixture("vmx128_registers.json");
|
|
}
|
|
|
|
/// Spot-check that the shim's `display()` returns the extended form when
|
|
/// present and falls back to the base otherwise. This is the contract
|
|
/// `formatter.rs` and the .asm output rely on.
|
|
#[test]
|
|
fn shim_display_prefers_extended() {
|
|
// ori r0, r0, 0 → base "ori r0, r0, 0x0", ext "nop"
|
|
let d = sylpheed_xexdb::ppc::disasm(0x60000000, 0);
|
|
assert_eq!(d.display(), "nop");
|
|
|
|
// addi r3, r1, 16 → no extended form, display falls back to base
|
|
let raw = (14u32 << 26) | (3 << 21) | (1 << 16) | 16;
|
|
let d = sylpheed_xexdb::ppc::disasm(raw, 0);
|
|
assert!(
|
|
d.ext.is_none(),
|
|
"addi r3, r1, 16 has no extended form (only addi r3, r0, … → li)"
|
|
);
|
|
assert_eq!(d.display(), d.base);
|
|
}
|