1use std::io::{Read, Write};
14use std::path::{Path, PathBuf};
15
16use monsoon_core::emulation::nes::{MASTER_CYCLES_PER_FRAME, Nes, RunOptions};
17use monsoon_core::emulation::savestate::{SaveState, try_load_state_from_bytes};
18use monsoon_core::util::ToBytes;
19
20use crate::cli::args::parse_hex_u8;
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum MemoryAccessType {
28 Read,
30 Write,
32 ReadWrite,
34}
35
36impl MemoryAccessType {
37 pub fn parse(s: &str) -> Result<Self, String> {
39 match s.to_lowercase().as_str() {
40 "r" | "read" => Ok(Self::Read),
41 "w" | "write" => Ok(Self::Write),
42 "rw" | "readwrite" | "both" => Ok(Self::ReadWrite),
43 _ => Err(format!(
44 "Invalid memory access type '{}'. Expected: r, w, or rw",
45 s
46 )),
47 }
48 }
49}
50
51#[derive(Debug, Clone, PartialEq, Eq)]
53pub enum StopReason {
54 CyclesReached(u128),
56 FramesReached(u64),
58 PcReached(u16),
60 MemoryCondition(u16, u8),
62 MemoryWatchpoint {
64 addr: u16,
65 access_type: MemoryAccessType,
66 was_read: bool,
67 },
68 Halted,
70 Breakpoint(u16),
72 Error(String),
74 Completed,
76}
77
78#[derive(Debug, Clone)]
80pub enum StopCondition {
81 Cycles(u128),
83 Frames(u64),
85 PcEquals(u16),
87 Opcode(u8),
89 MemoryEquals {
91 addr: u16,
92 value: u8,
93 and: Option<Box<StopCondition>>,
94 },
95 MemoryNotEquals {
97 addr: u16,
98 value: u8,
99 and: Option<Box<StopCondition>>,
100 },
101 OnHalt,
103 Breakpoint(u16),
105 MemoryWatch {
107 addr: u16,
108 access_type: MemoryAccessType,
109 },
110}
111
112impl StopCondition {
113 pub fn parse_memory_condition(vec: &Vec<String>) -> Result<Vec<Self>, String> {
115 let mut res = Vec::new();
116 for s in vec {
117 let cond = Self::parse_single_condition(s);
118
119 #[allow(clippy::question_mark)]
120 if let Ok(cond) = cond {
121 res.push(cond)
122 } else if let Err(cond) = cond {
123 return Err(cond);
124 }
125 }
126
127 Ok(res)
128 }
129
130 pub fn parse_single_condition(s: &String) -> Result<Self, String> {
131 if let Some((cond1, cond2)) = s.split_once("&&") {
132 let cond1 = Self::parse_single_condition(&cond1.to_string());
133 let cond2 = Self::parse_single_condition(&cond2.to_string());
134
135 if let (Ok(cond1), Ok(cond2)) = (cond1, cond2) {
136 match cond1 {
137 StopCondition::MemoryEquals {
138 addr,
139 value,
140 ..
141 } => {
142 return Ok(StopCondition::MemoryEquals {
143 addr,
144 value,
145 and: Some(Box::new(cond2)),
146 });
147 }
148 StopCondition::MemoryNotEquals {
149 addr,
150 value,
151 ..
152 } => {
153 return Ok(StopCondition::MemoryNotEquals {
154 addr,
155 value,
156 and: Some(Box::new(cond2)),
157 });
158 }
159 _ => {}
160 }
161 }
162 }
163
164 if let Some((addr_str, val_str)) = s.split_once("==") {
165 let addr = parse_hex_u16(addr_str.trim())?;
166 let value = parse_hex_u8(val_str.trim())?;
167 Ok(StopCondition::MemoryEquals {
168 addr,
169 value,
170 and: None,
171 })
172 } else if let Some((addr_str, val_str)) = s.split_once("!=") {
173 let addr = parse_hex_u16(addr_str.trim())?;
174 let value = parse_hex_u8(val_str.trim())?;
175 Ok(StopCondition::MemoryNotEquals {
176 addr,
177 value,
178 and: None,
179 })
180 } else {
181 Err(format!(
182 "Invalid memory condition '{}'. Expected format: ADDR==VALUE or ADDR!=VALUE",
183 s
184 ))
185 }
186 }
187
188 pub fn parse_memory_watch(s: &str) -> Result<Self, String> {
190 let (addr_str, access_type) = if let Some((addr_part, mode_part)) = s.split_once(':') {
191 (addr_part, MemoryAccessType::parse(mode_part)?)
192 } else {
193 (s, MemoryAccessType::ReadWrite) };
195
196 let addr = parse_hex_u16(addr_str.trim())?;
197 Ok(StopCondition::MemoryWatch {
198 addr,
199 access_type,
200 })
201 }
202
203 pub fn parse_memory_watches(watches: &[String]) -> Result<Vec<Self>, String> {
205 watches
206 .iter()
207 .map(|s| Self::parse_memory_watch(s))
208 .collect()
209 }
210
211 pub fn check(&self, emu: &Nes, cycles: u128, frames: u64) -> bool {
212 match self {
213 StopCondition::Cycles(target) => cycles >= *target,
214 StopCondition::Frames(target) => frames >= *target,
215 StopCondition::PcEquals(addr) | StopCondition::Breakpoint(addr) => {
216 emu.program_counter() == *addr
217 }
218 StopCondition::Opcode(op) => {
219 if let Some(opcode) = emu.current_opcode_byte()
220 && opcode == *op
221 {
222 return true;
223 }
224
225 false
226 }
227 StopCondition::MemoryEquals {
228 addr,
229 value,
230 and,
231 } => {
232 let and = and.as_ref().map(|and| and.check(emu, cycles, frames));
233
234 let mem_val = emu.get_memory_debug(Some(*addr..=*addr))[0]
235 .first()
236 .copied()
237 .unwrap_or(0);
238
239 if let Some(and) = and {
240 mem_val == *value && and
241 } else {
242 mem_val == *value
243 }
244 }
245 StopCondition::MemoryNotEquals {
246 addr,
247 value,
248 and,
249 } => {
250 let and = and.as_ref().map(|and| and.check(emu, cycles, frames));
251
252 let mem_val = emu.get_memory_debug(Some(*addr..=*addr))[0]
253 .first()
254 .copied()
255 .unwrap_or(0);
256
257 if let Some(and) = and {
258 mem_val != *value && and
259 } else {
260 mem_val != *value
261 }
262 }
263 StopCondition::OnHalt => emu.is_halted(),
264 StopCondition::MemoryWatch {
265 addr,
266 access_type,
267 } => {
268 if let Some(last_access) = emu.last_memory_access() {
270 let (access_addr, was_read, _) = last_access;
271 if access_addr == *addr {
272 match access_type {
273 MemoryAccessType::Read => was_read,
274 MemoryAccessType::Write => !was_read,
275 MemoryAccessType::ReadWrite => true,
276 }
277 } else {
278 false
279 }
280 } else {
281 false
282 }
283 }
284 }
285 }
286
287 pub fn reason(&self, emu: &Nes, cycles: u128, frames: u64) -> StopReason {
288 match self {
289 StopCondition::Cycles(_) => StopReason::CyclesReached(cycles),
290 StopCondition::Frames(_) => StopReason::FramesReached(frames),
291 StopCondition::PcEquals(addr) | StopCondition::Breakpoint(addr) => {
292 StopReason::PcReached(*addr)
293 }
294 StopCondition::Opcode(_) => StopReason::PcReached(emu.program_counter()),
295 StopCondition::MemoryEquals {
296 addr, ..
297 }
298 | StopCondition::MemoryNotEquals {
299 addr, ..
300 } => {
301 let mem_val = emu.get_memory_debug(Some(*addr..=*addr))[0]
302 .first()
303 .copied()
304 .unwrap_or(0);
305
306 StopReason::MemoryCondition(*addr, mem_val)
307 }
308 StopCondition::OnHalt => StopReason::Halted,
309 StopCondition::MemoryWatch {
310 addr,
311 access_type,
312 } => {
313 let was_read = emu
314 .last_memory_access()
315 .map(|(_, was_read, _)| was_read)
316 .unwrap_or(true);
317 StopReason::MemoryWatchpoint {
318 addr: *addr,
319 access_type: *access_type,
320 was_read,
321 }
322 }
323 }
324 }
325}
326
327#[derive(Debug, Clone, Default)]
336pub struct ExecutionConfig {
337 pub stop_conditions: Vec<StopCondition>,
339 pub stop_on_halt: bool,
341 pub trace_path: Option<PathBuf>,
343 pub verbose: bool,
345}
346
347impl ExecutionConfig {
348 pub fn new() -> Self { Self::default() }
350
351 pub fn with_stop_condition(mut self, condition: StopCondition) -> Self {
353 self.stop_conditions.push(condition);
354 self
355 }
356
357 pub fn with_cycles(mut self, cycles: u128) -> Self {
359 self.stop_conditions.push(StopCondition::Cycles(cycles));
360 self
361 }
362
363 pub fn with_frames(mut self, frames: u64) -> Self {
365 self.stop_conditions.push(StopCondition::Frames(frames));
366 self
367 }
368
369 pub fn with_pc_breakpoint(mut self, addr: u16) -> Self {
371 self.stop_conditions.push(StopCondition::PcEquals(addr));
372 self
373 }
374
375 pub fn with_breakpoint(mut self, addr: u16) -> Self {
377 self.stop_conditions.push(StopCondition::PcEquals(addr));
378 self
379 }
380
381 pub fn with_memory_watch(mut self, addr: u16, access_type: MemoryAccessType) -> Self {
383 self.stop_conditions.push(StopCondition::MemoryWatch {
384 addr,
385 access_type,
386 });
387 self
388 }
389
390 pub fn with_trace(mut self, path: PathBuf) -> Self {
392 self.trace_path = Some(path);
393 self
394 }
395
396 pub fn with_verbose(mut self, verbose: bool) -> Self {
398 self.verbose = verbose;
399 self
400 }
401
402 pub fn with_stop_on_halt(mut self, stop: bool) -> Self {
404 self.stop_on_halt = stop;
405 self
406 }
407
408 fn max_cycles(&self) -> u128 {
410 let mut max = u128::MAX;
411 for cond in &self.stop_conditions {
412 match cond {
413 StopCondition::Cycles(c) => max = max.min(*c),
414 StopCondition::Frames(f) => {
415 max = max.min(*f as u128 * MASTER_CYCLES_PER_FRAME as u128)
416 }
417 _ => {}
418 }
419 }
420 max
421 }
422
423 fn check_conditions(&self, emu: &Nes, cycles: u128, frames: u64) -> Option<StopReason> {
425 for cond in &self.stop_conditions {
426 if cond.check(emu, cycles, frames) {
427 return Some(cond.reason(emu, cycles, frames));
428 }
429 }
430
431 None
432 }
433}
434
435#[derive(Debug, Clone)]
441pub struct ExecutionResult {
442 pub stop_reason: StopReason,
444 pub total_cycles: u128,
446 pub total_frames: u64,
448}
449
450#[derive(Debug, Clone)]
456pub enum SavestateSource {
457 File(PathBuf),
459 Stdin,
461 Bytes(Vec<u8>),
463}
464
465#[derive(Debug, Clone)]
467pub enum SavestateDestination {
468 File(PathBuf),
470 Stdout,
472}
473
474pub use crate::cli::args::SavestateFormat;
476use crate::cli::{CliArgs, parse_hex_u16};
477
478#[derive(Debug, Clone, Default)]
480pub struct SavestateConfig {
481 pub load_from: Option<SavestateSource>,
483 pub save_to: Option<SavestateDestination>,
485 pub format: SavestateFormat,
487}
488
489impl SavestateConfig {
490 pub fn new() -> Self { Self::default() }
492
493 pub fn load_from_file(mut self, path: PathBuf) -> Self {
495 self.load_from = Some(SavestateSource::File(path));
496 self
497 }
498
499 pub fn load_from_stdin(mut self) -> Self {
501 self.load_from = Some(SavestateSource::Stdin);
502 self
503 }
504
505 pub fn save_to_file(mut self, path: PathBuf) -> Self {
507 self.save_to = Some(SavestateDestination::File(path));
508 self
509 }
510
511 pub fn save_to_stdout(mut self) -> Self {
513 self.save_to = Some(SavestateDestination::Stdout);
514 self
515 }
516
517 pub fn with_format(mut self, format: SavestateFormat) -> Self {
519 self.format = format;
520 self
521 }
522}
523
524pub struct ExecutionEngine {
542 pub emu: Nes,
544 pub config: ExecutionConfig,
546 pub savestate_config: SavestateConfig,
548 pub frames: Vec<Vec<u16>>,
550 frame_count: u64,
552 collect_frames: bool,
554}
555
556impl ExecutionEngine {
557 pub fn new() -> Self {
559 Self {
560 emu: Nes::default(),
561 config: ExecutionConfig::new(),
562 savestate_config: SavestateConfig::new(),
563 frames: vec![],
564 frame_count: 0,
565 collect_frames: true,
566 }
567 }
568
569 pub fn with_emulator(emu: Nes) -> Self {
571 Self {
572 emu,
573 config: ExecutionConfig::new(),
574 savestate_config: SavestateConfig::new(),
575 frames: vec![],
576 frame_count: 0,
577 collect_frames: true,
578 }
579 }
580
581 pub fn with_config(mut self, config: ExecutionConfig) -> Self {
583 self.config = config;
584 self
585 }
586
587 pub fn with_savestate_config(mut self, config: SavestateConfig) -> Self {
589 self.savestate_config = config;
590 self
591 }
592
593 pub fn load_rom(&mut self, path: &Path) -> Result<(), String> {
595 let path_str = path.to_string_lossy().to_string();
596 self.emu.load_rom(&path_str);
597 Ok(())
598 }
599
600 pub fn power_on(&mut self) { self.emu.power(); }
602
603 pub fn power_off(&mut self) { self.emu.power_off(); }
605
606 pub fn reset(&mut self) { self.emu.reset(); }
608
609 pub fn load_savestate(&mut self) -> Result<(), String> {
611 if let Some(ref source) = self.savestate_config.load_from {
612 let state = match source {
613 SavestateSource::File(path) => {
614 let data = std::fs::read(path).map_err(|e| {
615 format!("Failed to read savestate from {}: {}", path.display(), e)
616 })?;
617 try_load_state_from_bytes(&data).ok_or_else(|| {
618 format!("Failed to load savestate from {}", path.display())
619 })?
620 }
621 SavestateSource::Stdin => {
622 let mut buffer = Vec::new();
623 std::io::stdin()
624 .read_to_end(&mut buffer)
625 .map_err(|e| format!("Failed to read savestate from stdin: {}", e))?;
626 decode_savestate(&buffer)?
627 }
628 SavestateSource::Bytes(bytes) => decode_savestate(bytes)?,
629 };
630 self.emu.load_state(state);
631 }
632 Ok(())
633 }
634
635 pub fn save_savestate(&self) -> Result<(), String> {
637 if let Some(ref dest) = self.savestate_config.save_to {
638 let state = self
639 .emu
640 .save_state()
641 .ok_or_else(|| "No ROM loaded, cannot save state".to_string())?;
642 let encoded = encode_savestate(&state, self.savestate_config.format)?;
643
644 match dest {
645 SavestateDestination::File(path) => {
646 std::fs::write(path, &encoded).map_err(|e| {
647 format!("Failed to write savestate to {}: {}", path.display(), e)
648 })?;
649 }
650 SavestateDestination::Stdout => {
651 std::io::stdout()
652 .write_all(&encoded)
653 .map_err(|e| format!("Failed to write savestate to stdout: {}", e))?;
654 }
655 }
656 }
657 Ok(())
658 }
659
660 pub fn run(&mut self) -> Result<ExecutionResult, String> {
662 if self.config.trace_path.is_some() {
664 self.emu.enable_trace();
665 }
666
667 let max_cycles = self.config.max_cycles();
668 let start_cycles = self.emu.total_cycles;
669
670 let result = loop {
672 match self.emu.step_frame() {
674 Ok(_) => {}
675 Err(e) => {
676 break ExecutionResult {
677 stop_reason: StopReason::Error(e),
678 total_cycles: self.emu.total_cycles - start_cycles,
679 total_frames: self.frame_count,
680 };
681 }
682 }
683
684 if self.collect_frames {
686 self.frames.push(self.emu.get_pixel_buffer());
687 }
688
689 self.frame_count += 1;
690 let cycles_run = self.emu.total_cycles - start_cycles;
691
692 if let Some(reason) =
694 self.config
695 .check_conditions(&self.emu, cycles_run, self.frame_count)
696 {
697 break ExecutionResult {
698 stop_reason: reason,
699 total_cycles: cycles_run,
700 total_frames: self.frame_count,
701 };
702 }
703
704 if self.emu.total_cycles >= max_cycles {
706 break ExecutionResult {
707 stop_reason: StopReason::Completed,
708 total_cycles: cycles_run,
709 total_frames: self.frame_count,
710 };
711 }
712 };
713
714 self.write_trace_log()?;
716
717 Ok(result)
718 }
719
720 pub fn run_with_video_encoder(
745 &mut self,
746 encoder: &mut super::video::StreamingVideoEncoder,
747 renderer: &mut Box<dyn monsoon_core::emulation::screen_renderer::ScreenRenderer>,
748 ) -> Result<ExecutionResult, String> {
749 self.collect_frames = false;
751
752 if self.config.trace_path.is_some() {
754 self.emu.enable_trace();
755 }
756
757 let max_cycles = self.config.max_cycles();
758 let start_cycles = self.emu.total_cycles;
759
760 let captures_per_frame = encoder.captures_per_frame();
762
763 loop {
765 let frame_start_cycles = self.emu.total_cycles;
768
769 for capture_idx in 0..captures_per_frame {
771 let odd_frame_offset = if self.emu.is_even_frame() && self.emu.is_rendering() {
775 2
776 } else {
777 -2
778 };
779
780 let base = (capture_idx + 1) as u128 * MASTER_CYCLES_PER_FRAME as u128;
781
782 let base = if odd_frame_offset >= 0 {
783 base.saturating_add(odd_frame_offset as u128)
784 } else {
785 base.saturating_sub((-odd_frame_offset) as u128)
786 };
787
788 let capture_point = base / captures_per_frame as u128;
789 let target_cycles = frame_start_cycles + capture_point;
790
791 match self.emu.run_until(target_cycles, RunOptions::default()) {
793 Ok(_) => {}
794 Err(e) => {
795 return Ok(ExecutionResult {
796 stop_reason: StopReason::Error(e),
797 total_cycles: self.emu.total_cycles - start_cycles,
798 total_frames: self.frame_count,
799 });
800 }
801 }
802
803 let frame = self.emu.get_pixel_buffer();
806 let rgb_frame = renderer.buffer_to_image(&frame);
807 encoder
808 .write_frame(rgb_frame)
809 .map_err(|e| format!("Video encoding error: {}", e))?;
810
811 if capture_idx == captures_per_frame - 1 {
814 self.frame_count += 1;
815 }
816 }
817
818 let cycles_run = self.emu.total_cycles - start_cycles;
819
820 if let Some(reason) =
822 self.config
823 .check_conditions(&self.emu, cycles_run, self.frame_count)
824 {
825 self.write_trace_log()?;
826 return Ok(ExecutionResult {
827 stop_reason: reason,
828 total_cycles: cycles_run,
829 total_frames: self.frame_count,
830 });
831 }
832
833 if self.emu.total_cycles >= max_cycles {
835 self.write_trace_log()?;
836 return Ok(ExecutionResult {
837 stop_reason: StopReason::Completed,
838 total_cycles: cycles_run,
839 total_frames: self.frame_count,
840 });
841 }
842 }
843 }
844
845 pub fn set_collect_frames(&mut self, collect: bool) { self.collect_frames = collect; }
850
851 pub fn emulator(&self) -> &Nes { &self.emu }
853
854 pub fn emulator_mut(&mut self) -> &mut Nes { &mut self.emu }
856
857 fn write_trace_log(&self) -> Result<(), String> {
859 if let Some(ref path) = self.config.trace_path
860 && let Some(trace) = self.emu.trace_log()
861 {
862 std::fs::write(path, &trace.log)
863 .map_err(|e| format!("Failed to write trace log to {}: {}", path.display(), e))?;
864 }
865 Ok(())
866 }
867}
868
869impl Default for ExecutionEngine {
870 fn default() -> Self { Self::new() }
871}
872
873fn decode_savestate(bytes: &[u8]) -> Result<SaveState, String> {
883 try_load_state_from_bytes(bytes)
884 .ok_or_else(|| "Failed to decode savestate (tried all supported formats)".to_string())
885}
886
887fn encode_savestate(state: &SaveState, format: SavestateFormat) -> Result<Vec<u8>, String> {
889 match format {
890 SavestateFormat::Binary => Ok(state.to_bytes(None)),
891 SavestateFormat::Json => Ok(state.to_bytes(Some("json".to_string()))),
892 }
893}
894
895impl ExecutionConfig {
900 pub fn from_cli_args(args: &CliArgs) -> Self {
902 let mut config = Self::new();
903
904 if let Some(cycles) = args.execution.cycles {
906 config.stop_conditions.push(StopCondition::Cycles(cycles));
907 }
908 if let Some(frames) = args.execution.frames {
909 config.stop_conditions.push(StopCondition::Frames(frames));
910 }
911
912 if let Some(op) = args.execution.until_opcode {
914 config.stop_conditions.push(StopCondition::Opcode(op));
915 }
916
917 if let Some(ref mem_cond) = args.execution.until_mem
919 && let Ok(cond) = StopCondition::parse_memory_condition(mem_cond)
920 {
921 config.stop_conditions.extend(cond);
922 }
923
924 if !args.execution.watch_mem.is_empty()
926 && let Ok(watches) = StopCondition::parse_memory_watches(&args.execution.watch_mem)
927 {
928 config.stop_conditions.extend(watches);
929 }
930
931 if args.execution.until_hlt {
933 config.stop_on_halt = true;
934 }
935
936 for bp in &args.execution.breakpoint {
938 config.stop_conditions.push(StopCondition::PcEquals(*bp));
939 }
940
941 config.trace_path = args.execution.trace.clone();
943
944 config.verbose = args.verbose;
946
947 if config.stop_conditions.is_empty() && !config.stop_on_halt {
949 config.stop_conditions.push(StopCondition::Frames(60));
950 }
951
952 config
953 }
954}
955
956impl SavestateConfig {
957 pub fn from_cli_args(args: &CliArgs) -> Self {
959 let mut config = Self::new();
960
961 if args.savestate.state_stdin {
963 config.load_from = Some(SavestateSource::Stdin);
964 } else if let Some(ref path) = args.savestate.load_state {
965 config.load_from = Some(SavestateSource::File(path.clone()));
966 }
967
968 if args.savestate.state_stdout {
970 config.save_to = Some(SavestateDestination::Stdout);
971 } else if let Some(ref path) = args.savestate.save_state {
972 config.save_to = Some(SavestateDestination::File(path.clone()));
973 }
974
975 config.format = args.savestate.state_format;
977
978 config
979 }
980}