GBA.Unity/Assets/emulator/MemoryGba.cs

439 lines
15 KiB
C#
Raw Normal View History

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:
Console.WriteLine($"Save Type: {strings[matchedIndex]}");
switch (matchedIndex)
{
case 0: SaveProvider = new NullSaveProvider(); break;
case 1:
SaveProvider = new Eeprom(Gba, EepromSize.Eeprom64k);
if (RomSize < 16777216)
{
EepromThreshold = 0x1000000;
}
else
{
EepromThreshold = 0x1FFFF00;
}
Console.WriteLine("EEPROM Threshold: " + Util.Hex(EepromThreshold, 8));
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()
{
Console.WriteLine("Cleaning up GBA memory...");
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);
}
}
}
}