Cores/Nds/NDS.Memory.cs
namespace Emulotl.Nds;

public sealed partial class NDS
{
	public byte[] ARM9BIOS = new byte[0x1000];
	public byte[] ARM7BIOS = new byte[NdsConstants.Arm7BiosSize];

	public uint MainRAMMask = NdsConstants.MainRamSize - 1;
	public byte WRAMCnt;
	private MemRegion SWRAM_ARM9;
	private MemRegion SWRAM_ARM7;

	public uint[] ExMemCnt = new uint[2];
	public uint PowerControl9;
	public uint PowerControl7;
	public uint ARM7BIOSProt;
	public byte PostFlag9;
	public byte PostFlag7;

	private void ResetMemory()
	{
		MainRAMMask = NdsConstants.MainRamSize - 1;

		Array.Clear( ARM9BIOS );
		Array.Clear( ARM7BIOS );

		if ( _nativeArm9Bios != null )
			Array.Copy( _nativeArm9Bios, ARM9BIOS, Math.Min( _nativeArm9Bios.Length, ARM9BIOS.Length ) );
		else
			Array.Copy( FreeBIOS.NtrArm9, ARM9BIOS, FreeBIOS.NtrArm9.Length );

		if ( _nativeArm7Bios != null )
		{
			Array.Copy( _nativeArm7Bios, ARM7BIOS, Math.Min( _nativeArm7Bios.Length, ARM7BIOS.Length ) );
			IsNativeArm7Bios = true;
		}
		else
		{
			Array.Copy( FreeBIOS.NtrArm7, ARM7BIOS, FreeBIOS.NtrArm7.Length );
			IsNativeArm7Bios = false;
		}

		ExMemCnt[0] = ExMemCnt[1] = 0;
		PowerControl9 = 0;
		PowerControl7 = 0;
		ARM7BIOSProt = 0;
		PostFlag9 = 0;
		PostFlag7 = 0;

		WRAMCnt = 0xFF;
		MapSharedWRAM( 0 );
	}

	public void MapSharedWRAM( byte val )
	{
		if ( val == WRAMCnt )
			return;

		WRAMCnt = val;

		switch ( WRAMCnt & 0x3 )
		{
			case 0:
				SWRAM_ARM9 = new MemRegion { Mem = SharedWRAM, Offset = 0, Mask = 0x7FFF };
				SWRAM_ARM7 = default;
				break;
			case 1:
				SWRAM_ARM9 = new MemRegion { Mem = SharedWRAM, Offset = 0x4000, Mask = 0x3FFF };
				SWRAM_ARM7 = new MemRegion { Mem = SharedWRAM, Offset = 0, Mask = 0x3FFF };
				break;
			case 2:
				SWRAM_ARM9 = new MemRegion { Mem = SharedWRAM, Offset = 0, Mask = 0x3FFF };
				SWRAM_ARM7 = new MemRegion { Mem = SharedWRAM, Offset = 0x4000, Mask = 0x3FFF };
				break;
			case 3:
				SWRAM_ARM9 = default;
				SWRAM_ARM7 = new MemRegion { Mem = SharedWRAM, Offset = 0, Mask = 0x7FFF };
				break;
		}
	}

	public void GetARM9CodeMem( uint addr, ref MemRegion region )
	{
		if ( (addr & 0xFFFFF000) == 0xFFFF0000 )
		{
			region.Mem = ARM9BIOS;
			region.Offset = 0;
			region.Mask = 0xFFF;
			return;
		}

		switch ( addr & 0xFF000000 )
		{
			case 0x02000000:
				region.Mem = MainRAM;
				region.Offset = 0;
				region.Mask = MainRAMMask;
				return;
			case 0x03000000:
				region = SWRAM_ARM9;
				return;
			default:
				region.Mem = null;
				return;
		}
	}

