using Core.Interfaces; using System; namespace Core.Memory { public class SmsMemoryBus : IMemory { // SMS has 8KB of internal Work RAM private readonly byte[] _workRam = new byte[0x2000]; // The entire game cartridge loaded into one big array private byte[] _cartridgeRom = Array.Empty(); // The Paging Registers (Which 16KB chunk is in which slot?) // Default startup state: Bank 0, 1, and 2 in order. private int _romBank0 = 0; private int _romBank1 = 1; private int _romBank2 = 2; // A flag to handle cartridges that don't use paging (like early 32KB games) private bool _isCartridgeLoaded = false; public void LoadCartridge(byte[] romData) { _cartridgeRom = romData; _isCartridgeLoaded = true; // Reset the mapper on boot _romBank0 = 0; _romBank1 = 1; _romBank2 = 2; // Clean the RAM CleanRAMData(); } public byte Read(ushort address) { if (address < 0x4000) // ROM Slot 0 (0x0000 - 0x3FFF) { // SMS Hardware Quirk: The first 1KB (0x0000 - 0x03FF) is NEVER paged. // It is hardwired to Bank 0 so the interrupt handlers don't crash. if (address < 0x0400) return ReadFromCartridge(0, address); return ReadFromCartridge(_romBank0, address); } if (address < 0x8000) // ROM Slot 1 (0x4000 - 0x7FFF) { return ReadFromCartridge(_romBank1, address & 0x3FFF); } if (address < 0xC000) // ROM Slot 2 (0x8000 - 0xBFFF) { return ReadFromCartridge(_romBank2, address & 0x3FFF); } // If we are here, we are in System RAM (0xC000 - 0xFFFF) // The & 0x1FFF handles the 8KB mirroring automatically! return _workRam[address & 0x1FFF]; } public void Write(ushort address, byte value) { if (address < 0xC000) { // You cannot write to a ROM cartridge! return; } // Write to System RAM _workRam[address & 0x1FFF] = value; // --- THE SEGA MAPPER --- // If the CPU wrote to the very top of memory, it is commanding a bank swap! if (address == 0xFFFD) { _romBank0 = value; } else if (address == 0xFFFE) { _romBank1 = value; } else if (address == 0xFFFF) { _romBank2 = value; } } private byte ReadFromCartridge(int bankIndex, int offset) { if (!_isCartridgeLoaded) return 0xFF; // Floating bus if no game int absoluteAddress = (bankIndex * 0x4000) + offset; // Safety check in case a game asks for a bank outside its file size if (absoluteAddress >= _cartridgeRom.Length) { // Wrap around (mirrors the ROM if it's smaller than the requested bank) absoluteAddress %= _cartridgeRom.Length; } return _cartridgeRom[absoluteAddress]; } public void CleanRAMData() { Array.Clear(_workRam, 0, _workRam.Length); } } }