1use std::path::PathBuf;
7
8use clap::{Args, Parser, ValueEnum, value_parser};
9use serde::Deserialize;
10
11#[derive(Parser, Debug, Clone, Default)]
14#[command(name = "nes_main")]
15#[command(version, about, long_about = None)]
16#[command(after_help = "For more information, see docs/CLI_INTERFACE.md")]
17pub struct CliArgs {
18 #[arg(short, long, default_value_t = false)]
20 pub quiet: bool,
21
22 #[arg(short, long, default_value_t = false)]
24 pub verbose: bool,
25
26 #[arg(short, long, value_parser = value_parser!(PathBuf), value_hint = clap::ValueHint::FilePath)]
28 pub config: Option<PathBuf>,
29
30 #[command(flatten)]
31 pub rom: RomArgs,
32
33 #[command(flatten)]
34 pub savestate: SavestateArgs,
35
36 #[command(flatten)]
37 pub memory: MemoryArgs,
38
39 #[command(flatten)]
40 pub power: PowerArgs,
41
42 #[command(flatten)]
43 pub palette: PaletteArgs,
44
45 #[command(flatten)]
46 pub video: VideoArgs,
47
48 #[command(flatten)]
49 pub execution: ExecutionArgs,
50
51 #[command(flatten)]
52 pub output: OutputArgs,
53}
54
55#[derive(Args, Debug, Clone, Default)]
57pub struct RomArgs {
58 #[arg(short, long, value_parser = value_parser!(PathBuf), value_hint = clap::ValueHint::FilePath)]
60 pub rom: Option<PathBuf>,
61
62 #[arg(long, default_value_t = false)]
64 pub rom_info: bool,
65}
66
67#[derive(Args, Debug, Clone, Default)]
69pub struct SavestateArgs {
70 #[arg(short = 'l', long, value_parser = value_parser!(PathBuf), value_hint = clap::ValueHint::FilePath)]
72 pub load_state: Option<PathBuf>,
73
74 #[arg(short = 's', long, value_parser = value_parser!(PathBuf), value_hint = clap::ValueHint::FilePath)]
76 pub save_state: Option<PathBuf>,
77
78 #[arg(long, default_value_t = false)]
80 pub state_stdin: bool,
81
82 #[arg(long, default_value_t = false)]
84 pub state_stdout: bool,
85
86 #[arg(long)]
88 pub save_state_on: Option<String>,
89
90 #[arg(long, default_value = "binary")]
92 pub state_format: SavestateFormat,
93}
94
95#[derive(Debug, Clone, Copy, ValueEnum, Default, PartialEq, Eq, Deserialize)]
97pub enum SavestateFormat {
98 #[default]
100 Binary,
101 Json,
103}
104
105#[derive(Args, Debug, Clone, Default)]
107pub struct MemoryArgs {
108 #[arg(long)]
110 pub read_cpu: Option<String>,
111
112 #[arg(long)]
114 pub read_ppu: Option<String>,
115
116 #[arg(long, default_value_t = false)]
118 pub dump_oam: bool,
119
120 #[arg(long, default_value_t = false)]
122 pub dump_nametables: bool,
123
124 #[arg(long, default_value_t = false)]
126 pub dump_palette: bool,
127
128 #[arg(long, action = clap::ArgAction::Append)]
130 pub init_cpu: Vec<String>,
131
132 #[arg(long, action = clap::ArgAction::Append)]
134 pub init_ppu: Vec<String>,
135
136 #[arg(long, action = clap::ArgAction::Append)]
138 pub init_oam: Vec<String>,
139
140 #[arg(long, value_parser = value_parser!(PathBuf), value_hint = clap::ValueHint::FilePath)]
142 pub init_file: Option<PathBuf>,
143}
144
145#[derive(Args, Debug, Clone, Default)]
147pub struct PowerArgs {
148 #[arg(long, default_value_t = false)]
150 pub no_power: bool,
151
152 #[arg(long, default_value_t = false)]
154 pub reset: bool,
155}
156
157#[derive(Args, Debug, Clone, Default)]
159pub struct PaletteArgs {
160 #[arg(short, long, value_parser = value_parser!(PathBuf), value_hint = clap::ValueHint::FilePath)]
162 pub palette: Option<PathBuf>,
163
164 #[arg(long)]
166 pub palette_builtin: Option<BuiltinPalette>,
167}
168
169#[derive(Debug, Clone, Copy, ValueEnum, Default)]
171pub enum BuiltinPalette {
172 #[default]
174 #[value(name = "2C02G")]
175 Nes2C02G,
176 Composite,
178}
179
180#[derive(Args, Debug, Clone, Default)]
182pub struct VideoArgs {
183 #[arg(long, value_parser = value_parser!(PathBuf), value_hint = clap::ValueHint::FilePath)]
185 pub screenshot: Option<PathBuf>,
186
187 #[arg(long)]
189 pub screenshot_on: Option<String>,
190
191 #[arg(long, value_parser = value_parser!(PathBuf), value_hint = clap::ValueHint::FilePath)]
193 pub video_path: Option<PathBuf>,
194
195 #[arg(long, default_value = "raw")]
197 pub video_format: VideoFormat,
198
199 #[arg(long, default_value = "1x")]
203 pub video_fps: String,
204
205 #[arg(long, default_value = "accurate")]
210 pub video_mode: VideoExportMode,
211
212 #[arg(long, default_value = "native")]
215 pub video_scale: Option<String>,
216
217 #[arg(long)]
220 pub renderer: Option<String>,
221
222 #[arg(long, default_value_t = false)]
224 pub list_renderers: bool,
225}
226
227#[derive(Debug, Clone, Copy, ValueEnum, Default, PartialEq, Eq)]
229pub enum VideoExportMode {
230 #[default]
233 Accurate,
234 Smooth,
237}
238
239#[derive(Debug, Clone, Copy, ValueEnum, Default, PartialEq, Eq)]
241pub enum VideoFormat {
242 #[default]
244 Raw,
245 Ppm,
247 Png,
249 Mp4,
251}
252
253#[derive(Args, Debug, Clone, Default)]
255pub struct ExecutionArgs {
256 #[arg(long)]
258 pub cycles: Option<u128>,
259
260 #[arg(short, long)]
262 pub frames: Option<u64>,
263
264 #[arg(long, value_parser = parse_hex_u8)]
266 pub until_opcode: Option<u8>,
267
268 #[arg(long)]
270 pub until_mem: Option<Vec<String>>,
271
272 #[arg(long, default_value_t = false)]
274 pub until_hlt: bool,
275
276 #[arg(long, value_parser = value_parser!(PathBuf), value_hint = clap::ValueHint::FilePath)]
278 pub trace: Option<PathBuf>,
279
280 #[arg(long, value_parser = parse_hex_u16, action = clap::ArgAction::Append)]
282 pub breakpoint: Vec<u16>,
283
284 #[arg(long, action = clap::ArgAction::Append)]
289 pub watch_mem: Vec<String>,
290}
291
292#[derive(Args, Debug, Clone, Default)]
294pub struct OutputArgs {
295 #[arg(short, long, value_parser = value_parser!(PathBuf), value_hint = clap::ValueHint::FilePath)]
297 pub output: Option<PathBuf>,
298
299 #[arg(long, default_value = "hex")]
301 pub output_format: OutputFormat,
302
303 #[arg(long, default_value_t = false)]
305 pub json: bool,
306
307 #[arg(long, default_value_t = false)]
309 pub toml: bool,
310
311 #[arg(long, default_value_t = false)]
313 pub binary: bool,
314}
315
316#[derive(Debug, Clone, Copy, ValueEnum, Default, PartialEq, Eq)]
318pub enum OutputFormat {
319 #[default]
321 Hex,
322 Json,
324 Toml,
326 Binary,
328}
329
330impl std::str::FromStr for OutputFormat {
331 type Err = String;
332
333 fn from_str(s: &str) -> Result<Self, Self::Err> {
334 match s.to_lowercase().as_str() {
335 "hex" => Ok(OutputFormat::Hex),
336 "json" => Ok(OutputFormat::Json),
337 "toml" => Ok(OutputFormat::Toml),
338 "binary" => Ok(OutputFormat::Binary),
339 _ => Err(format!(
340 "Unknown output format: '{}'. Valid options: hex, json, toml, binary",
341 s
342 )),
343 }
344 }
345}
346
347impl std::str::FromStr for VideoFormat {
348 type Err = String;
349
350 fn from_str(s: &str) -> Result<Self, Self::Err> {
351 match s.to_lowercase().as_str() {
352 "raw" => Ok(VideoFormat::Raw),
353 "ppm" => Ok(VideoFormat::Ppm),
354 "png" => Ok(VideoFormat::Png),
355 "mp4" => Ok(VideoFormat::Mp4),
356 _ => Err(format!(
357 "Unknown video format: '{}'. Valid options: raw, ppm, png, mp4",
358 s
359 )),
360 }
361 }
362}
363
364impl std::str::FromStr for VideoExportMode {
365 type Err = String;
366
367 fn from_str(s: &str) -> Result<Self, Self::Err> {
368 match s.to_lowercase().as_str() {
369 "accurate" => Ok(VideoExportMode::Accurate),
370 "smooth" => Ok(VideoExportMode::Smooth),
371 _ => Err(format!(
372 "Unknown video export mode: '{}'. Valid options: accurate, smooth",
373 s
374 )),
375 }
376 }
377}
378
379impl std::str::FromStr for BuiltinPalette {
380 type Err = String;
381
382 fn from_str(s: &str) -> Result<Self, Self::Err> {
383 match s.to_lowercase().as_str() {
384 "2c02g" => Ok(BuiltinPalette::Nes2C02G),
385 "composite" => Ok(BuiltinPalette::Composite),
386 _ => Err(format!(
387 "Unknown palette: '{}'. Valid options: 2C02G, composite",
388 s
389 )),
390 }
391 }
392}
393
394pub fn parse_hex_u16(s: &str) -> Result<u16, String> {
396 let s = s
397 .strip_prefix("0x")
398 .or_else(|| s.strip_prefix("0X"))
399 .unwrap_or(s);
400 u16::from_str_radix(s, 16).map_err(|e| format!("Invalid hex value '{}': {}", s, e))
401}
402
403pub fn parse_hex_u8(s: &str) -> Result<u8, String> {
405 let s = s
406 .strip_prefix("0x")
407 .or_else(|| s.strip_prefix("0X"))
408 .unwrap_or(s);
409 u8::from_str_radix(s, 16).map_err(|e| format!("Invalid hex value '{}': {}", s, e))
410}
411
412impl OutputArgs {
413 pub fn effective_format(&self) -> OutputFormat {
415 if self.json {
416 OutputFormat::Json
417 } else if self.toml {
418 OutputFormat::Toml
419 } else if self.binary {
420 OutputFormat::Binary
421 } else {
422 self.output_format
423 }
424 }
425}