	public void GetARM7CodeMem( uint addr, ref MemRegion region )
	{
		switch ( addr & 0xFF800000 )
		{
			case 0x02000000:
			case 0x02800000:
				region.Mem = MainRAM;
				region.Offset = 0;
				region.Mask = MainRAMMask;
				return;
			case 0x03000000:
				if ( SWRAM_ARM7.Mem != null )
				{
					region = SWRAM_ARM7;
				}
				else
				{
					region.Mem = ARM7WRAM;
					region.Offset = 0;
					region.Mask = NdsConstants.Arm7WramSize - 1;
				}
				return;
			case 0x03800000:
				region.Mem = ARM7WRAM;
				region.Offset = 0;
				region.Mask = NdsConstants.Arm7WramSize - 1;
				return;
			default:
				region.Mem = null;
				return;
		}
	}

	private uint GbaSlotRead( uint addr, int bytes, int cpu )
	{
		bool owner = cpu == 0 ? (ExMemCnt[0] & 0x80) == 0 : (ExMemCnt[0] & 0x80) != 0;
		if ( !owner )
			return 0;
		if ( (addr & 0xFF000000) >= 0x0A000000 )
			return bytes == 1 ? 0xFFu : bytes == 2 ? 0xFFFFu : 0xFFFFFFFFu;
		uint lo = (addr >> 1) & 0xFFFF;
		if ( bytes == 1 ) return (lo >> (int)((addr & 1) * 8)) & 0xFF;
		if ( bytes == 2 ) return lo;
		return lo | ((((addr + 2) >> 1) & 0xFFFF) << 16);
	}

	private static ushort Read16( byte[] mem, uint offset ) => (ushort)(mem[offset] | (mem[offset + 1] << 8));
	private static uint Read32( byte[] mem, uint offset ) => (uint)(mem[offset] | (mem[offset + 1] << 8) | (mem[offset + 2] << 16) | (mem[offset + 3] << 24));
	private static void Write16( byte[] mem, uint offset, ushort val ) { mem[offset] = (byte)val; mem[offset + 1] = (byte)(val >> 8); }
	private static void Write32( byte[] mem, uint offset, uint val ) { mem[offset] = (byte)val; mem[offset + 1] = (byte)(val >> 8); mem[offset + 2] = (byte)(val >> 16); mem[offset + 3] = (byte)(val >> 24); }

	public byte ARM9Read8( uint addr )
	{
		if ( (addr & 0xFFFFF000) == 0xFFFF0000 )
			return ARM9BIOS[addr & 0xFFF];

		switch ( addr & 0xFF000000 )
		{
			case 0x02000000: return MainRAM[addr & MainRAMMask];
			case 0x03000000: return SWRAM_ARM9.Mem != null ? SWRAM_ARM9.Mem[SWRAM_ARM9.Offset + (addr & SWRAM_ARM9.Mask)] : (byte)0;
			case 0x04000000: return ARM9IORead8( addr );
			case 0x05000000: return PaletteEnabled9( addr ) ? (byte)GPU.ReadPalette( addr, 1 ) : (byte)0;
			case 0x06000000: return (byte)ReadVRAM9( addr, 1 );
			case 0x07000000: return PaletteEnabled9( addr ) ? (byte)GPU.ReadOAM( addr, 1 ) : (byte)0;
			case 0x08000000:
			case 0x09000000:
			case 0x0A000000: return (byte)GbaSlotRead( addr, 1, 0 );
		}
		return 0;
	}

	private uint ReadVRAM9( uint addr, int bytes )
	{
		return (addr & 0x00E00000) switch
		{
			0x00000000 => GPU.ReadVRAM_ABG( addr, bytes ),
			0x00200000 => GPU.ReadVRAM_BBG( addr, bytes ),
			0x00400000 => GPU.ReadVRAM_AOBJ( addr, bytes ),
			0x00600000 => GPU.ReadVRAM_BOBJ( addr, bytes ),
			_ => GPU.ReadVRAM_LCDC( addr, bytes ),
		};
	}

	private void WriteVRAM9( uint addr, uint val, int bytes )
	{
		switch ( addr & 0x00E00000 )
		{
			case 0x00000000: GPU.WriteVRAM_ABG( addr, val, bytes ); return;
			case 0x00200000: GPU.WriteVRAM_BBG( addr, val, bytes ); return;
			case 0x00400000: GPU.WriteVRAM_AOBJ( addr, val, bytes ); return;
			case 0x00600000: GPU.WriteVRAM_BOBJ( addr, val, bytes ); return;
			default: GPU.WriteVRAM_LCDC( addr, val, bytes ); return;
		}
	}

