1use std::path::PathBuf;
7
8use clap::{Args, Parser, ValueEnum, value_parser};
9use serde::Deserialize;
10
11#[derive(Parser, Debug, Clone, Default)]
13#[command(name = "nes_main")]
14#[command(version, about, long_about = None)]
15#[command(after_help = "For more information, see docs/CLI_INTERFACE.md")]
16pub struct CliArgs {
17 #[arg(short, long, default_value_t = false)]
19 pub quiet: bool,
20
21 #[arg(short, long, default_value_t = false)]
23 pub verbose: bool,
24
25 #[arg(short, long, value_parser = value_parser!(PathBuf), value_hint = clap::ValueHint::FilePath)]
27 pub config: Option<PathBuf>,
28
29 #[command(flatten)]
30 pub rom: RomArgs,
31
32 #[command(flatten)]
33 pub savestate: SavestateArgs,
34
35 #[command(flatten)]
36 pub memory: MemoryArgs,
37
38 #[command(flatten)]
39 pub power: PowerArgs,
40
41 #[command(flatten)]
42 pub palette: PaletteArgs,
43
44 #[command(flatten)]
45 pub video: VideoArgs,
46
47 #[command(flatten)]
48 pub execution: ExecutionArgs,
49
50 #[command(flatten)]
51 pub output: OutputArgs,
52}
53
54#[derive(Args, Debug, Clone, Default)]
56pub struct RomArgs {
57 #[arg(short, long, value_parser = value_parser!(PathBuf), value_hint = clap::ValueHint::FilePath)]
59 pub rom: Option<PathBuf>,
60
61 #[arg(long, default_value_t = false)]
63 pub rom_info: bool,
64}
65
66#[derive(Args, Debug, Clone, Default)]
68pub struct SavestateArgs {
69 #[arg(short = 'l', long, value_parser = value_parser!(PathBuf), value_hint = clap::ValueHint::FilePath)]
71 pub load_state: Option<PathBuf>,
72
73 #[arg(short = 's', long, value_parser = value_parser!(PathBuf), value_hint = clap::ValueHint::FilePath)]
75 pub save_state: Option<PathBuf>,
76
77 #[arg(long, default_value_t = false)]
79 pub state_stdin: bool,
80
81 #[arg(long, default_value_t = false)]
83 pub state_stdout: bool,
84
85 #[arg(long)]
87 pub save_state_on: Option<String>,
88
89 #[arg(long, default_value = "binary")]
91 pub state_format: SavestateFormat,
92}
93
94#[derive(Debug, Clone, Copy, ValueEnum, Default, PartialEq, Eq, Deserialize)]
96pub enum SavestateFormat {
97 #[default]
99 Binary,
100 Json,
102}
103
104#[derive(Args, Debug, Clone, Default)]
106pub struct MemoryArgs {
107 #[arg(long)]
109 pub read_cpu: Option<String>,
110
111 #[arg(long)]
113 pub read_ppu: Option<String>,
114
115 #[arg(long, default_value_t = false)]
117 pub dump_oam: bool,
118
119 #[arg(long, default_value_t = false)]
121 pub dump_nametables: bool,
122
123 #[arg(long, default_value_t = false)]
125 pub dump_palette: bool,
126
127 #[arg(long, action = clap::ArgAction::Append)]
129 pub init_cpu: Vec<String>,
130
131 #[arg(long, action = clap::ArgAction::Append)]
133 pub init_ppu: Vec<String>,
134
135 #[arg(long, action = clap::ArgAction::Append)]
137 pub init_oam: Vec<String>,
138
139 #[arg(long, value_parser = value_parser!(PathBuf), value_hint = clap::ValueHint::FilePath)]
141 pub init_file: Option<PathBuf>,
142}
143
144#[derive(Args, Debug, Clone, Default)]
146pub struct PowerArgs {
147 #[arg(long, default_value_t = false)]
149 pub no_power: bool,
150
151 #[arg(long, default_value_t = false)]
153 pub reset: bool,
154}
155
156#[derive(Args, Debug, Clone, Default)]
158pub struct PaletteArgs {
159 #[arg(short, long, value_parser = value_parser!(PathBuf), value_hint = clap::ValueHint::FilePath)]
161 pub palette: Option<PathBuf>,
162
163 #[arg(long)]
165 pub palette_builtin: Option<BuiltinPalette>,
166}
167
168#[derive(Debug, Clone, Copy, ValueEnum, Default)]
170pub enum BuiltinPalette {
171 #[default]
173 #[value(name = "2C02G")]
174 Nes2C02G,
175 Composite,
177}
178
179#[derive(Args, Debug, Clone, Default)]
181pub struct VideoArgs {
182 #[arg(long, value_parser = value_parser!(PathBuf), value_hint = clap::ValueHint::FilePath)]
184 pub screenshot: Option<PathBuf>,
185
186 #[arg(long)]
188 pub screenshot_on: Option<String>,
189
190 #[arg(long, value_parser = value_parser!(PathBuf), value_hint = clap::ValueHint::FilePath)]
192 pub video_path: Option<PathBuf>,
193
194 #[arg(long, default_value = "raw")]
196 pub video_format: VideoFormat,
197
198 #[arg(long, default_value = "1x")]
202 pub video_fps: String,
203
204 #[arg(long, default_value = "accurate")]
208 pub video_mode: VideoExportMode,
209
210 #[arg(long)]
212 pub video_scale: Option<String>,
213
214 #[arg(long)]
217 pub renderer: Option<String>,
218
219 #[arg(long, default_value_t = false)]
221 pub list_renderers: bool,
222}
223
224#[derive(Debug, Clone, Copy, ValueEnum, Default, PartialEq, Eq)]
226pub enum VideoExportMode {
227 #[default]
229 Accurate,
230 Smooth,
232}
233
234#[derive(Debug, Clone, Copy, ValueEnum, Default, PartialEq, Eq)]
236pub enum VideoFormat {
237 #[default]
239 Raw,
240 Ppm,
242 Png,
244 Mp4,
246}
247
248#[derive(Args, Debug, Clone, Default)]
250pub struct ExecutionArgs {
251 #[arg(long)]
253 pub cycles: Option<u128>,
254
255 #[arg(short, long)]
257 pub frames: Option<u64>,
258
259 #[arg(long, value_parser = parse_hex_u8)]
261 pub until_opcode: Option<u8>,
262
263 #[arg(long)]
265 pub until_mem: Option<Vec<String>>,
266
267 #[arg(long, default_value_t = false)]
269 pub until_hlt: bool,
270
271 #[arg(long, value_parser = value_parser!(PathBuf), value_hint = clap::ValueHint::FilePath)]
273 pub trace: Option<PathBuf>,
274
275 #[arg(long, value_parser = parse_hex_u16, action = clap::ArgAction::Append)]
277 pub breakpoint: Vec<u16>,
278
279 #[arg(long, action = clap::ArgAction::Append)]
283 pub watch_mem: Vec<String>,
284}
285
286#[derive(Args, Debug, Clone, Default)]
288pub struct OutputArgs {
289 #[arg(short, long, value_parser = value_parser!(PathBuf), value_hint = clap::ValueHint::FilePath)]
291 pub output: Option<PathBuf>,
292
293 #[arg(long, default_value = "hex")]
295 pub output_format: OutputFormat,
296
297 #[arg(long, default_value_t = false)]
299 pub json: bool,
300
301 #[arg(long, default_value_t = false)]
303 pub toml: bool,
304
305 #[arg(long, default_value_t = false)]
307 pub binary: bool,
308}
309
310#[derive(Debug, Clone, Copy, ValueEnum, Default, PartialEq, Eq)]
312pub enum OutputFormat {
313 #[default]
315 Hex,
316 Json,
318 Toml,
320 Binary,
322}
323
324impl std::str::FromStr for OutputFormat {
325 type Err = String;
326
327 fn from_str(s: &str) -> Result<Self, Self::Err> {
328 match s.to_lowercase().as_str() {
329 "hex" => Ok(OutputFormat::Hex),
330 "json" => Ok(OutputFormat::Json),
331 "toml" => Ok(OutputFormat::Toml),
332 "binary" => Ok(OutputFormat::Binary),
333 _ => Err(format!(
334 "Unknown output format: '{}'. Valid options: hex, json, toml, binary",
335 s
336 )),
337 }
338 }
339}
340
341impl std::str::FromStr for VideoFormat {
342 type Err = String;
343
344 fn from_str(s: &str) -> Result<Self, Self::Err> {
345 match s.to_lowercase().as_str() {
346 "raw" => Ok(VideoFormat::Raw),
347 "ppm" => Ok(VideoFormat::Ppm),
348 "png" => Ok(VideoFormat::Png),
349 "mp4" => Ok(VideoFormat::Mp4),
350 _ => Err(format!(
351 "Unknown video format: '{}'. Valid options: raw, ppm, png, mp4",
352 s
353 )),
354 }
355 }
356}
357
358impl std::str::FromStr for VideoExportMode {
359 type Err = String;
360
361 fn from_str(s: &str) -> Result<Self, Self::Err> {
362 match s.to_lowercase().as_str() {
363 "accurate" => Ok(VideoExportMode::Accurate),
364 "smooth" => Ok(VideoExportMode::Smooth),
365 _ => Err(format!(
366 "Unknown video export mode: '{}'. Valid options: accurate, smooth",
367 s
368 )),
369 }
370 }
371}
372
373impl std::str::FromStr for BuiltinPalette {
374 type Err = String;
375
376 fn from_str(s: &str) -> Result<Self, Self::Err> {
377 match s.to_lowercase().as_str() {
378 "2c02g" => Ok(BuiltinPalette::Nes2C02G),
379 "composite" => Ok(BuiltinPalette::Composite),
380 _ => Err(format!(
381 "Unknown palette: '{}'. Valid options: 2C02G, composite",
382 s
383 )),
384 }
385 }
386}
387
388pub fn parse_hex_u16(s: &str) -> Result<u16, String> {
390 let s = s
391 .strip_prefix("0x")
392 .or_else(|| s.strip_prefix("0X"))
393 .unwrap_or(s);
394 u16::from_str_radix(s, 16).map_err(|e| format!("Invalid hex value '{}': {}", s, e))
395}
396
397pub fn parse_hex_u8(s: &str) -> Result<u8, String> {
399 let s = s
400 .strip_prefix("0x")
401 .or_else(|| s.strip_prefix("0X"))
402 .unwrap_or(s);
403 u8::from_str_radix(s, 16).map_err(|e| format!("Invalid hex value '{}': {}", s, e))
404}
405
406impl OutputArgs {
407 pub fn effective_format(&self) -> OutputFormat {
409 if self.json {
410 OutputFormat::Json
411 } else if self.toml {
412 OutputFormat::Toml
413 } else if self.binary {
414 OutputFormat::Binary
415 } else {
416 self.output_format
417 }
418 }
419}