Emulator/GbaMemory.cs
namespace sGBA;
public class GbaMemory
{
public byte[] Bios { get; set; }
public byte[] Wram { get; set; }
public byte[] Iwram { get; set; }
public byte[] PaletteRam { get; set; }
public byte[] Vram { get; set; }
public byte[] Oam { get; set; }
public byte[] Rom { get; set; }
public byte[] Sram { get; set; }
public ushort[] Io { get; set; }
public Gba Gba { get; }
public uint BiosPrefetch;
public int[] WaitstatesNonseq16 = new int[16];
public int[] WaitstatesNonseq32 = new int[16];
public int[] WaitstatesSeq16 = new int[16];
public int[] WaitstatesSeq32 = new int[16];
private static readonly int[] RomWaitN = [4, 3, 2, 8];
private static readonly int[] RomWaitS = [2, 1, 4, 1, 8, 1];
public bool Prefetch;
public uint LastPrefetchedPc;
public bool Debug;
public byte[] DebugString = new byte[0x100];
public ushort DebugFlags;
public uint AgbPrintBase;
public ushort AgbPrintProtect;
public ushort AgbPrintRequest;
public ushort AgbPrintBank;
public ushort AgbPrintGet;
public ushort AgbPrintPut;
public byte[] AgbPrintBuffer = new byte[0x10000];
private byte[] _agbPrintBufferBackup = [];
private bool _agbPrintHasBackup;
private ushort _agbPrintProtectBackup;
private ushort _agbPrintRequestBackup;
private ushort _agbPrintBankBackup;
private ushort _agbPrintGetBackup;
private ushort _agbPrintPutBackup;
private uint _agbPrintFuncBackup;
private bool _agbPrintInitialized;
private const uint AgbPrintBufferBase = 0x00FD0000;
private const uint AgbPrintTop = 0x00FE0000;
private const uint AgbPrintProtectAddress = 0x00FE2FFE;
private const uint AgbPrintStructAddress = 0x00FE20F8;
private const uint AgbPrintFlushAddress = 0x00FE209C;
private const uint AgbPrintFlushFunction = 0x4770DFFA;
public GbaMemory( Gba gba )
{
Gba = gba;
Bios = new byte[GbaConstants.BiosSize];
Wram = new byte[GbaConstants.EwramSize];
Iwram = new byte[GbaConstants.IwramSize];
PaletteRam = new byte[GbaConstants.PaletteSize];
Vram = new byte[GbaConstants.VramSize];
Oam = new byte[GbaConstants.OamSize];
Sram = new byte[GbaConstants.SramSize];
Io = new ushort[GbaConstants.IoSize / 2];
Rom = [];
InitDefaultWaitstates();
}
public void Reset()
{
Array.Clear( Bios );
Array.Clear( Wram );
Array.Clear( Iwram );
Array.Clear( PaletteRam );
Array.Clear( Vram );
Array.Clear( Oam );
Array.Clear( Sram );
Array.Clear( Io );
BiosPrefetch = 0;
LastPrefetchedPc = 0;
Debug = false;
Array.Clear( DebugString );
DebugFlags = 0;
ClearAgbPrint();
InitDefaultWaitstates();
}
public void ClearAgbPrint()
{
if ( _agbPrintHasBackup )
ApplyAgbPrintRomWindow( false );
AgbPrintBase = 0;
AgbPrintProtect = 0;
AgbPrintRequest = 0;
AgbPrintBank = 0;
AgbPrintGet = 0;
AgbPrintPut = 0;
_agbPrintHasBackup = false;
_agbPrintProtectBackup = 0;
_agbPrintRequestBackup = 0;
_agbPrintBankBackup = 0;
_agbPrintGetBackup = 0;
_agbPrintPutBackup = 0;
_agbPrintFuncBackup = 0;
_agbPrintInitialized = false;
Array.Clear( AgbPrintBuffer );
_agbPrintBufferBackup = [];
}
public void FlushAgbPrint()
{
if ( !_agbPrintInitialized )
return;
byte[] message = new byte[0x100];
int length = 0;
while ( AgbPrintGet != AgbPrintPut && length < message.Length )
{
message[length++] = AgbPrintBuffer[AgbPrintGet & 0xFFFF];
AgbPrintGet++;
}
StoreAgbPrintHalf( (AgbPrintStructAddress + 4) | AgbPrintBase, AgbPrintGet );
int terminator = Array.IndexOf( message, (byte)0, 0, length );
if ( terminator < 0 )
terminator = length;
string text = System.Text.Encoding.ASCII.GetString( message, 0, terminator );
GbaLog.Write( LogCategory.GBADebug, LogLevel.Info, text );
}
private void InitDefaultWaitstates()
{
int[] n16 = [0, 0, 2, 0, 0, 0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 0];
int[] n32 = [0, 0, 5, 0, 0, 1, 1, 0, 7, 7, 9, 9, 13, 13, 9, 0];
int[] s16 = [0, 0, 2, 0, 0, 0, 0, 0, 2, 2, 4, 4, 8, 8, 4, 0];
int[] s32 = [0, 0, 5, 0, 0, 1, 1, 0, 5, 5, 9, 9, 17, 17, 9, 0];
Array.Copy( n16, WaitstatesNonseq16, 16 );
Array.Copy( n32, WaitstatesNonseq32, 16 );
Array.Copy( s16, WaitstatesSeq16, 16 );
Array.Copy( s32, WaitstatesSeq32, 16 );
Prefetch = false;
LastPrefetchedPc = 0;
}
public int MemoryStall( uint pc, int wait )
{
int activeRegion = (int)((pc >> 24) & 0xF);
if ( activeRegion < 8 || !Prefetch )
return wait;
int previousLoads = 0;
uint dist = LastPrefetchedPc - pc;
int maxLoads = 8;
if ( dist < 16 )
{
previousLoads = (int)(dist >> 1);
maxLoads -= previousLoads;
}
int s = WaitstatesSeq16[activeRegion];
int stall = s + 1;
int loads = 1;
while ( stall < wait && loads < maxLoads )
{
stall += s;
loads++;
}
LastPrefetchedPc = pc + (uint)(2 * (loads + previousLoads - 1));
if ( stall > wait )
wait = stall;
wait -= WaitstatesNonseq16[activeRegion] - s;
wait -= stall;
return wait;
}
public void AdjustWaitstates( ushort waitcnt )
{
Prefetch = (waitcnt & 0x4000) != 0;
int sramWait = RomWaitN[waitcnt & 3];
int ws0N = RomWaitN[(waitcnt >> 2) & 3];
int ws0S = RomWaitS[(waitcnt >> 4) & 1];
int ws1N = RomWaitN[(waitcnt >> 5) & 3];
int ws1S = RomWaitS[((waitcnt >> 7) & 1) + 2];
int ws2N = RomWaitN[(waitcnt >> 8) & 3];
int ws2S = RomWaitS[((waitcnt >> 10) & 1) + 4];
WaitstatesNonseq16[8] = WaitstatesNonseq16[9] = ws0N;
WaitstatesSeq16[8] = WaitstatesSeq16[9] = ws0S;
WaitstatesNonseq32[8] = WaitstatesNonseq32[9] = ws0N + 1 + ws0S;
WaitstatesSeq32[8] = WaitstatesSeq32[9] = ws0S + 1 + ws0S;
WaitstatesNonseq16[10] = WaitstatesNonseq16[11] = ws1N;
WaitstatesSeq16[10] = WaitstatesSeq16[11] = ws1S;
WaitstatesNonseq32[10] = WaitstatesNonseq32[11] = ws1N + 1 + ws1S;
WaitstatesSeq32[10] = WaitstatesSeq32[11] = ws1S + 1 + ws1S;
WaitstatesNonseq16[12] = WaitstatesNonseq16[13] = ws2N;
WaitstatesSeq16[12] = WaitstatesSeq16[13] = ws2S;
WaitstatesNonseq32[12] = WaitstatesNonseq32[13] = ws2N + 1 + ws2S;
WaitstatesSeq32[12] = WaitstatesSeq32[13] = ws2S + 1 + ws2S;
WaitstatesNonseq16[14] = WaitstatesNonseq16[15] = sramWait;
WaitstatesSeq16[14] = WaitstatesSeq16[15] = sramWait;
WaitstatesNonseq32[14] = WaitstatesNonseq32[15] = sramWait + 1 + sramWait;
WaitstatesSeq32[14] = WaitstatesSeq32[15] = sramWait + 1 + sramWait;
if ( _agbPrintHasBackup )
ApplyAgbPrintRomWindow( ((waitcnt >> 11) & 3) == 3 );
}
public void LoadRom( byte[] romData )
{
Rom = romData;
}
public void LoadBios( byte[] biosData )
{
Array.Copy( biosData, Bios, Math.Min( biosData.Length, GbaConstants.BiosSize ) );
}
public void InstallHleBios()
{
Array.Clear( Bios, 0, GbaConstants.BiosSize );
GbaBios.HleBiosBlob.AsSpan().CopyTo( Bios.AsSpan() );
}
public byte Load8( uint address )
{
int region = (int)(address >> 24);
switch ( region )
{
case 0x0:
if ( address < GbaConstants.BiosSize )
{
if ( Gba.Cpu.Gprs[15] < GbaConstants.BiosSize )
{
uint addr = address & 0x3FFF;
BiosPrefetch = ReadWordFromArray( Bios, addr & ~3u );
return Bios[addr];
}
GbaLog.Write( LogCategory.GBAMem, LogLevel.GameError, "Bad BIOS Load8: 0x{0:X8}", address );
return (byte)(BiosPrefetch >> (int)((address & 3) * 8));
}
GbaLog.Write( LogCategory.GBAMem, LogLevel.GameError, "Bad memory Load8: 0x{0:X8}", address );
return (byte)(Gba.Cpu.LoadBadValue() >> (int)((address & 3) * 8));
case 0x2: return Wram[address & 0x3FFFF];
case 0x3: return Iwram[address & 0x7FFF];
case 0x4: return ReadIO8( address );
case 0x5: return PaletteRam[address & 0x3FF];
case 0x6:
{
uint rawAddr = address & 0x1FFFF;
if ( (rawAddr & 0x1C000) == 0x18000 && (Gba.Video.DispCnt & 7) >= 3 )
{
GbaLog.Write( LogCategory.GBAMem, LogLevel.GameError, "Bad VRAM Load8: 0x{0:X8}", address );
return 0;
}
return Vram[MapVramAddress( address )];
}
case 0x7: return Oam[address & 0x3FF];
case 0x8:
case 0x9:
case 0xA:
case 0xB:
case 0xC:
{
uint romOffset = address & 0x1FFFFFF;
if ( romOffset < (uint)Rom.Length )
return Rom[romOffset];
GbaLog.Write( LogCategory.GBAMem, LogLevel.GameError, "Out of bounds ROM Load8: 0x{0:X8}", address );
return (byte)(romOffset >> 1 >> ((int)(address & 1) * 8));
}
case 0xD:
{
uint romAddr = address & 0x1FFFFFF;
if ( romAddr < (uint)Rom.Length )
return Rom[romAddr];
GbaLog.Write( LogCategory.GBAMem, LogLevel.GameError, "Out of bounds ROM Load8: 0x{0:X8}", address );
return (byte)(romAddr >> 1 >> ((int)(address & 1) * 8));
}
case 0xE:
case 0xF:
return Gba.Savedata.Read8( address );
default:
GbaLog.Write( LogCategory.GBAMem, LogLevel.GameError, "Bad memory Load8: 0x{0:X8}", address );
return (byte)(Gba.Cpu.LoadBadValue() >> (int)((address & 3) * 8));
}
}
public ushort Load16( uint address )
{
int region = (int)(address >> 24);
if ( region >= 0xE )
{
byte b = Gba.Savedata.Read8( address );
return (ushort)(b * 0x0101);
}
address &= ~1u;
switch ( region )
{
case 0x0:
if ( address < GbaConstants.BiosSize )
{
if ( Gba.Cpu.Gprs[15] < GbaConstants.BiosSize )
{
uint addr = address & 0x3FFF;
BiosPrefetch = ReadWordFromArray( Bios, addr & ~3u );
return ReadHalfFromArray( Bios, addr );
}
GbaLog.Write( LogCategory.GBAMem, LogLevel.GameError, "Bad BIOS Load16: 0x{0:X8}", address );
return (ushort)(BiosPrefetch >> (int)((address & 2) * 8));
}
GbaLog.Write( LogCategory.GBAMem, LogLevel.GameError, "Bad memory Load16: 0x{0:X8}", address );
return (ushort)(Gba.Cpu.LoadBadValue() >> (int)((address & 2) * 8));
case 0x2: return ReadHalfFromArray( Wram, address & 0x3FFFF );
case 0x3: return ReadHalfFromArray( Iwram, address & 0x7FFF );
case 0x4: return ReadIO16( address );
case 0x5: return ReadHalfFromArray( PaletteRam, address & 0x3FF );
case 0x6:
{
uint rawAddr = address & 0x1FFFF;
if ( (rawAddr & 0x1C000) == 0x18000 && (Gba.Video.DispCnt & 7) >= 3 )
{
GbaLog.Write( LogCategory.GBAMem, LogLevel.GameError, "Bad VRAM Load16: 0x{0:X8}", address );
return 0;
}
return ReadHalfFromArray( Vram, MapVramAddress( address ) );
}
case 0x7: return ReadHalfFromArray( Oam, address & 0x3FF );
case 0x8:
case 0x9:
case 0xA:
case 0xB:
case 0xC:
{
uint romOffset = address & 0x1FFFFFF;
if ( romOffset + 1 < (uint)Rom.Length )
return ReadHalfFromArray( Rom, romOffset );
uint romAddress = address & 0x01FFFFFF;
if ( TryReadAgbPrintHalf( romAddress, out ushort agbPrintValue ) )
return agbPrintValue;
GbaLog.Write( LogCategory.GBAMem, LogLevel.GameError, "Out of bounds ROM Load16: 0x{0:X8}", address );
return (ushort)(romOffset >> 1);
}
case 0xD:
{
if ( Gba.Savedata.Type == SavedataType.Eeprom )
return Gba.Savedata.ReadEEPROM();
uint romOffset = address & 0x1FFFFFF;
if ( romOffset + 1 < (uint)Rom.Length )
return ReadHalfFromArray( Rom, romOffset );
GbaLog.Write( LogCategory.GBAMem, LogLevel.GameError, "Out of bounds ROM Load16: 0x{0:X8}", address );
return (ushort)(romOffset >> 1);
}
default:
GbaLog.Write( LogCategory.GBAMem, LogLevel.GameError, "Bad memory Load16: 0x{0:X8}", address );
return (ushort)(Gba.Cpu.LoadBadValue() >> (int)((address & 2) * 8));
}
}
public uint Load32( uint address )
{
int region = (int)(address >> 24);
if ( region >= 0xE )
{
byte b = Gba.Savedata.Read8( address );
return (uint)(b | (b << 8) | (b << 16) | (b << 24));
}
address &= ~3u;
switch ( region )
{
case 0x0:
if ( address < GbaConstants.BiosSize )
{
if ( Gba.Cpu.Gprs[15] < GbaConstants.BiosSize )
{
uint addr = address & 0x3FFF;
BiosPrefetch = ReadWordFromArray( Bios, addr );
return BiosPrefetch;
}
GbaLog.Write( LogCategory.GBAMem, LogLevel.GameError, "Bad BIOS Load32: 0x{0:X8}", address );
return BiosPrefetch;
}
GbaLog.Write( LogCategory.GBAMem, LogLevel.GameError, "Bad memory Load32: 0x{0:X8}", address );
return Gba.Cpu.LoadBadValue();
case 0x2: return ReadWordFromArray( Wram, address & 0x3FFFF );
case 0x3: return ReadWordFromArray( Iwram, address & 0x7FFF );
case 0x4: return ReadIO32( address );
case 0x5: return ReadWordFromArray( PaletteRam, address & 0x3FF );
case 0x6:
{
uint rawAddr = address & 0x1FFFF;
if ( (rawAddr & 0x1C000) == 0x18000 && (Gba.Video.DispCnt & 7) >= 3 )
{
GbaLog.Write( LogCategory.GBAMem, LogLevel.GameError, "Bad VRAM Load32: 0x{0:X8}", address );
return 0;
}
return ReadWordFromArray( Vram, MapVramAddress( address ) );
}
case 0x7: return ReadWordFromArray( Oam, address & 0x3FF );
case 0x8:
case 0x9:
case 0xA:
case 0xB:
case 0xC:
{
uint romOffset = address & 0x1FFFFFF;
if ( romOffset + 3 < (uint)Rom.Length )
return ReadWordFromArray( Rom, romOffset );
GbaLog.Write( LogCategory.GBAMem, LogLevel.GameError, "Out of bounds ROM Load32: 0x{0:X8}", address );
return (romOffset >> 1) & 0xFFFF | ((romOffset >> 1) + 1) << 16;
}
case 0xD:
{
uint romOffset = address & 0x1FFFFFF;
if ( romOffset + 3 < (uint)Rom.Length )
return ReadWordFromArray( Rom, romOffset );
GbaLog.Write( LogCategory.GBAMem, LogLevel.GameError, "Out of bounds ROM Load32: 0x{0:X8}", address );
return (romOffset >> 1) & 0xFFFF | ((romOffset >> 1) + 1) << 16;
}
default:
GbaLog.Write( LogCategory.GBAMem, LogLevel.GameError, "Bad memory Load32: 0x{0:X8}", address );
return Gba.Cpu.LoadBadValue();
}
}
public void Store8( uint address, byte value )
{
int region = (int)(address >> 24);
switch ( region )
{
case 0x2: Wram[address & 0x3FFFF] = value; break;
case 0x3: Iwram[address & 0x7FFF] = value; break;
case 0x4: WriteIO8( address, value ); break;
case 0x5:
{
uint addr = address & 0x3FE;
PaletteRam[addr] = value;
PaletteRam[addr + 1] = value;
}
break;
case 0x6:
{
uint objThreshold = (uint)((Gba.Video.DispCnt & 7) >= 3 ? 0x14000 : 0x10000);
if ( (address & 0x1FFFF) >= objThreshold )
{
GbaLog.Write( LogCategory.GBAMem, LogLevel.GameError, "Cannot Store8 to OBJ: 0x{0:X8}", address );
break;
}
uint addr = address & 0x1FFFE;
Vram[addr] = value;
Vram[addr + 1] = value;
}
break;
case 0x7:
GbaLog.Write( LogCategory.GBAMem, LogLevel.GameError, "Cannot Store8 to OAM: 0x{0:X8}", address );
break;
case 0x8:
GbaLog.Write( LogCategory.GBAMem, LogLevel.Stub, "Unimplemented memory Store8: 0x{0:X8}", address );
break;
case 0x9:
case 0xA:
case 0xB:
case 0xC:
case 0xD:
GbaLog.Write( LogCategory.GBAMem, LogLevel.GameError, "Bad memory Store8: 0x{0:X8}", address );
break;
case 0xE:
case 0xF:
Gba.Savedata.Write8( address, value );
break;
}
}
public void Store16( uint address, ushort value )
{
int region = (int)(address >> 24);
if ( region >= 0xE )
{
byte b = (address & 1) != 0 ? (byte)(value >> 8) : (byte)value;
Gba.Savedata.Write8( address, b );
return;
}
address &= ~1u;
switch ( region )
{
case 0x2: WriteHalfToArray( Wram, address & 0x3FFFF, value ); break;
case 0x3: WriteHalfToArray( Iwram, address & 0x7FFF, value ); break;
case 0x4: WriteIO16( address, value ); break;
case 0x5: WriteHalfToArray( PaletteRam, address & 0x3FF, value ); break;
case 0x6:
{
uint rawAddr = address & 0x1FFFF;
if ( (rawAddr & 0x1C000) == 0x18000 && (Gba.Video.DispCnt & 7) >= 3 )
{
GbaLog.Write( LogCategory.GBAMem, LogLevel.GameError, "Bad VRAM Store16: 0x{0:X8}", address );
break;
}
WriteHalfToArray( Vram, MapVramAddress( address ), value );
}
break;
case 0x7: WriteHalfToArray( Oam, address & 0x3FF, value ); Gba.Video._oamDirty = true; break;
case 0x8:
case 0x9:
{
uint romOffset = address & 0x1FFFFFF;
if ( region == 0x8 && romOffset >= 0xC4 && romOffset <= 0xC8 && (romOffset & 1) == 0 )
{
if ( Gba.Hardware.HasRtc )
Gba.Hardware.GpioWrite( romOffset, value );
else
GbaLog.Write( LogCategory.GBAHardware, LogLevel.Warn, "Write to GPIO address {0:X8} on cartridge without GPIO", address );
break;
}
uint romAddress = address & 0x01FFFFFF;
if ( TryWriteAgbPrintHalf( romAddress, value ) )
break;
GbaLog.Write( LogCategory.GBAMem, LogLevel.GameError, "Bad cartridge Store16: 0x{0:X8}", address );
break;
}
case 0xA:
case 0xB:
case 0xC:
GbaLog.Write( LogCategory.GBAMem, LogLevel.GameError, "Bad memory Store16: 0x{0:X8}", address );
break;
case 0xD:
if ( Gba.Savedata.Type == SavedataType.Eeprom )
Gba.Savedata.WriteEEPROM( value, 1 );
else
GbaLog.Write( LogCategory.GBAMem, LogLevel.GameError, "Bad memory Store16: 0x{0:X8}", address );
break;
}
}
public void Store32( uint address, uint value )
{
int region = (int)(address >> 24);
if ( region >= 0xE )
{
Gba.Savedata.Write8( address, (byte)(value >> (int)(8 * (address & 3))) );
return;
}
address &= ~3u;
switch ( region )
{
case 0x2: WriteWordToArray( Wram, address & 0x3FFFF, value ); break;
case 0x3: WriteWordToArray( Iwram, address & 0x7FFF, value ); break;
case 0x4: WriteIO32( address, value ); break;
case 0x5: WriteWordToArray( PaletteRam, address & 0x3FF, value ); break;
case 0x6:
{
uint rawAddr = address & 0x1FFFF;
if ( (rawAddr & 0x1C000) == 0x18000 && (Gba.Video.DispCnt & 7) >= 3 )
{
GbaLog.Write( LogCategory.GBAMem, LogLevel.GameError, "Bad VRAM Store32: 0x{0:X8}", address );
break;
}
WriteWordToArray( Vram, MapVramAddress( address ), value );
}
break;
case 0x7: WriteWordToArray( Oam, address & 0x3FF, value ); Gba.Video._oamDirty = true; break;
case 0x8:
case 0x9:
case 0xA:
case 0xB:
case 0xC:
case 0xD:
GbaLog.Write( LogCategory.GBAMem, LogLevel.Stub, "Unimplemented memory Store32: 0x{0:X8}", address );
break;
}
}
private byte ReadIO8( uint address )
{
uint offset = address & 0x00FFFFFF;
if ( offset >= 0xFFF600 && offset < 0xFFF700 )
{
if ( Debug )
return DebugString[offset - 0xFFF600];
return 0;
}
if ( offset >= 0x400 ) return (byte)(Gba.Cpu.OpenBusPrefetch >> ((int)(address & 3) * 8));
ushort val = Gba.Io.Read16( offset & ~1u );
return (address & 1) == 0 ? (byte)val : (byte)(val >> 8);
}
private ushort ReadIO16( uint address )
{
uint offset = address & 0x00FFFFFF;
if ( offset >= 0xFFF600 )
{
if ( offset >= 0xFFF600 && offset < 0xFFF700 && Debug )
{
uint idx = offset - 0xFFF600;
if ( idx + 1 < 0x100 )
return (ushort)(DebugString[idx] | (DebugString[idx + 1] << 8));
return 0;
}
if ( offset == 0xFFF700 ) return DebugFlags;
if ( offset == 0xFFF780 ) return Debug ? (ushort)0x1DEA : (ushort)0;
return 0;
}
if ( offset >= 0x400 ) return (ushort)Gba.Cpu.OpenBusPrefetch;
return Gba.Io.Read16( offset );
}
private uint ReadIO32( uint address )
{
uint offset = address & 0x00FFFFFF;
if ( offset >= 0xFFF600 )
{
ushort lo = ReadIO16( address );
ushort hi = ReadIO16( address + 2 );
return (uint)(lo | (hi << 16));
}
if ( offset >= 0x400 ) return Gba.Cpu.OpenBusPrefetch;
ushort lo2 = Gba.Io.Read16( offset );
ushort hi2 = Gba.Io.Read16( offset + 2 );
return (uint)(lo2 | (hi2 << 16));
}
private void WriteIO8( uint address, byte value )
{
uint offset = address & 0x00FFFFFF;
if ( offset >= 0xFFF600 && offset < 0xFFF700 )
{
if ( Debug )
DebugString[offset - 0xFFF600] = value;
return;
}
if ( offset >= 0x400 ) return;
Gba.Io.Write8( offset, value );
}
private void WriteIO16( uint address, ushort value )
{
uint offset = address & 0x00FFFFFF;
if ( offset >= 0xFFF600 )
{
if ( offset >= 0xFFF600 && offset < 0xFFF700 && Debug )
{
uint idx = offset - 0xFFF600;
if ( idx + 1 < 0x100 )
{
DebugString[idx] = (byte)value;
DebugString[idx + 1] = (byte)(value >> 8);
}
return;
}
if ( offset == 0xFFF700 )
{
if ( Debug )
HandleDebugFlags( value );
return;
}
if ( offset == 0xFFF780 )
{
Debug = value == 0xC0DE;
return;
}
return;
}
if ( offset >= 0x400 ) return;
Gba.Io.Write16( offset, value );
}
private void WriteIO32( uint address, uint value )
{
uint offset = address & 0x00FFFFFF;
if ( offset >= 0xFFF600 )
{
if ( offset >= 0xFFF600 && offset < 0xFFF700 && Debug )
{
uint idx = offset - 0xFFF600;
if ( idx + 3 < 0x100 )
{
DebugString[idx] = (byte)value;
DebugString[idx + 1] = (byte)(value >> 8);
DebugString[idx + 2] = (byte)(value >> 16);
DebugString[idx + 3] = (byte)(value >> 24);
}
return;
}
WriteIO16( address, (ushort)value );
WriteIO16( address + 2, (ushort)(value >> 16) );
return;
}
if ( offset >= 0x400 ) return;
Gba.Io.Write16( offset, (ushort)value );
Gba.Io.Write16( offset + 2, (ushort)(value >> 16) );
}
private bool TryReadAgbPrintHalf( uint romAddress, out ushort value )
{
uint lowAddress = romAddress & 0x00FFFFFF;
lowAddress &= ~1u;
if ( lowAddress == AgbPrintProtectAddress )
{
value = AgbPrintProtect;
return true;
}
if ( lowAddress >= AgbPrintBufferBase && lowAddress < AgbPrintTop )
{
value = _agbPrintInitialized
? ReadHalfFromArray( AgbPrintBuffer, lowAddress & 0xFFFE )
: (ushort)(lowAddress >> 1);
return true;
}
if ( (lowAddress & 0x00FFFFF8) == AgbPrintStructAddress )
{
value = ReadAgbPrintContextHalf( lowAddress );
return true;
}
value = 0;
return false;
}
private bool TryWriteAgbPrintHalf( uint romAddress, ushort value )
{
uint lowAddress = romAddress & 0x00FFFFFF;
if ( lowAddress == AgbPrintProtectAddress )
{
AgbPrintProtect = value;
if ( !_agbPrintInitialized )
{
AgbPrintBase = 0;
if ( Rom.Length >= 0x01000000 )
{
if ( Rom.Length == 0x02000000 )
AgbPrintBase = romAddress & 0x01000000;
_agbPrintBufferBackup = new byte[AgbPrintBuffer.Length];
Array.Copy( Rom, (int)(AgbPrintTop | AgbPrintBase), _agbPrintBufferBackup, 0, AgbPrintBuffer.Length );
_agbPrintProtectBackup = ReadHalfFromArray( Rom, AgbPrintProtectAddress | AgbPrintBase );
_agbPrintRequestBackup = ReadHalfFromArray( Rom, AgbPrintStructAddress | AgbPrintBase );
_agbPrintBankBackup = ReadHalfFromArray( Rom, (AgbPrintStructAddress | AgbPrintBase) + 2 );
_agbPrintGetBackup = ReadHalfFromArray( Rom, (AgbPrintStructAddress | AgbPrintBase) + 4 );
_agbPrintPutBackup = ReadHalfFromArray( Rom, (AgbPrintStructAddress | AgbPrintBase) + 6 );
_agbPrintFuncBackup = ReadWordFromArray( Rom, AgbPrintFlushAddress | AgbPrintBase );
_agbPrintHasBackup = true;
}
_agbPrintInitialized = true;
}
if ( value == 0x20 )
StoreAgbPrintHalf( romAddress, value );
return true;
}
if ( AgbPrintProtect != 0x20 )
return false;
if ( (lowAddress >= AgbPrintBufferBase && lowAddress < AgbPrintTop) || (lowAddress & 0x00FFFFF8) == AgbPrintStructAddress )
{
StoreAgbPrintHalf( romAddress, value );
return true;
}
return false;
}
private void StoreAgbPrintHalf( uint romAddress, ushort value )
{
uint lowAddress = romAddress & 0x00FFFFFF;
if ( lowAddress >= AgbPrintBufferBase && lowAddress < AgbPrintTop )
WriteHalfToArray( AgbPrintBuffer, lowAddress & 0xFFFE, value );
else if ( (lowAddress & 0x00FFFFF8) == AgbPrintStructAddress )
WriteAgbPrintContextHalf( lowAddress, value );
if ( Rom.Length == 0x02000000 )
WriteHalfToArray( Rom, romAddress & 0x01FFFFFE, value );
else if ( AgbPrintBank == 0xFD && Rom.Length >= 0x01000000 )
WriteHalfToArray( Rom, romAddress & 0x00FFFFFE, value );
}
private void ApplyAgbPrintRomWindow( bool activeState )
{
int baseOffset = (int)(AgbPrintTop | AgbPrintBase);
Array.Copy( activeState ? AgbPrintBuffer : _agbPrintBufferBackup, 0, Rom, baseOffset, AgbPrintBuffer.Length );
if ( activeState )
{
WriteHalfToArray( Rom, AgbPrintProtectAddress | AgbPrintBase, AgbPrintProtect );
WriteHalfToArray( Rom, AgbPrintStructAddress | AgbPrintBase, AgbPrintRequest );
WriteHalfToArray( Rom, (AgbPrintStructAddress | AgbPrintBase) + 2, AgbPrintBank );
WriteHalfToArray( Rom, (AgbPrintStructAddress | AgbPrintBase) + 4, AgbPrintGet );
WriteHalfToArray( Rom, (AgbPrintStructAddress | AgbPrintBase) + 6, AgbPrintPut );
WriteWordToArray( Rom, AgbPrintFlushAddress | AgbPrintBase, AgbPrintFlushFunction );
}
else
{
WriteHalfToArray( Rom, AgbPrintProtectAddress | AgbPrintBase, _agbPrintProtectBackup );
WriteHalfToArray( Rom, AgbPrintStructAddress | AgbPrintBase, _agbPrintRequestBackup );
WriteHalfToArray( Rom, (AgbPrintStructAddress | AgbPrintBase) + 2, _agbPrintBankBackup );
WriteHalfToArray( Rom, (AgbPrintStructAddress | AgbPrintBase) + 4, _agbPrintGetBackup );
WriteHalfToArray( Rom, (AgbPrintStructAddress | AgbPrintBase) + 6, _agbPrintPutBackup );
WriteWordToArray( Rom, AgbPrintFlushAddress | AgbPrintBase, _agbPrintFuncBackup );
}
}
private ushort ReadAgbPrintContextHalf( uint romAddress ) => (romAddress & 6) switch
{
0 => AgbPrintRequest,
2 => AgbPrintBank,
4 => AgbPrintGet,
6 => AgbPrintPut,
_ => 0
};
private void WriteAgbPrintContextHalf( uint romAddress, ushort value )
{
switch ( romAddress & 6 )
{
case 0:
AgbPrintRequest = value;
break;
case 2:
AgbPrintBank = value;
break;
case 4:
AgbPrintGet = value;
break;
case 6:
AgbPrintPut = value;
break;
}
}
private void HandleDebugFlags( ushort flags )
{
DebugFlags = flags;
bool send = (flags & 0x100) != 0;
if ( !send ) return;
int level = (1 << (flags & 0x7)) & 0x1F;
int len = 0;
while ( len < 0x100 && DebugString[len] != 0 ) len++;
string msg = System.Text.Encoding.ASCII.GetString( DebugString, 0, len );
Array.Clear( DebugString );
DebugFlags = (ushort)(flags & ~0x100);
GbaLog.Write( LogCategory.GBADebug, (LogLevel)level, msg );
}
public static uint MapVramAddress( uint address )
{
uint addr = address & 0x1FFFF;
if ( addr >= 0x18000 ) addr -= 0x8000;
return addr;
}
private static ushort ReadHalfFromArray( byte[] arr, uint offset )
{
return (ushort)(arr[offset] | (arr[offset + 1] << 8));
}
private static uint ReadWordFromArray( byte[] arr, uint offset )
{
return (uint)(arr[offset] | (arr[offset + 1] << 8) | (arr[offset + 2] << 16) | (arr[offset + 3] << 24));
}
private static void WriteHalfToArray( byte[] arr, uint offset, ushort value )
{
arr[offset] = (byte)value;
arr[offset + 1] = (byte)(value >> 8);
}
private static void WriteWordToArray( byte[] arr, uint offset, uint value )
{
arr[offset] = (byte)value;
arr[offset + 1] = (byte)(value >> 8);
arr[offset + 2] = (byte)(value >> 16);
arr[offset + 3] = (byte)(value >> 24);
}
}