	public ushort ARM9Read16( uint addr )
	{
		addr &= ~0x1u;
		if ( (addr & 0xFFFFF000) == 0xFFFF0000 )
			return Read16( ARM9BIOS, addr & 0xFFF );

		switch ( addr & 0xFF000000 )
		{
			case 0x02000000: return Read16( MainRAM, addr & MainRAMMask );
			case 0x03000000: return SWRAM_ARM9.Mem != null ? Read16( SWRAM_ARM9.Mem, SWRAM_ARM9.Offset + (addr & SWRAM_ARM9.Mask) ) : (ushort)0;
			case 0x04000000: return ARM9IORead16( addr );
			case 0x05000000: return PaletteEnabled9( addr ) ? (ushort)GPU.ReadPalette( addr, 2 ) : (ushort)0;
			case 0x06000000: return (ushort)ReadVRAM9( addr, 2 );
			case 0x07000000: return PaletteEnabled9( addr ) ? (ushort)GPU.ReadOAM( addr, 2 ) : (ushort)0;
			case 0x08000000:
			case 0x09000000:
			case 0x0A000000: return (ushort)GbaSlotRead( addr, 2, 0 );
		}
		return 0;
	}

	public uint ARM9Read32( uint addr )
	{
		addr &= ~0x3u;
		if ( (addr & 0xFFFFF000) == 0xFFFF0000 )
			return Read32( ARM9BIOS, addr & 0xFFF );

		switch ( addr & 0xFF000000 )
		{
			case 0x02000000: return Read32( MainRAM, addr & MainRAMMask );
			case 0x03000000: return SWRAM_ARM9.Mem != null ? Read32( SWRAM_ARM9.Mem, SWRAM_ARM9.Offset + (addr & SWRAM_ARM9.Mask) ) : 0;
			case 0x04000000: return ARM9IORead32( addr );
			case 0x05000000: return PaletteEnabled9( addr ) ? GPU.ReadPalette( addr, 4 ) : 0;
			case 0x06000000: return ReadVRAM9( addr, 4 );
			case 0x07000000: return PaletteEnabled9( addr ) ? GPU.ReadOAM( addr, 4 ) : 0;
			case 0x08000000:
			case 0x09000000:
			case 0x0A000000: return GbaSlotRead( addr, 4, 0 );
		}
		return 0;
	}

	public void ARM9Write8( uint addr, byte val )
	{
		switch ( addr & 0xFF000000 )
		{
			case 0x02000000: MainRAM[addr & MainRAMMask] = val; return;
			case 0x03000000: if ( SWRAM_ARM9.Mem != null ) SWRAM_ARM9.Mem[SWRAM_ARM9.Offset + (addr & SWRAM_ARM9.Mask)] = val; return;
			case 0x04000000: ARM9IOWrite8( addr, val ); return;
		}
	}

	public void ARM9Write16( uint addr, ushort val )
	{
		addr &= ~0x1u;
		switch ( addr & 0xFF000000 )
		{
			case 0x02000000: Write16( MainRAM, addr & MainRAMMask, val ); return;
			case 0x03000000: if ( SWRAM_ARM9.Mem != null ) Write16( SWRAM_ARM9.Mem, SWRAM_ARM9.Offset + (addr & SWRAM_ARM9.Mask), val ); return;
			case 0x04000000: ARM9IOWrite16( addr, val ); return;
			case 0x05000000: if ( PaletteEnabled9( addr ) ) GPU.WritePalette( addr, val, 2 ); return;
			case 0x06000000: WriteVRAM9( addr, val, 2 ); return;
			case 0x07000000: if ( PaletteEnabled9( addr ) ) GPU.WriteOAM( addr, val, 2 ); return;
		}
	}

