1use std::io::{Read, Write};
14use std::path::{Path, PathBuf};
15
16use monsoon_core::emulation::nes::{MASTER_CYCLES_PER_FRAME, Nes};
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 if let Ok(cond) = cond {
120 res.push(cond)
121 } else if let Err(cond) = cond {
122 return Err(cond);
123 }
124 }
125
126 Ok(res)
127 }
128
129 pub fn parse_single_condition(s: &String) -> Result<Self, String> {
130 if let Some((cond1, cond2)) = s.split_once("&&") {
131 let cond1 = Self::parse_single_condition(&cond1.to_string());
132 let cond2 = Self::parse_single_condition(&cond2.to_string());
133
134 if let (Ok(cond1), Ok(cond2)) = (cond1, cond2) {
135 match cond1 {
136 StopCondition::MemoryEquals {
137 addr,
138 value,
139 ..
140 } => {
141 return Ok(StopCondition::MemoryEquals {
142 addr,
143 value,
144 and: Some(Box::new(cond2)),
145 });
146 }
147 StopCondition::MemoryNotEquals {
148 addr,
149 value,
150 ..
151 } => {
152 return Ok(StopCondition::MemoryNotEquals {
153 addr,
154 value,
155 and: Some(Box::new(cond2)),
156 });
157 }
158 _ => {}
159 }
160 }
161 }
162
163 if let Some((addr_str, val_str)) = s.split_once("==") {
164 let addr = parse_hex_u16(addr_str.trim())?;
165 let value = parse_hex_u8(val_str.trim())?;
166 Ok(StopCondition::MemoryEquals {
167 addr,
168 value,
169 and: None,
170 })
171 } else if let Some((addr_str, val_str)) = s.split_once("!=") {
172 let addr = parse_hex_u16(addr_str.trim())?;
173 let value = parse_hex_u8(val_str.trim())?;
174 Ok(StopCondition::MemoryNotEquals {
175 addr,
176 value,
177 and: None,
178 })
179 } else {
180 Err(format!(
181 "Invalid memory condition '{}'. Expected format: ADDR==VALUE or ADDR!=VALUE",
182 s
183 ))
184 }
185 }
186
187 pub fn parse_memory_watch(s: &str) -> Result<Self, String> {
189 let (addr_str, access_type) = if let Some((addr_part, mode_part)) = s.split_once(':') {
190 (addr_part, MemoryAccessType::parse(mode_part)?)
191 } else {
192 (s, MemoryAccessType::ReadWrite) };
194
195 let addr = parse_hex_u16(addr_str.trim())?;
196 Ok(StopCondition::MemoryWatch {
197 addr,
198 access_type,
199 })
200 }
201
202 pub fn parse_memory_watches(watches: &[String]) -> Result<Vec<Self>, String> {
204 watches
205 .iter()
206 .map(|s| Self::parse_memory_watch(s))
207 .collect()
208 }
209
210 pub fn check(&self, emu: &Nes, cycles: u128, frames: u64) -> bool {
211 match self {
212 StopCondition::Cycles(target) => cycles >= *target,
213 StopCondition::Frames(target) => frames >= *target,
214 StopCondition::PcEquals(addr) | StopCondition::Breakpoint(addr) => {
215 emu.program_counter() == *addr
216 }
217 StopCondition::Opcode(op) => {
218 if let Some(opcode) = emu.current_opcode_byte()
219 && opcode == *op
220 {
221 return true;
222 }
223
224 false
225 }
226 StopCondition::MemoryEquals {
227 addr,
228 value,
229 and,
230 } => {
231 let and = and.as_ref().map(|and| and.check(emu, cycles, frames));
232
233 let mem_val = emu.get_memory_debug(Some(*addr..=*addr))[0]
234 .first()
235 .copied()
236 .unwrap_or(0);
237
238 if let Some(and) = and {
239 mem_val == *value && and
240 } else {
241 mem_val == *value
242 }
243 }
244 StopCondition::MemoryNotEquals {
245 addr,
246 value,
247 and,
248 } => {
249 let and = and.as_ref().map(|and| and.check(emu, cycles, frames));
250
251 let mem_val = emu.get_memory_debug(Some(*addr..=*addr))[0]
252 .first()
253 .copied()
254 .unwrap_or(0);
255
256 if let Some(and) = and {
257 mem_val != *value && and
258 } else {
259 mem_val != *value
260 }
261 }
262 StopCondition::OnHalt => emu.is_halted(),
263 StopCondition::MemoryWatch {
264 addr,
265 access_type,
266 } => {
267 if let Some(last_access) = emu.last_memory_access() {
269 let (access_addr, was_read) = last_access;
270 if access_addr == *addr {
271 match access_type {
272 MemoryAccessType::Read => was_read,
273 MemoryAccessType::Write => !was_read,
274 MemoryAccessType::ReadWrite => true,
275 }
276 } else {
277 false
278 }
279 } else {
280 false
281 }
282 }
283 }
284 }
285
286 pub fn reason(&self, emu: &Nes, cycles: u128, frames: u64) -> StopReason {
287 match self {
288 StopCondition::Cycles(_) => StopReason::CyclesReached(cycles),
289 StopCondition::Frames(_) => StopReason::FramesReached(frames),
290 StopCondition::PcEquals(addr) | StopCondition::Breakpoint(addr) => {
291 StopReason::PcReached(*addr)
292 }
293 StopCondition::Opcode(_) => StopReason::PcReached(emu.program_counter()),
294 StopCondition::MemoryEquals {
295 addr, ..
296 }
297 | StopCondition::MemoryNotEquals {
298 addr, ..
299 } => {
300 let mem_val = emu.get_memory_debug(Some(*addr..=*addr))[0]
301 .first()
302 .copied()
303 .unwrap_or(0);
304
305 StopReason::MemoryCondition(*addr, mem_val)
306 }
307 StopCondition::OnHalt => StopReason::Halted,
308 StopCondition::MemoryWatch {
309 addr,
310 access_type,
311 } => {
312 let was_read = emu
313 .last_memory_access()
314 .map(|(_, was_read)| was_read)
315 .unwrap_or(true);
316 StopReason::MemoryWatchpoint {
317 addr: *addr,
318 access_type: *access_type,
319 was_read,
320 }
321 }
322 }
323 }
324}
325
326#[derive(Debug, Clone, Default)]
335pub struct ExecutionConfig {
336 pub stop_conditions: Vec<StopCondition>,
338 pub stop_on_halt: bool,
340 pub trace_path: Option<PathBuf>,
342 pub verbose: bool,
344}
345
346impl ExecutionConfig {
347 pub fn new() -> Self { Self::default() }
349
350 pub fn with_stop_condition(mut self, condition: StopCondition) -> Self {
352 self.stop_conditions.push(condition);
353 self
354 }
355
356 pub fn with_cycles(mut self, cycles: u128) -> Self {
358 self.stop_conditions.push(StopCondition::Cycles(cycles));
359 self
360 }
361
362 pub fn with_frames(mut self, frames: u64) -> Self {
364 self.stop_conditions.push(StopCondition::Frames(frames));
365 self
366 }
367
368 pub fn with_pc_breakpoint(mut self, addr: u16) -> Self {
370 self.stop_conditions.push(StopCondition::PcEquals(addr));
371 self
372 }
373
374 pub fn with_breakpoint(mut self, addr: u16) -> Self {
376 self.stop_conditions.push(StopCondition::PcEquals(addr));
377 self
378 }
379
380 pub fn with_memory_watch(mut self, addr: u16, access_type: MemoryAccessType) -> Self {
382 self.stop_conditions.push(StopCondition::MemoryWatch {
383 addr,
384 access_type,
385 });
386 self
387 }
388
389 pub fn with_trace(mut self, path: PathBuf) -> Self {
391 self.trace_path = Some(path);
392 self
393 }
394
395 pub fn with_verbose(mut self, verbose: bool) -> Self {
397 self.verbose = verbose;
398 self
399 }
400
401 pub fn with_stop_on_halt(mut self, stop: bool) -> Self {
403 self.stop_on_halt = stop;
404 self
405 }
406
407 fn max_cycles(&self) -> u128 {
409 let mut max = u128::MAX;
410 for cond in &self.stop_conditions {
411 match cond {
412 StopCondition::Cycles(c) => max = max.min(*c),
413 StopCondition::Frames(f) => {
414 max = max.min(*f as u128 * MASTER_CYCLES_PER_FRAME as u128)
415 }
416 _ => {}
417 }
418 }
419 max
420 }
421
422 fn check_conditions(&self, emu: &Nes, cycles: u128, frames: u64) -> Option<StopReason> {
424 for cond in &self.stop_conditions {
425 if cond.check(emu, cycles, frames) {
426 return Some(cond.reason(emu, cycles, frames));
427 }
428 }
429
430 None
431 }
432}
433
434#[derive(Debug, Clone)]
440pub struct ExecutionResult {
441 pub stop_reason: StopReason,
443 pub total_cycles: u128,
445 pub total_frames: u64,
447}
448
449#[derive(Debug, Clone)]
455pub enum SavestateSource {
456 File(PathBuf),
458 Stdin,
460 Bytes(Vec<u8>),
462}
463
464#[derive(Debug, Clone)]
466pub enum SavestateDestination {
467 File(PathBuf),
469 Stdout,
471}
472
473pub use crate::cli::args::SavestateFormat;
475use crate::cli::{CliArgs, parse_hex_u16};
476
477#[derive(Debug, Clone, Default)]
479pub struct SavestateConfig {
480 pub load_from: Option<SavestateSource>,
482 pub save_to: Option<SavestateDestination>,
484 pub format: SavestateFormat,
486}
487
488impl SavestateConfig {
489 pub fn new() -> Self { Self::default() }
491
492 pub fn load_from_file(mut self, path: PathBuf) -> Self {
494 self.load_from = Some(SavestateSource::File(path));
495 self
496 }
497
498 pub fn load_from_stdin(mut self) -> Self {
500 self.load_from = Some(SavestateSource::Stdin);
501 self
502 }
503
504 pub fn save_to_file(mut self, path: PathBuf) -> Self {
506 self.save_to = Some(SavestateDestination::File(path));
507 self
508 }
509
510 pub fn save_to_stdout(mut self) -> Self {
512 self.save_to = Some(SavestateDestination::Stdout);
513 self
514 }
515
516 pub fn with_format(mut self, format: SavestateFormat) -> Self {
518 self.format = format;
519 self
520 }
521}
522
523pub struct ExecutionEngine {
541 pub emu: Nes,
543 pub config: ExecutionConfig,
545 pub savestate_config: SavestateConfig,
547 pub frames: Vec<Vec<u16>>,
549 frame_count: u64,
551 collect_frames: bool,
553}
554
555impl ExecutionEngine {
556 pub fn new() -> Self {
558 Self {
559 emu: Nes::default(),
560 config: ExecutionConfig::new(),
561 savestate_config: SavestateConfig::new(),
562 frames: vec![],
563 frame_count: 0,
564 collect_frames: true,
565 }
566 }
567
568 pub fn with_emulator(emu: Nes) -> Self {
570 Self {
571 emu,
572 config: ExecutionConfig::new(),
573 savestate_config: SavestateConfig::new(),
574 frames: vec![],
575 frame_count: 0,
576 collect_frames: true,
577 }
578 }
579
580 pub fn with_config(mut self, config: ExecutionConfig) -> Self {
582 self.config = config;
583 self
584 }
585
586 pub fn with_savestate_config(mut self, config: SavestateConfig) -> Self {
588 self.savestate_config = config;
589 self
590 }
591
592 pub fn load_rom(&mut self, path: &Path) -> Result<(), String> {
594 let path_str = path.to_string_lossy().to_string();
595 self.emu.load_rom(&path_str);
596 Ok(())
597 }
598
599 pub fn power_on(&mut self) { self.emu.power(); }
601
602 pub fn power_off(&mut self) { self.emu.power_off(); }
604
605 pub fn reset(&mut self) { self.emu.reset(); }
607
608 pub fn load_savestate(&mut self) -> Result<(), String> {
610 if let Some(ref source) = self.savestate_config.load_from {
611 let state = match source {
612 SavestateSource::File(path) => {
613 let data = std::fs::read(path).map_err(|e| {
614 format!("Failed to read savestate from {}: {}", path.display(), e)
615 })?;
616 try_load_state_from_bytes(&data).ok_or_else(|| {
617 format!("Failed to load savestate from {}", path.display())
618 })?
619 }
620 SavestateSource::Stdin => {
621 let mut buffer = Vec::new();
622 std::io::stdin()
623 .read_to_end(&mut buffer)
624 .map_err(|e| format!("Failed to read savestate from stdin: {}", e))?;
625 decode_savestate(&buffer)?
626 }
627 SavestateSource::Bytes(bytes) => decode_savestate(bytes)?,
628 };
629 self.emu.load_state(state);
630 }
631 Ok(())
632 }
633
634 pub fn save_savestate(&self) -> Result<(), String> {
636 if let Some(ref dest) = self.savestate_config.save_to {
637 let state = self
638 .emu
639 .save_state()
640 .ok_or_else(|| "No ROM loaded, cannot save state".to_string())?;
641 let encoded = encode_savestate(&state, self.savestate_config.format)?;
642
643 match dest {
644 SavestateDestination::File(path) => {
645 std::fs::write(path, &encoded).map_err(|e| {
646 format!("Failed to write savestate to {}: {}", path.display(), e)
647 })?;
648 }
649 SavestateDestination::Stdout => {
650 std::io::stdout()
651 .write_all(&encoded)
652 .map_err(|e| format!("Failed to write savestate to stdout: {}", e))?;
653 }
654 }
655 }
656 Ok(())
657 }
658
659 pub fn run(&mut self) -> Result<ExecutionResult, String> {
661 if self.config.trace_path.is_some() {
663 self.emu.enable_trace();
664 }
665
666 let max_cycles = self.config.max_cycles();
667 let start_cycles = self.emu.total_cycles;
668
669 let result = loop {
671 match self.emu.step_frame() {
673 Ok(_) => {}
674 Err(e) => {
675 break ExecutionResult {
676 stop_reason: StopReason::Error(e),
677 total_cycles: self.emu.total_cycles - start_cycles,
678 total_frames: self.frame_count,
679 };
680 }
681 }
682
683 if self.collect_frames {
685 self.frames.push(self.emu.get_pixel_buffer());
686 }
687
688 self.frame_count += 1;
689 let cycles_run = self.emu.total_cycles - start_cycles;
690
691 if let Some(reason) =
693 self.config
694 .check_conditions(&self.emu, cycles_run, self.frame_count)
695 {
696 break ExecutionResult {
697 stop_reason: reason,
698 total_cycles: cycles_run,
699 total_frames: self.frame_count,
700 };
701 }
702
703 if self.emu.total_cycles >= max_cycles {
705 break ExecutionResult {
706 stop_reason: StopReason::Completed,
707 total_cycles: cycles_run,
708 total_frames: self.frame_count,
709 };
710 }
711 };
712
713 self.write_trace_log()?;
715
716 Ok(result)
717 }
718
719 pub fn run_with_video_encoder(
744 &mut self,
745 encoder: &mut super::video::StreamingVideoEncoder,
746 renderer: &mut Box<dyn monsoon_core::emulation::screen_renderer::ScreenRenderer>,
747 ) -> Result<ExecutionResult, String> {
748 self.collect_frames = false;
750
751 if self.config.trace_path.is_some() {
753 self.emu.enable_trace();
754 }
755
756 let max_cycles = self.config.max_cycles();
757 let start_cycles = self.emu.total_cycles;
758
759 let captures_per_frame = encoder.captures_per_frame();
761
762 loop {
764 let frame_start_cycles = self.emu.total_cycles;
767
768 for capture_idx in 0..captures_per_frame {
770 let odd_frame_offset = if self.emu.is_even_frame() && self.emu.is_rendering() {
774 2
775 } else {
776 -2
777 };
778
779 let base = (capture_idx + 1) as u128 * MASTER_CYCLES_PER_FRAME as u128;
780
781 let base = if odd_frame_offset >= 0 {
782 base.saturating_add(odd_frame_offset as u128)
783 } else {
784 base.saturating_sub((-odd_frame_offset) as u128)
785 };
786
787 let capture_point = base / captures_per_frame as u128;
788 let target_cycles = frame_start_cycles + capture_point;
789
790 match self.emu.run_until(target_cycles, false) {
792 Ok(_) => {}
793 Err(e) => {
794 return Ok(ExecutionResult {
795 stop_reason: StopReason::Error(e),
796 total_cycles: self.emu.total_cycles - start_cycles,
797 total_frames: self.frame_count,
798 });
799 }
800 }
801
802 let frame = self.emu.get_pixel_buffer();
805 let rgb_frame = renderer.buffer_to_image(&frame);
806 encoder
807 .write_frame(rgb_frame)
808 .map_err(|e| format!("Video encoding error: {}", e))?;
809
810 if capture_idx == captures_per_frame - 1 {
813 self.frame_count += 1;
814 }
815 }
816
817 let cycles_run = self.emu.total_cycles - start_cycles;
818
819 if let Some(reason) =
821 self.config
822 .check_conditions(&self.emu, cycles_run, self.frame_count)
823 {
824 self.write_trace_log()?;
825 return Ok(ExecutionResult {
826 stop_reason: reason,
827 total_cycles: cycles_run,
828 total_frames: self.frame_count,
829 });
830 }
831
832 if self.emu.total_cycles >= max_cycles {
834 self.write_trace_log()?;
835 return Ok(ExecutionResult {
836 stop_reason: StopReason::Completed,
837 total_cycles: cycles_run,
838 total_frames: self.frame_count,
839 });
840 }
841 }
842 }
843
844 pub fn set_collect_frames(&mut self, collect: bool) { self.collect_frames = collect; }
849
850 pub fn emulator(&self) -> &Nes { &self.emu }
852
853 pub fn emulator_mut(&mut self) -> &mut Nes { &mut self.emu }
855
856 fn write_trace_log(&self) -> Result<(), String> {
858 if let Some(ref path) = self.config.trace_path
859 && let Some(trace) = self.emu.trace_log()
860 {
861 std::fs::write(path, &trace.log)
862 .map_err(|e| format!("Failed to write trace log to {}: {}", path.display(), e))?;
863 }
864 Ok(())
865 }
866}
867
868impl Default for ExecutionEngine {
869 fn default() -> Self { Self::new() }
870}
871
872fn decode_savestate(bytes: &[u8]) -> Result<SaveState, String> {
882 try_load_state_from_bytes(bytes)
883 .ok_or_else(|| "Failed to decode savestate (tried all supported formats)".to_string())
884}
885
886fn encode_savestate(state: &SaveState, format: SavestateFormat) -> Result<Vec<u8>, String> {
888 match format {
889 SavestateFormat::Binary => Ok(state.to_bytes(None)),
890 SavestateFormat::Json => Ok(state.to_bytes(Some("json".to_string()))),
891 }
892}
893
894impl ExecutionConfig {
899 pub fn from_cli_args(args: &CliArgs) -> Self {
901 let mut config = Self::new();
902
903 if let Some(cycles) = args.execution.cycles {
905 config.stop_conditions.push(StopCondition::Cycles(cycles));
906 }
907 if let Some(frames) = args.execution.frames {
908 config.stop_conditions.push(StopCondition::Frames(frames));
909 }
910
911 if let Some(op) = args.execution.until_opcode {
913 config.stop_conditions.push(StopCondition::Opcode(op));
914 }
915
916 if let Some(ref mem_cond) = args.execution.until_mem
918 && let Ok(cond) = StopCondition::parse_memory_condition(mem_cond)
919 {
920 config.stop_conditions.extend(cond);
921 }
922
923 if !args.execution.watch_mem.is_empty()
925 && let Ok(watches) = StopCondition::parse_memory_watches(&args.execution.watch_mem)
926 {
927 config.stop_conditions.extend(watches);
928 }
929
930 if args.execution.until_hlt {
932 config.stop_on_halt = true;
933 }
934
935 for bp in &args.execution.breakpoint {
937 config.stop_conditions.push(StopCondition::PcEquals(*bp));
938 }
939
940 config.trace_path = args.execution.trace.clone();
942
943 config.verbose = args.verbose;
945
946 if config.stop_conditions.is_empty() && !config.stop_on_halt {
948 config.stop_conditions.push(StopCondition::Frames(60));
949 }
950
951 config
952 }
953}
954
955impl SavestateConfig {
956 pub fn from_cli_args(args: &CliArgs) -> Self {
958 let mut config = Self::new();
959
960 if args.savestate.state_stdin {
962 config.load_from = Some(SavestateSource::Stdin);
963 } else if let Some(ref path) = args.savestate.load_state {
964 config.load_from = Some(SavestateSource::File(path.clone()));
965 }
966
967 if args.savestate.state_stdout {
969 config.save_to = Some(SavestateDestination::Stdout);
970 } else if let Some(ref path) = args.savestate.save_state {
971 config.save_to = Some(SavestateDestination::File(path.clone()));
972 }
973
974 config.format = args.savestate.state_format;
976
977 config
978 }
979}