1use nes_sim::NES;
2use nes_sim::headless::{frame_to_ppm, stable_byte_hash};
3use std::env;
4use std::path::Path;
5use std::process::ExitCode;
6
7fn usage(program: &str) {
8 eprintln!("Usage: {program} <rom-path> [frames] [output-ppm]");
9 eprintln!(r#"Example: {program} "roms/mmc1/Rockman2(J).nes" 180 "out/rockman2-current.ppm""#);
10}
11
12fn main() -> ExitCode {
13 let mut args = env::args();
14 let program = args.next().unwrap_or_else(|| "hash_frame".to_string());
15
16 let Some(rom_path) = args.next() else {
17 usage(&program);
18 return ExitCode::from(2);
19 };
20 let frames = match args.next() {
21 Some(value) => match value.parse::<usize>() {
22 Ok(frames) => frames,
23 Err(error) => {
24 eprintln!("invalid frame count {value:?}: {error}");
25 return ExitCode::from(2);
26 }
27 },
28 None => 180,
29 };
30 let output_path = args.next();
31
32 let rom = match std::fs::read(&rom_path) {
33 Ok(rom) => rom,
34 Err(error) => {
35 eprintln!("failed to read ROM {rom_path:?}: {error}");
36 return ExitCode::from(1);
37 }
38 };
39
40 let mut nes = NES::new();
41 if let Err(error) = nes.load_cartridge_ines(&rom) {
42 eprintln!("failed to load ROM {rom_path:?}: {error}");
43 return ExitCode::from(1);
44 }
45 nes.reset();
46
47 for _ in 0..frames {
48 nes.run_frame();
49 }
50
51 let ppm = frame_to_ppm(nes.video_frame());
52 let hash = stable_byte_hash(&ppm);
53 println!(
54 "{} frame={} hash=0x{:016X}",
55 rom_path,
56 nes.frame_number(),
57 hash
58 );
59
60 if let Some(output_path) = output_path {
61 if let Some(parent) = Path::new(&output_path).parent()
62 && !parent.as_os_str().is_empty()
63 {
64 if let Err(error) = std::fs::create_dir_all(parent) {
65 eprintln!("failed to create output directory {:?}: {}", parent, error);
66 return ExitCode::from(1);
67 }
68 }
69
70 if let Err(error) = std::fs::write(&output_path, &ppm) {
71 eprintln!("failed to write PPM {output_path:?}: {error}");
72 return ExitCode::from(1);
73 }
74 println!("wrote {}", output_path);
75 }
76
77 ExitCode::SUCCESS
78}