2024-08-16 11:06:40 +08:00
|
|
|
using System;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.Runtime.CompilerServices;
|
|
|
|
using System.Collections.Concurrent;
|
|
|
|
using static OptimeGBA.Bits;
|
|
|
|
using System.Runtime.InteropServices;
|
|
|
|
using static OptimeGBA.MemoryUtil;
|
|
|
|
|
|
|
|
namespace OptimeGBA
|
|
|
|
{
|
|
|
|
public sealed unsafe class MemoryGba : Memory
|
|
|
|
{
|
|
|
|
Gba Gba;
|
|
|
|
|
|
|
|
public MemoryGba(Gba gba, ProviderGba provider)
|
|
|
|
{
|
|
|
|
Gba = gba;
|
|
|
|
|
|
|
|
for (uint i = 0; i < MaxRomSize && i < provider.Rom.Length; i++)
|
|
|
|
{
|
|
|
|
Rom[i] = provider.Rom[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
for (uint i = 0; i < BiosSize && i < provider.Bios.Length; i++)
|
|
|
|
{
|
|
|
|
Bios[i] = provider.Bios[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
RomSize = (uint)provider.Rom.Length;
|
|
|
|
|
|
|
|
// Detect save type
|
|
|
|
|
|
|
|
string[] strings = {
|
|
|
|
"NONE_LOLOLLEXTRATONOTMATCHRANDOMSTRINGS",
|
|
|
|
"EEPROM_",
|
|
|
|
"SRAM_",
|
|
|
|
"FLASH_",
|
|
|
|
"FLASH512_",
|
|
|
|
"FLASH1M_",
|
|
|
|
};
|
|
|
|
uint matchedIndex = 0;
|
|
|
|
|
|
|
|
for (uint i = 0; i < strings.Length; i++)
|
|
|
|
{
|
|
|
|
char[] chars = strings[i].ToCharArray();
|
|
|
|
|
|
|
|
int stringLength = chars.Length;
|
|
|
|
int matchLength = 0;
|
|
|
|
for (uint j = 0; j < provider.Rom.Length; j++)
|
|
|
|
{
|
|
|
|
if (provider.Rom[j] == chars[matchLength])
|
|
|
|
{
|
|
|
|
matchLength++;
|
|
|
|
if (matchLength >= chars.Length)
|
|
|
|
{
|
|
|
|
matchedIndex = i;
|
|
|
|
goto breakOuterLoop;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
matchLength = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
breakOuterLoop:
|
|
|
|
|
2024-08-16 14:51:15 +08:00
|
|
|
//Debug.Log($"Save Type: {strings[matchedIndex]}");
|
2024-08-16 11:06:40 +08:00
|
|
|
|
|
|
|
switch (matchedIndex)
|
|
|
|
{
|
|
|
|
case 0: SaveProvider = new NullSaveProvider(); break;
|
|
|
|
case 1:
|
|
|
|
SaveProvider = new Eeprom(Gba, EepromSize.Eeprom64k);
|
|
|
|
if (RomSize < 16777216)
|
|
|
|
{
|
|
|
|
EepromThreshold = 0x1000000;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
EepromThreshold = 0x1FFFF00;
|
|
|
|
}
|
2024-08-16 14:51:15 +08:00
|
|
|
//Debug.Log("EEPROM Threshold: " + Util.Hex(EepromThreshold, 8));
|
2024-08-16 11:06:40 +08:00
|
|
|
break;
|
|
|
|
case 2: SaveProvider = new Sram(); break;
|
|
|
|
case 3: SaveProvider = new Flash(Gba, FlashSize.Flash512k); break;
|
|
|
|
case 4: SaveProvider = new Flash(Gba, FlashSize.Flash512k); break;
|
|
|
|
case 5: SaveProvider = new Flash(Gba, FlashSize.Flash1m); break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public uint EepromThreshold = 0x2000000;
|
|
|
|
|
|
|
|
public const int BiosSize = 16384;
|
|
|
|
public const int MaxRomSize = 67108864;
|
|
|
|
public const int EwramSize = 262144;
|
|
|
|
public const int IwramSize = 32768;
|
|
|
|
public uint RomSize;
|
|
|
|
|
|
|
|
public byte[] Bios = new byte[BiosSize];
|
|
|
|
public byte[] Rom = new byte[MaxRomSize];
|
|
|
|
public byte[] Ewram = new byte[EwramSize];
|
|
|
|
public byte[] Iwram = new byte[IwramSize];
|
|
|
|
|
|
|
|
public override void InitPageTable(byte*[] table, uint[] maskTable, bool write)
|
|
|
|
{
|
|
|
|
byte* bios = TryPinByteArray(Bios);
|
|
|
|
byte* ewram = TryPinByteArray(Ewram);
|
|
|
|
byte* iwram = TryPinByteArray(Iwram);
|
|
|
|
byte* palettes = TryPinByteArray(Gba.Ppu.Renderer.Palettes);
|
|
|
|
byte* vram = TryPinByteArray(Gba.Ppu.Vram);
|
|
|
|
byte* emptyPage = TryPinByteArray(EmptyPage);
|
|
|
|
byte* oam = TryPinByteArray(Gba.Ppu.Renderer.Oam);
|
|
|
|
byte* rom = TryPinByteArray(Rom);
|
|
|
|
|
|
|
|
// 12 bits shaved off already, shave off another 12 to get 24
|
|
|
|
for (uint i = 0; i < 1048576; i++)
|
|
|
|
{
|
|
|
|
uint addr = (uint)(i << 12);
|
|
|
|
switch (i >> 12)
|
|
|
|
{
|
|
|
|
case 0x0: // BIOS
|
|
|
|
if (!write)
|
|
|
|
{
|
|
|
|
table[i] = bios;
|
|
|
|
}
|
|
|
|
maskTable[i] = 0x00003FFF;
|
|
|
|
break;
|
|
|
|
case 0x2: // EWRAM
|
|
|
|
table[i] = ewram;
|
|
|
|
maskTable[i] = 0x0003FFFF;
|
|
|
|
break;
|
|
|
|
case 0x3: // IWRAM
|
|
|
|
table[i] = iwram;
|
|
|
|
maskTable[i] = 0x00007FFF;
|
|
|
|
break;
|
|
|
|
case 0x5: // Palettes
|
|
|
|
if (!write)
|
|
|
|
{
|
|
|
|
table[i] = palettes;
|
|
|
|
}
|
|
|
|
maskTable[i] = 0x3FF;
|
|
|
|
break;
|
|
|
|
case 0x6: // PPU VRAM
|
|
|
|
addr &= 0x1FFFF;
|
|
|
|
if (addr < 0x18000)
|
|
|
|
{
|
|
|
|
table[i] = vram;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
table[i] = emptyPage;
|
|
|
|
}
|
|
|
|
maskTable[i] = 0x0001FFFF; // VRAM
|
|
|
|
break;
|
|
|
|
case 0x7: // PPU OAM
|
|
|
|
table[i] = oam;
|
|
|
|
maskTable[i] = 0x000003FF;
|
|
|
|
break;
|
|
|
|
case 0x8: // Game Pak ROM/FlashROM
|
|
|
|
case 0x9: // Game Pak ROM/FlashROM
|
|
|
|
case 0xA: // Game Pak ROM/FlashROM
|
|
|
|
case 0xB: // Game Pak ROM/FlashROM
|
|
|
|
case 0xC: // Game Pak ROM/FlashROM
|
|
|
|
if (!write)
|
|
|
|
{
|
|
|
|
table[i] = rom;
|
|
|
|
}
|
|
|
|
maskTable[i] = 0x01FFFFFF;
|
|
|
|
break;
|
|
|
|
case 0xD: // Game Pak ROM/FlashROM/EEPROM
|
|
|
|
maskTable[i] = 0x01FFFFFF;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
~MemoryGba()
|
|
|
|
{
|
2024-08-16 14:51:15 +08:00
|
|
|
//Debug.Log("Cleaning up GBA memory...");
|
2024-08-16 11:06:40 +08:00
|
|
|
UnpinByteArray(Bios);
|
|
|
|
UnpinByteArray(Ewram);
|
|
|
|
UnpinByteArray(Iwram);
|
|
|
|
UnpinByteArray(Gba.Ppu.Renderer.Palettes);
|
|
|
|
UnpinByteArray(Gba.Ppu.Vram);
|
|
|
|
UnpinByteArray(EmptyPage);
|
|
|
|
UnpinByteArray(Gba.Ppu.Renderer.Oam);
|
|
|
|
UnpinByteArray(Rom);
|
|
|
|
}
|
|
|
|
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
|
|
public override byte Read8Unregistered(bool debug, uint addr)
|
|
|
|
{
|
|
|
|
switch (addr >> 24)
|
|
|
|
{
|
|
|
|
case 0x4: // I/O Registers
|
|
|
|
// addr &= 0x400FFFF;
|
|
|
|
return ReadHwio8(debug, addr);
|
|
|
|
case 0xA: // ROM / EEPROM
|
|
|
|
case 0xB: // ROM / EEPROM
|
|
|
|
case 0xC: // ROM / EEPROM
|
|
|
|
case 0xD: // ROM / EEPROM
|
|
|
|
uint adjAddr = addr & 0x1FFFFFF;
|
|
|
|
if (adjAddr >= EepromThreshold)
|
|
|
|
{
|
|
|
|
return SaveProvider.Read8(adjAddr);
|
|
|
|
}
|
|
|
|
|
|
|
|
return GetByte(Rom, adjAddr);
|
|
|
|
case 0xE: // Game Pak SRAM/Flash
|
|
|
|
case 0xF: // Game Pak SRAM/Flash
|
|
|
|
return SaveProvider.Read8(addr);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
|
|
public override ushort Read16Unregistered(bool debug, uint addr)
|
|
|
|
{
|
|
|
|
switch (addr >> 24)
|
|
|
|
{
|
|
|
|
case 0x4: // I/O Registers
|
|
|
|
byte f0 = Read8Unregistered(debug, addr++);
|
|
|
|
byte f1 = Read8Unregistered(debug, addr++);
|
|
|
|
|
|
|
|
ushort u16 = (ushort)((f1 << 8) | (f0 << 0));
|
|
|
|
|
|
|
|
return u16;
|
|
|
|
case 0xA: // ROM / EEPROM
|
|
|
|
case 0xB: // ROM / EEPROM
|
|
|
|
case 0xC: // ROM / EEPROM
|
|
|
|
case 0xD: // ROM / EEPROM
|
|
|
|
uint adjAddr = addr & 0x1FFFFFF;
|
|
|
|
if (adjAddr >= EepromThreshold)
|
|
|
|
{
|
|
|
|
return SaveProvider.Read8(adjAddr);
|
|
|
|
}
|
|
|
|
|
|
|
|
return GetUshort(Rom, adjAddr);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
|
|
public override uint Read32Unregistered(bool debug, uint addr)
|
|
|
|
{
|
|
|
|
switch (addr >> 24)
|
|
|
|
{
|
|
|
|
case 0x4: // I/O Registers
|
|
|
|
byte f0 = ReadHwio8(debug, addr++);
|
|
|
|
byte f1 = ReadHwio8(debug, addr++);
|
|
|
|
byte f2 = ReadHwio8(debug, addr++);
|
|
|
|
byte f3 = ReadHwio8(debug, addr++);
|
|
|
|
|
|
|
|
uint u32 = (uint)((f3 << 24) | (f2 << 16) | (f1 << 8) | (f0 << 0));
|
|
|
|
|
|
|
|
return u32;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
|
|
public override void Write8Unregistered(bool debug, uint addr, byte val)
|
|
|
|
{
|
|
|
|
switch (addr >> 24)
|
|
|
|
{
|
|
|
|
case 0x4: // I/O Registers
|
|
|
|
// addr &= 0x400FFFF;
|
|
|
|
WriteHwio8(debug, addr, val);
|
|
|
|
break;
|
|
|
|
case 0xA: // ROM / EEPROM
|
|
|
|
case 0xB: // ROM / EEPROM
|
|
|
|
case 0xC: // ROM / EEPROM
|
|
|
|
case 0xD: // ROM / EEPROM
|
|
|
|
uint adjAddr = addr & 0x1FFFFFF;
|
|
|
|
if (adjAddr >= EepromThreshold)
|
|
|
|
{
|
|
|
|
SaveProvider.Write8(adjAddr, val);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 0xE: // Game Pak SRAM/Flash
|
|
|
|
case 0xF: // Game Pak SRAM/Flash
|
|
|
|
SaveProvider.Write8(addr, val);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
|
|
public override void Write16Unregistered(bool debug, uint addr, ushort val)
|
|
|
|
{
|
|
|
|
switch (addr >> 24)
|
|
|
|
{
|
|
|
|
case 0x4: // I/O Registers
|
|
|
|
WriteHwio8(debug, addr++, (byte)(val >> 0));
|
|
|
|
WriteHwio8(debug, addr++, (byte)(val >> 8));
|
|
|
|
break;
|
|
|
|
case 0x5: // PPU Palettes
|
|
|
|
addr &= 0x3FF;
|
|
|
|
if (GetUshort(Gba.Ppu.Renderer.Palettes, addr) != val)
|
|
|
|
{
|
|
|
|
SetUshort(Gba.Ppu.Renderer.Palettes, addr, val);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 0xA: // ROM / EEPROM
|
|
|
|
case 0xB: // ROM / EEPROM
|
|
|
|
case 0xC: // ROM / EEPROM
|
|
|
|
case 0xD: // ROM / EEPROM
|
|
|
|
uint adjAddr = addr & 0x1FFFFFF;
|
|
|
|
if (adjAddr >= EepromThreshold)
|
|
|
|
{
|
|
|
|
SaveProvider.Write8(adjAddr, (byte)val);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
|
|
public override void Write32Unregistered(bool debug, uint addr, uint val)
|
|
|
|
{
|
|
|
|
switch (addr >> 24)
|
|
|
|
{
|
|
|
|
case 0x4: // I/O Registers
|
|
|
|
WriteHwio8(debug, addr++, (byte)(val >> 0));
|
|
|
|
WriteHwio8(debug, addr++, (byte)(val >> 8));
|
|
|
|
WriteHwio8(debug, addr++, (byte)(val >> 16));
|
|
|
|
WriteHwio8(debug, addr++, (byte)(val >> 24));
|
|
|
|
break;
|
|
|
|
case 0x5: // PPU Palettes
|
|
|
|
addr &= 0x3FF;
|
|
|
|
if (GetUint(Gba.Ppu.Renderer.Palettes, addr) != val)
|
|
|
|
{
|
|
|
|
SetUint(Gba.Ppu.Renderer.Palettes, addr, val);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
case 0x6: // PPU VRAM
|
|
|
|
addr &= 0x1FFFF;
|
|
|
|
if (addr < 0x18000)
|
|
|
|
{
|
|
|
|
SetUint(Gba.Ppu.Vram, addr, val);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
public byte ReadHwio8(bool debug, uint addr)
|
|
|
|
{
|
|
|
|
if (LogHwioAccesses && (addr & ~1) != 0 && !debug)
|
|
|
|
{
|
|
|
|
uint count;
|
|
|
|
HwioWriteLog.TryGetValue(addr, out count);
|
|
|
|
HwioWriteLog[addr] = count + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (addr >= 0x4000000 && addr <= 0x4000056) // PPU
|
|
|
|
{
|
|
|
|
return Gba.Ppu.ReadHwio8(addr);
|
|
|
|
}
|
|
|
|
else if (addr >= 0x4000060 && addr <= 0x40000A8) // Sound
|
|
|
|
{
|
|
|
|
return Gba.GbaAudio.ReadHwio8(addr);
|
|
|
|
}
|
|
|
|
else if (addr >= 0x40000B0 && addr <= 0x40000DF) // DMA
|
|
|
|
{
|
|
|
|
return Gba.Dma.ReadHwio8(addr);
|
|
|
|
}
|
|
|
|
else if (addr >= 0x4000100 && addr <= 0x400010F) // Timer
|
|
|
|
{
|
|
|
|
return Gba.Timers.ReadHwio8(addr);
|
|
|
|
}
|
|
|
|
else if (addr >= 0x4000120 && addr <= 0x400012C) // Serial
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
else if (addr >= 0x4000130 && addr <= 0x4000132) // Keypad
|
|
|
|
{
|
|
|
|
return Gba.Keypad.ReadHwio8(addr);
|
|
|
|
}
|
|
|
|
else if (addr >= 0x4000134 && addr <= 0x400015A) // Serial Communications
|
|
|
|
{
|
|
|
|
switch (addr) {
|
|
|
|
case 0x4000135: return 0x80;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (addr >= 0x4000200 && addr <= 0x4FF0800) // Interrupt, Waitstate, and Power-Down Control
|
|
|
|
{
|
|
|
|
return Gba.HwControl.ReadHwio8(addr);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void WriteHwio8(bool debug, uint addr, byte val)
|
|
|
|
{
|
|
|
|
if (LogHwioAccesses && (addr & ~1) != 0 && !debug)
|
|
|
|
{
|
|
|
|
uint count;
|
|
|
|
HwioReadLog.TryGetValue(addr, out count);
|
|
|
|
HwioReadLog[addr] = count + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (addr >= 0x4000000 && addr <= 0x4000056) // PPU
|
|
|
|
{
|
|
|
|
Gba.Ppu.WriteHwio8(addr, val);
|
|
|
|
}
|
|
|
|
else if (addr >= 0x4000060 && addr <= 0x40000A7) // Sound
|
|
|
|
{
|
|
|
|
Gba.GbaAudio.WriteHwio8(addr, val);
|
|
|
|
}
|
|
|
|
else if (addr >= 0x40000B0 && addr <= 0x40000DF) // DMA
|
|
|
|
{
|
|
|
|
Gba.Dma.WriteHwio8(addr, val);
|
|
|
|
}
|
|
|
|
else if (addr >= 0x4000100 && addr <= 0x400010F) // Timer
|
|
|
|
{
|
|
|
|
Gba.Timers.WriteHwio8(addr, val);
|
|
|
|
}
|
|
|
|
else if (addr >= 0x4000120 && addr <= 0x400012C) // Serial
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
else if (addr >= 0x4000130 && addr <= 0x4000132) // Keypad
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
else if (addr >= 0x4000134 && addr <= 0x400015A) // Serial Communications
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
else if (addr >= 0x4000200 && addr <= 0x4FF0800) // Interrupt, Waitstate, and Power-Down Control
|
|
|
|
{
|
|
|
|
Gba.HwControl.WriteHwio8(addr, val);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|