	public void ARM9Write32( uint addr, uint val )
	{
		addr &= ~0x3u;
		switch ( addr & 0xFF000000 )
		{
			case 0x02000000:
				Write32( MainRAM, addr & MainRAMMask, val ); return;
			case 0x03000000: if ( SWRAM_ARM9.Mem != null ) Write32( SWRAM_ARM9.Mem, SWRAM_ARM9.Offset + (addr & SWRAM_ARM9.Mask), val ); return;
			case 0x04000000: ARM9IOWrite32( addr, val ); return;
			case 0x05000000: if ( PaletteEnabled9( addr ) ) GPU.WritePalette( addr, val, 4 ); return;
			case 0x06000000: WriteVRAM9( addr, val, 4 ); return;
			case 0x07000000: if ( PaletteEnabled9( addr ) ) GPU.WriteOAM( addr, val, 4 ); return;
		}
	}

	private bool PaletteEnabled9( uint addr ) => (PowerControl9 & ((addr & 0x400) != 0 ? (1u << 9) : (1u << 1))) != 0;

	public byte ARM7Read8( uint addr )
	{
		if ( addr < 0x00004000 )
		{
			if ( ARM7.R[15] >= 0x00004000 ) return 0xFF;
			if ( addr < ARM7BIOSProt && ARM7.R[15] >= ARM7BIOSProt ) return 0xFF;
			return ARM7BIOS[addr];
		}

		switch ( addr & 0xFF800000 )
		{
			case 0x02000000:
			case 0x02800000: return MainRAM[addr & MainRAMMask];
			case 0x03000000: return SWRAM_ARM7.Mem != null ? SWRAM_ARM7.Mem[SWRAM_ARM7.Offset + (addr & SWRAM_ARM7.Mask)] : ARM7WRAM[addr & (NdsConstants.Arm7WramSize - 1)];
			case 0x03800000: return ARM7WRAM[addr & (NdsConstants.Arm7WramSize - 1)];
			case 0x04000000: return ARM7IORead8( addr );
			case 0x04800000: return addr < 0x04810000 ? (byte)(Wifi.Read( addr & ~1u ) >> (int)((addr & 1) * 8)) : (byte)0;
			case 0x06000000:
			case 0x06800000: return (byte)GPU.ReadVRAM_ARM7( addr, 1 );
			case 0x08000000:
			case 0x08800000:
			case 0x09000000:
			case 0x09800000:
			case 0x0A000000:
			case 0x0A800000: return (byte)GbaSlotRead( addr, 1, 1 );
		}
		return 0;
	}

	public ushort ARM7Read16( uint addr )
	{
		addr &= ~0x1u;
		if ( addr < 0x00004000 )
		{
			if ( ARM7.R[15] >= 0x00004000 ) return 0xFFFF;
			if ( addr < ARM7BIOSProt && ARM7.R[15] >= ARM7BIOSProt ) return 0xFFFF;
			return Read16( ARM7BIOS, addr );
		}

		switch ( addr & 0xFF800000 )
		{
			case 0x02000000:
			case 0x02800000: return Read16( MainRAM, addr & MainRAMMask );
			case 0x03000000: return SWRAM_ARM7.Mem != null ? Read16( SWRAM_ARM7.Mem, SWRAM_ARM7.Offset + (addr & SWRAM_ARM7.Mask) ) : Read16( ARM7WRAM, addr & (NdsConstants.Arm7WramSize - 1) );
			case 0x03800000: return Read16( ARM7WRAM, addr & (NdsConstants.Arm7WramSize - 1) );
			case 0x04000000: return ARM7IORead16( addr );
			case 0x04800000: return addr < 0x04810000 ? Wifi.Read( addr ) : (ushort)0;
			case 0x06000000:
			case 0x06800000: return (ushort)GPU.ReadVRAM_ARM7( addr, 2 );
			case 0x08000000:
			case 0x08800000:
			case 0x09000000:
			case 0x09800000:
			case 0x0A000000:
			case 0x0A800000: return (ushort)GbaSlotRead( addr, 2, 1 );
		}
		return 0;
	}

