From de52cc23cfb17a505f52c4aa3b95054dade281d6 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Wed, 26 Aug 2026 15:12:19 +0200 Subject: [PATCH] feat(memory): add MMU memory map and OAM DMA --- src/GameBoy.Core/Memory/Mmu.cs | 142 ++++++++++++++++++++++++++++++ src/GameBoy.Core/Memory/OamDma.cs | 64 ++++++++++++++ 2 files changed, 206 insertions(+) create mode 100644 src/GameBoy.Core/Memory/Mmu.cs create mode 100644 src/GameBoy.Core/Memory/OamDma.cs diff --git a/src/GameBoy.Core/Memory/Mmu.cs b/src/GameBoy.Core/Memory/Mmu.cs new file mode 100644 index 0000000..21d5f48 --- /dev/null +++ b/src/GameBoy.Core/Memory/Mmu.cs @@ -0,0 +1,142 @@ +using System; +using System.IO; +using GameBoy.Audio; +using GameBoy.Cpu; +using GameBoy.Input; +using GameBoy.Serial; +using GameBoy.Timing; +using GameBoy.Video; + +namespace GameBoy.Memory +{ + public sealed class Mmu + { + private readonly Cartridge.Cartridge cartridge; + private readonly Ppu ppu; + private readonly Timer timer; + private readonly Joypad joypad; + private readonly SerialPort serial; + private readonly Apu apu; + private readonly InterruptController interrupts; + + private readonly byte[] wram = new byte[0x2000]; + private readonly byte[] hram = new byte[0x7F]; + + public OamDma? Dma; + + public Mmu( + Cartridge.Cartridge cartridge, + Ppu ppu, + Timer timer, + Joypad joypad, + SerialPort serial, + Apu apu, + InterruptController interrupts) + { + this.cartridge = cartridge; + this.ppu = ppu; + this.timer = timer; + this.joypad = joypad; + this.serial = serial; + this.apu = apu; + this.interrupts = interrupts; + } + + public byte Read(ushort address) + { + if (address < 0x8000) + return cartridge.Read(address); + if (address < 0xA000) + return ppu.ReadVram(address); + if (address < 0xC000) + return cartridge.Read(address); + if (address < 0xE000) + return wram[address - 0xC000]; + if (address < 0xFE00) + return wram[address - 0xE000]; + if (address < 0xFEA0) + return ppu.ReadOam(address); + if (address < 0xFF00) + return 0xFF; + if (address < 0xFF80) + return ReadIo(address); + if (address < 0xFFFF) + return hram[address - 0xFF80]; + return interrupts.Enable; + } + + public void Write(ushort address, byte value) + { + if (address < 0x8000) + cartridge.Write(address, value); + else if (address < 0xA000) + ppu.WriteVram(address, value); + else if (address < 0xC000) + cartridge.Write(address, value); + else if (address < 0xE000) + wram[address - 0xC000] = value; + else if (address < 0xFE00) + wram[address - 0xE000] = value; + else if (address < 0xFEA0) + ppu.WriteOam(address, value); + else if (address < 0xFF00) + { + } + else if (address < 0xFF80) + WriteIo(address, value); + else if (address < 0xFFFF) + hram[address - 0xFF80] = value; + else + interrupts.Enable = value; + } + + private byte ReadIo(ushort address) + { + if (address == 0xFF00) + return joypad.Read(); + if (address == 0xFF01 || address == 0xFF02) + return serial.Read(address); + if (address >= 0xFF04 && address <= 0xFF07) + return timer.Read(address); + if (address == 0xFF0F) + return interrupts.ReadFlag(); + if (address >= 0xFF10 && address <= 0xFF3F) + return apu.Read(address); + if (address == 0xFF46) + return Dma!.Page; + if (address >= 0xFF40 && address <= 0xFF4B) + return ppu.ReadRegister(address); + return 0xFF; + } + + private void WriteIo(ushort address, byte value) + { + if (address == 0xFF00) + joypad.Write(value); + else if (address == 0xFF01 || address == 0xFF02) + serial.Write(address, value); + else if (address >= 0xFF04 && address <= 0xFF07) + timer.Write(address, value); + else if (address == 0xFF0F) + interrupts.WriteFlag(value); + else if (address >= 0xFF10 && address <= 0xFF3F) + apu.Write(address, value); + else if (address == 0xFF46) + Dma!.Start(value); + else if (address >= 0xFF40 && address <= 0xFF4B) + ppu.WriteRegister(address, value); + } + + public void SaveState(BinaryWriter writer) + { + writer.Write(wram); + writer.Write(hram); + } + + public void LoadState(BinaryReader reader) + { + Array.Copy(reader.ReadBytes(wram.Length), wram, wram.Length); + Array.Copy(reader.ReadBytes(hram.Length), hram, hram.Length); + } + } +} diff --git a/src/GameBoy.Core/Memory/OamDma.cs b/src/GameBoy.Core/Memory/OamDma.cs new file mode 100644 index 0000000..97d436f --- /dev/null +++ b/src/GameBoy.Core/Memory/OamDma.cs @@ -0,0 +1,64 @@ +using System.IO; +using GameBoy.Video; + +namespace GameBoy.Memory +{ + public sealed class OamDma + { + private readonly Mmu mmu; + private readonly Ppu ppu; + + private bool active; + private int index; + private int subDot; + + public byte Page { get; private set; } + + public OamDma(Mmu mmu, Ppu ppu) + { + this.mmu = mmu; + this.ppu = ppu; + } + + public void Start(byte page) + { + Page = page; + active = true; + index = 0; + subDot = 0; + } + + public void Step() + { + if (!active) + return; + + subDot++; + if (subDot < 4) + return; + + subDot = 0; + ushort source = (ushort)((Page << 8) | index); + ppu.WriteOamDma(index, mmu.Read(source)); + index++; + if (index == 0xA0) + active = false; + } + + public void SaveState(BinaryWriter writer) + { + writer.Write(active); + writer.Write(index); + writer.Write(subDot); + writer.Write(Page); + } + + public void LoadState(BinaryReader reader) + { + active = reader.ReadBoolean(); + index = reader.ReadInt32(); + subDot = reader.ReadInt32(); + Page = reader.ReadByte(); + } + } +}