1//! SNES public interface and main loop 2 3use crate::apu::{Apu, ApuTickEffect}; 4use crate::audio::AudioResampler; 5use crate::bus::Bus; 6use crate::input::SnesInputs; 7use crate::memory::dma::{DmaStatus, DmaUnit}; 8use crate::memory::{CpuInternalRegisters, Memory}; 9use crate::ppu::{Ppu, PpuTickEffect}; 10use crate::{bus, ppu}; 11use bincode::error::EncodeError; 12use bincode::{Decode, Encode}; 13use crc::Crc; 14use jgenesis_common::frontend::{ 15 AudioOutput, Color, EmulatorConfigTrait, EmulatorTrait, InputPoller, NtscPerFrameParams, 16 PartialClone, RenderFrameOptions, Renderer, SaveWriter, TickEffect, TimingMode, 17}; 18use jgenesis_proc_macros::{ConfigDisplay, FakeDecode, FakeEncode}; 19use snes_config::{AudioInterpolationMode, SnesAspectRatio, SnesButton}; 20use snes_coprocessors::st018::St018LoadError; 21use std::fmt::{Debug, Display}; 22use std::num::NonZeroU64; 23use std::{io, mem}; 24use thiserror::Error; 25use wdc65816_emu::core::Wdc65816; 26use wdc65816_emu::traits::BusInterface; 27 28/// Length of work RAM: $7E0000-$7FFFFF. 29pub const WRAM_LEN: usize = crate::memory::MAIN_RAM_LEN; 30 31const MEMORY_REFRESH_MCLK: u64 = 536; 32const MEMORY_REFRESH_CYCLES: u64 = 40; 33 34const CRC: Crc<u32> = Crc::<u32>::new(&crc::CRC_32_ISO_HDLC); 35 36#[derive(Debug, Clone, Copy, Encode, Decode, ConfigDisplay)] 37pub struct SnesEmulatorConfig { 38 pub forced_timing_mode: Option<TimingMode>, 39 pub aspect_ratio: SnesAspectRatio, 40 pub deinterlace: bool, 41 pub audio_interpolation: AudioInterpolationMode, 42 pub audio_60hz_hack: bool, 43 pub gsu_overclock_factor: NonZeroU64, 44 pub allow_opposing_joypad_directions: bool, 45 pub mouse_sensitivity: f64, 46} 47 48impl EmulatorConfigTrait for SnesEmulatorConfig { 49 fn with_overclocking_disabled(&self) -> Self { 50 Self { gsu_overclock_factor: NonZeroU64::new(1).unwrap(), ..*self } 51 } 52} 53 54impl Default for SnesEmulatorConfig { 55 fn default() -> Self { 56 Self { 57 forced_timing_mode: None, 58 aspect_ratio: SnesAspectRatio::default(), 59 deinterlace: true, 60 audio_interpolation: AudioInterpolationMode::default(), 61 audio_60hz_hack: false, 62 gsu_overclock_factor: NonZeroU64::new(1).unwrap(), 63 allow_opposing_joypad_directions: false, 64 mouse_sensitivity: 1.0, 65 } 66 } 67} 68 69pub type CoprocessorRomFn = 70 dyn Fn() -> Result<Vec<u8>, (io::Error, String)> + Send + Sync + 'static; 71 72#[derive(Default, FakeEncode, FakeDecode)] 73pub struct CoprocessorRoms { 74 pub dsp1: Option<Box<CoprocessorRomFn>>, 75 pub dsp2: Option<Box<CoprocessorRomFn>>, 76 pub dsp3: Option<Box<CoprocessorRomFn>>, 77 pub dsp4: Option<Box<CoprocessorRomFn>>, 78 pub st010: Option<Box<CoprocessorRomFn>>, 79 pub st011: Option<Box<CoprocessorRomFn>>, 80 pub st018: Option<Box<CoprocessorRomFn>>, 81} 82 83impl CoprocessorRoms { 84 #[must_use] 85 pub fn none() -> Self { 86 Self::default() 87 } 88} 89 90#[derive(Debug, Error)] 91pub enum SnesError<RErr, AErr, SErr> { 92 #[error("Error rendering frame: {0}")] 93 Render(RErr), 94 #[error("Error outputting audio samples: {0}")] 95 AudioOutput(AErr), 96 #[error("Error persisting save file: {0}")] 97 SaveWrite(SErr), 98 #[error("Error encoding save file bytes: {0}")] 99 SaveEncode(#[from] EncodeError), 100} 101 102#[derive(Debug, Error)] 103pub enum SnesLoadError { 104 #[error("ROM is only {size} bytes; all SNES ROM chips should be at least 32768 bytes")] 105 RomTooSmall { size: usize }, 106 #[error("Cannot load DSP-1 cartridge because DSP-1 ROM is not configured")] 107 MissingDsp1Rom, 108 #[error("Cannot load DSP-2 cartridge because DSP-2 ROM is not configured")] 109 MissingDsp2Rom, 110 #[error("Cannot load DSP-3 cartridge because DSP-3 ROM is not configured")] 111 MissingDsp3Rom, 112 #[error("Cannot load DSP-4 cartridge because DSP-4 ROM is not configured")] 113 MissingDsp4Rom, 114 #[error("Cannot load ST010 cartridge because ST010 ROM is not configured")] 115 MissingSt010Rom, 116 #[error("Cannot load ST011 cartridge because ST011 ROM is not configured")] 117 MissingSt011Rom, 118 #[error("Cannot load ST018 cartridge because ST018 ROM is not configured")] 119 MissingSt018Rom, 120 #[error("Failed to load required coprocessor ROM from '{path}': {source}")] 121 CoprocessorRomLoad { 122 #[source] 123 source: io::Error, 124 path: String, 125 }, 126 #[error("Invalid ST018 coprocessor ROM: {0}")] 127 St018RomLoad(St018LoadError), 128} 129 130pub type SnesLoadResult<T> = Result<T, SnesLoadError>; 131 132macro_rules! new_bus { 133 ($self:expr) => { 134 Bus { 135 memory: &mut $self.memory, 136 cpu_registers: &mut $self.cpu_registers, 137 ppu: &mut $self.ppu, 138 apu: &mut $self.apu, 139 latched_interrupts: $self.latched_interrupts, 140 access_master_cycles: 0, 141 pending_write: None, 142 } 143 }; 144} 145 146#[derive(Debug, Clone, Copy, Encode, Decode)] 147pub(crate) struct LatchedInterrupts { 148 pub nmi: bool, 149 pub irq: bool, 150} 151 152#[derive(Encode, Decode, PartialClone)] 153pub struct SnesEmulator { 154 main_cpu: Wdc65816, 155 cpu_registers: CpuInternalRegisters, 156 dma_unit: DmaUnit, 157 #[partial_clone(partial)] 158 memory: Memory, 159 ppu: Ppu, 160 apu: Apu, 161 audio_resampler: AudioResampler, 162 total_master_cycles: u64, 163 latched_interrupts: Option<LatchedInterrupts>, 164 memory_refresh_pending: bool, 165 timing_mode: TimingMode, 166 aspect_ratio: SnesAspectRatio, 167 frame_count: u64, 168 last_sram_checksum: u32, 169 // Following fields only stored here to enable hard reset 170 #[partial_clone(default)] 171 coprocessor_roms: CoprocessorRoms, 172 emulator_config: SnesEmulatorConfig, 173} 174 175impl SnesEmulator { 176 /// # Errors 177 /// 178 /// This function will return an error if it is unable to load the cartridge ROM for any reason. 179 pub fn create<S: SaveWriter>( 180 rom: Vec<u8>, 181 config: SnesEmulatorConfig, 182 coprocessor_roms: CoprocessorRoms, 183 save_writer: &mut S, 184 ) -> SnesLoadResult<Self> { 185 let main_cpu = Wdc65816::new(); 186 let cpu_registers = CpuInternalRegisters::new(&config); 187 let dma_unit = DmaUnit::new(); 188 189 let initial_sram = save_writer.load_bytes("sav").ok(); 190 let sram_checksum = initial_sram.as_ref().map_or(0, |sram| CRC.checksum(sram)); 191 let memory = Memory::create( 192 rom, 193 initial_sram, 194 &coprocessor_roms, 195 config.forced_timing_mode, 196 config.gsu_overclock_factor, 197 save_writer, 198 )?; 199 200 let timing_mode = 201 config.forced_timing_mode.unwrap_or_else(|| memory.cartridge_timing_mode()); 202 let ppu = Ppu::new(timing_mode, config); 203 let apu = Apu::new(timing_mode, config); 204 205 log::info!("Running with timing/display mode {timing_mode}"); 206 207 let mut emulator = Self { 208 main_cpu, 209 cpu_registers, 210 dma_unit, 211 memory, 212 ppu, 213 apu, 214 audio_resampler: AudioResampler::new(), 215 total_master_cycles: 0, 216 latched_interrupts: None, 217 memory_refresh_pending: false, 218 timing_mode, 219 aspect_ratio: config.aspect_ratio, 220 frame_count: 0, 221 last_sram_checksum: sram_checksum, 222 coprocessor_roms, 223 emulator_config: config, 224 }; 225 226 // Reset CPU so that execution starts from the right place 227 emulator.main_cpu.reset(&mut new_bus!(emulator)); 228 229 Ok(emulator) 230 } 231 232 #[must_use] 233 pub fn cartridge_title(&mut self) -> String { 234 self.memory.cartridge_title() 235 } 236 237 #[inline] 238 #[must_use] 239 pub fn has_sram(&self) -> bool { 240 self.memory.has_battery_backed_sram() 241 } 242 243 /// The whole of work RAM, indexed by offset from $7E0000. A host reads game state from 244 /// here between frames. 245 #[inline] 246 #[must_use] 247 pub fn wram(&self) -> &[u8; WRAM_LEN] { 248 self.memory.wram() 249 } 250 251 /// Work RAM, writable, indexed like [`Self::wram`]. A host that plays the game from RAM (and 252 /// cheats in it, as a bot linked into an emulator does) writes here between frames. 253 #[inline] 254 #[must_use] 255 pub fn wram_mut(&mut self) -> &mut [u8; WRAM_LEN] { 256 self.memory.wram_mut() 257 } 258 259 pub fn copy_cgram(&self, out: &mut [Color]) { 260 self.ppu.copy_cgram(out); 261 } 262 263 pub fn copy_vram_2bpp(&self, out: &mut [Color], palette: u8, row_len: usize) { 264 self.ppu.copy_vram_2bpp(out, palette, row_len); 265 } 266 267 pub fn copy_vram_4bpp(&self, out: &mut [Color], palette: u8, row_len: usize) { 268 self.ppu.copy_vram_4bpp(out, palette, row_len); 269 } 270 271 pub fn copy_vram_8bpp(&self, out: &mut [Color], row_len: usize) { 272 self.ppu.copy_vram_8bpp(out, row_len); 273 } 274 275 pub fn copy_vram_mode7(&self, out: &mut [Color], row_len: usize) { 276 self.ppu.copy_vram_mode7(out, row_len); 277 } 278 279 fn render_options(&self) -> RenderFrameOptions { 280 let frame_size = self.ppu.frame_size(); 281 let aspect_ratio = self.aspect_ratio.to_pixel_aspect_ratio(frame_size); 282 283 RenderFrameOptions { 284 pixel_aspect_ratio: aspect_ratio, 285 composite_params: Some(self.ppu.composite_params()), 286 ntsc_per_frame_params: Some(NtscPerFrameParams { 287 frame_phase_offset: 2 * self.ppu.frame_start_cycles(), 288 per_line_phase_offset: 2 * ppu::MCLKS_PER_NORMAL_SCANLINE, 289 }), 290 ..RenderFrameOptions::default() 291 } 292 } 293} 294 295impl EmulatorTrait for SnesEmulator { 296 type Button = SnesButton; 297 type Inputs = SnesInputs; 298 type Config = SnesEmulatorConfig; 299 type SaveState = Self; 300 301 type Err< 302 RErr: Debug + Display + Send + Sync + 'static, 303 AErr: Debug + Display + Send + Sync + 'static, 304 SErr: Debug + Display + Send + Sync + 'static, 305 > = SnesError<RErr, AErr, SErr>; 306 307 fn tick<R, A, I, S>( 308 &mut self, 309 renderer: &mut R, 310 audio_output: &mut A, 311 input_poller: &mut I, 312 save_writer: &mut S, 313 ) -> Result<TickEffect, Self::Err<R::Err, A::Err, S::Err>> 314 where 315 R: Renderer, 316 A: AudioOutput, 317 I: InputPoller<Self::Inputs>, 318 S: SaveWriter, 319 { 320 let (master_cycles_elapsed, pending_write) = if self.memory_refresh_pending { 321 // The CPU (including DMA) halts for 40 cycles partway through every scanline so that 322 // the system can refresh DRAM (used for work RAM) 323 self.memory_refresh_pending = false; 324 (MEMORY_REFRESH_CYCLES, None) 325 } else { 326 let mut bus = new_bus!(self); 327 328 match self.dma_unit.tick( 329 &mut bus, 330 self.total_master_cycles, 331 bus::next_cpu_cycle_mclk(&self.main_cpu), 332 ) { 333 DmaStatus::None => { 334 // DMA not in progress, tick CPU 335 self.main_cpu.tick(&mut bus); 336 self.latched_interrupts = None; 337 338 bus.cpu_registers.tick_cpu_cycle(); 339 340 (bus.access_master_cycles, bus.pending_write) 341 } 342 DmaStatus::InProgress { master_cycles_elapsed } => { 343 // Latch interrupt lines at the start of DMA to emulate interrupt tests being 344 // delayed by one cycle after the DMA ends. 345 // Wild Guns depends on this 346 if self.latched_interrupts.is_none() { 347 self.latched_interrupts = 348 Some(LatchedInterrupts { nmi: bus.nmi(), irq: bus.irq() }); 349 } 350 351 (master_cycles_elapsed, None) 352 } 353 } 354 }; 355 debug_assert!(master_cycles_elapsed > 0); 356 357 // Copy WRIO from CPU to PPU for possible H/V counter latching 358 self.ppu.update_wrio(self.cpu_registers.wrio_register()); 359 360 // Possibly latch H/V counter from the controller (e.g. Super Scope) 361 if let Some((h, v)) = self.cpu_registers.controller_hv_latch() { 362 self.ppu.update_controller_hv_latch(h, v, master_cycles_elapsed); 363 } 364 365 if let ApuTickEffect::OutputSample(sample_l, sample_r) = 366 self.apu.tick(master_cycles_elapsed) 367 { 368 self.audio_resampler.collect_sample(sample_l, sample_r); 369 } 370 371 self.audio_resampler.output_samples(audio_output).map_err(SnesError::AudioOutput)?; 372 373 self.memory.tick(master_cycles_elapsed); 374 375 let prev_scanline_mclk = self.ppu.scanline_master_cycles(); 376 let mut tick_effect = TickEffect::None; 377 if self.ppu.tick(master_cycles_elapsed) == PpuTickEffect::FrameComplete { 378 renderer 379 .render_frame( 380 self.ppu.frame_buffer(), 381 self.ppu.frame_size(), 382 self.target_fps(), 383 self.render_options(), 384 ) 385 .map_err(SnesError::Render)?; 386 387 // Only persist SRAM if it's changed since the last write, and only check ~twice per 388 // second because of the checksum calculation 389 if self.memory.has_battery_backed_sram() 390 && let Some(sram) = self.memory.sram() 391 && self.frame_count.is_multiple_of(30) 392 { 393 let checksum = CRC.checksum(sram); 394 if checksum != self.last_sram_checksum { 395 save_writer.persist_bytes("sav", sram).map_err(SnesError::SaveWrite)?; 396 self.memory 397 .write_auxiliary_save_files(save_writer) 398 .map_err(SnesError::SaveWrite)?; 399 400 self.last_sram_checksum = checksum; 401 } 402 } 403 404 self.frame_count += 1; 405 tick_effect = TickEffect::FrameRendered; 406 } 407 408 let inputs = input_poller.poll(); 409 self.cpu_registers.tick(master_cycles_elapsed, &self.ppu, inputs); 410 411 // CPU reads are applied before advancing other components but CPU writes are applied after. 412 // This fixes freezing in Rendering Ranger R2 413 // TODO this is slightly inaccurate - reads should happen 4 mclks before writes, not a full CPU cycle 414 if let Some((address, value)) = pending_write { 415 new_bus!(self).apply_write(address, value); 416 } 417 418 self.total_master_cycles += master_cycles_elapsed; 419 if prev_scanline_mclk < MEMORY_REFRESH_MCLK 420 && self.ppu.scanline_master_cycles() >= MEMORY_REFRESH_MCLK 421 { 422 self.memory_refresh_pending = true; 423 } 424 425 Ok(tick_effect) 426 } 427 428 fn force_render<R>(&mut self, renderer: &mut R) -> Result<(), R::Err> 429 where 430 R: Renderer, 431 { 432 let frame_size = self.ppu.frame_size(); 433 renderer.render_frame( 434 self.ppu.frame_buffer(), 435 frame_size, 436 self.target_fps(), 437 self.render_options(), 438 ) 439 } 440 441 fn reload_config(&mut self, config: &Self::Config) { 442 self.aspect_ratio = config.aspect_ratio; 443 self.ppu.update_config(*config); 444 self.apu.update_config(*config); 445 self.memory.update_gsu_overclock_factor(config.gsu_overclock_factor); 446 self.cpu_registers.reload_config(config); 447 448 self.emulator_config = *config; 449 } 450 451 fn soft_reset(&mut self) { 452 log::info!("Soft resetting"); 453 454 // Reset memory before CPU because some coprocessors (Super FX) block access to the 455 // RESET interrupt vector while the coprocessor is running 456 self.memory.reset(); 457 458 self.main_cpu.reset(&mut new_bus!(self)); 459 self.cpu_registers.reset(); 460 self.ppu.reset(); 461 self.apu.reset(); 462 } 463 464 fn hard_reset<S: SaveWriter>(&mut self, save_writer: &mut S) { 465 log::info!("Hard resetting"); 466 467 let rom = self.memory.take_rom(); 468 469 let coprocessor_roms = mem::take(&mut self.coprocessor_roms); 470 *self = Self::create(rom, self.emulator_config, coprocessor_roms, save_writer) 471 .expect("Hard resetting should never fail to load"); 472 } 473 474 fn load_state(&mut self, mut state: Self::SaveState) { 475 state.memory.take_rom_from(&mut self.memory); 476 state.coprocessor_roms = mem::take(&mut self.coprocessor_roms); 477 *self = state; 478 } 479 480 fn to_save_state(&self) -> Self::SaveState { 481 self.partial_clone() 482 } 483 484 fn target_fps(&self) -> f64 { 485 match (self.timing_mode, self.emulator_config.audio_60hz_hack) { 486 (TimingMode::Ntsc, true) => 60.0, 487 (TimingMode::Ntsc, false) => 60.0988, 488 (TimingMode::Pal, true) => 50.0, 489 (TimingMode::Pal, false) => 50.007, 490 } 491 } 492 493 fn update_audio_output_frequency(&mut self, output_frequency: u64) { 494 self.audio_resampler.update_output_frequency(output_frequency); 495 } 496}