	public uint ARM7Read32( uint addr )
	{
		addr &= ~0x3u;
		if ( addr < 0x00004000 )
		{
			if ( ARM7.R[15] >= 0x00004000 ) return 0xFFFFFFFF;
			if ( addr < ARM7BIOSProt && ARM7.R[15] >= ARM7BIOSProt ) return 0xFFFFFFFF;
			return Read32( ARM7BIOS, addr );
		}

		switch ( addr & 0xFF800000 )
		{
			case 0x02000000:
			case 0x02800000: return Read32( MainRAM, addr & MainRAMMask );
			case 0x03000000: return SWRAM_ARM7.Mem != null ? Read32( SWRAM_ARM7.Mem, SWRAM_ARM7.Offset + (addr & SWRAM_ARM7.Mask) ) : Read32( ARM7WRAM, addr & (NdsConstants.Arm7WramSize - 1) );
			case 0x03800000: return Read32( ARM7WRAM, addr & (NdsConstants.Arm7WramSize - 1) );
			case 0x04000000: return ARM7IORead32( addr );
			case 0x04800000: return addr < 0x04810000 ? (uint)(Wifi.Read( addr ) | (Wifi.Read( addr + 2 ) << 16)) : 0u;
			case 0x06000000:
			case 0x06800000: return GPU.ReadVRAM_ARM7( addr, 4 );
			case 0x08000000:
			case 0x08800000:
			case 0x09000000:
			case 0x09800000:
			case 0x0A000000:
			case 0x0A800000: return GbaSlotRead( addr, 4, 1 );
		}
		return 0;
	}

	public void ARM7Write8( uint addr, byte val )
	{
		switch ( addr & 0xFF800000 )
		{
			case 0x02000000:
			case 0x02800000: MainRAM[addr & MainRAMMask] = val; return;
			case 0x03000000:
				if ( SWRAM_ARM7.Mem != null ) SWRAM_ARM7.Mem[SWRAM_ARM7.Offset + (addr & SWRAM_ARM7.Mask)] = val;
				else ARM7WRAM[addr & (NdsConstants.Arm7WramSize - 1)] = val;
				return;
			case 0x03800000: ARM7WRAM[addr & (NdsConstants.Arm7WramSize - 1)] = val; return;
			case 0x04000000: ARM7IOWrite8( addr, val ); return;
			case 0x06000000:
			case 0x06800000: GPU.WriteVRAM_ARM7( addr, val, 1 ); return;
		}
	}

	public void ARM7Write16( uint addr, ushort val )
	{
		addr &= ~0x1u;
		switch ( addr & 0xFF800000 )
		{
			case 0x02000000:
			case 0x02800000: Write16( MainRAM, addr & MainRAMMask, val ); return;
			case 0x03000000:
				if ( SWRAM_ARM7.Mem != null ) Write16( SWRAM_ARM7.Mem, SWRAM_ARM7.Offset + (addr & SWRAM_ARM7.Mask), val );
				else Write16( ARM7WRAM, addr & (NdsConstants.Arm7WramSize - 1), val );
				return;
			case 0x03800000: Write16( ARM7WRAM, addr & (NdsConstants.Arm7WramSize - 1), val ); return;
			case 0x04000000: ARM7IOWrite16( addr, val ); return;
			case 0x04800000: if ( addr < 0x04810000 ) Wifi.Write( addr, val ); return;
			case 0x06000000:
			case 0x06800000: GPU.WriteVRAM_ARM7( addr, val, 2 ); return;
		}
	}

	public void ARM7Write32( uint addr, uint val )
	{
		addr &= ~0x3u;
		switch ( addr & 0xFF800000 )
		{
			case 0x02000000:
			case 0x02800000:
				Write32( MainRAM, addr & MainRAMMask, val ); return;
			case 0x03000000:
				if ( SWRAM_ARM7.Mem != null ) Write32( SWRAM_ARM7.Mem, SWRAM_ARM7.Offset + (addr & SWRAM_ARM7.Mask), val );
				else Write32( ARM7WRAM, addr & (NdsConstants.Arm7WramSize - 1), val );
				return;
			case 0x03800000:
				Write32( ARM7WRAM, addr & (NdsConstants.Arm7WramSize - 1), val ); return;
			case 0x04000000: ARM7IOWrite32( addr, val ); return;
			case 0x04800000: if ( addr < 0x04810000 ) { Wifi.Write( addr, (ushort)val ); Wifi.Write( addr + 2, (ushort)(val >> 16) ); } return;
			case 0x06000000:
			case 0x06800000: GPU.WriteVRAM_ARM7( addr, val, 4 ); return;
		}
	}
}