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 readonly 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 readonly 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 readonly 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;

        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 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;

        public static bool IsFourPlayers;

        private static readonly 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_period;

        private static double emu_time_target_fps = 60.0;

        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);
            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<GameGenieCode> list = new List<GameGenieCode>();
                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();
            }
        }

        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;
            }
            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;
                ON = true;
                PAUSED = false;
                if (useThread)
                {
                    Tracer.WriteLine("Running in a thread ... using custom frame limiter.");
                    FrameLimiterEnabled = true;
                    currentFrame = 0;
                    mainThread = new Thread(EmuClock);
                    mainThread.Start();
                }
                MyNesMain.VideoProvider.SignalToggle(started: true);
                MyNesMain.AudioProvider.SignalToggle(started: true);
            }
            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 ..");
                mainThread.Abort();
                mainThread = null;
            }
            SaveSRAM();
            Tracer.WriteInformation("Emulation core shutdown successfully.");
            NesEmu.EmuShutdown?.Invoke(null, new EventArgs());
        }

        private static Stopwatch sw = new Stopwatch();
        private static double fixTime;
        public static ulong currentFrame;
        private static void EmuClock()
        {
            while (ON)
            {
                if (!PAUSED)
                {
                    var waitTime = GetTime() + fps_time_period + fixTime;

                    while (!ppu_frame_finished)
                        CPUClock();

                    FrameFinished();

                    fixTime = waitTime - GetTime();
                    while (fixTime > 0)
                    {
                        fixTime = waitTime - GetTime();
                    };

                    currentFrame++;

                    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);
                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;
            joypad1.Update();
            joypad2.Update();
            if (IsFourPlayers)
            {
                joypad3.Update();
                joypad4.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;
            }
        }

        private static double GetTime()
        {
            return (double)Stopwatch.GetTimestamp() / (double)Stopwatch.Frequency;
        }


        public static void SetFramePeriod(ref double period)
        {
            fps_time_period = period;
        }

        public static void RevertFramePeriod()
        {
            fps_time_period = 1 / emu_time_target_fps;
        }

        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.Supporter.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();
        }
    }
}