1use crate::{
4 apu::{self, Apu, Channel},
5 bus::Bus,
6 cart::{self, Cart},
7 common::{Clock, NesRegion, Regional, Reset, ResetKind, Sram},
8 cpu::Cpu,
9 debug::Debugger,
10 fs,
11 genie::{self, GenieCode},
12 input::{FourPlayer, Joypad, Player},
13 mapper::{Bf909Revision, Mapper, MapperRevision, Mmc3Revision},
14 mem::RamState,
15 ppu::Ppu,
16 video::{Video, VideoFilter},
17};
18use bitflags::bitflags;
19use serde::{Deserialize, Serialize};
20use std::{
21 io::Read,
22 path::{Path, PathBuf},
23};
24use thiserror::Error;
25use tracing::{error, info};
26
27pub type Result<T> = std::result::Result<T, Error>;
29
30#[derive(Error, Debug)]
32#[must_use]
33pub enum Error {
34 #[error(transparent)]
36 Cart(#[from] cart::Error),
37 #[error("sram error: {0:?}")]
39 Sram(fs::Error),
40 #[error("save state error: {0:?}")]
42 SaveState(fs::Error),
43 #[error("no save state found")]
45 NoSaveStateFound,
46 #[error("no rom is loaded")]
48 RomNotLoaded,
49 #[error("cpu state is corrupted")]
52 CpuCorrupted,
53 #[error(transparent)]
55 InvalidGenieCode(#[from] genie::Error),
56 #[error("invalid file path {0:?}")]
58 InvalidFilePath(PathBuf),
59 #[error("unimplemented mapper `{0}`")]
60 UnimplementedMapper(u16),
61 #[error(transparent)]
63 Fs(#[from] fs::Error),
64 #[error("{context}: {source:?}")]
66 Io {
67 context: String,
68 source: std::io::Error,
69 },
70}
71
72impl Error {
73 pub fn io(source: std::io::Error, context: impl Into<String>) -> Self {
74 Self::Io {
75 context: context.into(),
76 source,
77 }
78 }
79}
80
81bitflags! {
82 #[derive(Default, Debug, Copy, Clone, PartialEq, Serialize, Deserialize, )]
84 #[must_use]
85 pub struct HeadlessMode: u8 {
86 const NO_AUDIO = 0x01;
88 const NO_VIDEO = 0x02;
90 }
91}
92
93#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
95#[must_use]
96pub struct MapperRevisionsConfig {
97 pub mmc3: Mmc3Revision,
99 pub bf909: Bf909Revision,
101}
102
103impl MapperRevisionsConfig {
104 pub const fn set(&mut self, rev: MapperRevision) {
106 match rev {
107 MapperRevision::Mmc3(rev) => self.mmc3 = rev,
108 MapperRevision::Bf909(rev) => self.bf909 = rev,
109 }
110 }
111}
112
113#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
114#[serde(default)]
115#[must_use]
116pub struct Config {
118 pub cycle_accurate: bool,
121 pub filter: VideoFilter,
123 pub region: NesRegion,
125 pub ram_state: RamState,
127 pub four_player: FourPlayer,
129 pub zapper: bool,
131 pub genie_codes: Vec<GenieCode>,
133 pub concurrent_dpad: bool,
135 pub channels_enabled: [bool; Apu::MAX_CHANNEL_COUNT],
137 pub headless_mode: HeadlessMode,
139 pub data_dir: PathBuf,
141 pub mapper_revisions: MapperRevisionsConfig,
143 pub emulate_ppu_warmup: bool,
148}
149
150impl Config {
151 pub const BASE_DIR: &'static str = "tetanes";
153 pub const SRAM_DIR: &'static str = "sram";
155 pub const SRAM_EXTENSION: &'static str = "sram";
157
158 #[inline]
160 #[must_use]
161 pub fn default_data_dir() -> PathBuf {
162 dirs::data_local_dir().map_or_else(|| PathBuf::from("data"), |dir| dir.join(Self::BASE_DIR))
163 }
164
165 #[inline]
167 #[must_use]
168 pub fn sram_dir(&self) -> PathBuf {
169 self.data_dir.join(Self::SRAM_DIR)
170 }
171}
172
173impl Default for Config {
174 fn default() -> Self {
175 Self {
176 cycle_accurate: true,
177 filter: VideoFilter::default(),
178 region: NesRegion::Auto,
179 ram_state: RamState::Random,
180 four_player: FourPlayer::default(),
181 zapper: false,
182 genie_codes: vec![],
183 concurrent_dpad: false,
184 channels_enabled: [true; Apu::MAX_CHANNEL_COUNT],
185 headless_mode: HeadlessMode::empty(),
186 data_dir: Self::default_data_dir(),
187 mapper_revisions: MapperRevisionsConfig::default(),
188 emulate_ppu_warmup: false,
189 }
190 }
191}
192
193#[derive(Debug, Clone, PartialEq, Eq)]
195pub struct LoadedRom {
196 pub name: String,
198 pub battery_backed: bool,
200 pub region: NesRegion,
202}
203
204#[derive(Debug, Clone)]
206#[must_use]
207pub struct ControlDeck {
208 running: bool,
210 video: Video,
212 last_frame_number: u32,
214 loaded_rom: Option<LoadedRom>,
216 sram_dir: PathBuf,
218 mapper_revisions: MapperRevisionsConfig,
220 auto_detect_region: bool,
222 cycles_remaining: f32,
224 frame_speed: f32,
226 frame_accumulator: f32,
228 cpu: Cpu,
230}
231
232impl Default for ControlDeck {
233 fn default() -> Self {
234 Self::new()
235 }
236}
237
238impl ControlDeck {
239 pub fn new() -> Self {
241 Self::with_config(Config::default())
242 }
243
244 pub fn with_config(cfg: Config) -> Self {
246 let mut cpu = Cpu::new(Bus::new(cfg.region, cfg.ram_state));
247 cpu.bus.ppu.skip_rendering = cfg.headless_mode.contains(HeadlessMode::NO_VIDEO);
248 cpu.bus.ppu.emulate_warmup = cfg.emulate_ppu_warmup;
249 cpu.bus.apu.skip_mixing = cfg.headless_mode.contains(HeadlessMode::NO_AUDIO);
250 if cfg.region.is_auto() {
251 cpu.set_region(NesRegion::Ntsc);
252 } else {
253 cpu.set_region(cfg.region);
254 }
255 cpu.bus.input.set_concurrent_dpad(cfg.concurrent_dpad);
256 cpu.bus.input.set_four_player(cfg.four_player);
257 cpu.bus.input.connect_zapper(cfg.zapper);
258 for (i, enabled) in cfg.channels_enabled.iter().enumerate() {
259 match Channel::try_from(i) {
260 Ok(channel) => cpu.bus.apu.set_channel_enabled(channel, *enabled),
261 Err(apu::ParseChannelError) => tracing::error!("invalid APU channel: {i}"),
262 }
263 }
264 for genie_code in cfg.genie_codes.iter().cloned() {
265 cpu.bus.add_genie_code(genie_code);
266 }
267 let video = Video::with_filter(cfg.filter);
268 Self {
269 running: false,
270 video,
271 last_frame_number: 0,
272 loaded_rom: None,
273 sram_dir: cfg.sram_dir(),
274 mapper_revisions: cfg.mapper_revisions,
275 auto_detect_region: cfg.region.is_auto(),
276 cycles_remaining: 0.0,
277 frame_speed: 1.0,
278 frame_accumulator: 0.0,
279 cpu,
280 }
281 }
282
283 pub fn sram_dir(&self, name: &str) -> PathBuf {
287 self.sram_dir.join(name)
288 }
289
290 pub fn load_rom<S: ToString, F: Read>(&mut self, name: S, rom: &mut F) -> Result<LoadedRom> {
296 let name = name.to_string();
297 self.unload_rom()?;
298 let cart = Cart::from_rom(&name, rom, self.cpu.bus.ram_state)?;
299 if cart.mapper.is_none() {
300 return Err(Error::UnimplementedMapper(cart.mapper_num()));
301 }
302 let loaded_rom = LoadedRom {
303 name: name.clone(),
304 battery_backed: cart.battery_backed(),
305 region: cart.region(),
306 };
307 if self.auto_detect_region {
308 self.cpu.set_region(loaded_rom.region);
309 }
310 self.cpu.bus.load_cart(cart);
311 self.update_mapper_revisions();
312 self.reset(ResetKind::Hard);
313 self.running = true;
314 let sram_dir = self.sram_dir(&name);
315 if let Err(err) = self.load_sram(sram_dir) {
316 error!("failed to load SRAM: {err:?}");
317 }
318 self.loaded_rom = Some(loaded_rom.clone());
319 Ok(loaded_rom)
320 }
321
322 pub fn load_rom_path(&mut self, path: impl AsRef<std::path::Path>) -> Result<LoadedRom> {
328 use std::{fs::File, io::BufReader};
329
330 let path = path.as_ref();
331 let filename = fs::filename(path);
332 info!("loading ROM: {filename}");
333 File::open(path)
334 .map_err(|err| Error::io(err, format!("failed to open rom {path:?}")))
335 .and_then(|rom| self.load_rom(filename, &mut BufReader::new(rom)))
336 }
337
338 pub fn unload_rom(&mut self) -> Result<()> {
344 if let Some(rom) = &self.loaded_rom {
345 let sram_dir = self.sram_dir(&rom.name);
346 if let Err(err) = self.save_sram(sram_dir) {
347 error!("failed to save SRAM: {err:?}");
348 }
349 }
350 self.loaded_rom = None;
351 self.cpu.bus.unload_cart();
352 self.running = false;
353 Ok(())
354 }
355
356 #[inline]
358 pub fn load_cpu(&mut self, cpu: Cpu) {
359 self.cpu.load(cpu);
360 }
361
362 #[inline]
364 pub const fn set_mapper_revision(&mut self, rev: MapperRevision) {
365 self.mapper_revisions.set(rev);
366 self.update_mapper_revisions();
367 }
368
369 #[inline]
372 pub const fn set_mapper_revisions(&mut self, revs: MapperRevisionsConfig) {
373 self.mapper_revisions = revs;
374 self.update_mapper_revisions();
375 }
376
377 const fn update_mapper_revisions(&mut self) {
380 match &mut self.cpu.bus.ppu.bus.mapper {
381 Mapper::Txrom(mapper) => {
382 mapper.set_revision(self.mapper_revisions.mmc3);
383 }
384 Mapper::Bf909x(mapper) => {
385 mapper.set_revision(self.mapper_revisions.bf909);
386 }
387 Mapper::None(_)
389 | Mapper::Nrom(_)
390 | Mapper::Sxrom(_)
391 | Mapper::Uxrom(_)
392 | Mapper::Cnrom(_)
393 | Mapper::Exrom(_)
394 | Mapper::Axrom(_)
395 | Mapper::Pxrom(_)
396 | Mapper::Fxrom(_)
397 | Mapper::ColorDreams(_)
398 | Mapper::BandaiFCG(_)
399 | Mapper::JalecoSs88006(_)
400 | Mapper::Namco163(_)
401 | Mapper::Vrc6(_)
402 | Mapper::Bnrom(_)
403 | Mapper::Nina001(_)
404 | Mapper::Gxrom(_)
405 | Mapper::SunsoftFme7(_)
406 | Mapper::Dxrom76(_)
407 | Mapper::Nina003006(_)
408 | Mapper::Dxrom88(_)
409 | Mapper::Dxrom95(_)
410 | Mapper::Dxrom154(_)
411 | Mapper::Dxrom206(_) => (),
412 }
413 }
414
415 #[inline]
417 pub fn set_concurrent_dpad(&mut self, enabled: bool) {
418 self.cpu.bus.input.set_concurrent_dpad(enabled);
419 }
420
421 #[inline]
424 pub const fn set_cycle_accurate(&mut self, enabled: bool) {
425 self.cpu.cycle_accurate = enabled;
426 }
427
428 #[inline]
430 pub const fn set_ram_state(&mut self, ram_state: RamState) {
431 self.cpu.bus.ram_state = ram_state;
432 }
433
434 #[inline]
437 pub const fn set_headless_mode(&mut self, mode: HeadlessMode) {
438 self.cpu.bus.ppu.skip_rendering = mode.contains(HeadlessMode::NO_VIDEO);
439 self.cpu.bus.apu.skip_mixing = mode.contains(HeadlessMode::NO_AUDIO);
440 }
441
442 #[inline]
447 pub const fn set_emulate_ppu_warmup(&mut self, enabled: bool) {
448 self.cpu.bus.ppu.emulate_warmup = enabled;
449 }
450
451 pub fn add_debugger(&mut self, debugger: Debugger) {
454 match debugger {
455 Debugger::Ppu(debugger) => self.cpu.bus.ppu.debugger = Some(debugger),
456 }
457 }
458
459 pub fn remove_debugger(&mut self, debugger: Debugger) {
461 match debugger {
462 Debugger::Ppu(_) => self.cpu.bus.ppu.debugger = None,
463 }
464 }
465
466 #[inline]
468 #[must_use]
469 pub const fn loaded_rom(&self) -> Option<&LoadedRom> {
470 self.loaded_rom.as_ref()
471 }
472
473 #[inline]
476 #[must_use]
477 pub fn cart_region(&self) -> Option<NesRegion> {
478 self.loaded_rom.as_ref().map(|rom| rom.region)
479 }
480
481 #[inline]
483 #[must_use]
484 pub fn cart_battery_backed(&self) -> Option<bool> {
485 self.loaded_rom.as_ref().map(|rom| rom.battery_backed)
486 }
487
488 #[inline]
490 #[must_use]
491 pub fn wram(&self) -> &[u8] {
492 self.cpu.bus.wram()
493 }
494
495 #[inline]
497 #[must_use]
498 pub fn sram(&self) -> &[u8] {
499 self.cpu.bus.sram()
500 }
501
502 pub fn save_sram(&self, path: impl AsRef<Path>) -> Result<()> {
508 if let Some(true) = self.cart_battery_backed() {
509 let path = path.as_ref();
510 if path.is_dir() {
511 return Err(Error::InvalidFilePath(path.to_path_buf()));
512 }
513
514 info!("saving SRAM...");
515 self.cpu
516 .bus
517 .save(path.with_extension(Config::SRAM_EXTENSION))
518 .map_err(Error::Sram)?;
519 }
520 Ok(())
521 }
522
523 pub fn load_sram(&mut self, path: impl AsRef<Path>) -> Result<()> {
529 if let Some(true) = self.cart_battery_backed() {
530 let path = path.as_ref();
531 if path.is_dir() {
532 return Err(Error::InvalidFilePath(path.to_path_buf()));
533 }
534 if path.is_file() {
535 info!("loading SRAM...");
536 self.cpu
537 .bus
538 .load(path.with_extension(Config::SRAM_EXTENSION))
539 .map_err(Error::Sram)?;
540 }
541 }
542 Ok(())
543 }
544
545 pub fn save_state(&mut self, path: impl AsRef<Path>) -> Result<()> {
551 if self.loaded_rom().is_none() {
552 return Err(Error::RomNotLoaded);
553 };
554 let path = path.as_ref();
555 fs::save(path, &self.cpu).map_err(Error::SaveState)
556 }
557
558 pub fn load_state(&mut self, path: impl AsRef<Path>) -> Result<()> {
564 if self.loaded_rom().is_none() {
565 return Err(Error::RomNotLoaded);
566 };
567 let path = path.as_ref();
568 if fs::exists(path) {
569 fs::load::<Cpu>(path)
570 .map_err(Error::SaveState)
571 .map(|mut cpu| {
572 cpu.bus.input.clear(); self.load_cpu(cpu)
574 })
575 } else {
576 Err(Error::NoSaveStateFound)
577 }
578 }
579
580 pub fn frame_buffer_raw(&mut self) -> &[u16] {
582 self.cpu.bus.ppu.frame_buffer()
583 }
584
585 #[inline]
587 pub fn frame_buffer(&mut self) -> &[u8] {
588 let frame_number = self.cpu.bus.ppu.frame_number();
590 if self.last_frame_number == frame_number {
591 return &self.video.frame;
592 }
593
594 self.last_frame_number = frame_number;
595 self.video
596 .apply_filter(self.cpu.bus.ppu.frame_buffer(), frame_number)
597 }
598
599 #[inline]
601 pub fn frame_buffer_into(&self, buffer: &mut [u8]) {
602 self.video.apply_filter_into(
603 self.cpu.bus.ppu.frame_buffer(),
604 self.cpu.bus.ppu.frame_number(),
605 buffer,
606 );
607 }
608
609 #[inline]
611 #[must_use]
612 pub const fn frame_number(&self) -> u32 {
613 self.cpu.bus.ppu.frame_number()
614 }
615
616 #[inline]
618 #[must_use]
619 pub fn audio_samples(&self) -> &[f32] {
620 self.cpu.bus.audio_samples()
621 }
622
623 #[inline]
625 pub fn clear_audio_samples(&mut self) {
626 self.cpu.bus.clear_audio_samples();
627 }
628
629 #[inline]
631 #[must_use]
632 pub const fn clock_rate(&self) -> f32 {
633 self.cpu.clock_rate()
634 }
635
636 pub fn clock_instr(&mut self) -> Result<u64> {
642 if !self.running {
643 return Err(Error::RomNotLoaded);
644 }
645 let cycles = self.clock();
646 if self.cpu_corrupted() {
647 self.running = false;
648 return Err(Error::CpuCorrupted);
649 }
650 Ok(cycles)
651 }
652
653 pub fn clock_seconds(&mut self, seconds: f32) -> Result<u64> {
659 self.cycles_remaining += self.clock_rate() * seconds;
660 let mut total_cycles = 0;
661 while self.cycles_remaining > 0.0 {
662 let cycles = self.clock_instr()?;
663 total_cycles += cycles;
664 self.cycles_remaining -= cycles as f32;
665 }
666 Ok(total_cycles)
667 }
668
669 pub fn clock_frame(&mut self) -> Result<u64> {
675 #[cfg(feature = "profiling")]
676 puffin::profile_function!();
677
678 self.frame_accumulator += self.frame_speed;
683 let mut frames_to_clock = 0;
684 while self.frame_accumulator >= 1.0 {
685 self.frame_accumulator -= 1.0;
686 frames_to_clock += 1;
687 }
688
689 let mut total_cycles = 0;
690 for _ in 0..frames_to_clock {
691 let frame = self.frame_number();
692 while frame == self.frame_number() {
693 total_cycles += self.clock_instr()?;
694 }
695 }
696 self.cpu.bus.apu.clock_flush();
697
698 Ok(total_cycles)
699 }
700
701 pub fn clock_frame_output<T>(
708 &mut self,
709 handle_output: impl FnOnce(u64, &[u8], &[f32]) -> T,
710 ) -> Result<T> {
711 let cycles = self.clock_frame()?;
712 let frame = self.video.apply_filter(
713 self.cpu.bus.ppu.frame_buffer(),
714 self.cpu.bus.ppu.frame_number(),
715 );
716 let audio = self.cpu.bus.audio_samples();
717 let res = handle_output(cycles, frame, audio);
718 self.cpu.bus.clear_audio_samples();
719 Ok(res)
720 }
721
722 pub fn clock_frame_into(
729 &mut self,
730 frame_buffer: &mut [u8],
731 audio_samples: &mut [f32],
732 ) -> Result<u64> {
733 let cycles = self.clock_frame()?;
734 let frame = self.video.apply_filter(
735 self.cpu.bus.ppu.frame_buffer(),
736 self.cpu.bus.ppu.frame_number(),
737 );
738 frame_buffer.copy_from_slice(&frame[..frame_buffer.len()]);
739 let audio = self.cpu.bus.audio_samples();
740 audio_samples.copy_from_slice(&audio[..audio_samples.len()]);
741 self.clear_audio_samples();
742 Ok(cycles)
743 }
744
745 pub fn clock_frame_ahead<T>(
751 &mut self,
752 run_ahead: usize,
753 handle_output: impl FnOnce(u64, &[u8], &[f32]) -> T,
754 ) -> Result<T> {
755 #[cfg(feature = "profiling")]
756 puffin::profile_function!();
757
758 if run_ahead == 0 {
759 return self.clock_frame_output(handle_output);
760 }
761
762 self.clock_frame()?;
764 let frame = std::mem::take(&mut self.cpu.bus.ppu.frame.buffer);
765 let state = bincode::serialize(&self.cpu)
767 .map_err(|err| fs::Error::SerializationFailed(err.to_string()))?;
768
769 self.cpu.bus.ppu.skip_rendering = true;
771 for _ in 1..run_ahead {
772 self.clock_frame()?;
773 }
774 self.cpu.bus.ppu.skip_rendering = false;
775
776 self.clear_audio_samples();
778 let result = self.clock_frame_output(handle_output)?;
779
780 let mut state = bincode::deserialize::<Cpu>(&state)
782 .map_err(|err| fs::Error::DeserializationFailed(err.to_string()))?;
783 state.bus.ppu.frame.buffer = frame;
784 self.load_cpu(state);
785
786 Ok(result)
787 }
788
789 pub fn clock_frame_ahead_into(
795 &mut self,
796 run_ahead: usize,
797 frame_buffer: &mut [u8],
798 audio_samples: &mut [f32],
799 ) -> Result<u64> {
800 #[cfg(feature = "profiling")]
801 puffin::profile_function!();
802
803 if run_ahead == 0 {
804 return self.clock_frame_into(frame_buffer, audio_samples);
805 }
806
807 self.clock_frame()?;
809 let frame = std::mem::take(&mut self.cpu.bus.ppu.frame.buffer);
810 let state = bincode::serialize(&self.cpu)
812 .map_err(|err| fs::Error::SerializationFailed(err.to_string()))?;
813
814 for _ in 1..run_ahead {
816 self.clock_frame()?;
817 }
818
819 self.clear_audio_samples();
821 let cycles = self.clock_frame_into(frame_buffer, audio_samples)?;
822
823 let mut state = bincode::deserialize::<Cpu>(&state)
825 .map_err(|err| fs::Error::DeserializationFailed(err.to_string()))?;
826 state.bus.ppu.frame.buffer = frame;
827 self.load_cpu(state);
828
829 Ok(cycles)
830 }
831
832 pub fn clock_scanline(&mut self) -> Result<u64> {
838 #[cfg(feature = "profiling")]
839 puffin::profile_function!();
840
841 let mut total_cycles = 0;
842 let current_scanline = self.cpu.bus.ppu.scanline;
843 while current_scanline == self.cpu.bus.ppu.scanline {
844 total_cycles += self.clock_instr()?;
845 }
846 Ok(total_cycles)
847 }
848
849 #[inline]
852 #[must_use]
853 pub const fn cpu_corrupted(&self) -> bool {
854 self.cpu.corrupted
855 }
856
857 #[inline]
859 pub const fn cpu(&self) -> &Cpu {
860 &self.cpu
861 }
862
863 #[inline]
865 pub const fn cpu_mut(&mut self) -> &mut Cpu {
866 &mut self.cpu
867 }
868
869 #[inline]
871 pub const fn ppu(&self) -> &Ppu {
872 &self.cpu.bus.ppu
873 }
874
875 #[inline]
877 pub const fn ppu_mut(&mut self) -> &mut Ppu {
878 &mut self.cpu.bus.ppu
879 }
880
881 #[inline]
883 pub const fn bus(&self) -> &Bus {
884 &self.cpu.bus
885 }
886
887 #[inline]
889 pub const fn bus_mut(&mut self) -> &mut Bus {
890 &mut self.cpu.bus
891 }
892
893 #[inline]
895 pub const fn apu(&self) -> &Apu {
896 &self.cpu.bus.apu
897 }
898
899 #[inline]
901 pub const fn apu_mut(&mut self) -> &Apu {
902 &mut self.cpu.bus.apu
903 }
904
905 #[inline]
907 pub const fn mapper(&self) -> &Mapper {
908 &self.cpu.bus.ppu.bus.mapper
909 }
910
911 #[inline]
913 pub const fn mapper_mut(&mut self) -> &mut Mapper {
914 &mut self.cpu.bus.ppu.bus.mapper
915 }
916
917 #[inline]
919 pub const fn four_player(&self) -> FourPlayer {
920 self.cpu.bus.input.four_player
921 }
922
923 #[inline]
925 pub fn set_four_player(&mut self, four_player: FourPlayer) {
926 self.cpu.bus.input.set_four_player(four_player);
927 }
928
929 #[inline]
931 pub const fn joypad(&mut self, slot: Player) -> &Joypad {
932 self.cpu.bus.input.joypad(slot)
933 }
934
935 #[inline]
937 pub const fn joypad_mut(&mut self, slot: Player) -> &mut Joypad {
938 self.cpu.bus.input.joypad_mut(slot)
939 }
940
941 #[inline]
943 pub const fn zapper_connected(&self) -> bool {
944 self.cpu.bus.input.zapper.connected
945 }
946
947 #[inline]
949 pub const fn connect_zapper(&mut self, enabled: bool) {
950 self.cpu.bus.input.connect_zapper(enabled);
951 }
952
953 #[inline]
955 #[must_use]
956 pub const fn zapper_pos(&self) -> (u32, u32) {
957 let zapper = self.cpu.bus.input.zapper;
958 (zapper.x(), zapper.y())
959 }
960
961 #[inline]
963 pub fn trigger_zapper(&mut self) {
964 self.cpu.bus.input.zapper.trigger();
965 }
966
967 #[inline]
969 pub fn aim_zapper(&mut self, x: u32, y: u32) {
970 self.cpu.bus.input.zapper.aim(x, y);
971 }
972
973 #[inline]
975 pub const fn set_filter(&mut self, filter: VideoFilter) {
976 self.video.filter = filter;
977 }
978
979 #[inline]
981 pub fn set_sample_rate(&mut self, sample_rate: f32) {
982 self.cpu.bus.apu.set_sample_rate(sample_rate);
983 }
984
985 #[inline]
987 pub fn set_frame_speed(&mut self, speed: f32) {
988 self.frame_speed = speed;
989 self.cpu.bus.apu.set_frame_speed(speed);
990 }
991
992 #[inline]
998 pub fn add_genie_code(&mut self, genie_code: String) -> Result<()> {
999 self.cpu.bus.add_genie_code(GenieCode::new(genie_code)?);
1000 Ok(())
1001 }
1002
1003 #[inline]
1005 pub fn remove_genie_code(&mut self, genie_code: &str) {
1006 self.cpu.bus.remove_genie_code(genie_code);
1007 }
1008
1009 #[inline]
1011 pub fn clear_genie_codes(&mut self) {
1012 self.cpu.bus.clear_genie_codes();
1013 }
1014
1015 #[inline]
1017 #[must_use]
1018 pub const fn channel_enabled(&self, channel: Channel) -> bool {
1019 self.cpu.bus.apu.channel_enabled(channel)
1020 }
1021
1022 #[inline]
1024 pub const fn set_apu_channel_enabled(&mut self, channel: Channel, enabled: bool) {
1025 self.cpu.bus.apu.set_channel_enabled(channel, enabled);
1026 }
1027
1028 #[inline]
1030 pub const fn toggle_apu_channel(&mut self, channel: Channel) {
1031 self.cpu.bus.apu.toggle_channel(channel);
1032 }
1033
1034 #[inline]
1036 #[must_use]
1037 pub const fn is_running(&self) -> bool {
1038 self.running
1039 }
1040}
1041
1042impl Clock for ControlDeck {
1043 fn clock(&mut self) -> u64 {
1045 self.cpu.clock()
1046 }
1047}
1048
1049impl Regional for ControlDeck {
1050 fn region(&self) -> NesRegion {
1052 self.cpu.region
1053 }
1054
1055 fn set_region(&mut self, region: NesRegion) {
1057 self.auto_detect_region = region.is_auto();
1058 if self.auto_detect_region {
1059 self.cpu.set_region(self.cart_region().unwrap_or_default());
1060 } else {
1061 self.cpu.set_region(region);
1062 }
1063 }
1064}
1065
1066impl Reset for ControlDeck {
1067 fn reset(&mut self, kind: ResetKind) {
1069 self.cpu.reset(kind);
1070 if self.loaded_rom.is_some() {
1071 self.running = true;
1072 }
1073 }
1074}