jevsnes.git / apps / headless / src / main.rs
main.rsannotatedmain.rssource60 lines · 2.7 KB · raw
1//! Boot a cartridge with no window: run it, press Start a few times, write the
2//! last frame as a PPM and print the WRAM bytes the game-state reader is built
3//! on. The check that the embedded core runs the real game.
4
5use std::io::Write;
6use std::{env, fs, process};
7
8use console::{Console, Saves, SnesButton};
9
10/// Frames run on from a snapshot, twice, to check it restores the machine.
11const AFTER_SNAPSHOT: u32 = 300;
12
13fn main() {
14    let mut args = env::args().skip(1);
15    let (Some(rom), Some(frames), Some(out)) = (args.next(), args.next(), args.next()) else {
16        eprintln!("usage: headless <rom.sfc> <frames> <out.ppm>");
17        process::exit(2);
18    };
19    let frames: u32 = frames.parse().expect("frames is a number");
20    let rom = fs::read(&rom).expect("reading rom");
21
22    let mut console = Console::boot(rom, Saves::default()).expect("booting");
23    for n in 0..frames {
24        // Tap Start for 4 frames out of every 60: enough to leave the title
25        // screen and walk the file-select menus without holding it down.
26        console.set_button(SnesButton::Start, n % 60 < 4);
27        console.run_frame().expect("running frame");
28    }
29
30    let (w, h) = console.frame.size;
31    let mut ppm = fs::File::create(&out).expect("creating output");
32    write!(ppm, "P6\n{w} {h}\n255\n").unwrap();
33    let rgb: Vec<u8> = console.frame.pixels.iter().flat_map(|c| [c.r, c.g, c.b]).collect();
34    ppm.write_all(&rgb).unwrap();
35
36    // A snapshot has to be the machine, not something like it: run on from
37    // one, go back to it, run on again, and the two futures must be the same
38    // picture and the same work RAM.
39    let snapshot = console.snapshot().expect("taking snapshot");
40    let mut future = |console: &mut Console| {
41        for n in 0..AFTER_SNAPSHOT {
42            console.set_button(SnesButton::Start, n % 60 < 4);
43            console.run_frame().expect("running frame");
44        }
45        (console.frame.pixels.clone(), console.wram().to_vec())
46    };
47    let first = future(&mut console);
48    console.restore(&snapshot).expect("restoring snapshot");
49    let second = future(&mut console);
50    assert!(first == second, "the machine restored from a snapshot ran differently");
51    println!("snapshot {} bytes, identical after {AFTER_SNAPSHOT} frames", snapshot.len());
52
53    let ram = console.wram();
54    println!("frame {w}x{h}, {} audio samples", console.samples.0.len());
55    println!("$7E0010 mode      {:#04x}", ram[0x10]);
56    println!("$7E0011 submode   {:#04x}", ram[0x11]);
57    println!("$7E0020 link y    {:#06x}", u16::from_le_bytes([ram[0x20], ram[0x21]]));
58    println!("$7E0022 link x    {:#06x}", u16::from_le_bytes([ram[0x22], ram[0x23]]));
59    println!("$7EF36D health    {:#04x}", ram[0xF36D]);
60}