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),
106 MemoryWatch {
108 addr: u16,
109 access_type: MemoryAccessType,
110 },
111}
112
113impl StopCondition {
114 pub fn parse_memory_condition(vec: &Vec<String>) -> Result<Vec<Self>, String> {
116 let mut res = Vec::new();
117 for s in vec {
118 let cond = Self::parse_single_condition(s);
119
120 #[allow(clippy::question_mark)]
121 if let Ok(cond) = cond {
122 res.push(cond)
123 } else if let Err(cond) = cond {
124 return Err(cond);
125 }
126 }
127
128 Ok(res)
129 }
130
131 pub fn parse_single_condition(s: &String) -> Result<Self, String> {
132 if let Some((cond1, cond2)) = s.split_once("&&") {
133 let cond1 = Self::parse_single_condition(&cond1.to_string());
134 let cond2 = Self::parse_single_condition(&cond2.to_string());
135
136 if let (Ok(cond1), Ok(cond2)) = (cond1, cond2) {
137 match cond1 {
138 StopCondition::MemoryEquals {
139 addr,
140 value,
141 ..
142 } => {
143 return Ok(StopCondition::MemoryEquals {
144 addr,
145 value,
146 and: Some(Box::new(cond2)),
147 });
148 }
149 StopCondition::MemoryNotEquals {
150 addr,
151 value,
152 ..
153 } => {
154 return Ok(StopCondition::MemoryNotEquals {
155 addr,
156 value,
157 and: Some(Box::new(cond2)),
158 });
159 }
160 _ => {}
161 }
162 }
163 }
164
165 if let Some((addr_str, val_str)) = s.split_once("==") {
166 let addr = parse_hex_u16(addr_str.trim())?;
167 let value = parse_hex_u8(val_str.trim())?;
168 Ok(StopCondition::MemoryEquals {
169 addr,
170 value,
171 and: None,
172 })
173 } else if let Some((addr_str, val_str)) = s.split_once("!=") {
174 let addr = parse_hex_u16(addr_str.trim())?;
175 let value = parse_hex_u8(val_str.trim())?;
176 Ok(StopCondition::MemoryNotEquals {
177 addr,
178 value,
179 and: None,
180 })
181 } else {
182 Err(format!(
183 "Invalid memory condition '{}'. Expected format: ADDR==VALUE or ADDR!=VALUE",
184 s
185 ))
186 }
187 }
188
189 pub fn parse_memory_watch(s: &str) -> Result<Self, String> {
191 let (addr_str, access_type) = if let Some((addr_part, mode_part)) = s.split_once(':') {
192 (addr_part, MemoryAccessType::parse(mode_part)?)
193 } else {
194 (s, MemoryAccessType::ReadWrite) };
196
197 let addr = parse_hex_u16(addr_str.trim())?;
198 Ok(StopCondition::MemoryWatch {
199 addr,
200 access_type,
201 })
202 }
203
204 pub fn parse_memory_watches(watches: &[String]) -> Result<Vec<Self>, String> {
206 watches
207 .iter()
208 .map(|s| Self::parse_memory_watch(s))
209 .collect()
210 }
211
212 pub fn check(&self, emu: &Nes, cycles: u128, frames: u64) -> bool {
213 match self {
214 StopCondition::Cycles(target) => cycles >= *target,
215 StopCondition::Frames(target) => frames >= *target,
216 StopCondition::PcEquals(addr) | StopCondition::Breakpoint(addr) => {
217 emu.program_counter() == *addr
218 }
219 StopCondition::Opcode(op) => {
220 if let Some(opcode) = emu.current_opcode_byte()
221 && opcode == *op
222 {
223 return true;
224 }
225
226 false
227 }
228 StopCondition::MemoryEquals {
229 addr,
230 value,
231 and,
232 } => {
233 let and = and.as_ref().map(|and| and.check(emu, cycles, frames));
234
235 let mem_val = emu.get_memory_debug(Some(*addr..=*addr))[0]
236 .first()
237 .copied()
238 .unwrap_or(0);
239
240 if let Some(and) = and {
241 mem_val == *value && and
242 } else {
243 mem_val == *value
244 }
245 }
246 StopCondition::MemoryNotEquals {
247 addr,
248 value,
249 and,
250 } => {
251 let and = and.as_ref().map(|and| and.check(emu, cycles, frames));
252
253 let mem_val = emu.get_memory_debug(Some(*addr..=*addr))[0]
254 .first()
255 .copied()
256 .unwrap_or(0);
257
258 if let Some(and) = and {
259 mem_val != *value && and
260 } else {
261 mem_val != *value
262 }
263 }
264 StopCondition::OnHalt => emu.is_halted(),
265 StopCondition::MemoryWatch {
266 addr,
267 access_type,
268 } => {
269 if let Some(last_access) = emu.last_memory_access() {
271 let (access_addr, was_read, _) = last_access;
272 if access_addr == *addr {
273 match access_type {
274 MemoryAccessType::Read => was_read,
275 MemoryAccessType::Write => !was_read,
276 MemoryAccessType::ReadWrite => true,
277 }
278 } else {
279 false
280 }
281 } else {
282 false
283 }
284 }
285 }
286 }
287
288 pub fn reason(&self, emu: &Nes, cycles: u128, frames: u64) -> StopReason {
289 match self {
290 StopCondition::Cycles(_) => StopReason::CyclesReached(cycles),
291 StopCondition::Frames(_) => StopReason::FramesReached(frames),
292 StopCondition::PcEquals(addr) | StopCondition::Breakpoint(addr) => {
293 StopReason::PcReached(*addr)
294 }
295 StopCondition::Opcode(_) => StopReason::PcReached(emu.program_counter()),
296 StopCondition::MemoryEquals {
297 addr, ..
298 }
299 | StopCondition::MemoryNotEquals {
300 addr, ..
301 } => {
302 let mem_val = emu.get_memory_debug(Some(*addr..=*addr))[0]
303 .first()
304 .copied()
305 .unwrap_or(0);
306
307 StopReason::MemoryCondition(*addr, mem_val)
308 }
309 StopCondition::OnHalt => StopReason::Halted,
310 StopCondition::MemoryWatch {
311 addr,
312 access_type,
313 } => {
314 let was_read = emu
315 .last_memory_access()
316 .map(|(_, was_read, _)| was_read)
317 .unwrap_or(true);
318 StopReason::MemoryWatchpoint {
319 addr: *addr,
320 access_type: *access_type,
321 was_read,
322 }
323 }
324 }
325 }
326}
327
328#[derive(Debug, Clone, Default)]
337pub struct ExecutionConfig {
338 pub stop_conditions: Vec<StopCondition>,
340 pub stop_on_halt: bool,
342 pub trace_path: Option<PathBuf>,
344 pub verbose: bool,
346}
347
348impl ExecutionConfig {
349 pub fn new() -> Self { Self::default() }
351
352 pub fn with_stop_condition(mut self, condition: StopCondition) -> Self {
354 self.stop_conditions.push(condition);
355 self
356 }
357
358 pub fn with_cycles(mut self, cycles: u128) -> Self {
360 self.stop_conditions.push(StopCondition::Cycles(cycles));
361 self
362 }
363
364 pub fn with_frames(mut self, frames: u64) -> Self {
366 self.stop_conditions.push(StopCondition::Frames(frames));
367 self
368 }
369
370 pub fn with_pc_breakpoint(mut self, addr: u16) -> Self {
372 self.stop_conditions.push(StopCondition::PcEquals(addr));
373 self
374 }
375
376 pub fn with_breakpoint(mut self, addr: u16) -> Self {
378 self.stop_conditions.push(StopCondition::PcEquals(addr));
379 self
380 }
381
382 pub fn with_memory_watch(mut self, addr: u16, access_type: MemoryAccessType) -> Self {
384 self.stop_conditions.push(StopCondition::MemoryWatch {
385 addr,
386 access_type,
387 });
388 self
389 }
390
391 pub fn with_trace(mut self, path: PathBuf) -> Self {
393 self.trace_path = Some(path);
394 self
395 }
396
397 pub fn with_verbose(mut self, verbose: bool) -> Self {
399 self.verbose = verbose;
400 self
401 }
402
403 pub fn with_stop_on_halt(mut self, stop: bool) -> Self {
405 self.stop_on_halt = stop;
406 self
407 }
408
409 fn max_cycles(&self) -> u128 {
411 let mut max = u128::MAX;
412 for cond in &self.stop_conditions {
413 match cond {
414 StopCondition::Cycles(c) => max = max.min(*c),
415 StopCondition::Frames(f) => {
416 max = max.min(*f as u128 * MASTER_CYCLES_PER_FRAME as u128)
417 }
418 _ => {}
419 }
420 }
421 max
422 }
423
424 fn check_conditions(&self, emu: &Nes, cycles: u128, frames: u64) -> Option<StopReason> {
426 for cond in &self.stop_conditions {
427 if cond.check(emu, cycles, frames) {
428 return Some(cond.reason(emu, cycles, frames));
429 }
430 }
431
432 None
433 }
434}
435
436#[derive(Debug, Clone)]
442pub struct ExecutionResult {
443 pub stop_reason: StopReason,
445 pub total_cycles: u128,
447 pub total_frames: u64,
449}
450
451#[derive(Debug, Clone)]
457pub enum SavestateSource {
458 File(PathBuf),
460 Stdin,
462 Bytes(Vec<u8>),
464}
465
466#[derive(Debug, Clone)]
468pub enum SavestateDestination {
469 File(PathBuf),
471 Stdout,
473}
474
475pub use crate::cli::args::SavestateFormat;
477use crate::cli::{CliArgs, parse_hex_u16};
478
479#[derive(Debug, Clone, Default)]
481pub struct SavestateConfig {
482 pub load_from: Option<SavestateSource>,
484 pub save_to: Option<SavestateDestination>,
486 pub format: SavestateFormat,
488}
489
490impl SavestateConfig {
491 pub fn new() -> Self { Self::default() }
493
494 pub fn load_from_file(mut self, path: PathBuf) -> Self {
496 self.load_from = Some(SavestateSource::File(path));
497 self
498 }
499
500 pub fn load_from_stdin(mut self) -> Self {
502 self.load_from = Some(SavestateSource::Stdin);
503 self
504 }
505
506 pub fn save_to_file(mut self, path: PathBuf) -> Self {
508 self.save_to = Some(SavestateDestination::File(path));
509 self
510 }
511
512 pub fn save_to_stdout(mut self) -> Self {
514 self.save_to = Some(SavestateDestination::Stdout);
515 self
516 }
517
518 pub fn with_format(mut self, format: SavestateFormat) -> Self {
520 self.format = format;
521 self
522 }
523}
524
525pub struct ExecutionEngine {
544 pub emu: Nes,
546 pub config: ExecutionConfig,
548 pub savestate_config: SavestateConfig,
550 pub frames: Vec<Vec<u16>>,
552 frame_count: u64,
554 collect_frames: bool,
556}
557
558impl ExecutionEngine {
559 pub fn new() -> Self {
561 Self {
562 emu: Nes::default(),
563 config: ExecutionConfig::new(),
564 savestate_config: SavestateConfig::new(),
565 frames: vec![],
566 frame_count: 0,
567 collect_frames: true,
568 }
569 }
570
571 pub fn with_emulator(emu: Nes) -> Self {
573 Self {
574 emu,
575 config: ExecutionConfig::new(),
576 savestate_config: SavestateConfig::new(),
577 frames: vec![],
578 frame_count: 0,
579 collect_frames: true,
580 }
581 }
582
583 pub fn with_config(mut self, config: ExecutionConfig) -> Self {
585 self.config = config;
586 self
587 }
588
589 pub fn with_savestate_config(mut self, config: SavestateConfig) -> Self {
591 self.savestate_config = config;
592 self
593 }
594
595 pub fn load_rom(&mut self, path: &Path) -> Result<(), String> {
597 let path_str = path.to_string_lossy().to_string();
598 self.emu.load_rom(&path_str);
599 Ok(())
600 }
601
602 pub fn power_on(&mut self) { self.emu.power(); }
604
605 pub fn power_off(&mut self) { self.emu.power_off(); }
607
608 pub fn reset(&mut self) { self.emu.reset(); }
610
611 pub fn load_savestate(&mut self) -> Result<(), String> {
613 if let Some(ref source) = self.savestate_config.load_from {
614 let state = match source {
615 SavestateSource::File(path) => {
616 let data = std::fs::read(path).map_err(|e| {
617 format!("Failed to read savestate from {}: {}", path.display(), e)
618 })?;
619 try_load_state_from_bytes(&data).ok_or_else(|| {
620 format!("Failed to load savestate from {}", path.display())
621 })?
622 }
623 SavestateSource::Stdin => {
624 let mut buffer = Vec::new();
625 std::io::stdin()
626 .read_to_end(&mut buffer)
627 .map_err(|e| format!("Failed to read savestate from stdin: {}", e))?;
628 decode_savestate(&buffer)?
629 }
630 SavestateSource::Bytes(bytes) => decode_savestate(bytes)?,
631 };
632 self.emu.load_state(state);
633 }
634 Ok(())
635 }
636
637 pub fn save_savestate(&self) -> Result<(), String> {
639 if let Some(ref dest) = self.savestate_config.save_to {
640 let state = self
641 .emu
642 .save_state()
643 .ok_or_else(|| "No ROM loaded, cannot save state".to_string())?;
644 let encoded = encode_savestate(&state, self.savestate_config.format)?;
645
646 match dest {
647 SavestateDestination::File(path) => {
648 std::fs::write(path, &encoded).map_err(|e| {
649 format!("Failed to write savestate to {}: {}", path.display(), e)
650 })?;
651 }
652 SavestateDestination::Stdout => {
653 std::io::stdout()
654 .write_all(&encoded)
655 .map_err(|e| format!("Failed to write savestate to stdout: {}", e))?;
656 }
657 }
658 }
659 Ok(())
660 }
661
662 pub fn run(&mut self) -> Result<ExecutionResult, String> {
664 if self.config.trace_path.is_some() {
666 self.emu.enable_trace();
667 }
668
669 let max_cycles = self.config.max_cycles();
670 let start_cycles = self.emu.total_cycles;
671
672 let result = loop {
674 match self.emu.step_frame() {
676 Ok(_) => {}
677 Err(e) => {
678 break ExecutionResult {
679 stop_reason: StopReason::Error(e),
680 total_cycles: self.emu.total_cycles - start_cycles,
681 total_frames: self.frame_count,
682 };
683 }
684 }
685
686 if self.collect_frames {
688 self.frames.push(self.emu.get_pixel_buffer());
689 }
690
691 self.frame_count += 1;
692 let cycles_run = self.emu.total_cycles - start_cycles;
693
694 if let Some(reason) =
696 self.config
697 .check_conditions(&self.emu, cycles_run, self.frame_count)
698 {
699 break ExecutionResult {
700 stop_reason: reason,
701 total_cycles: cycles_run,
702 total_frames: self.frame_count,
703 };
704 }
705
706 if self.emu.total_cycles >= max_cycles {
708 break ExecutionResult {
709 stop_reason: StopReason::Completed,
710 total_cycles: cycles_run,
711 total_frames: self.frame_count,
712 };
713 }
714 };
715
716 self.write_trace_log()?;
718
719 Ok(result)
720 }
721
722 pub fn run_with_video_encoder(
749 &mut self,
750 encoder: &mut super::video::StreamingVideoEncoder,
751 renderer: &mut Box<dyn monsoon_core::emulation::screen_renderer::ScreenRenderer>,
752 ) -> Result<ExecutionResult, String> {
753 self.collect_frames = false;
755
756 if self.config.trace_path.is_some() {
758 self.emu.enable_trace();
759 }
760
761 let max_cycles = self.config.max_cycles();
762 let start_cycles = self.emu.total_cycles;
763
764 let captures_per_frame = encoder.captures_per_frame();
766
767 loop {
769 let frame_start_cycles = self.emu.total_cycles;
772
773 for capture_idx in 0..captures_per_frame {
775 let odd_frame_offset = if self.emu.is_even_frame() && self.emu.is_rendering() {
779 2
780 } else {
781 -2
782 };
783
784 let base = (capture_idx + 1) as u128 * MASTER_CYCLES_PER_FRAME as u128;
785
786 let base = if odd_frame_offset >= 0 {
787 base.saturating_add(odd_frame_offset as u128)
788 } else {
789 base.saturating_sub((-odd_frame_offset) as u128)
790 };
791
792 let capture_point = base / captures_per_frame as u128;
793 let target_cycles = frame_start_cycles + capture_point;
794
795 match self.emu.run_until(target_cycles, RunOptions::default()) {
797 Ok(_) => {}
798 Err(e) => {
799 return Ok(ExecutionResult {
800 stop_reason: StopReason::Error(e),
801 total_cycles: self.emu.total_cycles - start_cycles,
802 total_frames: self.frame_count,
803 });
804 }
805 }
806
807 let frame = self.emu.get_pixel_buffer();
810 let rgb_frame = renderer.buffer_to_image(&frame);
811 encoder
812 .write_frame(rgb_frame)
813 .map_err(|e| format!("Video encoding error: {}", e))?;
814
815 if capture_idx == captures_per_frame - 1 {
818 self.frame_count += 1;
819 }
820 }
821
822 let cycles_run = self.emu.total_cycles - start_cycles;
823
824 if let Some(reason) =
826 self.config
827 .check_conditions(&self.emu, cycles_run, self.frame_count)
828 {
829 self.write_trace_log()?;
830 return Ok(ExecutionResult {
831 stop_reason: reason,
832 total_cycles: cycles_run,
833 total_frames: self.frame_count,
834 });
835 }
836
837 if self.emu.total_cycles >= max_cycles {
839 self.write_trace_log()?;
840 return Ok(ExecutionResult {
841 stop_reason: StopReason::Completed,
842 total_cycles: cycles_run,
843 total_frames: self.frame_count,
844 });
845 }
846 }
847 }
848
849 pub fn set_collect_frames(&mut self, collect: bool) { self.collect_frames = collect; }
854
855 pub fn emulator(&self) -> &Nes { &self.emu }
857
858 pub fn emulator_mut(&mut self) -> &mut Nes { &mut self.emu }
860
861 fn write_trace_log(&self) -> Result<(), String> {
863 if let Some(ref path) = self.config.trace_path
864 && let Some(trace) = self.emu.trace_log()
865 {
866 std::fs::write(path, &trace.log)
867 .map_err(|e| format!("Failed to write trace log to {}: {}", path.display(), e))?;
868 }
869 Ok(())
870 }
871}
872
873impl Default for ExecutionEngine {
874 fn default() -> Self { Self::new() }
875}
876
877fn decode_savestate(bytes: &[u8]) -> Result<SaveState, String> {
887 try_load_state_from_bytes(bytes)
888 .ok_or_else(|| "Failed to decode savestate (tried all supported formats)".to_string())
889}
890
891fn encode_savestate(state: &SaveState, format: SavestateFormat) -> Result<Vec<u8>, String> {
893 match format {
894 SavestateFormat::Binary => Ok(state.to_bytes(None)),
895 SavestateFormat::Json => Ok(state.to_bytes(Some("json".to_string()))),
896 }
897}
898
899impl ExecutionConfig {
904 pub fn from_cli_args(args: &CliArgs) -> Self {
906 let mut config = Self::new();
907
908 if let Some(cycles) = args.execution.cycles {
910 config.stop_conditions.push(StopCondition::Cycles(cycles));
911 }
912 if let Some(frames) = args.execution.frames {
913 config.stop_conditions.push(StopCondition::Frames(frames));
914 }
915
916 if let Some(op) = args.execution.until_opcode {
918 config.stop_conditions.push(StopCondition::Opcode(op));
919 }
920
921 if let Some(ref mem_cond) = args.execution.until_mem
923 && let Ok(cond) = StopCondition::parse_memory_condition(mem_cond)
924 {
925 config.stop_conditions.extend(cond);
926 }
927
928 if !args.execution.watch_mem.is_empty()
930 && let Ok(watches) = StopCondition::parse_memory_watches(&args.execution.watch_mem)
931 {
932 config.stop_conditions.extend(watches);
933 }
934
935 if args.execution.until_hlt {
937 config.stop_on_halt = true;
938 }
939
940 for bp in &args.execution.breakpoint {
942 config.stop_conditions.push(StopCondition::PcEquals(*bp));
943 }
944
945 config.trace_path = args.execution.trace.clone();
947
948 config.verbose = args.verbose;
950
951 if config.stop_conditions.is_empty() && !config.stop_on_halt {
953 config.stop_conditions.push(StopCondition::Frames(60));
954 }
955
956 config
957 }
958}
959
960impl SavestateConfig {
961 pub fn from_cli_args(args: &CliArgs) -> Self {
963 let mut config = Self::new();
964
965 if args.savestate.state_stdin {
967 config.load_from = Some(SavestateSource::Stdin);
968 } else if let Some(ref path) = args.savestate.load_state {
969 config.load_from = Some(SavestateSource::File(path.clone()));
970 }
971
972 if args.savestate.state_stdout {
974 config.save_to = Some(SavestateDestination::Stdout);
975 } else if let Some(ref path) = args.savestate.save_state {
976 config.save_to = Some(SavestateDestination::File(path.clone()));
977 }
978
979 config.format = args.savestate.state_format;
981
982 config
983 }
984}