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}