using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; using System.Threading; using System.Xml; namespace MyNes.Core { public class NesEmu { [StructLayout(LayoutKind.Explicit)] private struct CPURegister { [FieldOffset(0)] internal byte l; [FieldOffset(1)] internal byte h; [FieldOffset(0)] internal ushort v; } private enum RequestMode { None, HardReset, SoftReset, LoadState, SaveState, TakeSnapshot } private static int[][] dmc_freq_table = new int[3][] { new int[16] { 428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54 }, new int[16] { 398, 354, 316, 298, 276, 236, 210, 198, 176, 148, 132, 118, 98, 78, 66, 50 }, new int[16] { 428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54 } }; private static int dmc_output_a; private static int dmc_output; private static int dmc_period_devider; private static bool dmc_irq_enabled; private static bool dmc_loop_flag; private static byte dmc_rate_index; private static ushort dmc_addr_refresh; private static int dmc_size_refresh; private static bool dmc_dmaEnabled; private static byte dmc_dmaByte; private static int dmc_dmaBits; private static bool dmc_bufferFull; private static byte dmc_dmaBuffer; private static int dmc_dmaSize; private static ushort dmc_dmaAddr; private static ushort[][] nos_freq_table = new ushort[3][] { new ushort[16] { 4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068 }, new ushort[16] { 4, 7, 14, 30, 60, 88, 118, 148, 188, 236, 354, 472, 708, 944, 1890, 3778 }, new ushort[16] { 4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068 } }; private static bool nos_length_halt; private static bool nos_constant_volume_envelope; private static byte nos_volume_devider_period; private static ushort nos_timer; private static bool nos_mode; private static int nos_period_devider; private static bool nos_length_enabled; private static int nos_length_counter; private static bool nos_envelope_start_flag; private static byte nos_envelope_devider; private static byte nos_envelope_decay_level_counter; private static byte nos_envelope; private static int nos_output; private static int nos_shift_reg; private static int nos_feedback; private static bool nos_ignore_reload; private static byte[][] sq_duty_cycle_sequences = new byte[4][] { new byte[8] { 0, 0, 0, 0, 0, 0, 0, 1 }, new byte[8] { 0, 0, 0, 0, 0, 0, 1, 1 }, new byte[8] { 0, 0, 0, 0, 1, 1, 1, 1 }, new byte[8] { 1, 1, 1, 1, 1, 1, 0, 0 } }; private static byte[] sq_duration_table = new byte[32] { 10, 254, 20, 2, 40, 4, 80, 6, 160, 8, 60, 10, 14, 12, 26, 14, 12, 16, 24, 18, 48, 20, 96, 22, 192, 24, 72, 26, 16, 28, 32, 30 }; private static byte sq1_duty_cycle; private static bool sq1_length_halt; private static bool sq1_constant_volume_envelope; private static byte sq1_volume_devider_period; private static bool sq1_sweep_enable; private static byte sq1_sweep_devider_period; private static bool sq1_sweep_negate; private static byte sq1_sweep_shift_count; private static int sq1_timer; private static int sq1_period_devider; private static byte sq1_seqencer; private static bool sq1_length_enabled; private static int sq1_length_counter; private static bool sq1_envelope_start_flag; private static byte sq1_envelope_devider; private static byte sq1_envelope_decay_level_counter; private static byte sq1_envelope; private static int sq1_sweep_counter; private static bool sq1_sweep_reload; private static int sq1_sweep_change; private static bool sq1_valid_freq; private static int sq1_output; private static bool sq1_ignore_reload; private static byte[] trl_step_seq = new byte[32] { 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; private static bool trl_liner_control_flag; private static byte trl_liner_control_reload; private static ushort trl_timer; private static bool trl_length_enabled; private static byte trl_length_counter; private static bool trl_liner_control_reload_flag; private static byte trl_liner_counter; private static int trl_output; private static int trl_period_devider; private static int trl_step; private static bool trl_ignore_reload; private static byte apu_reg_io_db; private static byte apu_reg_io_addr; private static bool apu_reg_access_happened; private static bool apu_reg_access_w; private static Action[] apu_reg_update_func; private static Action[] apu_reg_read_func; private static Action[] apu_reg_write_func; private static Action apu_update_playback_func; private static bool apu_odd_cycle; private static bool apu_irq_enabled; private static bool apu_irq_flag; private static bool apu_irq_do_it; internal static bool apu_irq_delta_occur; private static bool apu_seq_mode; private static int apu_ferq_f; private static int apu_ferq_l; private static int apu_ferq_e; private static int apu_cycle_f; private static int apu_cycle_f_t; private static int apu_cycle_e; private static int apu_cycle_l; private static bool apu_odd_l; private static bool apu_check_irq; private static bool apu_do_env; private static bool apu_do_length; public static bool SoundEnabled; public static double audio_playback_amplitude = 1.5; public static int audio_playback_peek_limit = 124; private static bool audio_playback_dac_initialized; public static int cpu_speed; private static short[] audio_samples; private static int audio_w_pos; private static int audio_samples_added; internal static int audio_samples_count; private static int[][][][][] mix_table; private static double audio_x; private static double audio_y; private static double audio_y_av; private static double audio_y_timer; public static double audio_timer_ratio = 40.0; private static double audio_timer; private static SoundLowPassFilter audio_low_pass_filter_14K; private static SoundHighPassFilter audio_high_pass_filter_90; private static SoundHighPassFilter audio_high_pass_filter_440; private static SoundDCBlockerFilter audio_dc_blocker_filter; private static bool audio_sq1_outputable; private static bool audio_sq2_outputable; private static bool audio_nos_outputable; private static bool audio_trl_outputable; private static bool audio_dmc_outputable; private static bool audio_signal_outputed; private static bool apu_use_external_sound; private static CPURegister cpu_reg_pc; private static CPURegister cpu_reg_sp; private static CPURegister cpu_reg_ea; private static byte cpu_reg_a; private static byte cpu_reg_x; private static byte cpu_reg_y; private static bool cpu_flag_n; private static bool cpu_flag_v; private static bool cpu_flag_d; private static bool cpu_flag_i; private static bool cpu_flag_z; private static bool cpu_flag_c; private static byte cpu_m; private static byte cpu_opcode; private static byte cpu_byte_temp; private static int cpu_int_temp; private static int cpu_int_temp1; private static byte cpu_dummy; private static bool cpu_bool_tmp; private static CPURegister temp_add; private static bool CPU_IRQ_PIN; private static bool CPU_NMI_PIN; private static bool cpu_suspend_nmi; private static bool cpu_suspend_irq; private static Action[] cpu_addressings; private static Action[] cpu_instructions; private static int dma_DMCDMAWaitCycles; private static int dma_OAMDMAWaitCycles; private static bool dma_isOamDma; private static int dma_oamdma_i; private static bool dma_DMCOn; private static bool dma_OAMOn; private static bool dma_DMC_occurring; private static bool dma_OAM_occurring; private static int dma_OAMFinishCounter; private static ushort dma_Oamaddress; private static int dma_OAMCYCLE; private static byte dma_latch; private static byte dma_dummy; private static ushort reg_2004; internal static int IRQFlags = 0; private static bool PPU_NMI_Current; private static bool PPU_NMI_Old; private const int IRQ_APU = 1; internal const int IRQ_DMC = 2; internal const int IRQ_BOARD = 8; private static ushort InterruptVector; private static byte[] mem_wram; internal static Board mem_board; private static MemReadAccess[] mem_read_accesses; private static MemWriteAccess[] mem_write_accesses; private static bool BUS_RW; private static ushort BUS_ADDRESS; private static string SRAMFileName; public static string GMFileName; private static int PORT0; private static int PORT1; private static int inputStrobe; private static IJoypadConnecter joypad1; private static IJoypadConnecter joypad2; private static IJoypadConnecter joypad3; private static IJoypadConnecter joypad4; private static IShortcutsHandler shortucts; public static bool IsFourPlayers; private static byte[] reverseLookup = new byte[256] { 0, 128, 64, 192, 32, 160, 96, 224, 16, 144, 80, 208, 48, 176, 112, 240, 8, 136, 72, 200, 40, 168, 104, 232, 24, 152, 88, 216, 56, 184, 120, 248, 4, 132, 68, 196, 36, 164, 100, 228, 20, 148, 84, 212, 52, 180, 116, 244, 12, 140, 76, 204, 44, 172, 108, 236, 28, 156, 92, 220, 60, 188, 124, 252, 2, 130, 66, 194, 34, 162, 98, 226, 18, 146, 82, 210, 50, 178, 114, 242, 10, 138, 74, 202, 42, 170, 106, 234, 26, 154, 90, 218, 58, 186, 122, 250, 6, 134, 70, 198, 38, 166, 102, 230, 22, 150, 86, 214, 54, 182, 118, 246, 14, 142, 78, 206, 46, 174, 110, 238, 30, 158, 94, 222, 62, 190, 126, 254, 1, 129, 65, 193, 33, 161, 97, 225, 17, 145, 81, 209, 49, 177, 113, 241, 9, 137, 73, 201, 41, 169, 105, 233, 25, 153, 89, 217, 57, 185, 121, 249, 5, 133, 69, 197, 37, 165, 101, 229, 21, 149, 85, 213, 53, 181, 117, 245, 13, 141, 77, 205, 45, 173, 109, 237, 29, 157, 93, 221, 61, 189, 125, 253, 3, 131, 67, 195, 35, 163, 99, 227, 19, 147, 83, 211, 51, 179, 115, 243, 11, 139, 75, 203, 43, 171, 107, 235, 27, 155, 91, 219, 59, 187, 123, 251, 7, 135, 71, 199, 39, 167, 103, 231, 23, 151, 87, 215, 55, 183, 119, 247, 15, 143, 79, 207, 47, 175, 111, 239, 31, 159, 95, 223, 63, 191, 127, 255 }; private static Action[] ppu_v_clocks; private static Action[] ppu_h_clocks; private static Action[] ppu_bkg_fetches; private static Action[] ppu_spr_fetches; private static Action[] ppu_oam_phases; private static int[] ppu_bkg_pixels; private static int[] ppu_spr_pixels; private static int[] ppu_screen_pixels; private static int[] ppu_palette; private static int ppu_clock_h; internal static ushort ppu_clock_v; private static ushort ppu_clock_vblank_start; private static ushort ppu_clock_vblank_end; private static bool ppu_use_odd_cycle; private static bool ppu_use_odd_swap; private static bool ppu_odd_swap_done; private static bool ppu_is_nmi_time; private static bool ppu_frame_finished; private static byte[] ppu_oam_bank; private static byte[] ppu_oam_bank_secondary; private static byte[] ppu_palette_bank; private static byte ppu_reg_io_db; private static byte ppu_reg_io_addr; private static bool ppu_reg_access_happened; private static bool ppu_reg_access_w; private static Action[] ppu_reg_update_func; private static Action[] ppu_reg_read_func; private static byte ppu_reg_2000_vram_address_increament; private static ushort ppu_reg_2000_sprite_pattern_table_address_for_8x8_sprites; private static ushort ppu_reg_2000_background_pattern_table_address; internal static byte ppu_reg_2000_Sprite_size; private static bool ppu_reg_2000_VBI; private static bool ppu_reg_2001_show_background_in_leftmost_8_pixels_of_screen; private static bool ppu_reg_2001_show_sprites_in_leftmost_8_pixels_of_screen; private static bool ppu_reg_2001_show_background; private static bool ppu_reg_2001_show_sprites; private static int ppu_reg_2001_grayscale; private static int ppu_reg_2001_emphasis; private static bool ppu_reg_2002_SpriteOverflow; private static bool ppu_reg_2002_Sprite0Hit; private static bool ppu_reg_2002_VblankStartedFlag; private static byte ppu_reg_2003_oam_addr; private static ushort ppu_vram_addr; private static byte ppu_vram_data; private static ushort ppu_vram_addr_temp; private static ushort ppu_vram_addr_access_temp; private static bool ppu_vram_flip_flop; private static byte ppu_vram_finex; private static ushort ppu_bkgfetch_nt_addr; private static byte ppu_bkgfetch_nt_data; private static ushort ppu_bkgfetch_at_addr; private static byte ppu_bkgfetch_at_data; private static ushort ppu_bkgfetch_lb_addr; private static byte ppu_bkgfetch_lb_data; private static ushort ppu_bkgfetch_hb_addr; private static byte ppu_bkgfetch_hb_data; private static int ppu_sprfetch_slot; private static byte ppu_sprfetch_y_data; private static byte ppu_sprfetch_t_data; private static byte ppu_sprfetch_at_data; private static byte ppu_sprfetch_x_data; private static ushort ppu_sprfetch_lb_addr; private static byte ppu_sprfetch_lb_data; private static ushort ppu_sprfetch_hb_addr; private static byte ppu_sprfetch_hb_data; internal static bool ppu_is_sprfetch; private static int ppu_bkg_render_i; private static int ppu_bkg_render_pos; private static int ppu_bkg_render_tmp_val; private static int ppu_bkg_current_pixel; private static int ppu_spr_current_pixel; private static int ppu_current_pixel; private static int ppu_render_x; private static int ppu_render_y; private static byte ppu_oamev_n; private static byte ppu_oamev_m; private static bool ppu_oamev_compare; private static byte ppu_oamev_slot; private static byte ppu_fetch_data; private static byte ppu_phase_index; private static bool ppu_sprite0_should_hit; private static int ppu_temp_comparator; public static bool ON; public static bool PAUSED; public static bool isPaused; public static string CurrentFilePath; public static bool FrameLimiterEnabled; private static Thread mainThread; private static double fps_time_last; private static double fps_time_start; private static double fps_time_token; private static double fps_time_dead; private static double fps_time_period; private static double fps_time_frame_time; private static double emu_time_target_fps = 60.0; private static bool emu_frame_clocking_mode; private static bool emu_frame_done; private static bool render_initialized; private static RenderVideoFrame render_video; private static RenderAudioSamples render_audio; private static TogglePause render_audio_toggle_pause; private static GetIsPlaying render_audio_get_is_playing; private static bool render_audio_is_playing; public static EmuRegion Region; private static int SystemIndex; private static RequestMode emu_request_mode = RequestMode.None; public static bool FrameSkipEnabled; public static int FrameSkipInterval; private static int FrameSkipCounter; private static byte sq2_duty_cycle; private static bool sq2_length_halt; private static bool sq2_constant_volume_envelope; private static byte sq2_volume_devider_period; private static bool sq2_sweep_enable; private static byte sq2_sweep_devider_period; private static bool sq2_sweep_negate; private static byte sq2_sweep_shift_count; private static int sq2_timer; private static int sq2_period_devider; private static byte sq2_seqencer; private static bool sq2_length_enabled; private static int sq2_length_counter; private static bool sq2_envelope_start_flag; private static byte sq2_envelope_devider; private static byte sq2_envelope_decay_level_counter; private static byte sq2_envelope; private static int sq2_sweep_counter; private static bool sq2_sweep_reload; private static int sq2_sweep_change; private static bool sq2_valid_freq; private static int sq2_output; private static bool sq2_ignore_reload; private static byte register_p { get { return (byte)((cpu_flag_n ? 128u : 0u) | (cpu_flag_v ? 64u : 0u) | (cpu_flag_d ? 8u : 0u) | (cpu_flag_i ? 4u : 0u) | (cpu_flag_z ? 2u : 0u) | (cpu_flag_c ? 1u : 0u) | 0x20u); } set { cpu_flag_n = (value & 0x80) != 0; cpu_flag_v = (value & 0x40) != 0; cpu_flag_d = (value & 8) != 0; cpu_flag_i = (value & 4) != 0; cpu_flag_z = (value & 2) != 0; cpu_flag_c = (value & 1) != 0; } } public static GameGenieCode[] GameGenieCodes { get { if (mem_board != null) { return mem_board.GameGenieCodes; } return null; } } public static bool IsGameGenieActive { get { if (mem_board != null) { return mem_board.IsGameGenieActive; } return false; } set { if (mem_board != null) { mem_board.IsGameGenieActive = value; } } } public static bool IsGameFoundOnDB { get { if (mem_board != null) { return mem_board.IsGameFoundOnDB; } return false; } } public static NesCartDatabaseGameInfo GameInfo { get { if (mem_board != null) { return mem_board.GameInfo; } return NesCartDatabaseGameInfo.Empty; } } public static NesCartDatabaseCartridgeInfo GameCartInfo { get { if (mem_board != null) { return mem_board.GameCartInfo; } return new NesCartDatabaseCartridgeInfo(); } } public static string SHA1 => mem_board.SHA1; public static event EventHandler EmuShutdown; private static void DMCHardReset() { dmc_output_a = 0; dmc_output = 0; dmc_period_devider = 0; dmc_loop_flag = false; dmc_rate_index = 0; dmc_irq_enabled = false; dmc_dmaAddr = 49152; dmc_addr_refresh = 49152; dmc_size_refresh = 0; dmc_dmaBits = 1; dmc_dmaByte = 1; dmc_period_devider = 0; dmc_dmaEnabled = false; dmc_bufferFull = false; dmc_dmaSize = 0; } private static void DMCSoftReset() { DMCHardReset(); } private static void DMCClock() { dmc_period_devider--; if (dmc_period_devider > 0) { return; } dmc_period_devider = dmc_freq_table[SystemIndex][dmc_rate_index]; if (dmc_dmaEnabled) { if (((uint)dmc_dmaByte & (true ? 1u : 0u)) != 0) { if (dmc_output_a <= 125) { dmc_output_a += 2; } } else if (dmc_output_a >= 2) { dmc_output_a -= 2; } dmc_dmaByte >>= 1; } dmc_dmaBits--; if (dmc_dmaBits == 0) { dmc_dmaBits = 8; if (dmc_bufferFull) { dmc_bufferFull = false; dmc_dmaEnabled = true; dmc_dmaByte = dmc_dmaBuffer; if (dmc_dmaSize > 0) { AssertDMCDMA(); } } else { dmc_dmaEnabled = false; } } if (audio_dmc_outputable) { dmc_output = dmc_output_a; } audio_signal_outputed = true; } private static void DMCDoDMA() { dmc_bufferFull = true; Read(ref dmc_dmaAddr, out dmc_dmaBuffer); if (dmc_dmaAddr == ushort.MaxValue) { dmc_dmaAddr = 32768; } else { dmc_dmaAddr++; } if (dmc_dmaSize > 0) { dmc_dmaSize--; } if (dmc_dmaSize == 0) { if (dmc_loop_flag) { dmc_dmaSize = dmc_size_refresh; dmc_dmaAddr = dmc_addr_refresh; } else if (dmc_irq_enabled) { IRQFlags |= 2; apu_irq_delta_occur = true; } } } private static void APUOnRegister4010() { if (apu_reg_access_w) { dmc_irq_enabled = (apu_reg_io_db & 0x80) != 0; dmc_loop_flag = (apu_reg_io_db & 0x40) != 0; if (!dmc_irq_enabled) { apu_irq_delta_occur = false; IRQFlags &= -3; } dmc_rate_index = (byte)(apu_reg_io_db & 0xFu); } } private static void APUOnRegister4011() { if (apu_reg_access_w) { dmc_output_a = (byte)(apu_reg_io_db & 0x7F); } } private static void APUOnRegister4012() { if (apu_reg_access_w) { dmc_addr_refresh = (ushort)((uint)(apu_reg_io_db << 6) | 0xC000u); } } private static void APUOnRegister4013() { if (apu_reg_access_w) { dmc_size_refresh = (apu_reg_io_db << 4) | 1; } } private static void DMCOn4015() { apu_irq_delta_occur = false; IRQFlags &= -3; } private static void DMCRead4015() { if (dmc_dmaSize > 0) { apu_reg_io_db = (byte)((apu_reg_io_db & 0xEFu) | 0x10u); } } private static void DMCWriteState(ref BinaryWriter bin) { bin.Write(dmc_output_a); bin.Write(dmc_output); bin.Write(dmc_period_devider); bin.Write(dmc_irq_enabled); bin.Write(dmc_loop_flag); bin.Write(dmc_rate_index); bin.Write(dmc_addr_refresh); bin.Write(dmc_size_refresh); bin.Write(dmc_dmaEnabled); bin.Write(dmc_dmaByte); bin.Write(dmc_dmaBits); bin.Write(dmc_bufferFull); bin.Write(dmc_dmaBuffer); bin.Write(dmc_dmaSize); bin.Write(dmc_dmaAddr); } private static void DMCReadState(ref BinaryReader bin) { dmc_output_a = bin.ReadInt32(); dmc_output = bin.ReadInt32(); dmc_period_devider = bin.ReadInt32(); dmc_irq_enabled = bin.ReadBoolean(); dmc_loop_flag = bin.ReadBoolean(); dmc_rate_index = bin.ReadByte(); dmc_addr_refresh = bin.ReadUInt16(); dmc_size_refresh = bin.ReadInt32(); dmc_dmaEnabled = bin.ReadBoolean(); dmc_dmaByte = bin.ReadByte(); dmc_dmaBits = bin.ReadInt32(); dmc_bufferFull = bin.ReadBoolean(); dmc_dmaBuffer = bin.ReadByte(); dmc_dmaSize = bin.ReadInt32(); dmc_dmaAddr = bin.ReadUInt16(); } private static void NOSHardReset() { nos_length_halt = false; nos_constant_volume_envelope = false; nos_volume_devider_period = 0; nos_shift_reg = 1; nos_timer = 0; nos_mode = false; nos_period_devider = 0; nos_length_enabled = false; nos_length_counter = 0; nos_envelope_start_flag = false; nos_envelope_devider = 0; nos_envelope_decay_level_counter = 0; nos_envelope = 0; nos_output = 0; nos_feedback = 0; nos_ignore_reload = false; } private static void NOSSoftReset() { NOSHardReset(); } private static void NOSClock() { nos_period_devider--; if (nos_period_devider > 0) { return; } nos_period_devider = nos_timer; if (nos_mode) { nos_feedback = ((nos_shift_reg >> 6) & 1) ^ (nos_shift_reg & 1); } else { nos_feedback = ((nos_shift_reg >> 1) & 1) ^ (nos_shift_reg & 1); } nos_shift_reg >>= 1; nos_shift_reg = (nos_shift_reg & 0x3FFF) | ((nos_feedback & 1) << 14); if (nos_length_counter > 0 && (nos_shift_reg & 1) == 0) { if (audio_nos_outputable) { nos_output = nos_envelope; } } else { nos_output = 0; } audio_signal_outputed = true; } private static void NOSClockLength() { if (nos_length_counter > 0 && !nos_length_halt) { nos_length_counter--; if (apu_reg_access_happened && apu_reg_io_addr == 15 && apu_reg_access_w) { nos_ignore_reload = true; } } } private static void NOSClockEnvelope() { if (nos_envelope_start_flag) { nos_envelope_start_flag = false; nos_envelope_decay_level_counter = 15; nos_envelope_devider = (byte)(nos_volume_devider_period + 1); } else if (nos_envelope_devider > 0) { nos_envelope_devider--; } else { nos_envelope_devider = (byte)(nos_volume_devider_period + 1); if (nos_envelope_decay_level_counter > 0) { nos_envelope_decay_level_counter--; } else if (nos_length_halt) { nos_envelope_decay_level_counter = 15; } } nos_envelope = (nos_constant_volume_envelope ? nos_volume_devider_period : nos_envelope_decay_level_counter); } private static void APUOnRegister400C() { if (apu_reg_access_w) { nos_volume_devider_period = (byte)(apu_reg_io_db & 0xFu); nos_length_halt = (apu_reg_io_db & 0x20) != 0; nos_constant_volume_envelope = (apu_reg_io_db & 0x10) != 0; nos_envelope = (nos_constant_volume_envelope ? nos_volume_devider_period : nos_envelope_decay_level_counter); } } private static void APUOnRegister400D() { } private static void APUOnRegister400E() { if (apu_reg_access_w) { nos_timer = (ushort)(nos_freq_table[SystemIndex][apu_reg_io_db & 0xF] / 2); nos_mode = (apu_reg_io_db & 0x80) == 128; } } private static void APUOnRegister400F() { if (apu_reg_access_w) { if (nos_length_enabled && !nos_ignore_reload) { nos_length_counter = sq_duration_table[apu_reg_io_db >> 3]; } if (nos_ignore_reload) { nos_ignore_reload = false; } nos_envelope_start_flag = true; } } private static void NOSOn4015() { nos_length_enabled = (apu_reg_io_db & 8) != 0; if (!nos_length_enabled) { nos_length_counter = 0; } } private static void NOSRead4015() { if (nos_length_counter > 0) { apu_reg_io_db = (byte)((apu_reg_io_db & 0xF7u) | 8u); } } private static void NOSWriteState(ref BinaryWriter bin) { bin.Write(nos_length_halt); bin.Write(nos_constant_volume_envelope); bin.Write(nos_volume_devider_period); bin.Write(nos_timer); bin.Write(nos_mode); bin.Write(nos_period_devider); bin.Write(nos_length_enabled); bin.Write(nos_length_counter); bin.Write(nos_envelope_start_flag); bin.Write(nos_envelope_devider); bin.Write(nos_envelope_decay_level_counter); bin.Write(nos_envelope); bin.Write(nos_output); bin.Write(nos_shift_reg); bin.Write(nos_feedback); bin.Write(nos_ignore_reload); } private static void NOSReadState(ref BinaryReader bin) { nos_length_halt = bin.ReadBoolean(); nos_constant_volume_envelope = bin.ReadBoolean(); nos_volume_devider_period = bin.ReadByte(); nos_timer = bin.ReadUInt16(); nos_mode = bin.ReadBoolean(); nos_period_devider = bin.ReadInt32(); nos_length_enabled = bin.ReadBoolean(); nos_length_counter = bin.ReadInt32(); nos_envelope_start_flag = bin.ReadBoolean(); nos_envelope_devider = bin.ReadByte(); nos_envelope_decay_level_counter = bin.ReadByte(); nos_envelope = bin.ReadByte(); nos_output = bin.ReadInt32(); nos_shift_reg = bin.ReadInt32(); nos_feedback = bin.ReadInt32(); nos_ignore_reload = bin.ReadBoolean(); } private static void SQ1HardReset() { sq1_duty_cycle = 0; sq1_length_halt = false; sq1_constant_volume_envelope = false; sq1_volume_devider_period = 0; sq1_sweep_enable = false; sq1_sweep_devider_period = 0; sq1_sweep_negate = false; sq1_sweep_shift_count = 0; sq1_timer = 0; sq1_period_devider = 0; sq1_seqencer = 0; sq1_length_enabled = false; sq1_length_counter = 0; sq1_envelope_start_flag = false; sq1_envelope_devider = 0; sq1_envelope_decay_level_counter = 0; sq1_envelope = 0; sq1_sweep_counter = 0; sq1_sweep_reload = false; sq1_sweep_change = 0; sq1_valid_freq = false; sq1_output = 0; sq1_ignore_reload = false; } private static void SQ1SoftReset() { SQ1HardReset(); } private static void SQ1Clock() { sq1_period_devider--; if (sq1_period_devider > 0) { return; } sq1_period_devider = sq1_timer + 1; sq1_seqencer = (byte)((uint)(sq1_seqencer + 1) & 7u); if (sq1_length_counter > 0 && sq1_valid_freq) { if (audio_sq1_outputable) { sq1_output = sq_duty_cycle_sequences[sq1_duty_cycle][sq1_seqencer] * sq1_envelope; } } else { sq1_output = 0; } audio_signal_outputed = true; } private static void SQ1ClockLength() { if (sq1_length_counter > 0 && !sq1_length_halt) { sq1_length_counter--; if (apu_reg_access_happened && apu_reg_io_addr == 3 && apu_reg_access_w) { sq1_ignore_reload = true; } } sq1_sweep_counter--; if (sq1_sweep_counter == 0) { sq1_sweep_counter = sq1_sweep_devider_period + 1; if (sq1_sweep_enable && sq1_sweep_shift_count > 0 && sq1_valid_freq) { sq1_sweep_change = sq1_timer >> (int)sq1_sweep_shift_count; sq1_timer += (sq1_sweep_negate ? (~sq1_sweep_change) : sq1_sweep_change); SQ1CalculateValidFreq(); } } if (sq1_sweep_reload) { sq1_sweep_counter = sq1_sweep_devider_period + 1; sq1_sweep_reload = false; } } private static void SQ1ClockEnvelope() { if (sq1_envelope_start_flag) { sq1_envelope_start_flag = false; sq1_envelope_decay_level_counter = 15; sq1_envelope_devider = (byte)(sq1_volume_devider_period + 1); } else if (sq1_envelope_devider > 0) { sq1_envelope_devider--; } else { sq1_envelope_devider = (byte)(sq1_volume_devider_period + 1); if (sq1_envelope_decay_level_counter > 0) { sq1_envelope_decay_level_counter--; } else if (sq1_length_halt) { sq1_envelope_decay_level_counter = 15; } } sq1_envelope = (sq1_constant_volume_envelope ? sq1_volume_devider_period : sq1_envelope_decay_level_counter); } private static void APUOnRegister4000() { if (apu_reg_access_w) { sq1_duty_cycle = (byte)((apu_reg_io_db & 0xC0) >> 6); sq1_volume_devider_period = (byte)(apu_reg_io_db & 0xFu); sq1_length_halt = (apu_reg_io_db & 0x20) != 0; sq1_constant_volume_envelope = (apu_reg_io_db & 0x10) != 0; sq1_envelope = (sq1_constant_volume_envelope ? sq1_volume_devider_period : sq1_envelope_decay_level_counter); } } private static void APUOnRegister4001() { if (apu_reg_access_w) { sq1_sweep_enable = (apu_reg_io_db & 0x80) == 128; sq1_sweep_devider_period = (byte)((uint)(apu_reg_io_db >> 4) & 7u); sq1_sweep_negate = (apu_reg_io_db & 8) == 8; sq1_sweep_shift_count = (byte)(apu_reg_io_db & 7u); sq1_sweep_reload = true; SQ1CalculateValidFreq(); } } private static void APUOnRegister4002() { if (apu_reg_access_w) { sq1_timer = (sq1_timer & 0xFF00) | apu_reg_io_db; SQ1CalculateValidFreq(); } } private static void APUOnRegister4003() { if (apu_reg_access_w) { sq1_timer = (sq1_timer & 0xFF) | ((apu_reg_io_db & 7) << 8); if (sq1_length_enabled && !sq1_ignore_reload) { sq1_length_counter = sq_duration_table[apu_reg_io_db >> 3]; } if (sq1_ignore_reload) { sq1_ignore_reload = false; } sq1_seqencer = 0; sq1_envelope_start_flag = true; SQ1CalculateValidFreq(); } } private static void SQ1On4015() { sq1_length_enabled = (apu_reg_io_db & 1) != 0; if (!sq1_length_enabled) { sq1_length_counter = 0; } } private static void SQ1Read4015() { if (sq1_length_counter > 0) { apu_reg_io_db = (byte)((apu_reg_io_db & 0xFEu) | 1u); } } private static void SQ1CalculateValidFreq() { sq1_valid_freq = sq1_timer >= 8 && (sq1_sweep_negate || ((sq1_timer + (sq1_timer >> (int)sq1_sweep_shift_count)) & 0x800) == 0); } private static void SQ1WriteState(ref BinaryWriter bin) { bin.Write(sq1_duty_cycle); bin.Write(sq1_length_halt); bin.Write(sq1_constant_volume_envelope); bin.Write(sq1_volume_devider_period); bin.Write(sq1_sweep_enable); bin.Write(sq1_sweep_devider_period); bin.Write(sq1_sweep_negate); bin.Write(sq1_sweep_shift_count); bin.Write(sq1_timer); bin.Write(sq1_period_devider); bin.Write(sq1_seqencer); bin.Write(sq1_length_enabled); bin.Write(sq1_length_counter); bin.Write(sq1_envelope_start_flag); bin.Write(sq1_envelope_devider); bin.Write(sq1_envelope_decay_level_counter); bin.Write(sq1_envelope); bin.Write(sq1_sweep_counter); bin.Write(sq1_sweep_reload); bin.Write(sq1_sweep_change); bin.Write(sq1_valid_freq); bin.Write(sq1_output); bin.Write(sq1_ignore_reload); } private static void SQ1ReadState(ref BinaryReader bin) { sq1_duty_cycle = bin.ReadByte(); sq1_length_halt = bin.ReadBoolean(); sq1_constant_volume_envelope = bin.ReadBoolean(); sq1_volume_devider_period = bin.ReadByte(); sq1_sweep_enable = bin.ReadBoolean(); sq1_sweep_devider_period = bin.ReadByte(); sq1_sweep_negate = bin.ReadBoolean(); sq1_sweep_shift_count = bin.ReadByte(); sq1_timer = bin.ReadInt32(); sq1_period_devider = bin.ReadInt32(); sq1_seqencer = bin.ReadByte(); sq1_length_enabled = bin.ReadBoolean(); sq1_length_counter = bin.ReadInt32(); sq1_envelope_start_flag = bin.ReadBoolean(); sq1_envelope_devider = bin.ReadByte(); sq1_envelope_decay_level_counter = bin.ReadByte(); sq1_envelope = bin.ReadByte(); sq1_sweep_counter = bin.ReadInt32(); sq1_sweep_reload = bin.ReadBoolean(); sq1_sweep_change = bin.ReadInt32(); sq1_valid_freq = bin.ReadBoolean(); sq1_output = bin.ReadInt32(); sq1_ignore_reload = bin.ReadBoolean(); } private static void TRLHardReset() { trl_liner_control_flag = false; trl_liner_control_reload = 0; trl_timer = 0; trl_length_enabled = false; trl_length_counter = 0; trl_liner_control_reload_flag = false; trl_liner_counter = 0; trl_output = 0; trl_period_devider = 0; trl_step = 0; trl_ignore_reload = false; } private static void TRLSoftReset() { TRLHardReset(); } private static void TRLClock() { trl_period_devider--; if (trl_period_devider > 0) { return; } trl_period_devider = trl_timer + 1; if (trl_length_counter > 0 && trl_liner_counter > 0 && trl_timer >= 4) { trl_step++; trl_step &= 31; if (audio_trl_outputable) { trl_output = trl_step_seq[trl_step]; } } audio_signal_outputed = true; } private static void TRLClockLength() { if (trl_length_counter > 0 && !trl_liner_control_flag) { trl_length_counter--; if (apu_reg_access_happened && apu_reg_io_addr == 11 && apu_reg_access_w) { trl_ignore_reload = true; } } } private static void TRLClockEnvelope() { if (trl_liner_control_reload_flag) { trl_liner_counter = trl_liner_control_reload; } else if (trl_liner_counter > 0) { trl_liner_counter--; } if (!trl_liner_control_flag) { trl_liner_control_reload_flag = false; } } private static void APUOnRegister4008() { if (apu_reg_access_w) { trl_liner_control_flag = (apu_reg_io_db & 0x80) == 128; trl_liner_control_reload = (byte)(apu_reg_io_db & 0x7Fu); } } private static void APUOnRegister4009() { } private static void APUOnRegister400A() { if (apu_reg_access_w) { trl_timer = (ushort)((trl_timer & 0x7F00u) | apu_reg_io_db); } } private static void APUOnRegister400B() { if (apu_reg_access_w) { trl_timer = (ushort)((trl_timer & 0xFFu) | (uint)((apu_reg_io_db & 7) << 8)); if (trl_length_enabled && !trl_ignore_reload) { trl_length_counter = sq_duration_table[apu_reg_io_db >> 3]; } if (trl_ignore_reload) { trl_ignore_reload = false; } trl_liner_control_reload_flag = true; } } private static void TRLOn4015() { trl_length_enabled = (apu_reg_io_db & 4) != 0; if (!trl_length_enabled) { trl_length_counter = 0; } } private static void TRLRead4015() { if (trl_length_counter > 0) { apu_reg_io_db = (byte)((apu_reg_io_db & 0xFBu) | 4u); } } private static void TRLWriteState(ref BinaryWriter bin) { bin.Write(trl_liner_control_flag); bin.Write(trl_liner_control_reload); bin.Write(trl_timer); bin.Write(trl_length_enabled); bin.Write(trl_length_counter); bin.Write(trl_liner_control_reload_flag); bin.Write(trl_liner_counter); bin.Write(trl_output); bin.Write(trl_period_devider); bin.Write(trl_step); bin.Write(trl_ignore_reload); } private static void TRLReadState(ref BinaryReader bin) { trl_liner_control_flag = bin.ReadBoolean(); trl_liner_control_reload = bin.ReadByte(); trl_timer = bin.ReadUInt16(); trl_length_enabled = bin.ReadBoolean(); trl_length_counter = bin.ReadByte(); trl_liner_control_reload_flag = bin.ReadBoolean(); trl_liner_counter = bin.ReadByte(); trl_output = bin.ReadInt32(); trl_period_devider = bin.ReadInt32(); trl_step = bin.ReadInt32(); trl_ignore_reload = bin.ReadBoolean(); } private static void APUInitialize() { apu_reg_update_func = new Action[32]; apu_reg_read_func = new Action[32]; apu_reg_write_func = new Action[32]; for (int i = 0; i < 32; i++) { apu_reg_update_func[i] = APUBlankAccess; apu_reg_read_func[i] = APUBlankAccess; apu_reg_write_func[i] = APUBlankAccess; } apu_reg_update_func[0] = APUOnRegister4000; apu_reg_update_func[1] = APUOnRegister4001; apu_reg_update_func[2] = APUOnRegister4002; apu_reg_update_func[3] = APUOnRegister4003; apu_reg_update_func[4] = APUOnRegister4004; apu_reg_update_func[5] = APUOnRegister4005; apu_reg_update_func[6] = APUOnRegister4006; apu_reg_update_func[7] = APUOnRegister4007; apu_reg_update_func[8] = APUOnRegister4008; apu_reg_update_func[9] = APUOnRegister4009; apu_reg_update_func[10] = APUOnRegister400A; apu_reg_update_func[11] = APUOnRegister400B; apu_reg_update_func[12] = APUOnRegister400C; apu_reg_update_func[13] = APUOnRegister400D; apu_reg_update_func[14] = APUOnRegister400E; apu_reg_update_func[15] = APUOnRegister400F; apu_reg_update_func[16] = APUOnRegister4010; apu_reg_update_func[17] = APUOnRegister4011; apu_reg_update_func[18] = APUOnRegister4012; apu_reg_update_func[19] = APUOnRegister4013; apu_reg_update_func[21] = APUOnRegister4015; apu_reg_update_func[22] = APUOnRegister4016; apu_reg_update_func[23] = APUOnRegister4017; apu_reg_read_func[21] = APURead4015; apu_reg_read_func[22] = APURead4016; apu_reg_read_func[23] = APURead4017; apu_reg_write_func[20] = APUWrite4014; apu_reg_write_func[21] = APUWrite4015; audio_low_pass_filter_14K = new SoundLowPassFilter(0.00815686); audio_high_pass_filter_90 = new SoundHighPassFilter(0.999835); audio_high_pass_filter_440 = new SoundHighPassFilter(0.996039); audio_dc_blocker_filter = new SoundDCBlockerFilter(0.995); apu_update_playback_func = APUUpdatePlaybackWithFilters; } public static void ApplyAudioSettings(bool all = true) { SoundEnabled = MyNesMain.RendererSettings.Audio_SoundEnabled; audio_sq1_outputable = MyNesMain.RendererSettings.Audio_ChannelEnabled_SQ1; audio_sq2_outputable = MyNesMain.RendererSettings.Audio_ChannelEnabled_SQ2; audio_nos_outputable = MyNesMain.RendererSettings.Audio_ChannelEnabled_NOZ; audio_trl_outputable = MyNesMain.RendererSettings.Audio_ChannelEnabled_TRL; audio_dmc_outputable = MyNesMain.RendererSettings.Audio_ChannelEnabled_DMC; if (apu_use_external_sound) { mem_board.APUApplyChannelsSettings(); } if (all) { CalculateAudioPlaybackValues(); } } private static void APUHardReset() { apu_reg_io_db = 0; apu_reg_io_addr = 0; apu_reg_access_happened = false; apu_reg_access_w = false; apu_seq_mode = false; apu_odd_cycle = true; apu_cycle_f_t = 0; apu_cycle_e = 4; apu_cycle_f = 4; apu_cycle_l = 4; apu_odd_l = false; apu_check_irq = false; apu_do_env = false; apu_do_length = false; switch (Region) { case EmuRegion.NTSC: cpu_speed = 1789773; apu_ferq_f = 14914; apu_ferq_e = 3728; apu_ferq_l = 7456; break; case EmuRegion.PALB: cpu_speed = 1662607; apu_ferq_f = 14914; apu_ferq_e = 3728; apu_ferq_l = 7456; break; case EmuRegion.DENDY: cpu_speed = 1773448; apu_ferq_f = 14914; apu_ferq_e = 3728; apu_ferq_l = 7456; break; } Tracer.WriteLine("NES: cpu speed = " + cpu_speed); SQ1HardReset(); SQ2HardReset(); NOSHardReset(); DMCHardReset(); TRLHardReset(); apu_irq_enabled = true; apu_irq_flag = false; reg_2004 = 8196; CalculateAudioPlaybackValues(); apu_use_external_sound = mem_board.enable_external_sound; if (apu_use_external_sound) { Tracer.WriteInformation("External sound channels has been enabled on apu."); } } private static void APUSoftReset() { apu_reg_io_db = 0; apu_reg_io_addr = 0; apu_reg_access_happened = false; apu_reg_access_w = false; apu_seq_mode = false; apu_odd_cycle = false; apu_cycle_f_t = 0; apu_cycle_e = 4; apu_cycle_f = 4; apu_cycle_l = 4; apu_odd_l = false; apu_check_irq = false; apu_do_env = false; apu_do_length = false; apu_irq_enabled = true; apu_irq_flag = false; SQ1SoftReset(); SQ2SoftReset(); TRLSoftReset(); NOSSoftReset(); DMCSoftReset(); } private static void APUIORead(ref ushort addr, out byte value) { if (addr >= 16416) { mem_board.ReadEX(ref addr, out value); return; } apu_reg_io_addr = (byte)(addr & 0x1Fu); apu_reg_access_happened = true; apu_reg_access_w = false; apu_reg_read_func[apu_reg_io_addr](); value = apu_reg_io_db; } private static void APUIOWrite(ref ushort addr, ref byte value) { if (addr >= 16416) { mem_board.WriteEX(ref addr, ref value); return; } apu_reg_io_addr = (byte)(addr & 0x1Fu); apu_reg_io_db = value; apu_reg_access_w = true; apu_reg_access_happened = true; apu_reg_write_func[apu_reg_io_addr](); } private static void APUBlankAccess() { } private static void APUWrite4014() { dma_Oamaddress = (ushort)(apu_reg_io_db << 8); AssertOAMDMA(); } private static void APUWrite4015() { if ((apu_reg_io_db & 0x10u) != 0) { if (dmc_dmaSize == 0) { dmc_dmaSize = dmc_size_refresh; dmc_dmaAddr = dmc_addr_refresh; } } else { dmc_dmaSize = 0; } if (!dmc_bufferFull && dmc_dmaSize > 0) { AssertDMCDMA(); } } private static void APUOnRegister4015() { if (apu_reg_access_w) { SQ1On4015(); SQ2On4015(); NOSOn4015(); TRLOn4015(); DMCOn4015(); } else { apu_irq_flag = false; IRQFlags &= -2; } } private static void APUOnRegister4016() { if (!apu_reg_access_w) { return; } if (inputStrobe > (apu_reg_io_db & 1)) { if (IsFourPlayers) { PORT0 = (joypad3.GetData() << 8) | joypad1.GetData() | 0x1010000; PORT1 = (joypad4.GetData() << 8) | joypad2.GetData() | 0x2020000; } else { PORT0 = joypad1.GetData() | 0x1010100; PORT1 = joypad2.GetData() | 0x2020200; } } inputStrobe = apu_reg_io_db & 1; } private static void APUOnRegister4017() { if (apu_reg_access_w) { apu_seq_mode = (apu_reg_io_db & 0x80) != 0; apu_irq_enabled = (apu_reg_io_db & 0x40) == 0; apu_cycle_e = -1; apu_cycle_l = -1; apu_cycle_f = -1; apu_odd_l = false; apu_do_length = apu_seq_mode; apu_do_env = apu_seq_mode; apu_check_irq = false; if (!apu_irq_enabled) { apu_irq_flag = false; IRQFlags &= -2; } } } private static void APURead4015() { apu_reg_io_db &= 32; SQ1Read4015(); SQ2Read4015(); NOSRead4015(); TRLRead4015(); DMCRead4015(); if (apu_irq_flag) { apu_reg_io_db = (byte)((apu_reg_io_db & 0xBFu) | 0x40u); } if (apu_irq_delta_occur) { apu_reg_io_db = (byte)((apu_reg_io_db & 0x7Fu) | 0x80u); } } private static void APURead4016() { apu_reg_io_db = (byte)((uint)PORT0 & 1u); PORT0 >>= 1; } private static void APURead4017() { apu_reg_io_db = (byte)((uint)PORT1 & 1u); PORT1 >>= 1; } private static void APUClock() { apu_odd_cycle = !apu_odd_cycle; if (apu_do_env) { APUClockEnvelope(); } if (apu_do_length) { APUClockDuration(); } if (apu_odd_cycle) { apu_cycle_f++; if (apu_cycle_f >= apu_ferq_f) { apu_cycle_f = -1; apu_check_irq = true; apu_cycle_f_t = 3; } apu_cycle_e++; if (apu_cycle_e >= apu_ferq_e) { apu_cycle_e = -1; if (apu_check_irq) { if (!apu_seq_mode) { apu_do_env = true; } else { apu_cycle_e = 4; } } else { apu_do_env = true; } } apu_cycle_l++; if (apu_cycle_l >= apu_ferq_l) { apu_odd_l = !apu_odd_l; apu_cycle_l = (apu_odd_l ? (-2) : (-1)); if (apu_check_irq && apu_seq_mode) { apu_cycle_l = 3730; apu_odd_l = true; } else { apu_do_length = true; } } SQ1Clock(); SQ2Clock(); NOSClock(); if (apu_use_external_sound) { mem_board.OnAPUClock(); } if (apu_reg_access_happened) { apu_reg_access_happened = false; apu_reg_update_func[apu_reg_io_addr](); } } TRLClock(); DMCClock(); if (apu_check_irq) { if (!apu_seq_mode) { APUCheckIRQ(); } apu_cycle_f_t--; if (apu_cycle_f_t == 0) { apu_check_irq = false; } } if (apu_use_external_sound) { mem_board.OnAPUClockSingle(); } apu_update_playback_func(); } private static void APUClockDuration() { SQ1ClockLength(); SQ2ClockLength(); NOSClockLength(); TRLClockLength(); if (apu_use_external_sound) { mem_board.OnAPUClockDuration(); } apu_do_length = false; } private static void APUClockEnvelope() { SQ1ClockEnvelope(); SQ2ClockEnvelope(); NOSClockEnvelope(); TRLClockEnvelope(); if (apu_use_external_sound) { mem_board.OnAPUClockEnvelope(); } apu_do_env = false; } private static void APUCheckIRQ() { if (apu_irq_enabled) { apu_irq_flag = true; } if (apu_irq_flag) { IRQFlags |= 1; } } private static void CalculateAudioPlaybackValues() { audio_timer_ratio = (double)cpu_speed / (double)MyNesMain.RendererSettings.Audio_Frequency; audio_playback_peek_limit = MyNesMain.RendererSettings.Audio_InternalPeekLimit; audio_samples_count = MyNesMain.RendererSettings.Audio_InternalSamplesCount; audio_playback_amplitude = MyNesMain.RendererSettings.Audio_PlaybackAmplitude; audio_samples = new short[audio_samples_count]; audio_w_pos = 0; audio_samples_added = 0; audio_timer = 0.0; audio_x = (audio_y = 0.0); Tracer.WriteLine("AUDIO: frequency = " + MyNesMain.RendererSettings.Audio_Frequency); Tracer.WriteLine("AUDIO: timer ratio = " + audio_timer_ratio); Tracer.WriteLine("AUDIO: internal samples count = " + audio_samples_count); Tracer.WriteLine("AUDIO: amplitude = " + audio_playback_amplitude); if (MyNesMain.RendererSettings.Audio_EnableFilters) { apu_update_playback_func = APUUpdatePlaybackWithFilters; audio_low_pass_filter_14K = new SoundLowPassFilter(SoundLowPassFilter.GetK((double)cpu_speed / 14000.0, 14000.0)); audio_high_pass_filter_90 = new SoundHighPassFilter(SoundHighPassFilter.GetK((double)cpu_speed / 90.0, 90.0)); audio_high_pass_filter_440 = new SoundHighPassFilter(SoundHighPassFilter.GetK((double)cpu_speed / 440.0, 440.0)); } else { apu_update_playback_func = APUUpdatePlaybackWithoutFilters; } InitializeDACTables(force_intitialize: false); } public static void InitializeDACTables(bool force_intitialize) { if (audio_playback_dac_initialized && !force_intitialize) { return; } int[] array = new int[5]; mix_table = new int[16][][][][]; for (int i = 0; i < 16; i++) { mix_table[i] = new int[16][][][]; for (int j = 0; j < 16; j++) { mix_table[i][j] = new int[16][][]; for (int k = 0; k < 16; k++) { mix_table[i][j][k] = new int[16][]; for (int l = 0; l < 16; l++) { mix_table[i][j][k][l] = new int[128]; for (int m = 0; m < 128; m++) { if (MyNesMain.RendererSettings.Audio_UseDefaultMixer) { double num = 95.88 / (8128.0 / (double)(i + j) + 100.0); double num2 = 159.79 / (1.0 / ((double)k / 8227.0 + (double)l / 12241.0 + (double)m / 22638.0) + 100.0); mix_table[i][j][k][l][m] = (int)Math.Ceiling((num + num2) * audio_playback_amplitude); continue; } GetPrec(i, 255, 2048, out array[0]); GetPrec(j, 255, 2048, out array[1]); GetPrec(l, 255, 2048, out array[2]); GetPrec(k, 255, 2048, out array[3]); GetPrec(m, 255, 2048, out array[4]); array[4] /= 2; int num3 = array[0] + array[1] + array[2] + array[3] + array[4]; num3 /= 5; mix_table[i][j][k][l][m] = num3; } } } } } audio_playback_dac_initialized = true; } private static void APUUpdatePlaybackWithFilters() { if (!SoundEnabled) { return; } audio_x = mix_table[sq1_output][sq2_output][trl_output][nos_output][dmc_output]; if (apu_use_external_sound) { audio_x = (audio_x + mem_board.APUGetSample() * audio_playback_amplitude) / 2.0; } audio_high_pass_filter_90.DoFiltering(audio_x, out audio_y); audio_high_pass_filter_440.DoFiltering(audio_y, out audio_y); audio_low_pass_filter_14K.DoFiltering(audio_y, out audio_y); audio_y_av += audio_y; audio_y_timer += 1.0; audio_timer += 1.0; if (!(audio_timer >= audio_timer_ratio)) { return; } if (audio_y_timer > 0.0) { audio_y = audio_y_av / audio_y_timer; } else { audio_y = 0.0; } audio_y_av = 0.0; audio_y_timer = 0.0; audio_timer -= audio_timer_ratio; if (audio_w_pos < audio_samples_count) { if (audio_y > (double)audio_playback_peek_limit) { audio_y = audio_playback_peek_limit; } if (audio_y < (double)(-audio_playback_peek_limit)) { audio_y = -audio_playback_peek_limit; } audio_samples[audio_w_pos] = (short)audio_y; if (MyNesMain.WaveRecorder.IsRecording) { MyNesMain.WaveRecorder.AddSample((short)audio_y); } audio_w_pos++; audio_samples_added++; } audio_y = 0.0; } private static void APUUpdatePlaybackWithoutFilters() { if (!SoundEnabled) { return; } audio_y = mix_table[sq1_output][sq2_output][trl_output][nos_output][dmc_output] / 2; if (apu_use_external_sound) { audio_y = (audio_y + mem_board.APUGetSample() * audio_playback_amplitude) / 2.0; } audio_y_av += audio_y; audio_y_timer += 1.0; audio_timer += 1.0; if (!(audio_timer >= audio_timer_ratio)) { return; } if (audio_y_timer > 0.0) { audio_y = audio_y_av / audio_y_timer; } else { audio_y = 0.0; } audio_y_av = 0.0; audio_y_timer = 0.0; audio_timer -= audio_timer_ratio; if (audio_w_pos < audio_samples_count) { if (audio_y > (double)audio_playback_peek_limit) { audio_y = audio_playback_peek_limit; } if (audio_y < (double)(-audio_playback_peek_limit)) { audio_y = -audio_playback_peek_limit; } audio_samples[audio_w_pos] = (short)audio_y; if (MyNesMain.WaveRecorder.IsRecording) { MyNesMain.WaveRecorder.AddSample((short)audio_y); } audio_w_pos++; audio_samples_added++; } audio_y = 0.0; } private static void GetPrec(int inVal, int inMax, int outMax, out int val) { val = outMax * inVal / inMax; } private static void APUWriteState(ref BinaryWriter bin) { bin.Write(apu_reg_io_db); bin.Write(apu_reg_io_addr); bin.Write(apu_reg_access_happened); bin.Write(apu_reg_access_w); bin.Write(apu_odd_cycle); bin.Write(apu_irq_enabled); bin.Write(apu_irq_flag); bin.Write(apu_irq_delta_occur); bin.Write(apu_seq_mode); bin.Write(apu_ferq_f); bin.Write(apu_ferq_l); bin.Write(apu_ferq_e); bin.Write(apu_cycle_f); bin.Write(apu_cycle_e); bin.Write(apu_cycle_l); bin.Write(apu_odd_l); bin.Write(apu_cycle_f_t); bin.Write(apu_check_irq); bin.Write(apu_do_env); bin.Write(apu_do_length); SQ1WriteState(ref bin); SQ2WriteState(ref bin); NOSWriteState(ref bin); TRLWriteState(ref bin); DMCWriteState(ref bin); } private static void APUReadState(ref BinaryReader bin) { apu_reg_io_db = bin.ReadByte(); apu_reg_io_addr = bin.ReadByte(); apu_reg_access_happened = bin.ReadBoolean(); apu_reg_access_w = bin.ReadBoolean(); apu_odd_cycle = bin.ReadBoolean(); apu_irq_enabled = bin.ReadBoolean(); apu_irq_flag = bin.ReadBoolean(); apu_irq_delta_occur = bin.ReadBoolean(); apu_seq_mode = bin.ReadBoolean(); apu_ferq_f = bin.ReadInt32(); apu_ferq_l = bin.ReadInt32(); apu_ferq_e = bin.ReadInt32(); apu_cycle_f = bin.ReadInt32(); apu_cycle_e = bin.ReadInt32(); apu_cycle_l = bin.ReadInt32(); apu_odd_l = bin.ReadBoolean(); apu_cycle_f_t = bin.ReadInt32(); apu_check_irq = bin.ReadBoolean(); apu_do_env = bin.ReadBoolean(); apu_do_length = bin.ReadBoolean(); SQ1ReadState(ref bin); SQ2ReadState(ref bin); NOSReadState(ref bin); TRLReadState(ref bin); DMCReadState(ref bin); } private static byte register_pb() { return (byte)((cpu_flag_n ? 128u : 0u) | (cpu_flag_v ? 64u : 0u) | (cpu_flag_d ? 8u : 0u) | (cpu_flag_i ? 4u : 0u) | (cpu_flag_z ? 2u : 0u) | (cpu_flag_c ? 1u : 0u) | 0x30u); } private static void CPUInitialize() { cpu_addressings = new Action[256] { Imp____, IndX_R_, ImA____, IndX_W_, Zpg_R__, Zpg_R__, Zpg_RW_, Zpg_W__, ImA____, Imm____, ImA____, Imm____, Abs_R__, Abs_R__, Abs_RW_, Abs_W__, Imp____, IndY_R_, Imp____, IndY_W_, ZpgX_R_, ZpgX_R_, ZpgX_RW, ZpgX_W_, ImA____, AbsY_R_, ImA____, AbsY_W_, AbsX_R_, AbsX_R_, AbsX_RW, AbsX_W_, Imp____, IndX_R_, ImA____, IndX_W_, Zpg_R__, Zpg_R__, Zpg_RW_, Zpg_W__, ImA____, Imm____, ImA____, Imm____, Abs_R__, Abs_R__, Abs_RW_, Abs_W__, Imp____, IndY_R_, Imp____, IndY_W_, ZpgX_R_, ZpgX_R_, ZpgX_RW, ZpgX_W_, ImA____, AbsY_R_, ImA____, AbsY_W_, AbsX_R_, AbsX_R_, AbsX_RW, AbsX_W_, ImA____, IndX_R_, ImA____, IndX_W_, Zpg_R__, Zpg_R__, Zpg_RW_, Zpg_W__, ImA____, Imm____, ImA____, Imm____, Abs_W__, Abs_R__, Abs_RW_, Abs_W__, Imp____, IndY_R_, Imp____, IndY_W_, ZpgX_R_, ZpgX_R_, ZpgX_RW, ZpgX_W_, ImA____, AbsY_R_, ImA____, AbsY_W_, AbsX_R_, AbsX_R_, AbsX_RW, AbsX_W_, ImA____, IndX_R_, ImA____, IndX_W_, Zpg_R__, Zpg_R__, Zpg_RW_, Zpg_W__, ImA____, Imm____, ImA____, Imm____, Imp____, Abs_R__, Abs_RW_, Abs_W__, Imp____, IndY_R_, Imp____, IndY_W_, ZpgX_R_, ZpgX_R_, ZpgX_RW, ZpgX_W_, ImA____, AbsY_R_, ImA____, AbsY_W_, AbsX_R_, AbsX_R_, AbsX_RW, AbsX_W_, Imm____, IndX_W_, Imm____, IndX_W_, Zpg_W__, Zpg_W__, Zpg_W__, Zpg_W__, ImA____, Imm____, ImA____, Imm____, Abs_W__, Abs_W__, Abs_W__, Abs_W__, Imp____, IndY_W_, Imp____, IndY_W_, ZpgX_W_, ZpgX_W_, ZpgY_W_, ZpgY_W_, ImA____, AbsY_W_, ImA____, AbsY_W_, Abs_W__, AbsX_W_, Abs_W__, AbsY_W_, Imm____, IndX_R_, Imm____, IndX_R_, Zpg_R__, Zpg_R__, Zpg_R__, Zpg_R__, ImA____, Imm____, ImA____, Imm____, Abs_R__, Abs_R__, Abs_R__, Abs_R__, Imp____, IndY_R_, Imp____, IndY_R_, ZpgX_R_, ZpgX_R_, ZpgY_R_, ZpgY_R_, ImA____, AbsY_R_, ImA____, AbsY_R_, AbsX_R_, AbsX_R_, AbsY_R_, AbsY_R_, Imm____, IndX_R_, Imm____, IndX_R_, Zpg_R__, Zpg_R__, Zpg_RW_, Zpg_R__, ImA____, Imm____, ImA____, Imm____, Abs_R__, Abs_R__, Abs_RW_, Abs_R__, Imp____, IndY_R_, Imp____, IndY_RW, ZpgX_R_, ZpgX_R_, ZpgX_RW, ZpgX_RW, ImA____, AbsY_R_, ImA____, AbsY_RW, AbsX_R_, AbsX_R_, AbsX_RW, AbsX_RW, Imm____, IndX_R_, Imm____, IndX_W_, Zpg_R__, Zpg_R__, Zpg_RW_, Zpg_W__, ImA____, Imm____, ImA____, Imm____, Abs_R__, Abs_R__, Abs_RW_, Abs_W__, Imp____, IndY_R_, Imp____, IndY_W_, ZpgX_R_, ZpgX_R_, ZpgX_RW, ZpgX_W_, ImA____, AbsY_R_, ImA____, AbsY_W_, AbsX_R_, AbsX_R_, AbsX_RW, AbsX_W_ }; cpu_instructions = new Action[256] { BRK__, ORA__, NOP__, SLO__, NOP__, ORA__, ASL_M, SLO__, PHP__, ORA__, ASL_A, ANC__, NOP__, ORA__, ASL_M, SLO__, BPL__, ORA__, NOP__, SLO__, NOP__, ORA__, ASL_M, SLO__, CLC__, ORA__, NOP__, SLO__, NOP__, ORA__, ASL_M, SLO__, JSR__, AND__, NOP__, RLA__, BIT__, AND__, ROL_M, RLA__, PLP__, AND__, ROL_A, ANC__, BIT__, AND__, ROL_M, RLA__, BMI__, AND__, NOP__, RLA__, NOP__, AND__, ROL_M, RLA__, SEC__, AND__, NOP__, RLA__, NOP__, AND__, ROL_M, RLA__, RTI__, EOR__, NOP__, SRE__, NOP__, EOR__, LSR_M, SRE__, PHA__, EOR__, LSR_A, ALR__, JMP__, EOR__, LSR_M, SRE__, BVC__, EOR__, NOP__, SRE__, NOP__, EOR__, LSR_M, SRE__, CLI__, EOR__, NOP__, SRE__, NOP__, EOR__, LSR_M, SRE__, RTS__, ADC__, NOP__, RRA__, NOP__, ADC__, ROR_M, RRA__, PLA__, ADC__, ROR_A, ARR__, JMP_I, ADC__, ROR_M, RRA__, BVS__, ADC__, NOP__, RRA__, NOP__, ADC__, ROR_M, RRA__, SEI__, ADC__, NOP__, RRA__, NOP__, ADC__, ROR_M, RRA__, NOP__, STA__, NOP__, SAX__, STY__, STA__, STX__, SAX__, DEY__, NOP__, TXA__, XAA__, STY__, STA__, STX__, SAX__, BCC__, STA__, NOP__, AHX__, STY__, STA__, STX__, SAX__, TYA__, STA__, TXS__, XAS__, SHY__, STA__, SHX__, AHX__, LDY__, LDA__, LDX__, LAX__, LDY__, LDA__, LDX__, LAX__, TAY__, LDA__, TAX__, LAX__, LDY__, LDA__, LDX__, LAX__, BCS__, LDA__, NOP__, LAX__, LDY__, LDA__, LDX__, LAX__, CLV__, LDA__, TSX__, LAR__, LDY__, LDA__, LDX__, LAX__, CPY__, CMP__, NOP__, DCP__, CPY__, CMP__, DEC__, DCP__, INY__, CMP__, DEX__, AXS__, CPY__, CMP__, DEC__, DCP__, BNE__, CMP__, NOP__, DCP__, NOP__, CMP__, DEC__, DCP__, CLD__, CMP__, NOP__, DCP__, NOP__, CMP__, DEC__, DCP__, CPX__, SBC__, NOP__, ISC__, CPX__, SBC__, INC__, ISC__, INX__, SBC__, NOP__, SBC__, CPX__, SBC__, INC__, ISC__, BEQ__, SBC__, NOP__, ISC__, NOP__, SBC__, INC__, ISC__, SED__, SBC__, NOP__, ISC__, NOP__, SBC__, INC__, ISC__ }; } private static void CPUClock() { Read(ref cpu_reg_pc.v, out cpu_opcode); cpu_reg_pc.v++; cpu_addressings[cpu_opcode](); cpu_instructions[cpu_opcode](); if (CPU_IRQ_PIN || CPU_NMI_PIN) { Read(ref cpu_reg_pc.v, out cpu_dummy); Read(ref cpu_reg_pc.v, out cpu_dummy); Interrupt(); } } private static void CPUHardReset() { cpu_reg_a = 0; cpu_reg_x = 0; cpu_reg_y = 0; cpu_reg_sp.l = 253; cpu_reg_sp.h = 1; ushort addr = 65532; mem_board.ReadPRG(ref addr, out cpu_reg_pc.l); addr++; mem_board.ReadPRG(ref addr, out cpu_reg_pc.h); register_p = 0; cpu_flag_i = true; cpu_reg_ea.v = 0; cpu_opcode = 0; CPU_IRQ_PIN = false; CPU_NMI_PIN = false; cpu_suspend_nmi = false; cpu_suspend_irq = false; IRQFlags = 0; } private static void CPUSoftReset() { cpu_flag_i = true; cpu_reg_sp.v -= 3; ushort addr = 65532; Read(ref addr, out cpu_reg_pc.l); addr++; Read(ref addr, out cpu_reg_pc.h); } private static void Imp____() { } private static void IndX_R_() { temp_add.h = 0; Read(ref cpu_reg_pc.v, out temp_add.l); cpu_reg_pc.v++; Read(ref temp_add.v, out cpu_dummy); temp_add.l += cpu_reg_x; Read(ref temp_add.v, out cpu_reg_ea.l); temp_add.l++; Read(ref temp_add.v, out cpu_reg_ea.h); Read(ref cpu_reg_ea.v, out cpu_m); } private static void IndX_W_() { temp_add.h = 0; Read(ref cpu_reg_pc.v, out temp_add.l); cpu_reg_pc.v++; Read(ref temp_add.v, out cpu_dummy); temp_add.l += cpu_reg_x; Read(ref temp_add.v, out cpu_reg_ea.l); temp_add.l++; Read(ref temp_add.v, out cpu_reg_ea.h); } private static void IndX_RW() { temp_add.h = 0; Read(ref cpu_reg_pc.v, out temp_add.l); cpu_reg_pc.v++; Read(ref temp_add.v, out cpu_dummy); temp_add.l += cpu_reg_x; Read(ref temp_add.v, out cpu_reg_ea.l); temp_add.l++; Read(ref temp_add.v, out cpu_reg_ea.h); Read(ref cpu_reg_ea.v, out cpu_m); } private static void IndY_R_() { temp_add.h = 0; Read(ref cpu_reg_pc.v, out temp_add.l); cpu_reg_pc.v++; Read(ref temp_add.v, out cpu_reg_ea.l); temp_add.l++; Read(ref temp_add.v, out cpu_reg_ea.h); cpu_reg_ea.l += cpu_reg_y; Read(ref cpu_reg_ea.v, out cpu_m); if (cpu_reg_ea.l < cpu_reg_y) { cpu_reg_ea.h++; Read(ref cpu_reg_ea.v, out cpu_m); } } private static void IndY_W_() { temp_add.h = 0; Read(ref cpu_reg_pc.v, out temp_add.l); cpu_reg_pc.v++; Read(ref temp_add.v, out cpu_reg_ea.l); temp_add.l++; Read(ref temp_add.v, out cpu_reg_ea.h); cpu_reg_ea.l += cpu_reg_y; Read(ref cpu_reg_ea.v, out cpu_m); if (cpu_reg_ea.l < cpu_reg_y) { cpu_reg_ea.h++; } } private static void IndY_RW() { temp_add.h = 0; Read(ref cpu_reg_pc.v, out temp_add.l); cpu_reg_pc.v++; Read(ref temp_add.v, out cpu_reg_ea.l); temp_add.l++; Read(ref temp_add.v, out cpu_reg_ea.h); cpu_reg_ea.l += cpu_reg_y; Read(ref cpu_reg_ea.v, out cpu_dummy); if (cpu_reg_ea.l < cpu_reg_y) { cpu_reg_ea.h++; } Read(ref cpu_reg_ea.v, out cpu_m); } private static void Zpg_R__() { cpu_reg_ea.h = 0; Read(ref cpu_reg_pc.v, out cpu_reg_ea.l); cpu_reg_pc.v++; Read(ref cpu_reg_ea.v, out cpu_m); } private static void Zpg_W__() { cpu_reg_ea.h = 0; Read(ref cpu_reg_pc.v, out cpu_reg_ea.l); cpu_reg_pc.v++; } private static void Zpg_RW_() { cpu_reg_ea.h = 0; Read(ref cpu_reg_pc.v, out cpu_reg_ea.l); cpu_reg_pc.v++; Read(ref cpu_reg_ea.v, out cpu_m); } private static void ZpgX_R_() { cpu_reg_ea.h = 0; Read(ref cpu_reg_pc.v, out cpu_reg_ea.l); cpu_reg_pc.v++; Read(ref cpu_reg_ea.v, out cpu_dummy); cpu_reg_ea.l += cpu_reg_x; Read(ref cpu_reg_ea.v, out cpu_m); } private static void ZpgX_W_() { cpu_reg_ea.h = 0; Read(ref cpu_reg_pc.v, out cpu_reg_ea.l); cpu_reg_pc.v++; Read(ref cpu_reg_ea.v, out cpu_dummy); cpu_reg_ea.l += cpu_reg_x; } private static void ZpgX_RW() { cpu_reg_ea.h = 0; Read(ref cpu_reg_pc.v, out cpu_reg_ea.l); cpu_reg_pc.v++; Read(ref cpu_reg_ea.v, out cpu_dummy); cpu_reg_ea.l += cpu_reg_x; Read(ref cpu_reg_ea.v, out cpu_m); } private static void ZpgY_R_() { cpu_reg_ea.h = 0; Read(ref cpu_reg_pc.v, out cpu_reg_ea.l); cpu_reg_pc.v++; Read(ref cpu_reg_ea.v, out cpu_dummy); cpu_reg_ea.l += cpu_reg_y; Read(ref cpu_reg_ea.v, out cpu_m); } private static void ZpgY_W_() { cpu_reg_ea.h = 0; Read(ref cpu_reg_pc.v, out cpu_reg_ea.l); cpu_reg_pc.v++; Read(ref cpu_reg_ea.v, out cpu_dummy); cpu_reg_ea.l += cpu_reg_y; } private static void ZpgY_RW() { cpu_reg_ea.h = 0; Read(ref cpu_reg_pc.v, out cpu_reg_ea.l); cpu_reg_pc.v++; Read(ref cpu_reg_ea.v, out cpu_dummy); cpu_reg_ea.l += cpu_reg_y; Read(ref cpu_reg_ea.v, out cpu_m); } private static void Imm____() { Read(ref cpu_reg_pc.v, out cpu_m); cpu_reg_pc.v++; } private static void ImA____() { Read(ref cpu_reg_pc.v, out cpu_dummy); } private static void Abs_R__() { Read(ref cpu_reg_pc.v, out cpu_reg_ea.l); cpu_reg_pc.v++; Read(ref cpu_reg_pc.v, out cpu_reg_ea.h); cpu_reg_pc.v++; Read(ref cpu_reg_ea.v, out cpu_m); } private static void Abs_W__() { Read(ref cpu_reg_pc.v, out cpu_reg_ea.l); cpu_reg_pc.v++; Read(ref cpu_reg_pc.v, out cpu_reg_ea.h); cpu_reg_pc.v++; } private static void Abs_RW_() { Read(ref cpu_reg_pc.v, out cpu_reg_ea.l); cpu_reg_pc.v++; Read(ref cpu_reg_pc.v, out cpu_reg_ea.h); cpu_reg_pc.v++; Read(ref cpu_reg_ea.v, out cpu_m); } private static void AbsX_R_() { Read(ref cpu_reg_pc.v, out cpu_reg_ea.l); cpu_reg_pc.v++; Read(ref cpu_reg_pc.v, out cpu_reg_ea.h); cpu_reg_pc.v++; cpu_reg_ea.l += cpu_reg_x; Read(ref cpu_reg_ea.v, out cpu_m); if (cpu_reg_ea.l < cpu_reg_x) { cpu_reg_ea.h++; Read(ref cpu_reg_ea.v, out cpu_m); } } private static void AbsX_W_() { Read(ref cpu_reg_pc.v, out cpu_reg_ea.l); cpu_reg_pc.v++; Read(ref cpu_reg_pc.v, out cpu_reg_ea.h); cpu_reg_pc.v++; cpu_reg_ea.l += cpu_reg_x; Read(ref cpu_reg_ea.v, out cpu_m); if (cpu_reg_ea.l < cpu_reg_x) { cpu_reg_ea.h++; } } private static void AbsX_RW() { Read(ref cpu_reg_pc.v, out cpu_reg_ea.l); cpu_reg_pc.v++; Read(ref cpu_reg_pc.v, out cpu_reg_ea.h); cpu_reg_pc.v++; cpu_reg_ea.l += cpu_reg_x; Read(ref cpu_reg_ea.v, out cpu_dummy); if (cpu_reg_ea.l < cpu_reg_x) { cpu_reg_ea.h++; } Read(ref cpu_reg_ea.v, out cpu_m); } private static void AbsY_R_() { Read(ref cpu_reg_pc.v, out cpu_reg_ea.l); cpu_reg_pc.v++; Read(ref cpu_reg_pc.v, out cpu_reg_ea.h); cpu_reg_pc.v++; cpu_reg_ea.l += cpu_reg_y; Read(ref cpu_reg_ea.v, out cpu_m); if (cpu_reg_ea.l < cpu_reg_y) { cpu_reg_ea.h++; Read(ref cpu_reg_ea.v, out cpu_m); } } private static void AbsY_W_() { Read(ref cpu_reg_pc.v, out cpu_reg_ea.l); cpu_reg_pc.v++; Read(ref cpu_reg_pc.v, out cpu_reg_ea.h); cpu_reg_pc.v++; cpu_reg_ea.l += cpu_reg_y; Read(ref cpu_reg_ea.v, out cpu_m); if (cpu_reg_ea.l < cpu_reg_y) { cpu_reg_ea.h++; } } private static void AbsY_RW() { Read(ref cpu_reg_pc.v, out cpu_reg_ea.l); cpu_reg_pc.v++; Read(ref cpu_reg_pc.v, out cpu_reg_ea.h); cpu_reg_pc.v++; cpu_reg_ea.l += cpu_reg_y; Read(ref cpu_reg_ea.v, out cpu_m); if (cpu_reg_ea.l < cpu_reg_y) { cpu_reg_ea.h++; } Read(ref cpu_reg_ea.v, out cpu_m); } private static void Interrupt() { Push(ref cpu_reg_pc.h); Push(ref cpu_reg_pc.l); cpu_dummy = ((cpu_opcode == 0) ? register_pb() : register_p); Push(ref cpu_dummy); temp_add.v = InterruptVector; cpu_suspend_nmi = true; cpu_flag_i = true; CPU_NMI_PIN = false; Read(ref temp_add.v, out cpu_reg_pc.l); temp_add.v++; Read(ref temp_add.v, out cpu_reg_pc.h); cpu_suspend_nmi = false; } private static void Branch(ref bool condition) { Read(ref cpu_reg_pc.v, out cpu_byte_temp); cpu_reg_pc.v++; if (!condition) { return; } cpu_suspend_irq = true; Read(ref cpu_reg_pc.v, out cpu_dummy); cpu_reg_pc.l += cpu_byte_temp; cpu_suspend_irq = false; if (cpu_byte_temp >= 128) { if (cpu_reg_pc.l >= cpu_byte_temp) { Read(ref cpu_reg_pc.v, out cpu_dummy); cpu_reg_pc.h--; } } else if (cpu_reg_pc.l < cpu_byte_temp) { Read(ref cpu_reg_pc.v, out cpu_dummy); cpu_reg_pc.h++; } } private static void Push(ref byte val) { Write(ref cpu_reg_sp.v, ref val); cpu_reg_sp.l--; } private static void Pull(out byte val) { cpu_reg_sp.l++; Read(ref cpu_reg_sp.v, out val); } private static void ADC__() { cpu_int_temp = cpu_reg_a + cpu_m + (cpu_flag_c ? 1 : 0); cpu_flag_v = ((cpu_int_temp ^ cpu_reg_a) & (cpu_int_temp ^ cpu_m) & 0x80) != 0; cpu_flag_n = (cpu_int_temp & 0x80) != 0; cpu_flag_z = (cpu_int_temp & 0xFF) == 0; cpu_flag_c = cpu_int_temp >> 8 != 0; cpu_reg_a = (byte)((uint)cpu_int_temp & 0xFFu); } private static void AHX__() { cpu_byte_temp = (byte)((uint)(cpu_reg_a & cpu_reg_x) & 7u); Write(ref cpu_reg_ea.v, ref cpu_byte_temp); } private static void ALR__() { cpu_reg_a &= cpu_m; cpu_flag_c = (cpu_reg_a & 1) != 0; cpu_reg_a >>= 1; cpu_flag_n = (cpu_reg_a & 0x80) != 0; cpu_flag_z = cpu_reg_a == 0; } private static void ANC__() { cpu_reg_a &= cpu_m; cpu_flag_n = (cpu_reg_a & 0x80) != 0; cpu_flag_z = cpu_reg_a == 0; cpu_flag_c = (cpu_reg_a & 0x80) != 0; } private static void AND__() { cpu_reg_a &= cpu_m; cpu_flag_n = (cpu_reg_a & 0x80) == 128; cpu_flag_z = cpu_reg_a == 0; } private static void ARR__() { cpu_reg_a = (byte)((uint)((cpu_m & cpu_reg_a) >> 1) | (cpu_flag_c ? 128u : 0u)); cpu_flag_z = (cpu_reg_a & 0xFF) == 0; cpu_flag_n = (cpu_reg_a & 0x80) != 0; cpu_flag_c = (cpu_reg_a & 0x40) != 0; cpu_flag_v = (((cpu_reg_a << 1) ^ cpu_reg_a) & 0x40) != 0; } private static void AXS__() { cpu_int_temp = (cpu_reg_a & cpu_reg_x) - cpu_m; cpu_flag_n = (cpu_int_temp & 0x80) != 0; cpu_flag_z = (cpu_int_temp & 0xFF) == 0; cpu_flag_c = ~cpu_int_temp >> 8 != 0; cpu_reg_x = (byte)((uint)cpu_int_temp & 0xFFu); } private static void ASL_M() { cpu_flag_c = (cpu_m & 0x80) == 128; Write(ref cpu_reg_ea.v, ref cpu_m); cpu_m = (byte)((uint)(cpu_m << 1) & 0xFEu); Write(ref cpu_reg_ea.v, ref cpu_m); cpu_flag_n = (cpu_m & 0x80) == 128; cpu_flag_z = cpu_m == 0; } private static void ASL_A() { cpu_flag_c = (cpu_reg_a & 0x80) == 128; cpu_reg_a = (byte)((uint)(cpu_reg_a << 1) & 0xFEu); cpu_flag_n = (cpu_reg_a & 0x80) == 128; cpu_flag_z = cpu_reg_a == 0; } private static void BCC__() { cpu_bool_tmp = !cpu_flag_c; Branch(ref cpu_bool_tmp); } private static void BCS__() { Branch(ref cpu_flag_c); } private static void BEQ__() { Branch(ref cpu_flag_z); } private static void BIT__() { cpu_flag_n = (cpu_m & 0x80) != 0; cpu_flag_v = (cpu_m & 0x40) != 0; cpu_flag_z = (cpu_m & cpu_reg_a) == 0; } private static void BRK__() { Read(ref cpu_reg_pc.v, out cpu_dummy); cpu_reg_pc.v++; Interrupt(); } private static void BPL__() { cpu_bool_tmp = !cpu_flag_n; Branch(ref cpu_bool_tmp); } private static void BNE__() { cpu_bool_tmp = !cpu_flag_z; Branch(ref cpu_bool_tmp); } private static void BMI__() { Branch(ref cpu_flag_n); } private static void BVC__() { cpu_bool_tmp = !cpu_flag_v; Branch(ref cpu_bool_tmp); } private static void BVS__() { Branch(ref cpu_flag_v); } private static void SED__() { cpu_flag_d = true; } private static void CLC__() { cpu_flag_c = false; } private static void CLD__() { cpu_flag_d = false; } private static void CLV__() { cpu_flag_v = false; } private static void CMP__() { cpu_int_temp = cpu_reg_a - cpu_m; cpu_flag_n = (cpu_int_temp & 0x80) == 128; cpu_flag_c = cpu_reg_a >= cpu_m; cpu_flag_z = cpu_int_temp == 0; } private static void CPX__() { cpu_int_temp = cpu_reg_x - cpu_m; cpu_flag_n = (cpu_int_temp & 0x80) == 128; cpu_flag_c = cpu_reg_x >= cpu_m; cpu_flag_z = cpu_int_temp == 0; } private static void CPY__() { cpu_int_temp = cpu_reg_y - cpu_m; cpu_flag_n = (cpu_int_temp & 0x80) == 128; cpu_flag_c = cpu_reg_y >= cpu_m; cpu_flag_z = cpu_int_temp == 0; } private static void CLI__() { cpu_flag_i = false; } private static void DCP__() { Write(ref cpu_reg_ea.v, ref cpu_m); cpu_m--; Write(ref cpu_reg_ea.v, ref cpu_m); cpu_int_temp = cpu_reg_a - cpu_m; cpu_flag_n = (cpu_int_temp & 0x80) != 0; cpu_flag_z = cpu_int_temp == 0; cpu_flag_c = ~cpu_int_temp >> 8 != 0; } private static void DEC__() { Write(ref cpu_reg_ea.v, ref cpu_m); cpu_m--; Write(ref cpu_reg_ea.v, ref cpu_m); cpu_flag_n = (cpu_m & 0x80) == 128; cpu_flag_z = cpu_m == 0; } private static void DEY__() { cpu_reg_y--; cpu_flag_z = cpu_reg_y == 0; cpu_flag_n = (cpu_reg_y & 0x80) == 128; } private static void DEX__() { cpu_reg_x--; cpu_flag_z = cpu_reg_x == 0; cpu_flag_n = (cpu_reg_x & 0x80) == 128; } private static void EOR__() { cpu_reg_a ^= cpu_m; cpu_flag_n = (cpu_reg_a & 0x80) == 128; cpu_flag_z = cpu_reg_a == 0; } private static void INC__() { Write(ref cpu_reg_ea.v, ref cpu_m); cpu_m++; Write(ref cpu_reg_ea.v, ref cpu_m); cpu_flag_n = (cpu_m & 0x80) == 128; cpu_flag_z = cpu_m == 0; } private static void INX__() { cpu_reg_x++; cpu_flag_z = cpu_reg_x == 0; cpu_flag_n = (cpu_reg_x & 0x80) == 128; } private static void INY__() { cpu_reg_y++; cpu_flag_n = (cpu_reg_y & 0x80) == 128; cpu_flag_z = cpu_reg_y == 0; } private static void ISC__() { Read(ref cpu_reg_ea.v, out cpu_byte_temp); Write(ref cpu_reg_ea.v, ref cpu_byte_temp); cpu_byte_temp++; Write(ref cpu_reg_ea.v, ref cpu_byte_temp); cpu_int_temp = cpu_byte_temp ^ 0xFF; cpu_int_temp1 = cpu_reg_a + cpu_int_temp + (cpu_flag_c ? 1 : 0); cpu_flag_n = (cpu_int_temp1 & 0x80) != 0; cpu_flag_v = ((cpu_int_temp1 ^ cpu_reg_a) & (cpu_int_temp1 ^ cpu_int_temp) & 0x80) != 0; cpu_flag_z = (cpu_int_temp1 & 0xFF) == 0; cpu_flag_c = cpu_int_temp1 >> 8 != 0; cpu_reg_a = (byte)((uint)cpu_int_temp1 & 0xFFu); } private static void JMP__() { cpu_reg_pc.v = cpu_reg_ea.v; } private static void JMP_I() { Read(ref cpu_reg_pc.v, out cpu_reg_ea.l); cpu_reg_pc.v++; Read(ref cpu_reg_pc.v, out cpu_reg_ea.h); Read(ref cpu_reg_ea.v, out cpu_reg_pc.l); cpu_reg_ea.l++; Read(ref cpu_reg_ea.v, out cpu_reg_pc.h); } private static void JSR__() { Read(ref cpu_reg_pc.v, out cpu_reg_ea.l); cpu_reg_pc.v++; Write(ref cpu_reg_sp.v, ref cpu_reg_ea.l); Push(ref cpu_reg_pc.h); Push(ref cpu_reg_pc.l); Read(ref cpu_reg_pc.v, out cpu_reg_ea.h); cpu_reg_pc.v = cpu_reg_ea.v; } private static void LAR__() { cpu_reg_sp.l &= cpu_m; cpu_reg_a = cpu_reg_sp.l; cpu_reg_x = cpu_reg_sp.l; cpu_flag_n = (cpu_reg_sp.l & 0x80) != 0; cpu_flag_z = (cpu_reg_sp.l & 0xFF) == 0; } private static void LAX__() { cpu_reg_x = (cpu_reg_a = cpu_m); cpu_flag_n = (cpu_reg_x & 0x80) != 0; cpu_flag_z = (cpu_reg_x & 0xFF) == 0; } private static void LDA__() { cpu_reg_a = cpu_m; cpu_flag_n = (cpu_reg_a & 0x80) == 128; cpu_flag_z = cpu_reg_a == 0; } private static void LDX__() { cpu_reg_x = cpu_m; cpu_flag_n = (cpu_reg_x & 0x80) == 128; cpu_flag_z = cpu_reg_x == 0; } private static void LDY__() { cpu_reg_y = cpu_m; cpu_flag_n = (cpu_reg_y & 0x80) == 128; cpu_flag_z = cpu_reg_y == 0; } private static void LSR_A() { cpu_flag_c = (cpu_reg_a & 1) == 1; cpu_reg_a >>= 1; cpu_flag_z = cpu_reg_a == 0; cpu_flag_n = (cpu_reg_a & 0x80) != 0; } private static void LSR_M() { cpu_flag_c = (cpu_m & 1) == 1; Write(ref cpu_reg_ea.v, ref cpu_m); cpu_m >>= 1; Write(ref cpu_reg_ea.v, ref cpu_m); cpu_flag_z = cpu_m == 0; cpu_flag_n = (cpu_m & 0x80) != 0; } private static void NOP__() { } private static void ORA__() { cpu_reg_a |= cpu_m; cpu_flag_n = (cpu_reg_a & 0x80) == 128; cpu_flag_z = cpu_reg_a == 0; } private static void PHA__() { Push(ref cpu_reg_a); } private static void PHP__() { cpu_dummy = register_pb(); Push(ref cpu_dummy); } private static void PLA__() { Read(ref cpu_reg_sp.v, out cpu_dummy); Pull(out cpu_reg_a); cpu_flag_n = (cpu_reg_a & 0x80) == 128; cpu_flag_z = cpu_reg_a == 0; } private static void PLP__() { Read(ref cpu_reg_sp.v, out cpu_dummy); Pull(out cpu_dummy); register_p = cpu_dummy; } private static void RLA__() { Read(ref cpu_reg_ea.v, out cpu_byte_temp); Write(ref cpu_reg_ea.v, ref cpu_byte_temp); cpu_dummy = (byte)((uint)(cpu_byte_temp << 1) | (cpu_flag_c ? 1u : 0u)); Write(ref cpu_reg_ea.v, ref cpu_dummy); cpu_flag_n = (cpu_dummy & 0x80) != 0; cpu_flag_z = (cpu_dummy & 0xFF) == 0; cpu_flag_c = (cpu_byte_temp & 0x80) != 0; cpu_reg_a &= cpu_dummy; cpu_flag_n = (cpu_reg_a & 0x80) != 0; cpu_flag_z = (cpu_reg_a & 0xFF) == 0; } private static void ROL_A() { cpu_byte_temp = (byte)((uint)(cpu_reg_a << 1) | (cpu_flag_c ? 1u : 0u)); cpu_flag_n = (cpu_byte_temp & 0x80) != 0; cpu_flag_z = (cpu_byte_temp & 0xFF) == 0; cpu_flag_c = (cpu_reg_a & 0x80) != 0; cpu_reg_a = cpu_byte_temp; } private static void ROL_M() { Write(ref cpu_reg_ea.v, ref cpu_m); cpu_byte_temp = (byte)((uint)(cpu_m << 1) | (cpu_flag_c ? 1u : 0u)); Write(ref cpu_reg_ea.v, ref cpu_byte_temp); cpu_flag_n = (cpu_byte_temp & 0x80) != 0; cpu_flag_z = (cpu_byte_temp & 0xFF) == 0; cpu_flag_c = (cpu_m & 0x80) != 0; } private static void ROR_A() { cpu_byte_temp = (byte)((uint)(cpu_reg_a >> 1) | (cpu_flag_c ? 128u : 0u)); cpu_flag_n = (cpu_byte_temp & 0x80) != 0; cpu_flag_z = (cpu_byte_temp & 0xFF) == 0; cpu_flag_c = (cpu_reg_a & 1) != 0; cpu_reg_a = cpu_byte_temp; } private static void ROR_M() { Write(ref cpu_reg_ea.v, ref cpu_m); cpu_byte_temp = (byte)((uint)(cpu_m >> 1) | (cpu_flag_c ? 128u : 0u)); Write(ref cpu_reg_ea.v, ref cpu_byte_temp); cpu_flag_n = (cpu_byte_temp & 0x80) != 0; cpu_flag_z = (cpu_byte_temp & 0xFF) == 0; cpu_flag_c = (cpu_m & 1) != 0; } private static void RRA__() { Read(ref cpu_reg_ea.v, out cpu_byte_temp); Write(ref cpu_reg_ea.v, ref cpu_byte_temp); cpu_dummy = (byte)((uint)(cpu_byte_temp >> 1) | (cpu_flag_c ? 128u : 0u)); Write(ref cpu_reg_ea.v, ref cpu_dummy); cpu_flag_n = (cpu_dummy & 0x80) != 0; cpu_flag_z = (cpu_dummy & 0xFF) == 0; cpu_flag_c = (cpu_byte_temp & 1) != 0; cpu_byte_temp = cpu_dummy; cpu_int_temp = cpu_reg_a + cpu_byte_temp + (cpu_flag_c ? 1 : 0); cpu_flag_n = (cpu_int_temp & 0x80) != 0; cpu_flag_v = ((cpu_int_temp ^ cpu_reg_a) & (cpu_int_temp ^ cpu_byte_temp) & 0x80) != 0; cpu_flag_z = (cpu_int_temp & 0xFF) == 0; cpu_flag_c = cpu_int_temp >> 8 != 0; cpu_reg_a = (byte)cpu_int_temp; } private static void RTI__() { Read(ref cpu_reg_sp.v, out cpu_dummy); Pull(out cpu_dummy); register_p = cpu_dummy; Pull(out cpu_reg_pc.l); Pull(out cpu_reg_pc.h); } private static void RTS__() { Read(ref cpu_reg_sp.v, out cpu_dummy); Pull(out cpu_reg_pc.l); Pull(out cpu_reg_pc.h); cpu_reg_pc.v++; Read(ref cpu_reg_pc.v, out cpu_dummy); } private static void SAX__() { cpu_dummy = (byte)(cpu_reg_x & cpu_reg_a); Write(ref cpu_reg_ea.v, ref cpu_dummy); } private static void SBC__() { cpu_m ^= byte.MaxValue; cpu_int_temp = cpu_reg_a + cpu_m + (cpu_flag_c ? 1 : 0); cpu_flag_n = (cpu_int_temp & 0x80) != 0; cpu_flag_v = ((cpu_int_temp ^ cpu_reg_a) & (cpu_int_temp ^ cpu_m) & 0x80) != 0; cpu_flag_z = (cpu_int_temp & 0xFF) == 0; cpu_flag_c = cpu_int_temp >> 8 != 0; cpu_reg_a = (byte)cpu_int_temp; } private static void SEC__() { cpu_flag_c = true; } private static void SEI__() { cpu_flag_i = true; } private static void SHX__() { cpu_byte_temp = (byte)(cpu_reg_x & (cpu_reg_ea.h + 1)); Read(ref cpu_reg_ea.v, out cpu_dummy); cpu_reg_ea.l += cpu_reg_y; if (cpu_reg_ea.l < cpu_reg_y) { cpu_reg_ea.h = cpu_byte_temp; } Write(ref cpu_reg_ea.v, ref cpu_byte_temp); } private static void SHY__() { cpu_byte_temp = (byte)(cpu_reg_y & (cpu_reg_ea.h + 1)); Read(ref cpu_reg_ea.v, out cpu_dummy); cpu_reg_ea.l += cpu_reg_x; if (cpu_reg_ea.l < cpu_reg_x) { cpu_reg_ea.h = cpu_byte_temp; } Write(ref cpu_reg_ea.v, ref cpu_byte_temp); } private static void SLO__() { Read(ref cpu_reg_ea.v, out cpu_byte_temp); cpu_flag_c = (cpu_byte_temp & 0x80) != 0; Write(ref cpu_reg_ea.v, ref cpu_byte_temp); cpu_byte_temp <<= 1; Write(ref cpu_reg_ea.v, ref cpu_byte_temp); cpu_flag_n = (cpu_byte_temp & 0x80) != 0; cpu_flag_z = (cpu_byte_temp & 0xFF) == 0; cpu_reg_a |= cpu_byte_temp; cpu_flag_n = (cpu_reg_a & 0x80) != 0; cpu_flag_z = (cpu_reg_a & 0xFF) == 0; } private static void SRE__() { Read(ref cpu_reg_ea.v, out cpu_byte_temp); cpu_flag_c = (cpu_byte_temp & 1) != 0; Write(ref cpu_reg_ea.v, ref cpu_byte_temp); cpu_byte_temp >>= 1; Write(ref cpu_reg_ea.v, ref cpu_byte_temp); cpu_flag_n = (cpu_byte_temp & 0x80) != 0; cpu_flag_z = (cpu_byte_temp & 0xFF) == 0; cpu_reg_a ^= cpu_byte_temp; cpu_flag_n = (cpu_reg_a & 0x80) != 0; cpu_flag_z = (cpu_reg_a & 0xFF) == 0; } private static void STA__() { Write(ref cpu_reg_ea.v, ref cpu_reg_a); } private static void STX__() { Write(ref cpu_reg_ea.v, ref cpu_reg_x); } private static void STY__() { Write(ref cpu_reg_ea.v, ref cpu_reg_y); } private static void TAX__() { cpu_reg_x = cpu_reg_a; cpu_flag_n = (cpu_reg_x & 0x80) == 128; cpu_flag_z = cpu_reg_x == 0; } private static void TAY__() { cpu_reg_y = cpu_reg_a; cpu_flag_n = (cpu_reg_y & 0x80) == 128; cpu_flag_z = cpu_reg_y == 0; } private static void TSX__() { cpu_reg_x = cpu_reg_sp.l; cpu_flag_n = (cpu_reg_x & 0x80) != 0; cpu_flag_z = cpu_reg_x == 0; } private static void TXA__() { cpu_reg_a = cpu_reg_x; cpu_flag_n = (cpu_reg_a & 0x80) == 128; cpu_flag_z = cpu_reg_a == 0; } private static void TXS__() { cpu_reg_sp.l = cpu_reg_x; } private static void TYA__() { cpu_reg_a = cpu_reg_y; cpu_flag_n = (cpu_reg_a & 0x80) == 128; cpu_flag_z = cpu_reg_a == 0; } private static void XAA__() { cpu_reg_a = (byte)(cpu_reg_x & cpu_m); cpu_flag_n = (cpu_reg_a & 0x80) != 0; cpu_flag_z = (cpu_reg_a & 0xFF) == 0; } private static void XAS__() { cpu_reg_sp.l = (byte)(cpu_reg_a & cpu_reg_x); Write(ref cpu_reg_ea.v, ref cpu_reg_sp.l); } private static void CPUWriteState(ref BinaryWriter bin) { bin.Write(cpu_reg_pc.v); bin.Write(cpu_reg_sp.v); bin.Write(cpu_reg_ea.v); bin.Write(cpu_reg_a); bin.Write(cpu_reg_x); bin.Write(cpu_reg_y); bin.Write(cpu_flag_n); bin.Write(cpu_flag_v); bin.Write(cpu_flag_d); bin.Write(cpu_flag_i); bin.Write(cpu_flag_z); bin.Write(cpu_flag_c); bin.Write(cpu_m); bin.Write(cpu_opcode); bin.Write(cpu_byte_temp); bin.Write(cpu_int_temp); bin.Write(cpu_int_temp1); bin.Write(cpu_dummy); bin.Write(cpu_bool_tmp); bin.Write(temp_add.v); bin.Write(CPU_IRQ_PIN); bin.Write(CPU_NMI_PIN); bin.Write(cpu_suspend_nmi); bin.Write(cpu_suspend_irq); } private static void CPUReadState(ref BinaryReader bin) { cpu_reg_pc.v = bin.ReadUInt16(); cpu_reg_sp.v = bin.ReadUInt16(); cpu_reg_ea.v = bin.ReadUInt16(); cpu_reg_a = bin.ReadByte(); cpu_reg_x = bin.ReadByte(); cpu_reg_y = bin.ReadByte(); cpu_flag_n = bin.ReadBoolean(); cpu_flag_v = bin.ReadBoolean(); cpu_flag_d = bin.ReadBoolean(); cpu_flag_i = bin.ReadBoolean(); cpu_flag_z = bin.ReadBoolean(); cpu_flag_c = bin.ReadBoolean(); cpu_m = bin.ReadByte(); cpu_opcode = bin.ReadByte(); cpu_byte_temp = bin.ReadByte(); cpu_int_temp = bin.ReadInt32(); cpu_int_temp1 = bin.ReadInt32(); cpu_dummy = bin.ReadByte(); cpu_bool_tmp = bin.ReadBoolean(); temp_add.v = bin.ReadUInt16(); CPU_IRQ_PIN = bin.ReadBoolean(); CPU_NMI_PIN = bin.ReadBoolean(); cpu_suspend_nmi = bin.ReadBoolean(); cpu_suspend_irq = bin.ReadBoolean(); } private static void DMAHardReset() { dma_DMCDMAWaitCycles = 0; dma_OAMDMAWaitCycles = 0; dma_isOamDma = false; dma_oamdma_i = 0; dma_DMCOn = false; dma_OAMOn = false; dma_DMC_occurring = false; dma_OAM_occurring = false; dma_OAMFinishCounter = 0; dma_Oamaddress = 0; dma_OAMCYCLE = 0; dma_latch = 0; reg_2004 = 8196; } private static void DMASoftReset() { dma_DMCDMAWaitCycles = 0; dma_OAMDMAWaitCycles = 0; dma_isOamDma = false; dma_oamdma_i = 0; dma_DMCOn = false; dma_OAMOn = false; dma_DMC_occurring = false; dma_OAM_occurring = false; dma_OAMFinishCounter = 0; dma_Oamaddress = 0; dma_OAMCYCLE = 0; dma_latch = 0; } internal static void AssertDMCDMA() { if (dma_OAM_occurring) { if (dma_OAMCYCLE < 508) { dma_DMCDMAWaitCycles = (BUS_RW ? 1 : 0); } else { dma_DMCDMAWaitCycles = 4 - (512 - dma_OAMCYCLE); } } else { if (dma_DMC_occurring) { return; } dma_DMCDMAWaitCycles = (BUS_RW ? 3 : 2); if (dma_OAMFinishCounter == 3) { dma_DMCDMAWaitCycles++; } } dma_isOamDma = false; dma_DMCOn = true; } private static void AssertOAMDMA() { if (!dma_OAM_occurring) { dma_OAMDMAWaitCycles = (apu_odd_cycle ? 1 : 2); dma_isOamDma = true; dma_OAMOn = true; } } private static void DMAClock() { if (dma_OAMFinishCounter > 0) { dma_OAMFinishCounter--; } if (!BUS_RW) { if (dma_DMCDMAWaitCycles > 0) { dma_DMCDMAWaitCycles--; } if (dma_OAMDMAWaitCycles > 0) { dma_OAMDMAWaitCycles--; } return; } if (dma_DMCOn) { dma_DMC_occurring = true; dma_DMCOn = false; if (dma_DMCDMAWaitCycles > 0) { if (BUS_ADDRESS == 16406 || BUS_ADDRESS == 16407) { Read(ref BUS_ADDRESS, out dma_dummy); dma_DMCDMAWaitCycles--; while (dma_DMCDMAWaitCycles > 0) { EmuClockComponents(); dma_DMCDMAWaitCycles--; } } else { if (dma_DMCDMAWaitCycles > 0) { EmuClockComponents(); dma_DMCDMAWaitCycles--; } while (dma_DMCDMAWaitCycles > 0) { Read(ref BUS_ADDRESS, out dma_dummy); dma_DMCDMAWaitCycles--; } } } DMCDoDMA(); dma_DMC_occurring = false; } if (!dma_OAMOn) { return; } dma_OAM_occurring = true; dma_OAMOn = false; if (dma_OAMDMAWaitCycles > 0) { if (BUS_ADDRESS == 16406 || BUS_ADDRESS == 16407) { Read(ref BUS_ADDRESS, out dma_dummy); dma_OAMDMAWaitCycles--; while (dma_OAMDMAWaitCycles > 0) { EmuClockComponents(); dma_OAMDMAWaitCycles--; } } else { if (dma_OAMDMAWaitCycles > 0) { EmuClockComponents(); dma_OAMDMAWaitCycles--; } while (dma_OAMDMAWaitCycles > 0) { Read(ref BUS_ADDRESS, out dma_dummy); dma_OAMDMAWaitCycles--; } } } dma_OAMCYCLE = 0; for (dma_oamdma_i = 0; dma_oamdma_i < 256; dma_oamdma_i++) { Read(ref dma_Oamaddress, out dma_latch); dma_OAMCYCLE++; Write(ref reg_2004, ref dma_latch); dma_OAMCYCLE++; dma_Oamaddress = (ushort)(++dma_Oamaddress & 0xFFFFu); } dma_OAMCYCLE = 0; dma_OAMFinishCounter = 5; dma_OAM_occurring = false; } private static void DMAWriteState(ref BinaryWriter bin) { bin.Write(dma_DMCDMAWaitCycles); bin.Write(dma_OAMDMAWaitCycles); bin.Write(dma_isOamDma); bin.Write(dma_oamdma_i); bin.Write(dma_DMCOn); bin.Write(dma_OAMOn); bin.Write(dma_DMC_occurring); bin.Write(dma_OAM_occurring); bin.Write(dma_OAMFinishCounter); bin.Write(dma_Oamaddress); bin.Write(dma_OAMCYCLE); bin.Write(dma_latch); bin.Write(dma_dummy); } private static void DMAReadState(ref BinaryReader bin) { dma_DMCDMAWaitCycles = bin.ReadInt32(); dma_OAMDMAWaitCycles = bin.ReadInt32(); dma_isOamDma = bin.ReadBoolean(); dma_oamdma_i = bin.ReadInt32(); dma_DMCOn = bin.ReadBoolean(); dma_OAMOn = bin.ReadBoolean(); dma_DMC_occurring = bin.ReadBoolean(); dma_OAM_occurring = bin.ReadBoolean(); dma_OAMFinishCounter = bin.ReadInt32(); dma_Oamaddress = bin.ReadUInt16(); dma_OAMCYCLE = bin.ReadInt32(); dma_latch = bin.ReadByte(); dma_dummy = bin.ReadByte(); } private static void PollInterruptStatus() { if (!cpu_suspend_nmi) { if (PPU_NMI_Current & !PPU_NMI_Old) { CPU_NMI_PIN = true; } PPU_NMI_Old = (PPU_NMI_Current = false); } if (!cpu_suspend_irq) { CPU_IRQ_PIN = !cpu_flag_i && IRQFlags != 0; } if (CPU_NMI_PIN) { InterruptVector = 65530; } else { InterruptVector = 65534; } } private static void InterruptsWriteState(ref BinaryWriter bin) { bin.Write(IRQFlags); bin.Write(PPU_NMI_Current); bin.Write(PPU_NMI_Old); bin.Write(InterruptVector); } private static void InterruptsReadState(ref BinaryReader bin) { IRQFlags = bin.ReadInt32(); PPU_NMI_Current = bin.ReadBoolean(); PPU_NMI_Old = bin.ReadBoolean(); InterruptVector = bin.ReadUInt16(); } public static void SetupGameGenie(bool IsGameGenieActive, GameGenieCode[] GameGenieCodes) { if (mem_board != null) { mem_board.SetupGameGenie(IsGameGenieActive, GameGenieCodes); } } private static void MEMInitialize(IRom rom) { Tracer.WriteLine("Looking for mapper # " + rom.MapperNumber + "...."); if (MyNesMain.IsBoardExist(rom.MapperNumber)) { Tracer.WriteLine("Mapper # " + rom.MapperNumber + " located, assigning..."); mem_board = MyNesMain.GetBoard(rom.MapperNumber); Tracer.WriteInformation("Mapper # " + rom.MapperNumber + " assigned successfully."); if (mem_board.HasIssues) { Tracer.WriteWarning(MNInterfaceLanguage.Mapper + " # " + mem_board.MapperNumber + " [" + mem_board.Name + "] " + MNInterfaceLanguage.Message_Error17); MyNesMain.VideoProvider.WriteWarningNotification(MNInterfaceLanguage.Mapper + " # " + mem_board.MapperNumber + " [" + mem_board.Name + "] " + MNInterfaceLanguage.Message_Error17, instant: false); } } else { Tracer.WriteError("Mapper # " + rom.MapperNumber + " IS NOT LOCATED, mapper is not supported or unable to find it."); MyNesMain.VideoProvider.WriteErrorNotification(MNInterfaceLanguage.Mapper + " # " + rom.MapperNumber + " " + MNInterfaceLanguage.Message_Error14, instant: false); mem_board = MyNesMain.GetBoard(0); Tracer.WriteWarning("Mapper # 0 [NROM] will be used instead, assigned successfully."); MyNesMain.VideoProvider.WriteErrorNotification(MNInterfaceLanguage.Mapper + " # 0 [NROM] " + MNInterfaceLanguage.Message_Error15, instant: false); } mem_read_accesses = new MemReadAccess[65536]; mem_write_accesses = new MemWriteAccess[65536]; MEMMap(MEMReadWRAM, new ushort[2] { 0, 4096 }); MEMMap(MEMWriteWRAM, new ushort[2] { 0, 4096 }); MEMMap(PPUIORead, new ushort[2] { 8192, 12288 }); MEMMap(PPUIOWrite, new ushort[2] { 8192, 12288 }); MEMMap(APUIORead, new ushort[1] { 16384 }); MEMMap(APUIOWrite, new ushort[1] { 16384 }); MEMMap(mem_board.ReadEX, new ushort[1] { 20480 }); MEMMap(mem_board.WriteEX, new ushort[1] { 20480 }); MEMMap(mem_board.ReadSRM, new ushort[2] { 24576, 28672 }); MEMMap(mem_board.WriteSRM, new ushort[2] { 24576, 28672 }); MEMMap(mem_board.ReadPRG, new ushort[8] { 32768, 36864, 40960, 45056, 49152, 53248, 57344, 61440 }); MEMMap(mem_board.WritePRG, new ushort[8] { 32768, 36864, 40960, 45056, 49152, 53248, 57344, 61440 }); mem_board.Initialize(rom); mem_wram = new byte[2048]; } private static void MEMHardReset() { mem_wram = new byte[2048]; mem_wram[8] = 247; mem_wram[9] = 239; mem_wram[10] = 223; mem_wram[15] = 191; Tracer.WriteLine("Reading SRAM ..."); SRAMFileName = Path.Combine(MyNesMain.EmuSettings.SRAMFolder, Path.GetFileNameWithoutExtension(CurrentFilePath) + ".srm"); if (File.Exists(SRAMFileName)) { FileStream fileStream = new FileStream(SRAMFileName, FileMode.Open, FileAccess.Read); byte[] array = new byte[fileStream.Length]; fileStream.Read(array, 0, array.Length); fileStream.Flush(); fileStream.Close(); byte[] outData = new byte[0]; ZlipWrapper.DecompressData(array, out outData); mem_board.LoadSRAM(outData); Tracer.WriteLine("SRAM read successfully."); } else { Tracer.WriteLine("SRAM file not found; rom has no SRAM or file not exist."); } ReloadGameGenieCodes(); mem_board.HardReset(); } public static void ReloadGameGenieCodes() { Tracer.WriteLine("Reading game genie codes (if available)...."); GMFileName = Path.Combine(MyNesMain.EmuSettings.GameGenieFolder, Path.GetFileNameWithoutExtension(CurrentFilePath) + ".txt"); mem_board.GameGenieCodes = new GameGenieCode[0]; if (File.Exists(GMFileName)) { XmlReaderSettings xmlReaderSettings = new XmlReaderSettings(); xmlReaderSettings.DtdProcessing = DtdProcessing.Ignore; xmlReaderSettings.IgnoreWhitespace = true; XmlReader xmlReader = XmlReader.Create(GMFileName, xmlReaderSettings); xmlReader.Read(); xmlReader.Read(); if (xmlReader.Name != "MyNesGameGenieCodesList") { xmlReader.Close(); return; } GameGenie gameGenie = new GameGenie(); List list = new List(); while (xmlReader.Read()) { if (xmlReader.Name == "Code") { GameGenieCode item = default(GameGenieCode); item.Enabled = true; xmlReader.MoveToAttribute("code"); item.Name = xmlReader.Value.ToString(); if (item.Name.Length == 6) { item.Address = gameGenie.GetGGAddress(gameGenie.GetCodeAsHEX(item.Name), 6) | 0x8000; item.Value = gameGenie.GetGGValue(gameGenie.GetCodeAsHEX(item.Name), 6); item.IsCompare = false; } else { item.Address = gameGenie.GetGGAddress(gameGenie.GetCodeAsHEX(item.Name), 8) | 0x8000; item.Value = gameGenie.GetGGValue(gameGenie.GetCodeAsHEX(item.Name), 8); item.Compare = gameGenie.GetGGCompareValue(gameGenie.GetCodeAsHEX(item.Name)); item.IsCompare = true; } list.Add(item); } } xmlReader.Close(); if (list.Count > 0) { mem_board.GameGenieCodes = list.ToArray(); Tracer.WriteInformation("Game Genie codes loaded successfully, total of " + list.Count); } else { Tracer.WriteError("There is no Game Genie code in the file to load."); } } else { Tracer.WriteWarning("No Game Genie file found for this game."); } } private static void MEMMap(MemReadAccess readAccess, ushort[] addresses) { for (int i = 0; i < addresses.Length; i++) { mem_read_accesses[(addresses[i] & 0xF000) >> 12] = readAccess; } } private static void MEMMap(MemWriteAccess writeAccess, ushort[] addresses) { for (int i = 0; i < addresses.Length; i++) { mem_write_accesses[(addresses[i] & 0xF000) >> 12] = writeAccess; } } private static void MEMReadWRAM(ref ushort addr, out byte value) { value = mem_wram[addr & 0x7FF]; } private static void MEMWriteWRAM(ref ushort addr, ref byte value) { mem_wram[addr & 0x7FF] = value; } internal static void Read(ref ushort addr, out byte value) { BUS_RW = true; BUS_ADDRESS = addr; EmuClockComponents(); mem_read_accesses[(addr & 0xF000) >> 12](ref addr, out value); } private static void Write(ref ushort addr, ref byte value) { BUS_RW = false; BUS_ADDRESS = addr; EmuClockComponents(); mem_write_accesses[(addr & 0xF000) >> 12](ref addr, ref value); } internal static void SaveSRAM() { if (mem_board != null && MyNesMain.EmuSettings.SaveSRAMAtEmuShutdown && mem_board.SRAMSaveRequired) { Tracer.WriteLine("Saving SRAM ..."); byte[] outData = new byte[0]; ZlipWrapper.CompressData(mem_board.GetSRAMBuffer(), out outData); FileStream fileStream = new FileStream(SRAMFileName, FileMode.Create, FileAccess.Write); fileStream.Write(outData, 0, outData.Length); fileStream.Flush(); fileStream.Close(); Tracer.WriteLine("SRAM saved successfully."); } } private static void MEMWriteState(ref BinaryWriter bin) { mem_board.WriteStateData(ref bin); bin.Write(mem_wram); bin.Write(BUS_RW); bin.Write(BUS_ADDRESS); } private static void MEMReadState(ref BinaryReader bin) { mem_board.ReadStateData(ref bin); bin.Read(mem_wram, 0, mem_wram.Length); BUS_RW = bin.ReadBoolean(); BUS_ADDRESS = bin.ReadUInt16(); } private static void PORTSInitialize() { if (joypad1 == null) { joypad1 = new BlankJoypad(); } if (joypad2 == null) { joypad2 = new BlankJoypad(); } if (joypad3 == null) { joypad3 = new BlankJoypad(); } if (joypad4 == null) { joypad4 = new BlankJoypad(); } if (shortucts == null) { shortucts = new BlankShortuctsHandler(); } } public static void SetupShortcutsHandler(IShortcutsHandler hh) { shortucts = hh; } public static void SetupControllers(IJoypadConnecter joy1, IJoypadConnecter joy2, IJoypadConnecter joy3, IJoypadConnecter joy4) { joypad1 = joy1; joypad2 = joy2; joypad3 = joy3; joypad4 = joy4; } public static void SetupVSUnisystemDIP(IVSUnisystemDIPConnecter uni) { } public static void SetupControllersP1(IJoypadConnecter joy) { joypad1 = joy; } public static void SetupControllersP2(IJoypadConnecter joy) { joypad2 = joy; } public static void SetupControllersP3(IJoypadConnecter joy) { joypad3 = joy; } public static void SetupControllersP4(IJoypadConnecter joy) { joypad4 = joy; } public static void DestroyJoypads() { if (joypad1 == null) { joypad1 = new BlankJoypad(); } else { joypad1.Destroy(); } if (joypad2 == null) { joypad2 = new BlankJoypad(); } else { joypad1.Destroy(); } if (joypad3 == null) { joypad3 = new BlankJoypad(); } else { joypad1.Destroy(); } if (joypad4 == null) { joypad4 = new BlankJoypad(); } else { joypad1.Destroy(); } } private static void PORTWriteState(ref BinaryWriter bin) { bin.Write(PORT0); bin.Write(PORT1); bin.Write(inputStrobe); } private static void PORTReadState(ref BinaryReader bin) { PORT0 = bin.ReadInt32(); PORT1 = bin.ReadInt32(); inputStrobe = bin.ReadInt32(); } public static void SetupPalette(int[] pal) { ppu_palette = pal; } private static void PPUInitialize() { ppu_reg_update_func = new Action[8] { PPUOnRegister2000, PPUOnRegister2001, PPUOnRegister2002, PPUOnRegister2003, PPUOnRegister2004, PPUOnRegister2005, PPUOnRegister2006, PPUOnRegister2007 }; ppu_reg_read_func = new Action[8] { PPURead2000, PPURead2001, PPURead2002, PPURead2003, PPURead2004, PPURead2005, PPURead2006, PPURead2007 }; ppu_bkg_fetches = new Action[8] { PPUBKFetch0, PPUBKFetch1, PPUBKFetch2, PPUBKFetch3, PPUBKFetch4, PPUBKFetch5, PPUBKFetch6, PPUBKFetch7 }; ppu_spr_fetches = new Action[8] { PPUBKFetch0, PPUBKFetch1, PPUBKFetch2, PPUBKFetch3, PPUSPRFetch0, PPUSPRFetch1, PPUSPRFetch2, PPUSPRFetch3 }; ppu_oam_phases = new Action[9] { PPUOamPhase0, PPUOamPhase1, PPUOamPhase2, PPUOamPhase3, PPUOamPhase4, PPUOamPhase5, PPUOamPhase6, PPUOamPhase7, PPUOamPhase8 }; ppu_h_clocks = new Action[341]; ppu_h_clocks[0] = PPUHClock_000_Idle; for (int i = 1; i < 257; i++) { ppu_h_clocks[i] = PPUHClock_1_256_BKGClocks; } for (int j = 257; j < 321; j++) { ppu_h_clocks[j] = PPUHClock_257_320_SPRClocks; } for (int k = 321; k < 337; k++) { ppu_h_clocks[k] = PPUHClock_321_336_DUMClocks; } for (int l = 337; l < 341; l++) { ppu_h_clocks[l] = PPUHClock_337_340_DUMClocks; } ppu_v_clocks = new Action[320]; for (int m = 0; m < 240; m++) { ppu_v_clocks[m] = PPUScanlineRender; } ppu_v_clocks[240] = PPUScanlineVBLANK; ppu_oam_bank = new byte[256]; ppu_oam_bank_secondary = new byte[32]; ppu_palette_bank = new byte[32]; ppu_bkg_pixels = new int[512]; ppu_spr_pixels = new int[512]; ppu_screen_pixels = new int[61440]; ppu_palette = NTSCPaletteGenerator.GeneratePalette(); } private static void PPUHardReset() { ppu_reg_2001_grayscale = 243; switch (Region) { case EmuRegion.NTSC: ppu_clock_vblank_start = 241; ppu_clock_vblank_end = 261; ppu_use_odd_cycle = true; break; case EmuRegion.PALB: ppu_clock_vblank_start = 241; ppu_clock_vblank_end = 311; ppu_use_odd_cycle = false; break; case EmuRegion.DENDY: { ppu_clock_vblank_start = 291; ppu_clock_vblank_end = 311; for (int i = 241; i <= 290; i++) { ppu_v_clocks[i] = PPUScanlineVBLANK; } ppu_use_odd_cycle = false; break; } } ppu_v_clocks[ppu_clock_vblank_start] = PPUScanlineVBLANKStart; for (int j = ppu_clock_vblank_start + 1; j <= ppu_clock_vblank_end - 1; j++) { ppu_v_clocks[j] = PPUScanlineVBLANK; } ppu_v_clocks[ppu_clock_vblank_end] = PPUScanlineVBLANKEnd; ppu_oam_bank = new byte[256]; ppu_oam_bank_secondary = new byte[32]; PPUOamReset(); ppu_palette_bank = new byte[32] { 9, 1, 0, 1, 0, 2, 2, 13, 8, 16, 8, 36, 0, 0, 4, 44, 9, 1, 52, 3, 0, 4, 0, 20, 8, 58, 0, 2, 0, 32, 44, 8 }; ppu_reg_io_db = 0; ppu_reg_io_addr = 0; ppu_reg_access_happened = false; ppu_reg_access_w = false; ppu_reg_2000_vram_address_increament = 1; ppu_reg_2000_sprite_pattern_table_address_for_8x8_sprites = 0; ppu_reg_2000_background_pattern_table_address = 0; ppu_reg_2000_Sprite_size = 0; ppu_reg_2000_VBI = false; ppu_reg_2001_show_background_in_leftmost_8_pixels_of_screen = false; ppu_reg_2001_show_sprites_in_leftmost_8_pixels_of_screen = false; ppu_reg_2001_show_background = false; ppu_reg_2001_show_sprites = false; ppu_reg_2001_grayscale = 63; ppu_reg_2001_emphasis = 0; ppu_reg_2002_SpriteOverflow = false; ppu_reg_2002_Sprite0Hit = false; ppu_reg_2002_VblankStartedFlag = false; ppu_reg_2003_oam_addr = 0; ppu_is_sprfetch = false; ppu_use_odd_swap = false; ppu_clock_h = 0; ppu_clock_v = 0; } private static void PPUClock() { mem_board.OnPPUClock(); ppu_v_clocks[ppu_clock_v](); ppu_clock_h++; if (ppu_clock_h >= 341) { mem_board.OnPPUScanlineTick(); if (ppu_clock_v == ppu_clock_vblank_end) { ppu_clock_v = 0; ppu_frame_finished = true; } else { ppu_clock_v++; } ppu_clock_h -= 341; } if (ppu_reg_access_happened) { ppu_reg_access_happened = false; ppu_reg_update_func[ppu_reg_io_addr](); } } public static int GetPixel(int x, int y) { return ppu_screen_pixels[y * 256 + x]; } private static void PPUScanlineRender() { ppu_h_clocks[ppu_clock_h](); } private static void PPUScanlineVBLANKStart() { ppu_is_nmi_time = (ppu_clock_h >= 1) & (ppu_clock_h <= 3); if (ppu_is_nmi_time) { if (ppu_clock_h == 1) { ppu_reg_2002_VblankStartedFlag = true; } PPU_NMI_Current = ppu_reg_2002_VblankStartedFlag & ppu_reg_2000_VBI; } } private static void PPUScanlineVBLANKEnd() { ppu_is_nmi_time = (ppu_clock_h >= 1) & (ppu_clock_h <= 3); if (ppu_clock_h == 1) { ppu_reg_2002_Sprite0Hit = false; ppu_reg_2002_VblankStartedFlag = false; ppu_reg_2002_SpriteOverflow = false; } PPUScanlineRender(); if (ppu_use_odd_cycle && ppu_clock_h == 339) { ppu_use_odd_swap = !ppu_use_odd_swap; if (!ppu_use_odd_swap & (ppu_reg_2001_show_background || ppu_reg_2001_show_sprites)) { ppu_odd_swap_done = true; ppu_clock_h++; } } } private static void PPUScanlineVBLANK() { } private static void PPUHClock_000_Idle() { if (ppu_odd_swap_done) { ppu_bkg_fetches[1](); ppu_odd_swap_done = false; } } private static void PPUHClock_1_256_BKGClocks() { if (ppu_reg_2001_show_background || ppu_reg_2001_show_sprites) { if (ppu_clock_v != ppu_clock_vblank_end) { if (ppu_clock_h > 0 && ppu_clock_h < 65) { ppu_oam_bank_secondary[(ppu_clock_h - 1) & 0x1F] = byte.MaxValue; } else { if (ppu_clock_h == 65) { PPUOamReset(); } if (((ppu_clock_h - 1) & 1) == 0) { PPUOamEvFetch(); } else { ppu_oam_phases[ppu_phase_index](); } if (ppu_clock_h == 256) { PPUOamClear(); } } } ppu_bkg_fetches[(ppu_clock_h - 1) & 7](); if (ppu_clock_v < 240) { RenderPixel(); } } else { if (ppu_clock_v >= 240) { return; } if ((ppu_vram_addr & 0x3F00) == 16128) { if ((ppu_vram_addr & 3) == 0) { ppu_screen_pixels[ppu_clock_h - 1 + ppu_clock_v * 256] = ppu_palette[(ppu_palette_bank[ppu_vram_addr & 0xC] & ppu_reg_2001_grayscale) | ppu_reg_2001_emphasis]; } else { ppu_screen_pixels[ppu_clock_h - 1 + ppu_clock_v * 256] = ppu_palette[(ppu_palette_bank[ppu_vram_addr & 0x1F] & ppu_reg_2001_grayscale) | ppu_reg_2001_emphasis]; } } else { ppu_screen_pixels[ppu_clock_h - 1 + ppu_clock_v * 256] = ppu_palette[(ppu_palette_bank[0] & ppu_reg_2001_grayscale) | ppu_reg_2001_emphasis]; } } } private static void PPUHClock_257_320_SPRClocks() { if (ppu_reg_2001_show_background || ppu_reg_2001_show_sprites) { ppu_spr_fetches[(ppu_clock_h - 1) & 7](); if (ppu_clock_h == 257) { ppu_vram_addr = (ushort)((ppu_vram_addr & 0x7BE0u) | (ppu_vram_addr_temp & 0x41Fu)); } if (ppu_clock_v == ppu_clock_vblank_end && ppu_clock_h >= 280 && ppu_clock_h <= 304) { ppu_vram_addr = (ushort)((ppu_vram_addr & 0x41Fu) | (ppu_vram_addr_temp & 0x7BE0u)); } } } private static void PPUHClock_321_336_DUMClocks() { if (ppu_reg_2001_show_background || ppu_reg_2001_show_sprites) { ppu_bkg_fetches[(ppu_clock_h - 1) & 7](); } } private static void PPUHClock_337_340_DUMClocks() { if (ppu_reg_2001_show_background || ppu_reg_2001_show_sprites) { ppu_bkg_fetches[(ppu_clock_h - 1) & 1](); } } private static void PPUBKFetch0() { ppu_bkgfetch_nt_addr = (ushort)(0x2000u | (ppu_vram_addr & 0xFFFu)); mem_board.OnPPUAddressUpdate(ref ppu_bkgfetch_nt_addr); } private static void PPUBKFetch1() { mem_board.ReadNMT(ref ppu_bkgfetch_nt_addr, out ppu_bkgfetch_nt_data); } private static void PPUBKFetch2() { ppu_bkgfetch_at_addr = (ushort)(0x23C0u | (ppu_vram_addr & 0xC00u) | ((uint)(ppu_vram_addr >> 4) & 0x38u) | ((uint)(ppu_vram_addr >> 2) & 7u)); mem_board.OnPPUAddressUpdate(ref ppu_bkgfetch_at_addr); } private static void PPUBKFetch3() { mem_board.ReadNMT(ref ppu_bkgfetch_at_addr, out ppu_bkgfetch_at_data); ppu_bkgfetch_at_data = (byte)(ppu_bkgfetch_at_data >> (((ppu_vram_addr >> 4) & 4) | (ppu_vram_addr & 2))); } private static void PPUBKFetch4() { ppu_bkgfetch_lb_addr = (ushort)((uint)(ppu_reg_2000_background_pattern_table_address | (ppu_bkgfetch_nt_data << 4)) | ((uint)(ppu_vram_addr >> 12) & 7u)); mem_board.OnPPUAddressUpdate(ref ppu_bkgfetch_lb_addr); } private static void PPUBKFetch5() { mem_board.ReadCHR(ref ppu_bkgfetch_lb_addr, out ppu_bkgfetch_lb_data); } private static void PPUBKFetch6() { ppu_bkgfetch_hb_addr = (ushort)((uint)(ppu_reg_2000_background_pattern_table_address | (ppu_bkgfetch_nt_data << 4)) | 8u | ((uint)(ppu_vram_addr >> 12) & 7u)); mem_board.OnPPUAddressUpdate(ref ppu_bkgfetch_hb_addr); } private static void PPUBKFetch7() { mem_board.ReadCHR(ref ppu_bkgfetch_hb_addr, out ppu_bkgfetch_hb_data); ppu_bkg_render_pos = ppu_clock_h + 8; ppu_bkg_render_pos %= 336; if (ppu_clock_h == 256) { if ((ppu_vram_addr & 0x7000) != 28672) { ppu_vram_addr += 4096; } else { ppu_vram_addr ^= 28672; switch (ppu_vram_addr & 0x3E0) { case 928: ppu_vram_addr ^= 2976; break; case 992: ppu_vram_addr ^= 992; break; default: ppu_vram_addr += 32; break; } } } else if ((ppu_vram_addr & 0x1F) == 31) { ppu_vram_addr ^= 1055; } else { ppu_vram_addr++; } for (ppu_bkg_render_i = 0; ppu_bkg_render_i < 8; ppu_bkg_render_i++) { ppu_bkg_render_tmp_val = ((ppu_bkgfetch_at_data << 2) & 0xC) | ((ppu_bkgfetch_lb_data >> 7) & 1) | ((ppu_bkgfetch_hb_data >> 6) & 2); ppu_bkg_pixels[ppu_bkg_render_i + ppu_bkg_render_pos] = ppu_bkg_render_tmp_val; ppu_bkgfetch_lb_data <<= 1; ppu_bkgfetch_hb_data <<= 1; } } private static void PPUSPRFetch0() { ppu_sprfetch_slot = (ppu_clock_h - 1 >> 3) & 7; ppu_sprfetch_slot = 7 - ppu_sprfetch_slot; ppu_sprfetch_y_data = ppu_oam_bank_secondary[ppu_sprfetch_slot * 4]; ppu_sprfetch_t_data = ppu_oam_bank_secondary[ppu_sprfetch_slot * 4 + 1]; ppu_sprfetch_at_data = ppu_oam_bank_secondary[ppu_sprfetch_slot * 4 + 2]; ppu_sprfetch_x_data = ppu_oam_bank_secondary[ppu_sprfetch_slot * 4 + 3]; ppu_temp_comparator = (ppu_clock_v - ppu_sprfetch_y_data) ^ (((ppu_sprfetch_at_data & 0x80u) != 0) ? 15 : 0); if (ppu_reg_2000_Sprite_size == 16) { ppu_sprfetch_lb_addr = (ushort)(((uint)(ppu_sprfetch_t_data << 12) & 0x1000u) | ((uint)(ppu_sprfetch_t_data << 4) & 0xFE0u) | ((uint)(ppu_temp_comparator << 1) & 0x10u) | ((uint)ppu_temp_comparator & 7u)); } else { ppu_sprfetch_lb_addr = (ushort)((uint)(ppu_reg_2000_sprite_pattern_table_address_for_8x8_sprites | (ppu_sprfetch_t_data << 4)) | ((uint)ppu_temp_comparator & 7u)); } mem_board.OnPPUAddressUpdate(ref ppu_sprfetch_lb_addr); } private static void PPUSPRFetch1() { ppu_is_sprfetch = true; mem_board.ReadCHR(ref ppu_sprfetch_lb_addr, out ppu_sprfetch_lb_data); ppu_is_sprfetch = false; if ((ppu_sprfetch_at_data & 0x40u) != 0) { ppu_sprfetch_lb_data = reverseLookup[ppu_sprfetch_lb_data]; } } private static void PPUSPRFetch2() { ppu_sprfetch_hb_addr = (ushort)(ppu_sprfetch_lb_addr | 8u); mem_board.OnPPUAddressUpdate(ref ppu_sprfetch_hb_addr); } private static void PPUSPRFetch3() { ppu_is_sprfetch = true; mem_board.ReadCHR(ref ppu_sprfetch_hb_addr, out ppu_sprfetch_hb_data); ppu_is_sprfetch = false; if ((ppu_sprfetch_at_data & 0x40u) != 0) { ppu_sprfetch_hb_data = reverseLookup[ppu_sprfetch_hb_data]; } if (ppu_sprfetch_x_data == byte.MaxValue) { return; } for (ppu_bkg_render_i = 0; ppu_bkg_render_i < 8; ppu_bkg_render_i++) { if (ppu_sprfetch_x_data < byte.MaxValue) { ppu_bkg_render_tmp_val = ((ppu_sprfetch_at_data << 2) & 0xC) | ((ppu_sprfetch_lb_data >> 7) & 1) | ((ppu_sprfetch_hb_data >> 6) & 2); if (((uint)ppu_bkg_render_tmp_val & 3u) != 0) { ppu_spr_pixels[ppu_sprfetch_x_data] = ppu_bkg_render_tmp_val; if (ppu_sprfetch_slot == 0 && ppu_sprite0_should_hit) { ppu_spr_pixels[ppu_sprfetch_x_data] |= 16384; } if ((ppu_sprfetch_at_data & 0x20) == 0) { ppu_spr_pixels[ppu_sprfetch_x_data] |= 32768; } } ppu_sprfetch_lb_data <<= 1; ppu_sprfetch_hb_data <<= 1; ppu_sprfetch_x_data++; } } } private static void PPUOamReset() { ppu_oamev_n = 0; ppu_oamev_m = 0; ppu_oamev_slot = 0; ppu_phase_index = 0; ppu_sprite0_should_hit = false; } private static void PPUOamClear() { for (int i = 0; i < ppu_spr_pixels.Length; i++) { ppu_spr_pixels[i] = 0; } } private static void PPUOamEvFetch() { ppu_fetch_data = ppu_oam_bank[ppu_oamev_n * 4 + ppu_oamev_m]; } private static void PPUOamPhase0() { ppu_oamev_compare = ppu_clock_v >= ppu_fetch_data && ppu_clock_v < ppu_fetch_data + ppu_reg_2000_Sprite_size; if (ppu_oamev_compare) { ppu_oam_bank_secondary[ppu_oamev_slot * 4] = ppu_fetch_data; ppu_oamev_m = 1; ppu_phase_index++; if (ppu_oamev_n == 0) { ppu_sprite0_should_hit = true; } } else { ppu_oamev_m = 0; ppu_oamev_n++; if (ppu_oamev_n == 64) { ppu_oamev_n = 0; ppu_phase_index = 8; } } } private static void PPUOamPhase1() { ppu_oam_bank_secondary[ppu_oamev_slot * 4 + ppu_oamev_m] = ppu_fetch_data; ppu_oamev_m = 2; ppu_phase_index++; } private static void PPUOamPhase2() { ppu_oam_bank_secondary[ppu_oamev_slot * 4 + ppu_oamev_m] = ppu_fetch_data; ppu_oamev_m = 3; ppu_phase_index++; } private static void PPUOamPhase3() { ppu_oam_bank_secondary[ppu_oamev_slot * 4 + ppu_oamev_m] = ppu_fetch_data; ppu_oamev_m = 0; ppu_oamev_n++; ppu_oamev_slot++; if (ppu_oamev_n == 64) { ppu_oamev_n = 0; ppu_phase_index = 8; } else if (ppu_oamev_slot < 8) { ppu_phase_index = 0; } else if (ppu_oamev_slot == 8) { ppu_phase_index = 4; } } private static void PPUOamPhase4() { ppu_oamev_compare = ppu_clock_v >= ppu_fetch_data && ppu_clock_v < ppu_fetch_data + ppu_reg_2000_Sprite_size; if (ppu_oamev_compare) { ppu_oamev_m = 1; ppu_phase_index++; ppu_reg_2002_SpriteOverflow = true; return; } ppu_oamev_m++; if (ppu_oamev_m == 4) { ppu_oamev_m = 0; } ppu_oamev_n++; if (ppu_oamev_n == 64) { ppu_oamev_n = 0; ppu_phase_index = 8; } else { ppu_phase_index = 4; } } private static void PPUOamPhase5() { ppu_oamev_m = 2; ppu_phase_index++; } private static void PPUOamPhase6() { ppu_oamev_m = 3; ppu_phase_index++; } private static void PPUOamPhase7() { ppu_oamev_m = 0; ppu_oamev_n++; if (ppu_oamev_n == 64) { ppu_oamev_n = 0; } ppu_phase_index = 8; } private static void PPUOamPhase8() { ppu_oamev_n++; if (ppu_oamev_n >= 64) { ppu_oamev_n = 0; } } private static void RenderPixel() { if (ppu_clock_v == ppu_clock_vblank_end) { return; } ppu_render_x = ppu_clock_h - 1; ppu_render_y = ppu_clock_v * 256; if (ppu_render_x < 8) { if (ppu_reg_2001_show_background_in_leftmost_8_pixels_of_screen) { ppu_bkg_current_pixel = 0x3F00 | ppu_bkg_pixels[ppu_render_x + ppu_vram_finex]; } else { ppu_bkg_current_pixel = 16128; } if (ppu_reg_2001_show_sprites_in_leftmost_8_pixels_of_screen) { ppu_spr_current_pixel = 0x3F10 | ppu_spr_pixels[ppu_render_x]; } else { ppu_spr_current_pixel = 16144; } } else { if (!ppu_reg_2001_show_background) { ppu_bkg_current_pixel = 16128; } else { ppu_bkg_current_pixel = 0x3F00 | ppu_bkg_pixels[ppu_render_x + ppu_vram_finex]; } if (!ppu_reg_2001_show_sprites || ppu_clock_v == 0) { ppu_spr_current_pixel = 16144; } else { ppu_spr_current_pixel = 0x3F10 | ppu_spr_pixels[ppu_render_x]; } } ppu_current_pixel = 0; if (((uint)ppu_spr_current_pixel & 0x8000u) != 0) { ppu_current_pixel = ppu_spr_current_pixel; } else { ppu_current_pixel = ppu_bkg_current_pixel; } if ((ppu_bkg_current_pixel & 3) == 0) { ppu_current_pixel = ppu_spr_current_pixel; } else if ((ppu_spr_current_pixel & 3) == 0) { ppu_current_pixel = ppu_bkg_current_pixel; } else if (((uint)ppu_spr_pixels[ppu_render_x] & 0x4000u) != 0) { ppu_reg_2002_Sprite0Hit = true; } if ((ppu_current_pixel & 3) == 0) { ppu_screen_pixels[ppu_render_x + ppu_render_y] = ppu_palette[(ppu_palette_bank[ppu_current_pixel & 0xC] & ppu_reg_2001_grayscale) | ppu_reg_2001_emphasis]; } else { ppu_screen_pixels[ppu_render_x + ppu_render_y] = ppu_palette[(ppu_palette_bank[ppu_current_pixel & 0x1F] & ppu_reg_2001_grayscale) | ppu_reg_2001_emphasis]; } } private static void PPUIORead(ref ushort addr, out byte value) { ppu_reg_io_addr = (byte)(addr & 7u); ppu_reg_access_happened = true; ppu_reg_access_w = false; ppu_reg_read_func[ppu_reg_io_addr](); value = ppu_reg_io_db; } private static void PPUIOWrite(ref ushort addr, ref byte value) { ppu_reg_io_addr = (byte)(addr & 7u); ppu_reg_io_db = value; ppu_reg_access_w = true; ppu_reg_access_happened = true; } private static void PPUOnRegister2000() { if (ppu_reg_access_w) { ppu_vram_addr_temp = (ushort)((ppu_vram_addr_temp & 0x73FFu) | (uint)((ppu_reg_io_db & 3) << 10)); if ((ppu_reg_io_db & 4u) != 0) { ppu_reg_2000_vram_address_increament = 32; } else { ppu_reg_2000_vram_address_increament = 1; } if ((ppu_reg_io_db & 8u) != 0) { ppu_reg_2000_sprite_pattern_table_address_for_8x8_sprites = 4096; } else { ppu_reg_2000_sprite_pattern_table_address_for_8x8_sprites = 0; } if ((ppu_reg_io_db & 0x10u) != 0) { ppu_reg_2000_background_pattern_table_address = 4096; } else { ppu_reg_2000_background_pattern_table_address = 0; } if ((ppu_reg_io_db & 0x20u) != 0) { ppu_reg_2000_Sprite_size = 16; } else { ppu_reg_2000_Sprite_size = 8; } if (!ppu_reg_2000_VBI && (ppu_reg_io_db & 0x80u) != 0 && ppu_reg_2002_VblankStartedFlag) { PPU_NMI_Current = true; } ppu_reg_2000_VBI = (ppu_reg_io_db & 0x80) != 0; if (!ppu_reg_2000_VBI && ppu_is_nmi_time) { PPU_NMI_Current = false; } } } private static void PPUOnRegister2001() { if (ppu_reg_access_w) { ppu_reg_2001_show_background_in_leftmost_8_pixels_of_screen = (ppu_reg_io_db & 2) != 0; ppu_reg_2001_show_sprites_in_leftmost_8_pixels_of_screen = (ppu_reg_io_db & 4) != 0; ppu_reg_2001_show_background = (ppu_reg_io_db & 8) != 0; ppu_reg_2001_show_sprites = (ppu_reg_io_db & 0x10) != 0; ppu_reg_2001_grayscale = ((((uint)ppu_reg_io_db & (true ? 1u : 0u)) != 0) ? 48 : 63); ppu_reg_2001_emphasis = (ppu_reg_io_db & 0xE0) << 1; } } private static void PPUOnRegister2002() { if (!ppu_reg_access_w) { ppu_vram_flip_flop = false; ppu_reg_2002_VblankStartedFlag = false; if (ppu_clock_v == ppu_clock_vblank_start) { PPU_NMI_Current = ppu_reg_2002_VblankStartedFlag & ppu_reg_2000_VBI; } } } private static void PPUOnRegister2003() { if (ppu_reg_access_w) { ppu_reg_2003_oam_addr = ppu_reg_io_db; } } private static void PPUOnRegister2004() { if (ppu_reg_access_w) { if (ppu_clock_v < 240 && IsRenderingOn()) { ppu_reg_io_db = byte.MaxValue; } if ((ppu_reg_2003_oam_addr & 3) == 2) { ppu_reg_io_db &= 227; } ppu_oam_bank[ppu_reg_2003_oam_addr] = ppu_reg_io_db; ppu_reg_2003_oam_addr = (byte)((uint)(ppu_reg_2003_oam_addr + 1) & 0xFFu); } } private static void PPUOnRegister2005() { if (ppu_reg_access_w) { if (!ppu_vram_flip_flop) { ppu_vram_addr_temp = (ushort)((ppu_vram_addr_temp & 0x7FE0u) | (uint)((ppu_reg_io_db & 0xF8) >> 3)); ppu_vram_finex = (byte)(ppu_reg_io_db & 7u); } else { ppu_vram_addr_temp = (ushort)((ppu_vram_addr_temp & 0xC1Fu) | (uint)((ppu_reg_io_db & 7) << 12) | (uint)((ppu_reg_io_db & 0xF8) << 2)); } ppu_vram_flip_flop = !ppu_vram_flip_flop; } } private static void PPUOnRegister2006() { if (ppu_reg_access_w) { if (!ppu_vram_flip_flop) { ppu_vram_addr_temp = (ushort)((ppu_vram_addr_temp & 0xFFu) | (uint)((ppu_reg_io_db & 0x3F) << 8)); } else { ppu_vram_addr_temp = (ushort)((ppu_vram_addr_temp & 0x7F00u) | ppu_reg_io_db); ppu_vram_addr = ppu_vram_addr_temp; mem_board.OnPPUAddressUpdate(ref ppu_vram_addr); } ppu_vram_flip_flop = !ppu_vram_flip_flop; } } private static void PPUOnRegister2007() { if (ppu_reg_access_w) { ppu_vram_addr_access_temp = (ushort)(ppu_vram_addr & 0x3FFFu); if (ppu_vram_addr_access_temp < 8192) { mem_board.WriteCHR(ref ppu_vram_addr_access_temp, ref ppu_reg_io_db); } else if (ppu_vram_addr_access_temp < 16128) { mem_board.WriteNMT(ref ppu_vram_addr_access_temp, ref ppu_reg_io_db); } else if ((ppu_vram_addr_access_temp & 3u) != 0) { ppu_palette_bank[ppu_vram_addr_access_temp & 0x1F] = ppu_reg_io_db; } else { ppu_palette_bank[ppu_vram_addr_access_temp & 0xC] = ppu_reg_io_db; } } else { if ((ppu_vram_addr & 0x3F00) == 16128) { ppu_vram_addr_access_temp = (ushort)(ppu_vram_addr & 0x2FFFu); } else { ppu_vram_addr_access_temp = (ushort)(ppu_vram_addr & 0x3FFFu); } if (ppu_vram_addr_access_temp < 8192) { mem_board.ReadCHR(ref ppu_vram_addr_access_temp, out ppu_vram_data); } else if (ppu_vram_addr_access_temp < 16128) { mem_board.ReadNMT(ref ppu_vram_addr_access_temp, out ppu_vram_data); } } ppu_vram_addr = (ushort)((uint)(ppu_vram_addr + ppu_reg_2000_vram_address_increament) & 0x7FFFu); mem_board.OnPPUAddressUpdate(ref ppu_vram_addr); } private static void PPURead2000() { } private static void PPURead2001() { } private static void PPURead2002() { ppu_reg_io_db = (byte)((ppu_reg_io_db & 0xDFu) | (ppu_reg_2002_SpriteOverflow ? 32u : 0u)); ppu_reg_io_db = (byte)((ppu_reg_io_db & 0xBFu) | (ppu_reg_2002_Sprite0Hit ? 64u : 0u)); ppu_reg_io_db = (byte)((ppu_reg_io_db & 0x7Fu) | (ppu_reg_2002_VblankStartedFlag ? 128u : 0u)); } private static void PPURead2003() { } private static void PPURead2004() { ppu_reg_io_db = ppu_oam_bank[ppu_reg_2003_oam_addr]; if (ppu_clock_v < 240 && IsRenderingOn()) { if (ppu_clock_h < 64) { ppu_reg_io_db = byte.MaxValue; } else if (ppu_clock_h < 192) { ppu_reg_io_db = ppu_oam_bank[(ppu_clock_h - 64 << 1) & 0xFC]; } else if (ppu_clock_h < 256) { ppu_reg_io_db = (((ppu_clock_h & 1) == 1) ? ppu_oam_bank[252] : ppu_oam_bank[(ppu_clock_h - 192 << 1) & 0xFC]); } else if (ppu_clock_h < 320) { ppu_reg_io_db = byte.MaxValue; } else { ppu_reg_io_db = ppu_oam_bank[0]; } } } private static void PPURead2005() { } private static void PPURead2006() { } private static void PPURead2007() { ppu_vram_addr_access_temp = (ushort)(ppu_vram_addr & 0x3FFFu); if (ppu_vram_addr_access_temp < 16128) { ppu_reg_io_db = ppu_vram_data; } else if ((ppu_vram_addr_access_temp & 3u) != 0) { ppu_reg_io_db = ppu_palette_bank[ppu_vram_addr_access_temp & 0x1F]; } else { ppu_reg_io_db = ppu_palette_bank[ppu_vram_addr_access_temp & 0xC]; } } internal static bool IsRenderingOn() { if (!ppu_reg_2001_show_background) { return ppu_reg_2001_show_sprites; } return true; } internal static bool IsInRender() { if (ppu_clock_v >= 240) { return ppu_clock_v == ppu_clock_vblank_end; } return true; } private static void PPUWriteState(ref BinaryWriter bin) { bin.Write(ppu_clock_h); bin.Write(ppu_clock_v); bin.Write(ppu_clock_vblank_start); bin.Write(ppu_clock_vblank_end); bin.Write(ppu_use_odd_cycle); bin.Write(ppu_use_odd_swap); bin.Write(ppu_is_nmi_time); bin.Write(ppu_frame_finished); bin.Write(ppu_oam_bank); bin.Write(ppu_oam_bank_secondary); bin.Write(ppu_palette_bank); bin.Write(ppu_reg_io_db); bin.Write(ppu_reg_io_addr); bin.Write(ppu_reg_access_happened); bin.Write(ppu_reg_access_w); bin.Write(ppu_reg_2000_vram_address_increament); bin.Write(ppu_reg_2000_sprite_pattern_table_address_for_8x8_sprites); bin.Write(ppu_reg_2000_background_pattern_table_address); bin.Write(ppu_reg_2000_Sprite_size); bin.Write(ppu_reg_2000_VBI); bin.Write(ppu_reg_2001_show_background_in_leftmost_8_pixels_of_screen); bin.Write(ppu_reg_2001_show_sprites_in_leftmost_8_pixels_of_screen); bin.Write(ppu_reg_2001_show_background); bin.Write(ppu_reg_2001_show_sprites); bin.Write(ppu_reg_2001_grayscale); bin.Write(ppu_reg_2001_emphasis); bin.Write(ppu_reg_2002_SpriteOverflow); bin.Write(ppu_reg_2002_Sprite0Hit); bin.Write(ppu_reg_2002_VblankStartedFlag); bin.Write(ppu_reg_2003_oam_addr); bin.Write(ppu_vram_addr); bin.Write(ppu_vram_data); bin.Write(ppu_vram_addr_temp); bin.Write(ppu_vram_addr_access_temp); bin.Write(ppu_vram_flip_flop); bin.Write(ppu_vram_finex); bin.Write(ppu_bkgfetch_nt_addr); bin.Write(ppu_bkgfetch_nt_data); bin.Write(ppu_bkgfetch_at_addr); bin.Write(ppu_bkgfetch_at_data); bin.Write(ppu_bkgfetch_lb_addr); bin.Write(ppu_bkgfetch_lb_data); bin.Write(ppu_bkgfetch_hb_addr); bin.Write(ppu_bkgfetch_hb_data); bin.Write(ppu_sprfetch_slot); bin.Write(ppu_sprfetch_y_data); bin.Write(ppu_sprfetch_t_data); bin.Write(ppu_sprfetch_at_data); bin.Write(ppu_sprfetch_x_data); bin.Write(ppu_sprfetch_lb_addr); bin.Write(ppu_sprfetch_lb_data); bin.Write(ppu_sprfetch_hb_addr); bin.Write(ppu_sprfetch_hb_data); bin.Write(ppu_bkg_render_i); bin.Write(ppu_bkg_render_pos); bin.Write(ppu_bkg_render_tmp_val); bin.Write(ppu_bkg_current_pixel); bin.Write(ppu_spr_current_pixel); bin.Write(ppu_current_pixel); bin.Write(ppu_render_x); bin.Write(0); bin.Write(ppu_oamev_n); bin.Write(ppu_oamev_m); bin.Write(ppu_oamev_compare); bin.Write(ppu_oamev_slot); bin.Write(ppu_fetch_data); bin.Write(ppu_phase_index); bin.Write(ppu_sprite0_should_hit); } private static void PPUReadState(ref BinaryReader bin) { ppu_clock_h = bin.ReadInt32(); ppu_clock_v = bin.ReadUInt16(); ppu_clock_vblank_start = bin.ReadUInt16(); ppu_clock_vblank_end = bin.ReadUInt16(); ppu_use_odd_cycle = bin.ReadBoolean(); ppu_use_odd_swap = bin.ReadBoolean(); ppu_is_nmi_time = bin.ReadBoolean(); ppu_frame_finished = bin.ReadBoolean(); bin.Read(ppu_oam_bank, 0, ppu_oam_bank.Length); bin.Read(ppu_oam_bank_secondary, 0, ppu_oam_bank_secondary.Length); bin.Read(ppu_palette_bank, 0, ppu_palette_bank.Length); ppu_reg_io_db = bin.ReadByte(); ppu_reg_io_addr = bin.ReadByte(); ppu_reg_access_happened = bin.ReadBoolean(); ppu_reg_access_w = bin.ReadBoolean(); ppu_reg_2000_vram_address_increament = bin.ReadByte(); ppu_reg_2000_sprite_pattern_table_address_for_8x8_sprites = bin.ReadUInt16(); ppu_reg_2000_background_pattern_table_address = bin.ReadUInt16(); ppu_reg_2000_Sprite_size = bin.ReadByte(); ppu_reg_2000_VBI = bin.ReadBoolean(); ppu_reg_2001_show_background_in_leftmost_8_pixels_of_screen = bin.ReadBoolean(); ppu_reg_2001_show_sprites_in_leftmost_8_pixels_of_screen = bin.ReadBoolean(); ppu_reg_2001_show_background = bin.ReadBoolean(); ppu_reg_2001_show_sprites = bin.ReadBoolean(); ppu_reg_2001_grayscale = bin.ReadInt32(); ppu_reg_2001_emphasis = bin.ReadInt32(); ppu_reg_2002_SpriteOverflow = bin.ReadBoolean(); ppu_reg_2002_Sprite0Hit = bin.ReadBoolean(); ppu_reg_2002_VblankStartedFlag = bin.ReadBoolean(); ppu_reg_2003_oam_addr = bin.ReadByte(); ppu_vram_addr = bin.ReadUInt16(); ppu_vram_data = bin.ReadByte(); ppu_vram_addr_temp = bin.ReadUInt16(); ppu_vram_addr_access_temp = bin.ReadUInt16(); ppu_vram_flip_flop = bin.ReadBoolean(); ppu_vram_finex = bin.ReadByte(); ppu_bkgfetch_nt_addr = bin.ReadUInt16(); ppu_bkgfetch_nt_data = bin.ReadByte(); ppu_bkgfetch_at_addr = bin.ReadUInt16(); ppu_bkgfetch_at_data = bin.ReadByte(); ppu_bkgfetch_lb_addr = bin.ReadUInt16(); ppu_bkgfetch_lb_data = bin.ReadByte(); ppu_bkgfetch_hb_addr = bin.ReadUInt16(); ppu_bkgfetch_hb_data = bin.ReadByte(); ppu_sprfetch_slot = bin.ReadInt32(); ppu_sprfetch_y_data = bin.ReadByte(); ppu_sprfetch_t_data = bin.ReadByte(); ppu_sprfetch_at_data = bin.ReadByte(); ppu_sprfetch_x_data = bin.ReadByte(); ppu_sprfetch_lb_addr = bin.ReadUInt16(); ppu_sprfetch_lb_data = bin.ReadByte(); ppu_sprfetch_hb_addr = bin.ReadUInt16(); ppu_sprfetch_hb_data = bin.ReadByte(); ppu_bkg_render_i = bin.ReadInt32(); ppu_bkg_render_pos = bin.ReadInt32(); ppu_bkg_render_tmp_val = bin.ReadInt32(); ppu_bkg_current_pixel = bin.ReadInt32(); ppu_spr_current_pixel = bin.ReadInt32(); ppu_current_pixel = bin.ReadInt32(); ppu_render_x = bin.ReadInt32(); bin.ReadInt32(); ppu_oamev_n = bin.ReadByte(); ppu_oamev_m = bin.ReadByte(); ppu_oamev_compare = bin.ReadBoolean(); ppu_oamev_slot = bin.ReadByte(); ppu_fetch_data = bin.ReadByte(); ppu_phase_index = bin.ReadByte(); ppu_sprite0_should_hit = bin.ReadBoolean(); } internal static void CheckGame(string fileName, out bool valid) { string text = Path.GetExtension(fileName).ToLower(); if (text != null && text == ".nes") { Tracer.WriteLine("Checking INES header ..."); INes nes = new INes(); nes.Load(fileName, loadDumps: false); valid = nes.IsValid; Tracer.WriteLine("INES header is valid."); } else { Tracer.WriteWarning("File format is not supported. Format: " + Path.GetExtension(fileName)); valid = false; } } internal static void Initialize() { Tracer.WriteLine("Loading database file ..."); NesCartDatabase.LoadDatabase(out bool success); if (success) { Tracer.WriteInformation("Nes Cart database file loaded successfully."); } else { Tracer.WriteError("Error loading Nes Cart database file."); } FrameLimiterEnabled = true; CPUInitialize(); PPUInitialize(); APUInitialize(); PORTSInitialize(); } internal static void SetupRenderingMethods(RenderVideoFrame renderVideo, RenderAudioSamples renderAudio, TogglePause renderTogglePause, GetIsPlaying renderGetIsPlaying) { render_initialized = false; render_video = renderVideo; render_audio = renderAudio; render_audio_toggle_pause = renderTogglePause; render_audio_get_is_playing = renderGetIsPlaying; render_initialized = render_video != null && render_audio != null && render_audio_toggle_pause != null && render_audio_get_is_playing != null; if (render_initialized) { Tracer.WriteInformation("Renderer methods initialized successfully."); return; } Tracer.WriteError("ERROR RENDERER INITIALIZING !!"); Tracer.WriteError("Faild to initialize the renderers methods. Please use the method 'SetupRenderingMethods' to initialize the renderers methods before you can run the emulation."); } public static void LoadGame(string fileName, out bool success, bool useThread) { if (!render_initialized) { Tracer.WriteError("NO RENDERER INITIALIZED !! EMU CANNOT BE INTIALIZED WITHOUT A RENDERER !!"); Tracer.WriteError("Please use the method 'SetupRenderingMethods' to initialize the renderers methods before you can run the emulation."); success = false; return; } string text = Path.GetExtension(fileName).ToLower(); if (text != null && text == ".nes") { Tracer.WriteLine("Checking INES header ..."); INes nes = new INes(); nes.Load(fileName, loadDumps: true); if (nes.IsValid) { emu_request_mode = RequestMode.None; CurrentFilePath = fileName; if (ON) { ShutDown(); } Tracer.WriteLine("INES header is valid, loading game ..."); ApplyRegionSetting(); MEMInitialize(nes); ApplyAudioSettings(); ApplyFrameSkipSettings(); ApplyPaletteSetting(); PORTSInitialize(); hardReset(); Tracer.WriteLine("EMU is ready."); success = true; emu_frame_clocking_mode = !useThread; ON = true; PAUSED = false; if (useThread) { Tracer.WriteLine("Running in a thread ... using custom frame limiter."); FrameLimiterEnabled = true; mainThread = new Thread(EmuClock); mainThread.Start(); } MyNesMain.VideoProvider.SignalToggle(started: true); MyNesMain.AudioProvider.SignalToggle(started: true); } else { success = false; } } else { success = false; } } public static void HardReset() { PAUSED = true; emu_request_mode = RequestMode.HardReset; } private static void hardReset() { if (MyNesMain.WaveRecorder.IsRecording) { MyNesMain.WaveRecorder.Stop(); } render_audio_toggle_pause(paused: true); switch (Region) { case EmuRegion.NTSC: emu_time_target_fps = 60.0988; break; case EmuRegion.PALB: case EmuRegion.DENDY: emu_time_target_fps = 50.0; break; } fps_time_period = 1.0 / emu_time_target_fps; MEMHardReset(); CPUHardReset(); PPUHardReset(); APUHardReset(); DMAHardReset(); render_audio_toggle_pause(paused: false); MyNesMain.VideoProvider.WriteWarningNotification(MNInterfaceLanguage.Message_HardReset, instant: false); } public static void SoftReset() { PAUSED = true; emu_request_mode = RequestMode.SoftReset; } private static void softReset() { CPUSoftReset(); APUSoftReset(); MyNesMain.VideoProvider.WriteWarningNotification(MNInterfaceLanguage.Message_SoftReset, instant: false); } public static void SaveState() { PAUSED = true; emu_request_mode = RequestMode.SaveState; } public static void LoadState() { PAUSED = true; emu_request_mode = RequestMode.LoadState; } internal static void TakeSnapshot() { PAUSED = true; emu_request_mode = RequestMode.TakeSnapshot; } public static void ShutDown() { MyNesMain.VideoProvider.SignalToggle(started: false); MyNesMain.AudioProvider.SignalToggle(started: false); if (MyNesMain.WaveRecorder.IsRecording) { MyNesMain.WaveRecorder.Stop(); } render_audio_get_is_playing(out render_audio_is_playing); if (render_audio_is_playing) { render_audio_toggle_pause(paused: true); } Tracer.WriteLine("Shutting down the emulation core..."); ON = false; if (mainThread != null) { Tracer.WriteLine("Aborting thread .."); while (mainThread.IsAlive) { } mainThread.Abort(); mainThread = null; } SaveSRAM(); Tracer.WriteInformation("Emulation core shutdown successfully."); NesEmu.EmuShutdown?.Invoke(null, new EventArgs()); } internal static void EMUClockFrame() { emu_frame_done = false; while (!emu_frame_done && ON) { if (!PAUSED) { CPUClock(); } else { Thread.Sleep(100); } } } private static void EmuClock() { while (ON) { if (!PAUSED) { CPUClock(); if (ppu_frame_finished) { FrameFinished(); } continue; } render_audio_get_is_playing(out render_audio_is_playing); if (render_audio_is_playing) { render_audio_toggle_pause(paused: true); } Thread.Sleep(100); shortucts.Update(); switch (emu_request_mode) { case RequestMode.HardReset: hardReset(); PAUSED = false; emu_request_mode = RequestMode.None; break; case RequestMode.SoftReset: softReset(); PAUSED = false; emu_request_mode = RequestMode.None; break; case RequestMode.SaveState: StateHandler.SaveState(); PAUSED = false; emu_request_mode = RequestMode.None; break; case RequestMode.LoadState: StateHandler.LoadState(); PAUSED = false; emu_request_mode = RequestMode.None; break; case RequestMode.TakeSnapshot: MyNesMain.VideoProvider.TakeSnapshot(); PAUSED = false; emu_request_mode = RequestMode.None; break; } isPaused = true; } } internal static void EmuClockComponents() { PPUClock(); PollInterruptStatus(); PPUClock(); PPUClock(); APUClock(); DMAClock(); mem_board.OnCPUClock(); } internal static void ApplyFrameSkipSettings() { FrameSkipEnabled = MyNesMain.RendererSettings.FrameSkipEnabled; FrameSkipInterval = MyNesMain.RendererSettings.FrameSkipInterval; } private static void FrameFinished() { if (!FrameSkipEnabled) { render_video(ref ppu_screen_pixels); } else { FrameSkipCounter++; if (FrameSkipCounter >= FrameSkipInterval) { render_video(ref ppu_screen_pixels); FrameSkipCounter = 0; } } isPaused = false; ppu_frame_finished = false; emu_frame_done = true; joypad1.Update(); joypad2.Update(); if (IsFourPlayers) { joypad3.Update(); joypad4.Update(); } shortucts.Update(); if (SoundEnabled) { render_audio_get_is_playing(out render_audio_is_playing); if (!render_audio_is_playing) { render_audio_toggle_pause(paused: false); } render_audio(ref audio_samples, ref audio_samples_added); audio_w_pos = 0; audio_samples_added = 0; audio_timer = 0.0; } fps_time_token = GetTime() - fps_time_start; if (FrameLimiterEnabled) { if (fps_time_token > 0.0) { fps_time_dead = fps_time_period - fps_time_token; if (fps_time_dead > 0.0) { Thread.Sleep((int)Math.Floor(fps_time_dead * 1000.0)); fps_time_dead = GetTime() - fps_time_start; while (fps_time_period - fps_time_dead > 0.0) { fps_time_dead = GetTime() - fps_time_start; } } } fps_time_last = GetTime(); fps_time_frame_time = fps_time_last - fps_time_start; } fps_time_start = GetTime(); } private static double GetTime() { return (double)Stopwatch.GetTimestamp() / (double)Stopwatch.Frequency; } public static void GetSpeedValues(out double frame_time, out double immediate_frame_time) { frame_time = fps_time_token; immediate_frame_time = fps_time_frame_time; } public static void SetFramePeriod(ref double period) { fps_time_period = period; } public static void ApplyRegionSetting() { switch ((RegionSetting)MyNesMain.EmuSettings.RegionSetting) { case RegionSetting.AUTO: Tracer.WriteLine("REGION = AUTO"); Region = EmuRegion.NTSC; if (CurrentFilePath.Contains("(E)")) { Region = EmuRegion.PALB; } Tracer.WriteLine("REGION SELECTED: " + Region); break; case RegionSetting.ForceNTSC: Tracer.WriteLine("REGION: FORCE NTSC"); Region = EmuRegion.NTSC; break; case RegionSetting.ForcePALB: Tracer.WriteLine("REGION: FORCE PALB"); Region = EmuRegion.PALB; break; case RegionSetting.ForceDENDY: Tracer.WriteLine("REGION: FORCE DENDY"); Region = EmuRegion.DENDY; break; } SystemIndex = (int)Region; } public static void ApplyPaletteSetting() { Tracer.WriteLine("Loading palette generators values from settings..."); NTSCPaletteGenerator.brightness = MyNesMain.RendererSettings.Palette_NTSC_brightness; NTSCPaletteGenerator.contrast = MyNesMain.RendererSettings.Palette_NTSC_contrast; NTSCPaletteGenerator.gamma = MyNesMain.RendererSettings.Palette_NTSC_gamma; NTSCPaletteGenerator.hue_tweak = MyNesMain.RendererSettings.Palette_NTSC_hue_tweak; NTSCPaletteGenerator.saturation = MyNesMain.RendererSettings.Palette_NTSC_saturation; PALBPaletteGenerator.brightness = MyNesMain.RendererSettings.Palette_PALB_brightness; PALBPaletteGenerator.contrast = MyNesMain.RendererSettings.Palette_PALB_contrast; PALBPaletteGenerator.gamma = MyNesMain.RendererSettings.Palette_PALB_gamma; PALBPaletteGenerator.hue_tweak = MyNesMain.RendererSettings.Palette_PALB_hue_tweak; PALBPaletteGenerator.saturation = MyNesMain.RendererSettings.Palette_PALB_saturation; Tracer.WriteLine("Setting up palette ...."); switch ((PaletteSelectSetting)MyNesMain.RendererSettings.Palette_PaletteSetting) { case PaletteSelectSetting.AUTO: Tracer.WriteLine("Palette set to auto detect depending on region."); switch (Region) { case EmuRegion.NTSC: SetupPalette(NTSCPaletteGenerator.GeneratePalette()); Tracer.WriteLine("Region is NTSC, Palette set from NTSC generator."); break; case EmuRegion.PALB: case EmuRegion.DENDY: SetupPalette(PALBPaletteGenerator.GeneratePalette()); Tracer.WriteLine("Region is PALB/DENDY, Palette set from PALB generator."); break; } break; case PaletteSelectSetting.ForceNTSC: Tracer.WriteLine("Palette set to always use NTSC palette generator."); SetupPalette(NTSCPaletteGenerator.GeneratePalette()); Tracer.WriteLine("Palette set from NTSC generator."); break; case PaletteSelectSetting.ForcePALB: Tracer.WriteLine("Palette set to always use PALB palette generator."); SetupPalette(NTSCPaletteGenerator.GeneratePalette()); Tracer.WriteLine("Palette set from PALB generator."); break; case PaletteSelectSetting.File: { Tracer.WriteLine("Palette set to load from file."); var paletteFileStream = MyNesMain.FileManager.OpenPaletteFile(); if (paletteFileStream != null) { PaletteFileWrapper.LoadFile(paletteFileStream, out var palette); SetupPalette(palette); Tracer.WriteLine("Palette set from file"); break; } Tracer.WriteError("Palette from file is not exist is not exist. Setting up palette from generators."); switch (Region) { case EmuRegion.NTSC: SetupPalette(NTSCPaletteGenerator.GeneratePalette()); Tracer.WriteLine("Region is NTSC, Palette set from NTSC generator."); break; case EmuRegion.PALB: case EmuRegion.DENDY: SetupPalette(PALBPaletteGenerator.GeneratePalette()); Tracer.WriteLine("Region is PALB/DENDY, Palette set from PALB generator."); break; } break; } } } internal static void WriteStateData(ref BinaryWriter bin) { APUWriteState(ref bin); CPUWriteState(ref bin); DMAWriteState(ref bin); InterruptsWriteState(ref bin); MEMWriteState(ref bin); PORTWriteState(ref bin); PPUWriteState(ref bin); } internal static void ReadStateData(ref BinaryReader bin) { APUReadState(ref bin); CPUReadState(ref bin); DMAReadState(ref bin); InterruptsReadState(ref bin); MEMReadState(ref bin); PORTReadState(ref bin); PPUReadState(ref bin); } private static void SQ2HardReset() { sq2_duty_cycle = 0; sq2_length_halt = false; sq2_constant_volume_envelope = false; sq2_volume_devider_period = 0; sq2_sweep_enable = false; sq2_sweep_devider_period = 0; sq2_sweep_negate = false; sq2_sweep_shift_count = 0; sq2_timer = 0; sq2_period_devider = 0; sq2_seqencer = 0; sq2_length_enabled = false; sq2_length_counter = 0; sq2_envelope_start_flag = false; sq2_envelope_devider = 0; sq2_envelope_decay_level_counter = 0; sq2_envelope = 0; sq2_sweep_counter = 0; sq2_sweep_reload = false; sq2_sweep_change = 0; sq2_valid_freq = false; sq2_output = 0; sq2_ignore_reload = false; } private static void SQ2SoftReset() { SQ2HardReset(); } private static void SQ2Clock() { sq2_period_devider--; if (sq2_period_devider > 0) { return; } sq2_period_devider = sq2_timer + 1; sq2_seqencer = (byte)((uint)(sq2_seqencer + 1) & 7u); if (sq2_length_counter > 0 && sq2_valid_freq) { if (audio_sq2_outputable) { sq2_output = sq_duty_cycle_sequences[sq2_duty_cycle][sq2_seqencer] * sq2_envelope; } } else { sq2_output = 0; } audio_signal_outputed = true; } private static void SQ2ClockLength() { if (sq2_length_counter > 0 && !sq2_length_halt) { sq2_length_counter--; if (apu_reg_access_happened && apu_reg_io_addr == 7 && apu_reg_access_w) { sq2_ignore_reload = true; } } sq2_sweep_counter--; if (sq2_sweep_counter == 0) { sq2_sweep_counter = sq2_sweep_devider_period + 1; if (sq2_sweep_enable && sq2_sweep_shift_count > 0 && sq2_valid_freq) { sq2_sweep_change = sq2_timer >> (int)sq2_sweep_shift_count; sq2_timer += (sq2_sweep_negate ? (-sq2_sweep_change) : sq2_sweep_change); SQ2CalculateValidFreq(); } } else if (sq2_sweep_reload) { sq2_sweep_counter = sq2_sweep_devider_period + 1; sq2_sweep_reload = false; } } private static void SQ2ClockEnvelope() { if (sq2_envelope_start_flag) { sq2_envelope_start_flag = false; sq2_envelope_decay_level_counter = 15; sq2_envelope_devider = (byte)(sq2_volume_devider_period + 1); } else if (sq2_envelope_devider > 0) { sq2_envelope_devider--; } else { sq2_envelope_devider = (byte)(sq2_volume_devider_period + 1); if (sq2_envelope_decay_level_counter > 0) { sq2_envelope_decay_level_counter--; } else if (sq2_length_halt) { sq2_envelope_decay_level_counter = 15; } } sq2_envelope = (sq2_constant_volume_envelope ? sq2_volume_devider_period : sq2_envelope_decay_level_counter); } private static void APUOnRegister4004() { if (apu_reg_access_w) { sq2_duty_cycle = (byte)((apu_reg_io_db & 0xC0) >> 6); sq2_volume_devider_period = (byte)(apu_reg_io_db & 0xFu); sq2_length_halt = (apu_reg_io_db & 0x20) != 0; sq2_constant_volume_envelope = (apu_reg_io_db & 0x10) != 0; sq2_envelope = (sq2_constant_volume_envelope ? sq2_volume_devider_period : sq2_envelope_decay_level_counter); } } private static void APUOnRegister4005() { if (apu_reg_access_w) { sq2_sweep_enable = (apu_reg_io_db & 0x80) == 128; sq2_sweep_devider_period = (byte)((uint)(apu_reg_io_db >> 4) & 7u); sq2_sweep_negate = (apu_reg_io_db & 8) == 8; sq2_sweep_shift_count = (byte)(apu_reg_io_db & 7u); sq2_sweep_reload = true; SQ2CalculateValidFreq(); } } private static void APUOnRegister4006() { if (apu_reg_access_w) { sq2_timer = (sq2_timer & 0xFF00) | apu_reg_io_db; SQ2CalculateValidFreq(); } } private static void APUOnRegister4007() { if (apu_reg_access_w) { sq2_timer = (sq2_timer & 0xFF) | ((apu_reg_io_db & 7) << 8); if (sq2_length_enabled && !sq2_ignore_reload) { sq2_length_counter = sq_duration_table[apu_reg_io_db >> 3]; } if (sq2_ignore_reload) { sq2_ignore_reload = false; } sq2_seqencer = 0; sq2_envelope_start_flag = true; SQ2CalculateValidFreq(); } } private static void SQ2On4015() { sq2_length_enabled = (apu_reg_io_db & 2) != 0; if (!sq2_length_enabled) { sq2_length_counter = 0; } } private static void SQ2Read4015() { if (sq2_length_counter > 0) { apu_reg_io_db = (byte)((apu_reg_io_db & 0xFDu) | 2u); } } private static void SQ2CalculateValidFreq() { sq2_valid_freq = sq2_timer >= 8 && (sq2_sweep_negate || ((sq2_timer + (sq2_timer >> (int)sq2_sweep_shift_count)) & 0x800) == 0); } private static void SQ2WriteState(ref BinaryWriter bin) { bin.Write(sq2_duty_cycle); bin.Write(sq2_length_halt); bin.Write(sq2_constant_volume_envelope); bin.Write(sq2_volume_devider_period); bin.Write(sq2_sweep_enable); bin.Write(sq2_sweep_devider_period); bin.Write(sq2_sweep_negate); bin.Write(sq2_sweep_shift_count); bin.Write(sq2_timer); bin.Write(sq2_period_devider); bin.Write(sq2_seqencer); bin.Write(sq2_length_enabled); bin.Write(sq2_length_counter); bin.Write(sq2_envelope_start_flag); bin.Write(sq2_envelope_devider); bin.Write(sq2_envelope_decay_level_counter); bin.Write(sq2_envelope); bin.Write(sq2_sweep_counter); bin.Write(sq2_sweep_reload); bin.Write(sq2_sweep_change); bin.Write(sq2_valid_freq); bin.Write(sq2_output); bin.Write(sq2_ignore_reload); } private static void SQ2ReadState(ref BinaryReader bin) { sq2_duty_cycle = bin.ReadByte(); sq2_length_halt = bin.ReadBoolean(); sq2_constant_volume_envelope = bin.ReadBoolean(); sq2_volume_devider_period = bin.ReadByte(); sq2_sweep_enable = bin.ReadBoolean(); sq2_sweep_devider_period = bin.ReadByte(); sq2_sweep_negate = bin.ReadBoolean(); sq2_sweep_shift_count = bin.ReadByte(); sq2_timer = bin.ReadInt32(); sq2_period_devider = bin.ReadInt32(); sq2_seqencer = bin.ReadByte(); sq2_length_enabled = bin.ReadBoolean(); sq2_length_counter = bin.ReadInt32(); sq2_envelope_start_flag = bin.ReadBoolean(); sq2_envelope_devider = bin.ReadByte(); sq2_envelope_decay_level_counter = bin.ReadByte(); sq2_envelope = bin.ReadByte(); sq2_sweep_counter = bin.ReadInt32(); sq2_sweep_reload = bin.ReadBoolean(); sq2_sweep_change = bin.ReadInt32(); sq2_valid_freq = bin.ReadBoolean(); sq2_output = bin.ReadInt32(); sq2_ignore_reload = bin.ReadBoolean(); } } }