using System; using System.Collections.Generic; using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace Iris.GBA { internal sealed class Memory : IDisposable { private Communication _communication; private Timer _timer; private Sound _sound; private DMA _dma; private KeyInput _keyInput; private SystemControl _systemControl; private InterruptControl _interruptControl; private Video _video; private BIOS _bios; [Flags] internal enum Flag { Read8 = 1 << 0, Read16 = 1 << 1, Read32 = 1 << 2, Write8 = 1 << 3, Write16 = 1 << 4, Write32 = 1 << 5, Mirrored = 1 << 6, None = 0, AllRead = Read8 | Read16 | Read32, AllWrite = Write8 | Write16 | Write32, All = AllRead | AllWrite | Mirrored, } private const int KB = 1024; private const int EWRAM_Size = 256 * KB; private const int IWRAM_Size = 32 * KB; //private const int SRAM_Size = 64 * KB; private readonly IntPtr _ewram = Marshal.AllocHGlobal(EWRAM_Size); private readonly IntPtr _iwram = Marshal.AllocHGlobal(IWRAM_Size); //private readonly IntPtr _sram = Marshal.AllocHGlobal(SRAM_Size); private const UInt32 EWRAM_StartAddress = 0x0200_0000; private const UInt32 EWRAM_EndAddress = 0x0300_0000; private const UInt32 IWRAM_StartAddress = 0x0300_0000; private const UInt32 IWRAM_EndAddress = 0x0400_0000; //private const UInt32 SRAM_StartAddress = 0x0e00_0000; //private const UInt32 SRAM_EndAddress = 0x1000_0000; private int _romSize; private IntPtr _rom; private const UInt32 ROM_WaitState0_StartAddress = 0x0800_0000; private const UInt32 ROM_WaitState0_EndAddress = 0x0a00_0000; private const UInt32 ROM_WaitState1_StartAddress = 0x0a00_0000; private const UInt32 ROM_WaitState1_EndAddress = 0x0c00_0000; private const UInt32 ROM_WaitState2_StartAddress = 0x0c00_0000; private const UInt32 ROM_WaitState2_EndAddress = 0x0e00_0000; private const int PageSize = 1 * KB; private const int PageTableSize = 1 << 18; private readonly IntPtr[] _read8PageTable = new IntPtr[PageTableSize]; private readonly IntPtr[] _read16PageTable = new IntPtr[PageTableSize]; private readonly IntPtr[] _read32PageTable = new IntPtr[PageTableSize]; private readonly IntPtr[] _write8PageTable = new IntPtr[PageTableSize]; private readonly IntPtr[] _write16PageTable = new IntPtr[PageTableSize]; private readonly IntPtr[] _write32PageTable = new IntPtr[PageTableSize]; private bool _disposed; ~Memory() { Dispose(); } public void Dispose() { if (_disposed) return; Marshal.FreeHGlobal(_ewram); Marshal.FreeHGlobal(_iwram); //Marshal.FreeHGlobal(_sram); GC.SuppressFinalize(this); _disposed = true; } internal void Initialize(Communication communication, Timer timer, Sound sound, DMA dma, KeyInput keyInput, SystemControl systemControl, InterruptControl interruptControl, Video video, BIOS bios) { _communication = communication; _timer = timer; _sound = sound; _dma = dma; _keyInput = keyInput; _systemControl = systemControl; _interruptControl = interruptControl; _video = video; _bios = bios; Map(_ewram, EWRAM_Size, EWRAM_StartAddress, EWRAM_EndAddress, Flag.All); Map(_iwram, IWRAM_Size, IWRAM_StartAddress, IWRAM_EndAddress, Flag.All); //Map(_sram, SRAM_Size, SRAM_StartAddress, SRAM_EndAddress, Flag.Read8 | Flag.Write8 | Flag.Mirrored); } internal void ResetState() { byte[] ewramData = new byte[EWRAM_Size]; byte[] iwramData = new byte[IWRAM_Size]; //byte[] sramData = new byte[SRAM_Size]; Marshal.Copy(ewramData, 0, _ewram, EWRAM_Size); Marshal.Copy(iwramData, 0, _iwram, IWRAM_Size); //Marshal.Copy(sramData, 0, _sram, SRAM_Size); } internal void LoadState(BinaryReader reader) { byte[] ewramData = reader.ReadBytes(EWRAM_Size); byte[] iwramData = reader.ReadBytes(IWRAM_Size); //byte[] sramData = reader.ReadBytes(SRAM_Size); Marshal.Copy(ewramData, 0, _ewram, EWRAM_Size); Marshal.Copy(iwramData, 0, _iwram, IWRAM_Size); //Marshal.Copy(sramData, 0, _sram, SRAM_Size); } internal void SaveState(BinaryWriter writer) { byte[] ewramData = new byte[EWRAM_Size]; byte[] iwramData = new byte[IWRAM_Size]; //byte[] sramData = new byte[SRAM_Size]; Marshal.Copy(_ewram, ewramData, 0, EWRAM_Size); Marshal.Copy(_iwram, iwramData, 0, IWRAM_Size); //Marshal.Copy(_sram, sramData, 0, SRAM_Size); writer.Write(ewramData); writer.Write(iwramData); //writer.Write(sramData); } internal void Map(IntPtr data, int size, UInt32 startAddress, UInt32 endAddress, Flag flags) { int pageCount = size / PageSize; int startTablePageIndex = (int)(startAddress >> 10); int endPageTableIndex = (int)(endAddress >> 10); bool readable8 = flags.HasFlag(Flag.Read8); bool readable16 = flags.HasFlag(Flag.Read16); bool readable32 = flags.HasFlag(Flag.Read32); bool writable8 = flags.HasFlag(Flag.Write8); bool writable16 = flags.HasFlag(Flag.Write16); bool writable32 = flags.HasFlag(Flag.Write32); bool mirrored = flags.HasFlag(Flag.Mirrored); for (int pageTableIndex = startTablePageIndex, pageIndex = 0; pageTableIndex != endPageTableIndex; ++pageTableIndex, ++pageIndex) { if (pageIndex < pageCount) { int pageOffset = pageIndex * PageSize; IntPtr page = data + pageOffset; _read8PageTable[pageTableIndex] = readable8 ? page : IntPtr.Zero; _read16PageTable[pageTableIndex] = readable16 ? page : IntPtr.Zero; _read32PageTable[pageTableIndex] = readable32 ? page : IntPtr.Zero; _write8PageTable[pageTableIndex] = writable8 ? page : IntPtr.Zero; _write16PageTable[pageTableIndex] = writable16 ? page : IntPtr.Zero; _write32PageTable[pageTableIndex] = writable32 ? page : IntPtr.Zero; } else if (mirrored) { int pageOffset = (pageIndex % pageCount) * PageSize; IntPtr page = data + pageOffset; _read8PageTable[pageTableIndex] = readable8 ? page : IntPtr.Zero; _read16PageTable[pageTableIndex] = readable16 ? page : IntPtr.Zero; _read32PageTable[pageTableIndex] = readable32 ? page : IntPtr.Zero; _write8PageTable[pageTableIndex] = writable8 ? page : IntPtr.Zero; _write16PageTable[pageTableIndex] = writable16 ? page : IntPtr.Zero; _write32PageTable[pageTableIndex] = writable32 ? page : IntPtr.Zero; } else { _read8PageTable[pageTableIndex] = IntPtr.Zero; _read16PageTable[pageTableIndex] = IntPtr.Zero; _read32PageTable[pageTableIndex] = IntPtr.Zero; _write8PageTable[pageTableIndex] = IntPtr.Zero; _write16PageTable[pageTableIndex] = IntPtr.Zero; _write32PageTable[pageTableIndex] = IntPtr.Zero; } } if (writable8 || writable16 || writable32) { int length = pageCount * PageSize; for (int offset = 0; offset < length; ++offset) Marshal.WriteByte(data, offset, 0); } } internal void LoadROM(string filename) { Byte[] data = File.ReadAllBytes(filename); _romSize = data.Length; if (_rom != IntPtr.Zero) Marshal.FreeHGlobal(_rom); _rom = Marshal.AllocHGlobal(_romSize); Marshal.Copy(data, 0, _rom, _romSize); Map(_rom, _romSize, ROM_WaitState0_StartAddress, ROM_WaitState0_EndAddress, Flag.AllRead); Map(_rom, _romSize, ROM_WaitState1_StartAddress, ROM_WaitState1_EndAddress, Flag.AllRead); Map(_rom, _romSize, ROM_WaitState2_StartAddress, ROM_WaitState2_EndAddress, Flag.AllRead); } internal Byte Read8(UInt32 address) { address &= 0x0fff_ffff; IntPtr page = Unsafe.Add(ref MyUnSafeCommon.GetArrayDataReference(_read8PageTable), (int)(address >> 10)); if (page != IntPtr.Zero) { unsafe { // much faster than Marshal.ReadByte return Unsafe.Read((Byte*)page + (address & 0x3ff)); } } // page fault switch (address >> 24) { // BIOS case 0x0: case 0x1: return _bios!.Read8(address); // IO and registers case 0x4: { [MethodImpl(MethodImplOptions.AggressiveInlining)] static Byte GetLowByte(UInt16 value) => (Byte)value; [MethodImpl(MethodImplOptions.AggressiveInlining)] static Byte GetHighByte(UInt16 value) => (Byte)(value >> 8); UInt32 offset = address - 0x400_0000; return offset switch { 0x000 => GetLowByte(_video!.ReadRegister(Video.Register.DISPCNT)), 0x001 => GetHighByte(_video!.ReadRegister(Video.Register.DISPCNT)), 0x004 => GetLowByte(_video!.ReadRegister(Video.Register.DISPSTAT)), 0x005 => GetHighByte(_video!.ReadRegister(Video.Register.DISPSTAT)), 0x006 => GetLowByte(_video!.ReadRegister(Video.Register.VCOUNT)), 0x007 => GetHighByte(_video!.ReadRegister(Video.Register.VCOUNT)), 0x008 => GetLowByte(_video!.ReadRegister(Video.Register.BG0CNT)), 0x009 => GetHighByte(_video!.ReadRegister(Video.Register.BG0CNT)), 0x00a => GetLowByte(_video!.ReadRegister(Video.Register.BG1CNT)), 0x00b => GetHighByte(_video!.ReadRegister(Video.Register.BG1CNT)), 0x00c => GetLowByte(_video!.ReadRegister(Video.Register.BG2CNT)), 0x00d => GetHighByte(_video!.ReadRegister(Video.Register.BG2CNT)), 0x00e => GetLowByte(_video!.ReadRegister(Video.Register.BG3CNT)), 0x00f => GetHighByte(_video!.ReadRegister(Video.Register.BG3CNT)), 0x048 => GetLowByte(_video!.ReadRegister(Video.Register.WININ)), 0x049 => GetHighByte(_video!.ReadRegister(Video.Register.WININ)), 0x04a => GetLowByte(_video!.ReadRegister(Video.Register.WINOUT)), 0x04b => GetHighByte(_video!.ReadRegister(Video.Register.WINOUT)), 0x050 => GetLowByte(_video!.ReadRegister(Video.Register.BLDCNT)), 0x051 => GetHighByte(_video!.ReadRegister(Video.Register.BLDCNT)), 0x052 => GetLowByte(_video!.ReadRegister(Video.Register.BLDALPHA)), 0x053 => GetHighByte(_video!.ReadRegister(Video.Register.BLDALPHA)), 0x060 => GetLowByte(_sound!.ReadRegister(Sound.Register.SOUND1CNT_L)), 0x061 => GetHighByte(_sound!.ReadRegister(Sound.Register.SOUND1CNT_L)), 0x062 => GetLowByte(_sound!.ReadRegister(Sound.Register.SOUND1CNT_H)), 0x063 => GetHighByte(_sound!.ReadRegister(Sound.Register.SOUND1CNT_H)), 0x064 => GetLowByte(_sound!.ReadRegister(Sound.Register.SOUND1CNT_X)), 0x065 => GetHighByte(_sound!.ReadRegister(Sound.Register.SOUND1CNT_X)), 0x068 => GetLowByte(_sound!.ReadRegister(Sound.Register.SOUND2CNT_L)), 0x069 => GetHighByte(_sound!.ReadRegister(Sound.Register.SOUND2CNT_L)), 0x06c => GetLowByte(_sound!.ReadRegister(Sound.Register.SOUND2CNT_H)), 0x06d => GetHighByte(_sound!.ReadRegister(Sound.Register.SOUND2CNT_H)), 0x070 => GetLowByte(_sound!.ReadRegister(Sound.Register.SOUND3CNT_L)), 0x071 => GetHighByte(_sound!.ReadRegister(Sound.Register.SOUND3CNT_L)), 0x072 => GetLowByte(_sound!.ReadRegister(Sound.Register.SOUND3CNT_H)), 0x073 => GetHighByte(_sound!.ReadRegister(Sound.Register.SOUND3CNT_H)), 0x074 => GetLowByte(_sound!.ReadRegister(Sound.Register.SOUND3CNT_X)), 0x075 => GetHighByte(_sound!.ReadRegister(Sound.Register.SOUND3CNT_X)), 0x078 => GetLowByte(_sound!.ReadRegister(Sound.Register.SOUND4CNT_L)), 0x079 => GetHighByte(_sound!.ReadRegister(Sound.Register.SOUND4CNT_L)), 0x07c => GetLowByte(_sound!.ReadRegister(Sound.Register.SOUND4CNT_H)), 0x07d => GetHighByte(_sound!.ReadRegister(Sound.Register.SOUND4CNT_H)), 0x080 => GetLowByte(_sound!.ReadRegister(Sound.Register.SOUNDCNT_L)), 0x081 => GetHighByte(_sound!.ReadRegister(Sound.Register.SOUNDCNT_L)), 0x082 => GetLowByte(_sound!.ReadRegister(Sound.Register.SOUNDCNT_H)), 0x083 => GetHighByte(_sound!.ReadRegister(Sound.Register.SOUNDCNT_H)), 0x084 => GetLowByte(_sound!.ReadRegister(Sound.Register.SOUNDCNT_X)), 0x085 => GetHighByte(_sound!.ReadRegister(Sound.Register.SOUNDCNT_X)), 0x088 => GetLowByte(_sound!.ReadRegister(Sound.Register.SOUNDBIAS)), 0x089 => GetHighByte(_sound!.ReadRegister(Sound.Register.SOUNDBIAS)), 0x090 => GetLowByte(_sound!.ReadRegister(Sound.Register.WAVE_RAM0_L)), 0x091 => GetHighByte(_sound!.ReadRegister(Sound.Register.WAVE_RAM0_L)), 0x092 => GetLowByte(_sound!.ReadRegister(Sound.Register.WAVE_RAM0_H)), 0x093 => GetHighByte(_sound!.ReadRegister(Sound.Register.WAVE_RAM0_H)), 0x094 => GetLowByte(_sound!.ReadRegister(Sound.Register.WAVE_RAM1_L)), 0x095 => GetHighByte(_sound!.ReadRegister(Sound.Register.WAVE_RAM1_L)), 0x096 => GetLowByte(_sound!.ReadRegister(Sound.Register.WAVE_RAM1_H)), 0x097 => GetHighByte(_sound!.ReadRegister(Sound.Register.WAVE_RAM1_H)), 0x098 => GetLowByte(_sound!.ReadRegister(Sound.Register.WAVE_RAM2_L)), 0x099 => GetHighByte(_sound!.ReadRegister(Sound.Register.WAVE_RAM2_L)), 0x09a => GetLowByte(_sound!.ReadRegister(Sound.Register.WAVE_RAM2_H)), 0x09b => GetHighByte(_sound!.ReadRegister(Sound.Register.WAVE_RAM2_H)), 0x09c => GetLowByte(_sound!.ReadRegister(Sound.Register.WAVE_RAM3_L)), 0x09d => GetHighByte(_sound!.ReadRegister(Sound.Register.WAVE_RAM3_L)), 0x09e => GetLowByte(_sound!.ReadRegister(Sound.Register.WAVE_RAM3_H)), 0x09f => GetHighByte(_sound!.ReadRegister(Sound.Register.WAVE_RAM3_H)), 0x0ba => GetLowByte(_dma!.ReadRegister(DMA.Register.DMA0CNT_H)), 0x0bb => GetHighByte(_dma!.ReadRegister(DMA.Register.DMA0CNT_H)), 0x0c6 => GetLowByte(_dma!.ReadRegister(DMA.Register.DMA1CNT_H)), 0x0c7 => GetHighByte(_dma!.ReadRegister(DMA.Register.DMA1CNT_H)), 0x0d2 => GetLowByte(_dma!.ReadRegister(DMA.Register.DMA2CNT_H)), 0x0d3 => GetHighByte(_dma!.ReadRegister(DMA.Register.DMA2CNT_H)), 0x0de => GetLowByte(_dma!.ReadRegister(DMA.Register.DMA3CNT_H)), 0x0df => GetHighByte(_dma!.ReadRegister(DMA.Register.DMA3CNT_H)), 0x100 => GetLowByte(_timer!.ReadRegister(Timer.Register.TM0CNT_L)), 0x101 => GetHighByte(_timer!.ReadRegister(Timer.Register.TM0CNT_L)), 0x102 => GetLowByte(_timer!.ReadRegister(Timer.Register.TM0CNT_H)), 0x103 => GetHighByte(_timer!.ReadRegister(Timer.Register.TM0CNT_H)), 0x104 => GetLowByte(_timer!.ReadRegister(Timer.Register.TM1CNT_L)), 0x105 => GetHighByte(_timer!.ReadRegister(Timer.Register.TM1CNT_L)), 0x106 => GetLowByte(_timer!.ReadRegister(Timer.Register.TM1CNT_H)), 0x107 => GetHighByte(_timer!.ReadRegister(Timer.Register.TM1CNT_H)), 0x108 => GetLowByte(_timer!.ReadRegister(Timer.Register.TM2CNT_L)), 0x109 => GetHighByte(_timer!.ReadRegister(Timer.Register.TM2CNT_L)), 0x10a => GetLowByte(_timer!.ReadRegister(Timer.Register.TM2CNT_H)), 0x10b => GetHighByte(_timer!.ReadRegister(Timer.Register.TM2CNT_H)), 0x10c => GetLowByte(_timer!.ReadRegister(Timer.Register.TM3CNT_L)), 0x10d => GetHighByte(_timer!.ReadRegister(Timer.Register.TM3CNT_L)), 0x10e => GetLowByte(_timer!.ReadRegister(Timer.Register.TM3CNT_H)), 0x10f => GetHighByte(_timer!.ReadRegister(Timer.Register.TM3CNT_H)), 0x120 => GetLowByte(_communication!.ReadRegister(Communication.Register.SIODATA0)), 0x121 => GetHighByte(_communication!.ReadRegister(Communication.Register.SIODATA0)), 0x122 => GetLowByte(_communication!.ReadRegister(Communication.Register.SIODATA1)), 0x123 => GetHighByte(_communication!.ReadRegister(Communication.Register.SIODATA1)), 0x124 => GetLowByte(_communication!.ReadRegister(Communication.Register.SIODATA2)), 0x125 => GetHighByte(_communication!.ReadRegister(Communication.Register.SIODATA2)), 0x126 => GetLowByte(_communication!.ReadRegister(Communication.Register.SIODATA3)), 0x127 => GetHighByte(_communication!.ReadRegister(Communication.Register.SIODATA3)), 0x128 => GetLowByte(_communication!.ReadRegister(Communication.Register.SIOCNT)), 0x129 => GetHighByte(_communication!.ReadRegister(Communication.Register.SIOCNT)), 0x12a => GetLowByte(_communication!.ReadRegister(Communication.Register.SIODATA_SEND)), 0x12b => GetHighByte(_communication!.ReadRegister(Communication.Register.SIODATA_SEND)), 0x130 => GetLowByte(_keyInput!.ReadRegister(KeyInput.Register.KEYINPUT)), 0x131 => GetHighByte(_keyInput!.ReadRegister(KeyInput.Register.KEYINPUT)), 0x132 => GetLowByte(_keyInput!.ReadRegister(KeyInput.Register.KEYCNT)), 0x133 => GetHighByte(_keyInput!.ReadRegister(KeyInput.Register.KEYCNT)), 0x134 => GetLowByte(_communication!.ReadRegister(Communication.Register.RCNT)), 0x135 => GetHighByte(_communication!.ReadRegister(Communication.Register.RCNT)), 0x200 => GetLowByte(_interruptControl!.ReadRegister(InterruptControl.Register.IE)), 0x201 => GetHighByte(_interruptControl!.ReadRegister(InterruptControl.Register.IE)), 0x202 => GetLowByte(_interruptControl!.ReadRegister(InterruptControl.Register.IF)), 0x203 => GetHighByte(_interruptControl!.ReadRegister(InterruptControl.Register.IF)), 0x204 => GetLowByte(_systemControl!.ReadRegister(SystemControl.Register.WAITCNT)), 0x205 => GetHighByte(_systemControl!.ReadRegister(SystemControl.Register.WAITCNT)), 0x208 => GetLowByte(_interruptControl!.ReadRegister(InterruptControl.Register.IME)), 0x209 => GetHighByte(_interruptControl!.ReadRegister(InterruptControl.Register.IME)), 0x300 => GetLowByte(_systemControl!.ReadRegister(SystemControl.Register.SYSCNT_UND0)), 0x301 => GetHighByte(_systemControl!.ReadRegister(SystemControl.Register.SYSCNT_UND0)), _ => 0, }; } // ROM wait state 0 case 0x8: case 0x9: { UInt32 offset = address - ROM_WaitState0_StartAddress; if (offset < _romSize) { unsafe { // much faster than Marshal.ReadByte return Unsafe.Read((Byte*)_rom + offset); } } } break; // ROM wait state 1 case 0xa: case 0xb: { UInt32 offset = address - ROM_WaitState1_StartAddress; if (offset < _romSize) { unsafe { // much faster than Marshal.ReadByte return Unsafe.Read((Byte*)_rom + offset); } } } break; // ROM wait state 2 case 0xc: case 0xd: { UInt32 offset = address - ROM_WaitState2_StartAddress; if (offset < _romSize) { unsafe { // much faster than Marshal.ReadByte return Unsafe.Read((Byte*)_rom + offset); } } } break; // SRAM/Flash case 0xe: return address switch { 0xe00_0000 => 0x62, 0xe00_0001 => 0x13, _ => 0, }; } return 0; } internal UInt16 Read16(UInt32 address) { address &= 0x0fff_fffe; IntPtr page = Unsafe.Add(ref MyUnSafeCommon.GetArrayDataReference(_read16PageTable), (int)(address >> 10)); if (page != IntPtr.Zero) { unsafe { // much faster than Marshal.ReadInt16 return Unsafe.Read((Byte*)page + (address & 0x3ff)); } } // page fault switch (address >> 24) { // BIOS case 0x0: case 0x1: return _bios!.Read16(address); // IO and registers case 0x4: { UInt32 offset = address - 0x400_0000; return offset switch { 0x000 => _video!.ReadRegister(Video.Register.DISPCNT), 0x004 => _video!.ReadRegister(Video.Register.DISPSTAT), 0x006 => _video!.ReadRegister(Video.Register.VCOUNT), 0x008 => _video!.ReadRegister(Video.Register.BG0CNT), 0x00a => _video!.ReadRegister(Video.Register.BG1CNT), 0x00c => _video!.ReadRegister(Video.Register.BG2CNT), 0x00e => _video!.ReadRegister(Video.Register.BG3CNT), 0x048 => _video!.ReadRegister(Video.Register.WININ), 0x04a => _video!.ReadRegister(Video.Register.WINOUT), 0x050 => _video!.ReadRegister(Video.Register.BLDCNT), 0x052 => _video!.ReadRegister(Video.Register.BLDALPHA), 0x060 => _sound!.ReadRegister(Sound.Register.SOUND1CNT_L), 0x062 => _sound!.ReadRegister(Sound.Register.SOUND1CNT_H), 0x064 => _sound!.ReadRegister(Sound.Register.SOUND1CNT_X), 0x068 => _sound!.ReadRegister(Sound.Register.SOUND2CNT_L), 0x06c => _sound!.ReadRegister(Sound.Register.SOUND2CNT_H), 0x070 => _sound!.ReadRegister(Sound.Register.SOUND3CNT_L), 0x072 => _sound!.ReadRegister(Sound.Register.SOUND3CNT_H), 0x074 => _sound!.ReadRegister(Sound.Register.SOUND3CNT_X), 0x078 => _sound!.ReadRegister(Sound.Register.SOUND4CNT_L), 0x07c => _sound!.ReadRegister(Sound.Register.SOUND4CNT_H), 0x080 => _sound!.ReadRegister(Sound.Register.SOUNDCNT_L), 0x082 => _sound!.ReadRegister(Sound.Register.SOUNDCNT_H), 0x084 => _sound!.ReadRegister(Sound.Register.SOUNDCNT_X), 0x088 => _sound!.ReadRegister(Sound.Register.SOUNDBIAS), 0x090 => _sound!.ReadRegister(Sound.Register.WAVE_RAM0_L), 0x092 => _sound!.ReadRegister(Sound.Register.WAVE_RAM0_H), 0x094 => _sound!.ReadRegister(Sound.Register.WAVE_RAM1_L), 0x096 => _sound!.ReadRegister(Sound.Register.WAVE_RAM1_H), 0x098 => _sound!.ReadRegister(Sound.Register.WAVE_RAM2_L), 0x09a => _sound!.ReadRegister(Sound.Register.WAVE_RAM2_H), 0x09c => _sound!.ReadRegister(Sound.Register.WAVE_RAM3_L), 0x09e => _sound!.ReadRegister(Sound.Register.WAVE_RAM3_H), 0x0ba => _dma!.ReadRegister(DMA.Register.DMA0CNT_H), 0x0c6 => _dma!.ReadRegister(DMA.Register.DMA1CNT_H), 0x0d2 => _dma!.ReadRegister(DMA.Register.DMA2CNT_H), 0x0de => _dma!.ReadRegister(DMA.Register.DMA3CNT_H), 0x100 => _timer!.ReadRegister(Timer.Register.TM0CNT_L), 0x102 => _timer!.ReadRegister(Timer.Register.TM0CNT_H), 0x104 => _timer!.ReadRegister(Timer.Register.TM1CNT_L), 0x106 => _timer!.ReadRegister(Timer.Register.TM1CNT_H), 0x108 => _timer!.ReadRegister(Timer.Register.TM2CNT_L), 0x10a => _timer!.ReadRegister(Timer.Register.TM2CNT_H), 0x10c => _timer!.ReadRegister(Timer.Register.TM3CNT_L), 0x10e => _timer!.ReadRegister(Timer.Register.TM3CNT_H), 0x120 => _communication!.ReadRegister(Communication.Register.SIODATA0), 0x122 => _communication!.ReadRegister(Communication.Register.SIODATA1), 0x124 => _communication!.ReadRegister(Communication.Register.SIODATA2), 0x126 => _communication!.ReadRegister(Communication.Register.SIODATA3), 0x128 => _communication!.ReadRegister(Communication.Register.SIOCNT), 0x12a => _communication!.ReadRegister(Communication.Register.SIODATA_SEND), 0x130 => _keyInput!.ReadRegister(KeyInput.Register.KEYINPUT), 0x132 => _keyInput!.ReadRegister(KeyInput.Register.KEYCNT), 0x134 => _communication!.ReadRegister(Communication.Register.RCNT), 0x200 => _interruptControl!.ReadRegister(InterruptControl.Register.IE), 0x202 => _interruptControl!.ReadRegister(InterruptControl.Register.IF), 0x204 => _systemControl!.ReadRegister(SystemControl.Register.WAITCNT), 0x208 => _interruptControl!.ReadRegister(InterruptControl.Register.IME), 0x300 => _systemControl!.ReadRegister(SystemControl.Register.SYSCNT_UND0), _ => 0, }; } // ROM wait state 0 case 0x8: case 0x9: { UInt32 offset = address - ROM_WaitState0_StartAddress; if (offset < _romSize) { unsafe { // much faster than Marshal.ReadInt16 return Unsafe.Read((Byte*)_rom + offset); } } } break; // ROM wait state 1 case 0xa: case 0xb: { UInt32 offset = address - ROM_WaitState1_StartAddress; if (offset < _romSize) { unsafe { // much faster than Marshal.ReadInt16 return Unsafe.Read((Byte*)_rom + offset); } } } break; // ROM wait state 2 case 0xc: case 0xd: { UInt32 offset = address - ROM_WaitState2_StartAddress; if (offset < _romSize) { unsafe { // much faster than Marshal.ReadInt16 return Unsafe.Read((Byte*)_rom + offset); } } } break; } return 0; } internal UInt32 Read32(UInt32 address) { address &= 0x0fff_fffc; IntPtr page = Unsafe.Add(ref MyUnSafeCommon.GetArrayDataReference(_read32PageTable), (int)(address >> 10)); if (page != IntPtr.Zero) { unsafe { // much faster than Marshal.ReadInt32 return Unsafe.Read((Byte*)page + (address & 0x3ff)); } } // page fault switch (address >> 24) { // BIOS case 0x0: case 0x1: return _bios!.Read32(address); // IO and registers case 0x4: { UInt32 offset = address - 0x400_0000; return offset switch { 0x000 => _video!.ReadRegister(Video.Register.DISPCNT), 0x004 => (UInt32)((_video!.ReadRegister(Video.Register.VCOUNT) << 16) | _video.ReadRegister(Video.Register.DISPSTAT)), 0x008 => (UInt32)((_video!.ReadRegister(Video.Register.BG1CNT) << 16) | _video.ReadRegister(Video.Register.BG0CNT)), 0x00c => (UInt32)((_video!.ReadRegister(Video.Register.BG3CNT) << 16) | _video.ReadRegister(Video.Register.BG2CNT)), 0x0b8 => (UInt32)(_dma!.ReadRegister(DMA.Register.DMA0CNT_H) << 16), 0x0c4 => (UInt32)(_dma!.ReadRegister(DMA.Register.DMA1CNT_H) << 16), 0x0d0 => (UInt32)(_dma!.ReadRegister(DMA.Register.DMA2CNT_H) << 16), 0x0dc => (UInt32)(_dma!.ReadRegister(DMA.Register.DMA3CNT_H) << 16), 0x150 => (UInt32)((_communication.ReadRegister(Communication.Register.JOY_RECV_H) << 16) | _communication.ReadRegister(Communication.Register.JOY_RECV_L)), 0x200 => (UInt32)((_interruptControl!.ReadRegister(InterruptControl.Register.IF) << 16) | _interruptControl.ReadRegister(InterruptControl.Register.IE)), 0x208 => _interruptControl!.ReadRegister(InterruptControl.Register.IME), _ => 0, }; } // ROM wait state 0 case 0x8: case 0x9: { UInt32 offset = address - ROM_WaitState0_StartAddress; if (offset < _romSize) { unsafe { // much faster than Marshal.ReadInt32 return Unsafe.Read((Byte*)_rom + offset); } } } break; // ROM wait state 1 case 0xa: case 0xb: { UInt32 offset = address - ROM_WaitState1_StartAddress; if (offset < _romSize) { unsafe { // much faster than Marshal.ReadInt32 return Unsafe.Read((Byte*)_rom + offset); } } } break; // ROM wait state 2 case 0xc: case 0xd: { UInt32 offset = address - ROM_WaitState2_StartAddress; if (offset < _romSize) { unsafe { // much faster than Marshal.ReadInt32 return Unsafe.Read((Byte*)_rom + offset); } } } break; } return 0; } internal enum RegisterWriteMode { LowByte, HighByte, HalfWord } internal static void WriteRegisterHelper(ref UInt16 registerValue, UInt16 value, RegisterWriteMode mode) { switch (mode) { case RegisterWriteMode.LowByte: registerValue = (UInt16)((registerValue & 0xff00) | value); break; case RegisterWriteMode.HighByte: registerValue = (UInt16)((registerValue & 0x00ff) | (value << 8)); break; case RegisterWriteMode.HalfWord: registerValue = value; break; } } internal void Write8(UInt32 address, Byte value) { address &= 0x0fff_ffff; IntPtr page = Unsafe.Add(ref MyUnSafeCommon.GetArrayDataReference(_write8PageTable), (int)(address >> 10)); if (page != IntPtr.Zero) { unsafe { // much faster than Marshal.WriteByte Unsafe.Write((Byte*)page + (address & 0x3ff), value); } return; } // page fault switch (address >> 24) { // BIOS case 0x0: case 0x1: break; // IO and registers case 0x4: { UInt32 offset = address - 0x400_0000; switch (offset) { case 0x000: _video!.WriteRegister(Video.Register.DISPCNT, value, RegisterWriteMode.LowByte); break; case 0x001: _video!.WriteRegister(Video.Register.DISPCNT, value, RegisterWriteMode.HighByte); break; case 0x004: _video!.WriteRegister(Video.Register.DISPSTAT, value, RegisterWriteMode.LowByte); break; case 0x005: _video!.WriteRegister(Video.Register.DISPSTAT, value, RegisterWriteMode.HighByte); break; case 0x008: _video!.WriteRegister(Video.Register.BG0CNT, value, RegisterWriteMode.LowByte); break; case 0x009: _video!.WriteRegister(Video.Register.BG0CNT, value, RegisterWriteMode.HighByte); break; case 0x00a: _video!.WriteRegister(Video.Register.BG1CNT, value, RegisterWriteMode.LowByte); break; case 0x00b: _video!.WriteRegister(Video.Register.BG1CNT, value, RegisterWriteMode.HighByte); break; case 0x00c: _video!.WriteRegister(Video.Register.BG2CNT, value, RegisterWriteMode.LowByte); break; case 0x00d: _video!.WriteRegister(Video.Register.BG2CNT, value, RegisterWriteMode.HighByte); break; case 0x00e: _video!.WriteRegister(Video.Register.BG3CNT, value, RegisterWriteMode.LowByte); break; case 0x00f: _video!.WriteRegister(Video.Register.BG3CNT, value, RegisterWriteMode.HighByte); break; case 0x010: _video!.WriteRegister(Video.Register.BG0HOFS, value, RegisterWriteMode.LowByte); break; case 0x011: _video!.WriteRegister(Video.Register.BG0HOFS, value, RegisterWriteMode.HighByte); break; case 0x012: _video!.WriteRegister(Video.Register.BG0VOFS, value, RegisterWriteMode.LowByte); break; case 0x013: _video!.WriteRegister(Video.Register.BG0VOFS, value, RegisterWriteMode.HighByte); break; case 0x014: _video!.WriteRegister(Video.Register.BG1HOFS, value, RegisterWriteMode.LowByte); break; case 0x015: _video!.WriteRegister(Video.Register.BG1HOFS, value, RegisterWriteMode.HighByte); break; case 0x016: _video!.WriteRegister(Video.Register.BG1VOFS, value, RegisterWriteMode.LowByte); break; case 0x017: _video!.WriteRegister(Video.Register.BG1VOFS, value, RegisterWriteMode.HighByte); break; case 0x018: _video!.WriteRegister(Video.Register.BG2HOFS, value, RegisterWriteMode.LowByte); break; case 0x019: _video!.WriteRegister(Video.Register.BG2HOFS, value, RegisterWriteMode.HighByte); break; case 0x01a: _video!.WriteRegister(Video.Register.BG2VOFS, value, RegisterWriteMode.LowByte); break; case 0x01b: _video!.WriteRegister(Video.Register.BG2VOFS, value, RegisterWriteMode.HighByte); break; case 0x01c: _video!.WriteRegister(Video.Register.BG3HOFS, value, RegisterWriteMode.LowByte); break; case 0x01d: _video!.WriteRegister(Video.Register.BG3HOFS, value, RegisterWriteMode.HighByte); break; case 0x01e: _video!.WriteRegister(Video.Register.BG3VOFS, value, RegisterWriteMode.LowByte); break; case 0x01f: _video!.WriteRegister(Video.Register.BG3VOFS, value, RegisterWriteMode.HighByte); break; case 0x040: _video!.WriteRegister(Video.Register.WIN0H, value, RegisterWriteMode.LowByte); break; case 0x041: _video!.WriteRegister(Video.Register.WIN0H, value, RegisterWriteMode.HighByte); break; case 0x042: _video!.WriteRegister(Video.Register.WIN1H, value, RegisterWriteMode.LowByte); break; case 0x043: _video!.WriteRegister(Video.Register.WIN1H, value, RegisterWriteMode.HighByte); break; case 0x044: _video!.WriteRegister(Video.Register.WIN0V, value, RegisterWriteMode.LowByte); break; case 0x045: _video!.WriteRegister(Video.Register.WIN0V, value, RegisterWriteMode.HighByte); break; case 0x046: _video!.WriteRegister(Video.Register.WIN1V, value, RegisterWriteMode.LowByte); break; case 0x047: _video!.WriteRegister(Video.Register.WIN1V, value, RegisterWriteMode.HighByte); break; case 0x048: _video!.WriteRegister(Video.Register.WININ, value, RegisterWriteMode.LowByte); break; case 0x049: _video!.WriteRegister(Video.Register.WININ, value, RegisterWriteMode.HighByte); break; case 0x04a: _video!.WriteRegister(Video.Register.WINOUT, value, RegisterWriteMode.LowByte); break; case 0x04b: _video!.WriteRegister(Video.Register.WINOUT, value, RegisterWriteMode.HighByte); break; case 0x04c: _video!.WriteRegister(Video.Register.MOSAIC, value, RegisterWriteMode.LowByte); break; case 0x04d: _video!.WriteRegister(Video.Register.MOSAIC, value, RegisterWriteMode.HighByte); break; case 0x050: _video!.WriteRegister(Video.Register.BLDCNT, value, RegisterWriteMode.LowByte); break; case 0x051: _video!.WriteRegister(Video.Register.BLDCNT, value, RegisterWriteMode.HighByte); break; case 0x052: _video!.WriteRegister(Video.Register.BLDALPHA, value, RegisterWriteMode.LowByte); break; case 0x053: _video!.WriteRegister(Video.Register.BLDALPHA, value, RegisterWriteMode.HighByte); break; case 0x054: _video!.WriteRegister(Video.Register.BLDY, value, RegisterWriteMode.LowByte); break; case 0x055: _video!.WriteRegister(Video.Register.BLDY, value, RegisterWriteMode.HighByte); break; case 0x060: _sound!.WriteRegister(Sound.Register.SOUND1CNT_L, value, RegisterWriteMode.LowByte); break; case 0x061: _sound!.WriteRegister(Sound.Register.SOUND1CNT_L, value, RegisterWriteMode.HighByte); break; case 0x062: _sound!.WriteRegister(Sound.Register.SOUND1CNT_H, value, RegisterWriteMode.LowByte); break; case 0x063: _sound!.WriteRegister(Sound.Register.SOUND1CNT_H, value, RegisterWriteMode.HighByte); break; case 0x064: _sound!.WriteRegister(Sound.Register.SOUND1CNT_X, value, RegisterWriteMode.LowByte); break; case 0x065: _sound!.WriteRegister(Sound.Register.SOUND1CNT_X, value, RegisterWriteMode.HighByte); break; case 0x068: _sound!.WriteRegister(Sound.Register.SOUND2CNT_L, value, RegisterWriteMode.LowByte); break; case 0x069: _sound!.WriteRegister(Sound.Register.SOUND2CNT_L, value, RegisterWriteMode.HighByte); break; case 0x06c: _sound!.WriteRegister(Sound.Register.SOUND2CNT_H, value, RegisterWriteMode.LowByte); break; case 0x06d: _sound!.WriteRegister(Sound.Register.SOUND2CNT_H, value, RegisterWriteMode.HighByte); break; case 0x070: _sound!.WriteRegister(Sound.Register.SOUND3CNT_L, value, RegisterWriteMode.LowByte); break; case 0x071: _sound!.WriteRegister(Sound.Register.SOUND3CNT_L, value, RegisterWriteMode.HighByte); break; case 0x072: _sound!.WriteRegister(Sound.Register.SOUND3CNT_H, value, RegisterWriteMode.LowByte); break; case 0x073: _sound!.WriteRegister(Sound.Register.SOUND3CNT_H, value, RegisterWriteMode.HighByte); break; case 0x074: _sound!.WriteRegister(Sound.Register.SOUND3CNT_X, value, RegisterWriteMode.LowByte); break; case 0x075: _sound!.WriteRegister(Sound.Register.SOUND3CNT_X, value, RegisterWriteMode.HighByte); break; case 0x078: _sound!.WriteRegister(Sound.Register.SOUND4CNT_L, value, RegisterWriteMode.LowByte); break; case 0x079: _sound!.WriteRegister(Sound.Register.SOUND4CNT_L, value, RegisterWriteMode.HighByte); break; case 0x07c: _sound!.WriteRegister(Sound.Register.SOUND4CNT_H, value, RegisterWriteMode.LowByte); break; case 0x07d: _sound!.WriteRegister(Sound.Register.SOUND4CNT_H, value, RegisterWriteMode.HighByte); break; case 0x080: _sound!.WriteRegister(Sound.Register.SOUNDCNT_L, value, RegisterWriteMode.LowByte); break; case 0x081: _sound!.WriteRegister(Sound.Register.SOUNDCNT_L, value, RegisterWriteMode.HighByte); break; case 0x082: _sound!.WriteRegister(Sound.Register.SOUNDCNT_H, value, RegisterWriteMode.LowByte); break; case 0x083: _sound!.WriteRegister(Sound.Register.SOUNDCNT_H, value, RegisterWriteMode.HighByte); break; case 0x084: _sound!.WriteRegister(Sound.Register.SOUNDCNT_X, value, RegisterWriteMode.LowByte); break; case 0x085: _sound!.WriteRegister(Sound.Register.SOUNDCNT_X, value, RegisterWriteMode.HighByte); break; case 0x088: _sound!.WriteRegister(Sound.Register.SOUNDBIAS, value, RegisterWriteMode.LowByte); break; case 0x089: _sound!.WriteRegister(Sound.Register.SOUNDBIAS, value, RegisterWriteMode.HighByte); break; case 0x090: _sound!.WriteRegister(Sound.Register.WAVE_RAM0_L, value, RegisterWriteMode.LowByte); break; case 0x091: _sound!.WriteRegister(Sound.Register.WAVE_RAM0_L, value, RegisterWriteMode.HighByte); break; case 0x092: _sound!.WriteRegister(Sound.Register.WAVE_RAM0_H, value, RegisterWriteMode.LowByte); break; case 0x093: _sound!.WriteRegister(Sound.Register.WAVE_RAM0_H, value, RegisterWriteMode.HighByte); break; case 0x094: _sound!.WriteRegister(Sound.Register.WAVE_RAM1_L, value, RegisterWriteMode.LowByte); break; case 0x095: _sound!.WriteRegister(Sound.Register.WAVE_RAM1_L, value, RegisterWriteMode.HighByte); break; case 0x096: _sound!.WriteRegister(Sound.Register.WAVE_RAM1_H, value, RegisterWriteMode.LowByte); break; case 0x097: _sound!.WriteRegister(Sound.Register.WAVE_RAM1_H, value, RegisterWriteMode.HighByte); break; case 0x098: _sound!.WriteRegister(Sound.Register.WAVE_RAM2_L, value, RegisterWriteMode.LowByte); break; case 0x099: _sound!.WriteRegister(Sound.Register.WAVE_RAM2_L, value, RegisterWriteMode.HighByte); break; case 0x09a: _sound!.WriteRegister(Sound.Register.WAVE_RAM2_H, value, RegisterWriteMode.LowByte); break; case 0x09b: _sound!.WriteRegister(Sound.Register.WAVE_RAM2_H, value, RegisterWriteMode.HighByte); break; case 0x09c: _sound!.WriteRegister(Sound.Register.WAVE_RAM3_L, value, RegisterWriteMode.LowByte); break; case 0x09d: _sound!.WriteRegister(Sound.Register.WAVE_RAM3_L, value, RegisterWriteMode.HighByte); break; case 0x09e: _sound!.WriteRegister(Sound.Register.WAVE_RAM3_H, value, RegisterWriteMode.LowByte); break; case 0x09f: _sound!.WriteRegister(Sound.Register.WAVE_RAM3_H, value, RegisterWriteMode.HighByte); break; case 0x0b0: _dma!.WriteRegister(DMA.Register.DMA0SAD_L, value, RegisterWriteMode.LowByte); break; case 0x0b1: _dma!.WriteRegister(DMA.Register.DMA0SAD_L, value, RegisterWriteMode.HighByte); break; case 0x0b2: _dma!.WriteRegister(DMA.Register.DMA0SAD_H, value, RegisterWriteMode.LowByte); break; case 0x0b3: _dma!.WriteRegister(DMA.Register.DMA0SAD_H, value, RegisterWriteMode.HighByte); break; case 0x0b4: _dma!.WriteRegister(DMA.Register.DMA0DAD_L, value, RegisterWriteMode.LowByte); break; case 0x0b5: _dma!.WriteRegister(DMA.Register.DMA0DAD_L, value, RegisterWriteMode.HighByte); break; case 0x0b6: _dma!.WriteRegister(DMA.Register.DMA0DAD_H, value, RegisterWriteMode.LowByte); break; case 0x0b7: _dma!.WriteRegister(DMA.Register.DMA0DAD_H, value, RegisterWriteMode.HighByte); break; case 0x0b8: _dma!.WriteRegister(DMA.Register.DMA0CNT_L, value, RegisterWriteMode.LowByte); break; case 0x0b9: _dma!.WriteRegister(DMA.Register.DMA0CNT_L, value, RegisterWriteMode.HighByte); break; case 0x0ba: _dma!.WriteRegister(DMA.Register.DMA0CNT_H, value, RegisterWriteMode.LowByte); break; case 0x0bb: _dma!.WriteRegister(DMA.Register.DMA0CNT_H, value, RegisterWriteMode.HighByte); break; case 0x0bc: _dma!.WriteRegister(DMA.Register.DMA1SAD_L, value, RegisterWriteMode.LowByte); break; case 0x0bd: _dma!.WriteRegister(DMA.Register.DMA1SAD_L, value, RegisterWriteMode.HighByte); break; case 0x0be: _dma!.WriteRegister(DMA.Register.DMA1SAD_H, value, RegisterWriteMode.LowByte); break; case 0x0bf: _dma!.WriteRegister(DMA.Register.DMA1SAD_H, value, RegisterWriteMode.HighByte); break; case 0x0c0: _dma!.WriteRegister(DMA.Register.DMA1DAD_L, value, RegisterWriteMode.LowByte); break; case 0x0c1: _dma!.WriteRegister(DMA.Register.DMA1DAD_L, value, RegisterWriteMode.HighByte); break; case 0x0c2: _dma!.WriteRegister(DMA.Register.DMA1DAD_H, value, RegisterWriteMode.LowByte); break; case 0x0c3: _dma!.WriteRegister(DMA.Register.DMA1DAD_H, value, RegisterWriteMode.HighByte); break; case 0x0c4: _dma!.WriteRegister(DMA.Register.DMA1CNT_L, value, RegisterWriteMode.LowByte); break; case 0x0c5: _dma!.WriteRegister(DMA.Register.DMA1CNT_L, value, RegisterWriteMode.HighByte); break; case 0x0c6: _dma!.WriteRegister(DMA.Register.DMA1CNT_H, value, RegisterWriteMode.LowByte); break; case 0x0c7: _dma!.WriteRegister(DMA.Register.DMA1CNT_H, value, RegisterWriteMode.HighByte); break; case 0x0c8: _dma!.WriteRegister(DMA.Register.DMA2SAD_L, value, RegisterWriteMode.LowByte); break; case 0x0c9: _dma!.WriteRegister(DMA.Register.DMA2SAD_L, value, RegisterWriteMode.HighByte); break; case 0x0ca: _dma!.WriteRegister(DMA.Register.DMA2SAD_H, value, RegisterWriteMode.LowByte); break; case 0x0cb: _dma!.WriteRegister(DMA.Register.DMA2SAD_H, value, RegisterWriteMode.HighByte); break; case 0x0cc: _dma!.WriteRegister(DMA.Register.DMA2DAD_L, value, RegisterWriteMode.LowByte); break; case 0x0cd: _dma!.WriteRegister(DMA.Register.DMA2DAD_L, value, RegisterWriteMode.HighByte); break; case 0x0ce: _dma!.WriteRegister(DMA.Register.DMA2DAD_H, value, RegisterWriteMode.LowByte); break; case 0x0cf: _dma!.WriteRegister(DMA.Register.DMA2DAD_H, value, RegisterWriteMode.HighByte); break; case 0x0d0: _dma!.WriteRegister(DMA.Register.DMA2CNT_L, value, RegisterWriteMode.LowByte); break; case 0x0d1: _dma!.WriteRegister(DMA.Register.DMA2CNT_L, value, RegisterWriteMode.HighByte); break; case 0x0d2: _dma!.WriteRegister(DMA.Register.DMA2CNT_H, value, RegisterWriteMode.LowByte); break; case 0x0d3: _dma!.WriteRegister(DMA.Register.DMA2CNT_H, value, RegisterWriteMode.HighByte); break; case 0x0d4: _dma!.WriteRegister(DMA.Register.DMA3SAD_L, value, RegisterWriteMode.LowByte); break; case 0x0d5: _dma!.WriteRegister(DMA.Register.DMA3SAD_L, value, RegisterWriteMode.HighByte); break; case 0x0d6: _dma!.WriteRegister(DMA.Register.DMA3SAD_H, value, RegisterWriteMode.LowByte); break; case 0x0d7: _dma!.WriteRegister(DMA.Register.DMA3SAD_H, value, RegisterWriteMode.HighByte); break; case 0x0d8: _dma!.WriteRegister(DMA.Register.DMA3DAD_L, value, RegisterWriteMode.LowByte); break; case 0x0d9: _dma!.WriteRegister(DMA.Register.DMA3DAD_L, value, RegisterWriteMode.HighByte); break; case 0x0da: _dma!.WriteRegister(DMA.Register.DMA3DAD_H, value, RegisterWriteMode.LowByte); break; case 0x0db: _dma!.WriteRegister(DMA.Register.DMA3DAD_H, value, RegisterWriteMode.HighByte); break; case 0x0dc: _dma!.WriteRegister(DMA.Register.DMA3CNT_L, value, RegisterWriteMode.LowByte); break; case 0x0dd: _dma!.WriteRegister(DMA.Register.DMA3CNT_L, value, RegisterWriteMode.HighByte); break; case 0x0de: _dma!.WriteRegister(DMA.Register.DMA3CNT_H, value, RegisterWriteMode.LowByte); break; case 0x0df: _dma!.WriteRegister(DMA.Register.DMA3CNT_H, value, RegisterWriteMode.HighByte); break; case 0x100: _timer!.WriteRegister(Timer.Register.TM0CNT_L, value, RegisterWriteMode.LowByte); break; case 0x101: _timer!.WriteRegister(Timer.Register.TM0CNT_L, value, RegisterWriteMode.HighByte); break; case 0x102: _timer!.WriteRegister(Timer.Register.TM0CNT_H, value, RegisterWriteMode.LowByte); break; case 0x103: _timer!.WriteRegister(Timer.Register.TM0CNT_H, value, RegisterWriteMode.HighByte); break; case 0x104: _timer!.WriteRegister(Timer.Register.TM1CNT_L, value, RegisterWriteMode.LowByte); break; case 0x105: _timer!.WriteRegister(Timer.Register.TM1CNT_L, value, RegisterWriteMode.HighByte); break; case 0x106: _timer!.WriteRegister(Timer.Register.TM1CNT_H, value, RegisterWriteMode.LowByte); break; case 0x107: _timer!.WriteRegister(Timer.Register.TM1CNT_H, value, RegisterWriteMode.HighByte); break; case 0x108: _timer!.WriteRegister(Timer.Register.TM2CNT_L, value, RegisterWriteMode.LowByte); break; case 0x109: _timer!.WriteRegister(Timer.Register.TM2CNT_L, value, RegisterWriteMode.HighByte); break; case 0x10a: _timer!.WriteRegister(Timer.Register.TM2CNT_H, value, RegisterWriteMode.LowByte); break; case 0x10b: _timer!.WriteRegister(Timer.Register.TM2CNT_H, value, RegisterWriteMode.HighByte); break; case 0x10c: _timer!.WriteRegister(Timer.Register.TM3CNT_L, value, RegisterWriteMode.LowByte); break; case 0x10d: _timer!.WriteRegister(Timer.Register.TM3CNT_L, value, RegisterWriteMode.HighByte); break; case 0x10e: _timer!.WriteRegister(Timer.Register.TM3CNT_H, value, RegisterWriteMode.LowByte); break; case 0x10f: _timer!.WriteRegister(Timer.Register.TM3CNT_H, value, RegisterWriteMode.HighByte); break; case 0x120: _communication!.WriteRegister(Communication.Register.SIODATA0, value, RegisterWriteMode.LowByte); break; case 0x121: _communication!.WriteRegister(Communication.Register.SIODATA0, value, RegisterWriteMode.HighByte); break; case 0x122: _communication!.WriteRegister(Communication.Register.SIODATA1, value, RegisterWriteMode.LowByte); break; case 0x123: _communication!.WriteRegister(Communication.Register.SIODATA1, value, RegisterWriteMode.HighByte); break; case 0x124: _communication!.WriteRegister(Communication.Register.SIODATA2, value, RegisterWriteMode.LowByte); break; case 0x125: _communication!.WriteRegister(Communication.Register.SIODATA2, value, RegisterWriteMode.HighByte); break; case 0x126: _communication!.WriteRegister(Communication.Register.SIODATA3, value, RegisterWriteMode.LowByte); break; case 0x127: _communication!.WriteRegister(Communication.Register.SIODATA3, value, RegisterWriteMode.HighByte); break; case 0x128: _communication!.WriteRegister(Communication.Register.SIOCNT, value, RegisterWriteMode.LowByte); break; case 0x129: _communication!.WriteRegister(Communication.Register.SIOCNT, value, RegisterWriteMode.HighByte); break; case 0x12a: _communication!.WriteRegister(Communication.Register.SIODATA_SEND, value, RegisterWriteMode.LowByte); break; case 0x12b: _communication!.WriteRegister(Communication.Register.SIODATA_SEND, value, RegisterWriteMode.HighByte); break; case 0x130: case 0x131: // KEYINPUT (read-only) break; case 0x132: _keyInput!.WriteRegister(KeyInput.Register.KEYCNT, value, RegisterWriteMode.LowByte); break; case 0x133: _keyInput!.WriteRegister(KeyInput.Register.KEYCNT, value, RegisterWriteMode.HighByte); break; case 0x134: _communication!.WriteRegister(Communication.Register.RCNT, value, RegisterWriteMode.LowByte); break; case 0x135: _communication!.WriteRegister(Communication.Register.RCNT, value, RegisterWriteMode.HighByte); break; case 0x140: _communication!.WriteRegister(Communication.Register.JOYCNT, value, RegisterWriteMode.LowByte); break; case 0x141: _communication!.WriteRegister(Communication.Register.JOYCNT, value, RegisterWriteMode.HighByte); break; case 0x200: _interruptControl!.WriteRegister(InterruptControl.Register.IE, value, RegisterWriteMode.LowByte); break; case 0x201: _interruptControl!.WriteRegister(InterruptControl.Register.IE, value, RegisterWriteMode.HighByte); break; case 0x202: _interruptControl!.WriteRegister(InterruptControl.Register.IF, value, RegisterWriteMode.LowByte); break; case 0x203: _interruptControl!.WriteRegister(InterruptControl.Register.IF, value, RegisterWriteMode.HighByte); break; case 0x204: _systemControl!.WriteRegister(SystemControl.Register.WAITCNT, value, RegisterWriteMode.LowByte); break; case 0x205: _systemControl!.WriteRegister(SystemControl.Register.WAITCNT, value, RegisterWriteMode.HighByte); break; case 0x208: _interruptControl!.WriteRegister(InterruptControl.Register.IME, value, RegisterWriteMode.LowByte); break; case 0x209: _interruptControl!.WriteRegister(InterruptControl.Register.IME, value, RegisterWriteMode.HighByte); break; case 0x300: _systemControl!.WriteRegister(SystemControl.Register.SYSCNT_UND0, value, RegisterWriteMode.LowByte); break; case 0x301: _systemControl!.WriteRegister(SystemControl.Register.SYSCNT_UND0, value, RegisterWriteMode.HighByte); break; case 0x410: // undocumented break; default: Console.WriteLine($"[Iris.GBA.Memory] Unhandled write to address 0x{address:x8}"); break; } } break; // Palette RAM case 0x5: _video!.Write8_PaletteRAM(address, value); break; // VRAM case 0x6: _video!.Write8_VRAM(address, value); break; // OAM case 0x7: break; // ROM case 0x8: case 0x9: case 0xa: case 0xb: case 0xc: case 0xd: break; default: Console.WriteLine($"[Iris.GBA.Memory] Unhandled write to address 0x{address:x8}"); break; } } internal void Write16(UInt32 address, UInt16 value) { address &= 0x0fff_fffe; IntPtr page = Unsafe.Add(ref MyUnSafeCommon.GetArrayDataReference(_write16PageTable), (int)(address >> 10)); if (page != IntPtr.Zero) { unsafe { // much faster than Marshal.WriteInt16 Unsafe.Write((Byte*)page + (address & 0x3ff), value); } return; } // page fault switch (address >> 24) { // BIOS case 0x0: case 0x1: break; // IO and registers case 0x4: { UInt32 offset = address - 0x400_0000; switch (offset) { case 0x000: _video!.WriteRegister(Video.Register.DISPCNT, value, RegisterWriteMode.HalfWord); break; case 0x004: _video!.WriteRegister(Video.Register.DISPSTAT, value, RegisterWriteMode.HalfWord); break; case 0x008: _video!.WriteRegister(Video.Register.BG0CNT, value, RegisterWriteMode.HalfWord); break; case 0x00a: _video!.WriteRegister(Video.Register.BG1CNT, value, RegisterWriteMode.HalfWord); break; case 0x00c: _video!.WriteRegister(Video.Register.BG2CNT, value, RegisterWriteMode.HalfWord); break; case 0x00e: _video!.WriteRegister(Video.Register.BG3CNT, value, RegisterWriteMode.HalfWord); break; case 0x010: _video!.WriteRegister(Video.Register.BG0HOFS, value, RegisterWriteMode.HalfWord); break; case 0x012: _video!.WriteRegister(Video.Register.BG0VOFS, value, RegisterWriteMode.HalfWord); break; case 0x014: _video!.WriteRegister(Video.Register.BG1HOFS, value, RegisterWriteMode.HalfWord); break; case 0x016: _video!.WriteRegister(Video.Register.BG1VOFS, value, RegisterWriteMode.HalfWord); break; case 0x018: _video!.WriteRegister(Video.Register.BG2HOFS, value, RegisterWriteMode.HalfWord); break; case 0x01a: _video!.WriteRegister(Video.Register.BG2VOFS, value, RegisterWriteMode.HalfWord); break; case 0x01c: _video!.WriteRegister(Video.Register.BG3HOFS, value, RegisterWriteMode.HalfWord); break; case 0x01e: _video!.WriteRegister(Video.Register.BG3VOFS, value, RegisterWriteMode.HalfWord); break; case 0x020: _video!.WriteRegister(Video.Register.BG2PA, value, RegisterWriteMode.HalfWord); break; case 0x022: _video!.WriteRegister(Video.Register.BG2PB, value, RegisterWriteMode.HalfWord); break; case 0x024: _video!.WriteRegister(Video.Register.BG2PC, value, RegisterWriteMode.HalfWord); break; case 0x026: _video!.WriteRegister(Video.Register.BG2PD, value, RegisterWriteMode.HalfWord); break; case 0x028: _video!.WriteRegister(Video.Register.BG2X_L, value, RegisterWriteMode.HalfWord); break; case 0x02a: _video!.WriteRegister(Video.Register.BG2X_H, value, RegisterWriteMode.HalfWord); break; case 0x02c: _video!.WriteRegister(Video.Register.BG2Y_L, value, RegisterWriteMode.HalfWord); break; case 0x02e: _video!.WriteRegister(Video.Register.BG2Y_H, value, RegisterWriteMode.HalfWord); break; case 0x030: _video!.WriteRegister(Video.Register.BG3PA, value, RegisterWriteMode.HalfWord); break; case 0x032: _video!.WriteRegister(Video.Register.BG3PB, value, RegisterWriteMode.HalfWord); break; case 0x034: _video!.WriteRegister(Video.Register.BG3PC, value, RegisterWriteMode.HalfWord); break; case 0x036: _video!.WriteRegister(Video.Register.BG3PD, value, RegisterWriteMode.HalfWord); break; case 0x038: _video!.WriteRegister(Video.Register.BG3X_L, value, RegisterWriteMode.HalfWord); break; case 0x03a: _video!.WriteRegister(Video.Register.BG3X_H, value, RegisterWriteMode.HalfWord); break; case 0x03c: _video!.WriteRegister(Video.Register.BG3Y_L, value, RegisterWriteMode.HalfWord); break; case 0x03e: _video!.WriteRegister(Video.Register.BG3Y_H, value, RegisterWriteMode.HalfWord); break; case 0x040: _video!.WriteRegister(Video.Register.WIN0H, value, RegisterWriteMode.HalfWord); break; case 0x042: _video!.WriteRegister(Video.Register.WIN1H, value, RegisterWriteMode.HalfWord); break; case 0x044: _video!.WriteRegister(Video.Register.WIN0V, value, RegisterWriteMode.HalfWord); break; case 0x046: _video!.WriteRegister(Video.Register.WIN1V, value, RegisterWriteMode.HalfWord); break; case 0x048: _video!.WriteRegister(Video.Register.WININ, value, RegisterWriteMode.HalfWord); break; case 0x04a: _video!.WriteRegister(Video.Register.WINOUT, value, RegisterWriteMode.HalfWord); break; case 0x04c: _video!.WriteRegister(Video.Register.MOSAIC, value, RegisterWriteMode.HalfWord); break; case 0x050: _video!.WriteRegister(Video.Register.BLDCNT, value, RegisterWriteMode.HalfWord); break; case 0x052: _video!.WriteRegister(Video.Register.BLDALPHA, value, RegisterWriteMode.HalfWord); break; case 0x054: _video!.WriteRegister(Video.Register.BLDY, value, RegisterWriteMode.HalfWord); break; case 0x060: _sound!.WriteRegister(Sound.Register.SOUND1CNT_L, value, RegisterWriteMode.HalfWord); break; case 0x062: _sound!.WriteRegister(Sound.Register.SOUND1CNT_H, value, RegisterWriteMode.HalfWord); break; case 0x064: _sound!.WriteRegister(Sound.Register.SOUND1CNT_X, value, RegisterWriteMode.HalfWord); break; case 0x068: _sound!.WriteRegister(Sound.Register.SOUND2CNT_L, value, RegisterWriteMode.HalfWord); break; case 0x06c: _sound!.WriteRegister(Sound.Register.SOUND2CNT_H, value, RegisterWriteMode.HalfWord); break; case 0x070: _sound!.WriteRegister(Sound.Register.SOUND3CNT_L, value, RegisterWriteMode.HalfWord); break; case 0x072: _sound!.WriteRegister(Sound.Register.SOUND3CNT_H, value, RegisterWriteMode.HalfWord); break; case 0x074: _sound!.WriteRegister(Sound.Register.SOUND3CNT_X, value, RegisterWriteMode.HalfWord); break; case 0x078: _sound!.WriteRegister(Sound.Register.SOUND4CNT_L, value, RegisterWriteMode.HalfWord); break; case 0x07c: _sound!.WriteRegister(Sound.Register.SOUND4CNT_H, value, RegisterWriteMode.HalfWord); break; case 0x080: _sound!.WriteRegister(Sound.Register.SOUNDCNT_L, value, RegisterWriteMode.HalfWord); break; case 0x082: _sound!.WriteRegister(Sound.Register.SOUNDCNT_H, value, RegisterWriteMode.HalfWord); break; case 0x084: _sound!.WriteRegister(Sound.Register.SOUNDCNT_X, value, RegisterWriteMode.HalfWord); break; case 0x088: _sound!.WriteRegister(Sound.Register.SOUNDBIAS, value, RegisterWriteMode.HalfWord); break; case 0x090: _sound!.WriteRegister(Sound.Register.WAVE_RAM0_L, value, RegisterWriteMode.HalfWord); break; case 0x092: _sound!.WriteRegister(Sound.Register.WAVE_RAM0_H, value, RegisterWriteMode.HalfWord); break; case 0x094: _sound!.WriteRegister(Sound.Register.WAVE_RAM1_L, value, RegisterWriteMode.HalfWord); break; case 0x096: _sound!.WriteRegister(Sound.Register.WAVE_RAM1_H, value, RegisterWriteMode.HalfWord); break; case 0x098: _sound!.WriteRegister(Sound.Register.WAVE_RAM2_L, value, RegisterWriteMode.HalfWord); break; case 0x09a: _sound!.WriteRegister(Sound.Register.WAVE_RAM2_H, value, RegisterWriteMode.HalfWord); break; case 0x09c: _sound!.WriteRegister(Sound.Register.WAVE_RAM3_L, value, RegisterWriteMode.HalfWord); break; case 0x09e: _sound!.WriteRegister(Sound.Register.WAVE_RAM3_H, value, RegisterWriteMode.HalfWord); break; case 0x0b0: _dma!.WriteRegister(DMA.Register.DMA0SAD_L, value, RegisterWriteMode.HalfWord); break; case 0x0b2: _dma!.WriteRegister(DMA.Register.DMA0SAD_H, value, RegisterWriteMode.HalfWord); break; case 0x0b4: _dma!.WriteRegister(DMA.Register.DMA0DAD_L, value, RegisterWriteMode.HalfWord); break; case 0x0b6: _dma!.WriteRegister(DMA.Register.DMA0DAD_H, value, RegisterWriteMode.HalfWord); break; case 0x0b8: _dma!.WriteRegister(DMA.Register.DMA0CNT_L, value, RegisterWriteMode.HalfWord); break; case 0x0ba: _dma!.WriteRegister(DMA.Register.DMA0CNT_H, value, RegisterWriteMode.HalfWord); break; case 0x0bc: _dma!.WriteRegister(DMA.Register.DMA1SAD_L, value, RegisterWriteMode.HalfWord); break; case 0x0be: _dma!.WriteRegister(DMA.Register.DMA1SAD_H, value, RegisterWriteMode.HalfWord); break; case 0x0c0: _dma!.WriteRegister(DMA.Register.DMA1DAD_L, value, RegisterWriteMode.HalfWord); break; case 0x0c2: _dma!.WriteRegister(DMA.Register.DMA1DAD_H, value, RegisterWriteMode.HalfWord); break; case 0x0c4: _dma!.WriteRegister(DMA.Register.DMA1CNT_L, value, RegisterWriteMode.HalfWord); break; case 0x0c6: _dma!.WriteRegister(DMA.Register.DMA1CNT_H, value, RegisterWriteMode.HalfWord); break; case 0x0c8: _dma!.WriteRegister(DMA.Register.DMA2SAD_L, value, RegisterWriteMode.HalfWord); break; case 0x0ca: _dma!.WriteRegister(DMA.Register.DMA2SAD_H, value, RegisterWriteMode.HalfWord); break; case 0x0cc: _dma!.WriteRegister(DMA.Register.DMA2DAD_L, value, RegisterWriteMode.HalfWord); break; case 0x0ce: _dma!.WriteRegister(DMA.Register.DMA2DAD_H, value, RegisterWriteMode.HalfWord); break; case 0x0d0: _dma!.WriteRegister(DMA.Register.DMA2CNT_L, value, RegisterWriteMode.HalfWord); break; case 0x0d2: _dma!.WriteRegister(DMA.Register.DMA2CNT_H, value, RegisterWriteMode.HalfWord); break; case 0x0d4: _dma!.WriteRegister(DMA.Register.DMA3SAD_L, value, RegisterWriteMode.HalfWord); break; case 0x0d6: _dma!.WriteRegister(DMA.Register.DMA3SAD_H, value, RegisterWriteMode.HalfWord); break; case 0x0d8: _dma!.WriteRegister(DMA.Register.DMA3DAD_L, value, RegisterWriteMode.HalfWord); break; case 0x0da: _dma!.WriteRegister(DMA.Register.DMA3DAD_H, value, RegisterWriteMode.HalfWord); break; case 0x0dc: _dma!.WriteRegister(DMA.Register.DMA3CNT_L, value, RegisterWriteMode.HalfWord); break; case 0x0de: _dma!.WriteRegister(DMA.Register.DMA3CNT_H, value, RegisterWriteMode.HalfWord); break; case 0x100: _timer!.WriteRegister(Timer.Register.TM0CNT_L, value, RegisterWriteMode.HalfWord); break; case 0x102: _timer!.WriteRegister(Timer.Register.TM0CNT_H, value, RegisterWriteMode.HalfWord); break; case 0x104: _timer!.WriteRegister(Timer.Register.TM1CNT_L, value, RegisterWriteMode.HalfWord); break; case 0x106: _timer!.WriteRegister(Timer.Register.TM1CNT_H, value, RegisterWriteMode.HalfWord); break; case 0x108: _timer!.WriteRegister(Timer.Register.TM2CNT_L, value, RegisterWriteMode.HalfWord); break; case 0x10a: _timer!.WriteRegister(Timer.Register.TM2CNT_H, value, RegisterWriteMode.HalfWord); break; case 0x10c: _timer!.WriteRegister(Timer.Register.TM3CNT_L, value, RegisterWriteMode.HalfWord); break; case 0x10e: _timer!.WriteRegister(Timer.Register.TM3CNT_H, value, RegisterWriteMode.HalfWord); break; case 0x120: _communication!.WriteRegister(Communication.Register.SIODATA0, value, RegisterWriteMode.HalfWord); break; case 0x122: _communication!.WriteRegister(Communication.Register.SIODATA1, value, RegisterWriteMode.HalfWord); break; case 0x124: _communication!.WriteRegister(Communication.Register.SIODATA2, value, RegisterWriteMode.HalfWord); break; case 0x126: _communication!.WriteRegister(Communication.Register.SIODATA3, value, RegisterWriteMode.HalfWord); break; case 0x128: _communication!.WriteRegister(Communication.Register.SIOCNT, value, RegisterWriteMode.HalfWord); break; case 0x12a: _communication!.WriteRegister(Communication.Register.SIODATA_SEND, value, RegisterWriteMode.HalfWord); break; case 0x130: // KEYINPUT (read-only) break; case 0x132: _keyInput!.WriteRegister(KeyInput.Register.KEYCNT, value, RegisterWriteMode.HalfWord); break; case 0x134: _communication!.WriteRegister(Communication.Register.RCNT, value, RegisterWriteMode.HalfWord); break; case 0x140: _communication!.WriteRegister(Communication.Register.JOYCNT, value, RegisterWriteMode.HalfWord); break; case 0x158: _communication!.WriteRegister(Communication.Register.JOYSTAT, value, RegisterWriteMode.HalfWord); break; case 0x200: _interruptControl!.WriteRegister(InterruptControl.Register.IE, value, RegisterWriteMode.HalfWord); break; case 0x202: _interruptControl!.WriteRegister(InterruptControl.Register.IF, value, RegisterWriteMode.HalfWord); break; case 0x204: _systemControl!.WriteRegister(SystemControl.Register.WAITCNT, value, RegisterWriteMode.HalfWord); break; case 0x208: _interruptControl!.WriteRegister(InterruptControl.Register.IME, value, RegisterWriteMode.HalfWord); break; case 0x300: _systemControl!.WriteRegister(SystemControl.Register.SYSCNT_UND0, value, RegisterWriteMode.HalfWord); break; default: Console.WriteLine($"[Iris.GBA.Memory] Unhandled write to address 0x{address:x8}"); break; } } break; // ROM case 0x8: case 0x9: case 0xa: case 0xb: case 0xc: case 0xd: break; default: Console.WriteLine($"[Iris.GBA.Memory] Unhandled write to address 0x{address:x8}"); break; } } internal void Write32(UInt32 address, UInt32 value) { address &= 0x0fff_fffc; IntPtr page = Unsafe.Add(ref MyUnSafeCommon.GetArrayDataReference(_write32PageTable), (int)(address >> 10)); if (page != IntPtr.Zero) { unsafe { // much faster than Marshal.WriteInt32 Unsafe.Write((Byte*)page + (address & 0x3ff), value); } return; } // page fault switch (address >> 24) { // BIOS case 0x0: case 0x1: break; // IO and registers case 0x4: { [MethodImpl(MethodImplOptions.AggressiveInlining)] static UInt16 GetLowHalfword(UInt32 value) => (UInt16)value; [MethodImpl(MethodImplOptions.AggressiveInlining)] static UInt16 GetHighHalfword(UInt32 value) => (UInt16)(value >> 16); UInt32 offset = address - 0x400_0000; switch (offset) { case 0x000: _video!.WriteRegister(Video.Register.DISPCNT, GetLowHalfword(value), RegisterWriteMode.HalfWord); // 16 upper bits are undocumented (green swap register) break; case 0x004: _video!.WriteRegister(Video.Register.DISPSTAT, GetLowHalfword(value), RegisterWriteMode.HalfWord); break; case 0x008: _video!.WriteRegister(Video.Register.BG0CNT, GetLowHalfword(value), RegisterWriteMode.HalfWord); _video!.WriteRegister(Video.Register.BG1CNT, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x00c: _video!.WriteRegister(Video.Register.BG2CNT, GetLowHalfword(value), RegisterWriteMode.HalfWord); _video!.WriteRegister(Video.Register.BG3CNT, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x010: _video!.WriteRegister(Video.Register.BG0HOFS, GetLowHalfword(value), RegisterWriteMode.HalfWord); _video!.WriteRegister(Video.Register.BG0VOFS, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x014: _video!.WriteRegister(Video.Register.BG1HOFS, GetLowHalfword(value), RegisterWriteMode.HalfWord); _video!.WriteRegister(Video.Register.BG1VOFS, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x018: _video!.WriteRegister(Video.Register.BG2HOFS, GetLowHalfword(value), RegisterWriteMode.HalfWord); _video!.WriteRegister(Video.Register.BG2VOFS, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x01c: _video!.WriteRegister(Video.Register.BG3HOFS, GetLowHalfword(value), RegisterWriteMode.HalfWord); _video!.WriteRegister(Video.Register.BG3VOFS, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x020: _video!.WriteRegister(Video.Register.BG2PA, GetLowHalfword(value), RegisterWriteMode.HalfWord); _video!.WriteRegister(Video.Register.BG2PB, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x024: _video!.WriteRegister(Video.Register.BG2PC, GetLowHalfword(value), RegisterWriteMode.HalfWord); _video!.WriteRegister(Video.Register.BG2PD, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x028: _video!.WriteRegister(Video.Register.BG2X_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); _video!.WriteRegister(Video.Register.BG2X_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x02c: _video!.WriteRegister(Video.Register.BG2Y_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); _video!.WriteRegister(Video.Register.BG2Y_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x030: _video!.WriteRegister(Video.Register.BG3PA, GetLowHalfword(value), RegisterWriteMode.HalfWord); _video!.WriteRegister(Video.Register.BG3PB, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x034: _video!.WriteRegister(Video.Register.BG3PC, GetLowHalfword(value), RegisterWriteMode.HalfWord); _video!.WriteRegister(Video.Register.BG3PD, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x038: _video!.WriteRegister(Video.Register.BG3X_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); _video!.WriteRegister(Video.Register.BG3X_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x03c: _video!.WriteRegister(Video.Register.BG3Y_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); _video!.WriteRegister(Video.Register.BG3Y_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x040: _video!.WriteRegister(Video.Register.WIN0H, GetLowHalfword(value), RegisterWriteMode.HalfWord); _video!.WriteRegister(Video.Register.WIN1H, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x044: _video!.WriteRegister(Video.Register.WIN0V, GetLowHalfword(value), RegisterWriteMode.HalfWord); _video!.WriteRegister(Video.Register.WIN1V, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x048: _video!.WriteRegister(Video.Register.WININ, GetLowHalfword(value), RegisterWriteMode.HalfWord); _video!.WriteRegister(Video.Register.WINOUT, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x04c: _video!.WriteRegister(Video.Register.MOSAIC, GetLowHalfword(value), RegisterWriteMode.HalfWord); // 16 upper bits are unused break; case 0x050: _video!.WriteRegister(Video.Register.BLDCNT, GetLowHalfword(value), RegisterWriteMode.HalfWord); _video!.WriteRegister(Video.Register.BLDALPHA, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x054: _video!.WriteRegister(Video.Register.BLDY, GetLowHalfword(value), RegisterWriteMode.HalfWord); // 16 upper bits are unused break; case 0x58: case 0x5c: // unused break; case 0x080: _sound!.WriteRegister(Sound.Register.SOUNDCNT_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); _sound.WriteRegister(Sound.Register.SOUNDCNT_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x090: _sound!.WriteRegister(Sound.Register.WAVE_RAM0_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); _sound.WriteRegister(Sound.Register.WAVE_RAM0_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x094: _sound!.WriteRegister(Sound.Register.WAVE_RAM1_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); _sound.WriteRegister(Sound.Register.WAVE_RAM1_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x098: _sound!.WriteRegister(Sound.Register.WAVE_RAM2_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); _sound.WriteRegister(Sound.Register.WAVE_RAM2_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x09c: _sound!.WriteRegister(Sound.Register.WAVE_RAM3_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); _sound.WriteRegister(Sound.Register.WAVE_RAM3_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x0a0: _sound!.WriteRegister(Sound.Register.FIFO_A_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); _sound.WriteRegister(Sound.Register.FIFO_A_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x0a4: _sound!.WriteRegister(Sound.Register.FIFO_B_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); _sound.WriteRegister(Sound.Register.FIFO_B_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x0a8: case 0x0ac: // unused break; case 0x0b0: _dma!.WriteRegister(DMA.Register.DMA0SAD_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); _dma.WriteRegister(DMA.Register.DMA0SAD_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x0b4: _dma!.WriteRegister(DMA.Register.DMA0DAD_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); _dma.WriteRegister(DMA.Register.DMA0DAD_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x0b8: _dma!.WriteRegister(DMA.Register.DMA0CNT_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); _dma.WriteRegister(DMA.Register.DMA0CNT_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x0bc: _dma!.WriteRegister(DMA.Register.DMA1SAD_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); _dma.WriteRegister(DMA.Register.DMA1SAD_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x0c0: _dma!.WriteRegister(DMA.Register.DMA1DAD_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); _dma.WriteRegister(DMA.Register.DMA1DAD_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x0c4: _dma!.WriteRegister(DMA.Register.DMA1CNT_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); _dma.WriteRegister(DMA.Register.DMA1CNT_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x0c8: _dma!.WriteRegister(DMA.Register.DMA2SAD_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); _dma.WriteRegister(DMA.Register.DMA2SAD_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x0cc: _dma!.WriteRegister(DMA.Register.DMA2DAD_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); _dma.WriteRegister(DMA.Register.DMA2DAD_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x0d0: _dma!.WriteRegister(DMA.Register.DMA2CNT_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); _dma.WriteRegister(DMA.Register.DMA2CNT_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x0d4: _dma!.WriteRegister(DMA.Register.DMA3SAD_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); _dma.WriteRegister(DMA.Register.DMA3SAD_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x0d8: _dma!.WriteRegister(DMA.Register.DMA3DAD_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); _dma.WriteRegister(DMA.Register.DMA3DAD_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x0dc: _dma!.WriteRegister(DMA.Register.DMA3CNT_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); _dma.WriteRegister(DMA.Register.DMA3CNT_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x0e0: case 0x0e4: case 0x0e8: case 0x0ec: case 0x0f0: case 0x0f4: case 0x0f8: case 0x0fc: // unused break; case 0x100: _timer!.WriteRegister(Timer.Register.TM0CNT_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); _timer!.WriteRegister(Timer.Register.TM0CNT_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x104: _timer!.WriteRegister(Timer.Register.TM1CNT_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); _timer!.WriteRegister(Timer.Register.TM1CNT_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x108: _timer!.WriteRegister(Timer.Register.TM2CNT_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); _timer!.WriteRegister(Timer.Register.TM2CNT_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x10c: _timer!.WriteRegister(Timer.Register.TM3CNT_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); _timer!.WriteRegister(Timer.Register.TM3CNT_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x110: case 0x114: case 0x118: case 0x11c: // unused break; case 0x120: _communication!.WriteRegister(Communication.Register.SIODATA0, GetLowHalfword(value), RegisterWriteMode.HalfWord); _communication.WriteRegister(Communication.Register.SIODATA1, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x124: _communication!.WriteRegister(Communication.Register.SIODATA2, GetLowHalfword(value), RegisterWriteMode.HalfWord); _communication.WriteRegister(Communication.Register.SIODATA3, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x128: _communication!.WriteRegister(Communication.Register.SIOCNT, GetLowHalfword(value), RegisterWriteMode.HalfWord); _communication.WriteRegister(Communication.Register.SIODATA_SEND, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x12c: // unused break; case 0x130: // 16 lower bits are read-only (KEYINPUT) _keyInput!.WriteRegister(KeyInput.Register.KEYCNT, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x140: _communication!.WriteRegister(Communication.Register.JOYCNT, GetLowHalfword(value), RegisterWriteMode.HalfWord); // 16 upper bits are unused break; case 0x144: case 0x148: case 0x14c: // unused break; case 0x150: _communication!.WriteRegister(Communication.Register.JOY_RECV_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); _communication.WriteRegister(Communication.Register.JOY_RECV_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x154: _communication!.WriteRegister(Communication.Register.JOY_TRANS_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); _communication.WriteRegister(Communication.Register.JOY_TRANS_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x158: _communication!.WriteRegister(Communication.Register.JOYSTAT, GetLowHalfword(value), RegisterWriteMode.HalfWord); // 16 upper bits are unused break; case 0x15c: // unused break; case 0x200: _interruptControl!.WriteRegister(InterruptControl.Register.IE, GetLowHalfword(value), RegisterWriteMode.HalfWord); _interruptControl!.WriteRegister(InterruptControl.Register.IF, GetHighHalfword(value), RegisterWriteMode.HalfWord); break; case 0x204: _systemControl!.WriteRegister(SystemControl.Register.WAITCNT, GetLowHalfword(value), RegisterWriteMode.HalfWord); // 16 upper bits are unused break; case 0x208: _interruptControl!.WriteRegister(InterruptControl.Register.IME, GetLowHalfword(value), RegisterWriteMode.HalfWord); // 16 upper bits are unused break; case 0x20c: case 0x210: case 0x214: case 0x218: case 0x21c: // unused break; case 0x300: _systemControl!.WriteRegister(SystemControl.Register.SYSCNT_UND0, GetLowHalfword(value), RegisterWriteMode.HalfWord); // 16 upper bits are unused break; case 0x800: // 16 lower bits are undocumented (internal memory control) // 16 upper bits are unused break; default: Console.WriteLine($"[Iris.GBA.Memory] Unhandled write to address 0x{address:x8}"); break; } } break; // ROM case 0x8: case 0x9: case 0xa: case 0xb: case 0xc: case 0xd: break; default: Console.WriteLine($"[Iris.GBA.Memory] Unhandled write to address 0x{address:x8}"); break; } } } }