From 5f055110fa81d3683060fbfe749a9b1f274412e1 Mon Sep 17 00:00:00 2001 From: Sour Date: Mon, 18 May 2020 16:10:53 -0400 Subject: [PATCH] Added Game Boy support CPU/APU are decent - PPU is still just a scanline renderer No Super Game Boy support yet --- Core/BaseCartridge.cpp | 58 +- Core/BaseCartridge.h | 5 + Core/BaseRenderer.cpp | 10 +- Core/CartTypes.h | 3 +- Core/Console.cpp | 143 +- Core/Console.h | 7 +- Core/Core.vcxproj | 28 + Core/Core.vcxproj.filters | 93 ++ Core/CpuDebugger.cpp | 8 +- Core/CpuDebugger.h | 1 - Core/Cx4Debugger.cpp | 2 +- Core/DebugStats.cpp | 5 +- Core/DebugTypes.h | 34 +- Core/DebugUtilities.h | 13 +- Core/Debugger.cpp | 43 +- Core/Debugger.h | 4 +- Core/Disassembler.cpp | 81 +- Core/Disassembler.h | 19 +- Core/DisassemblyInfo.cpp | 80 +- Core/DisassemblyInfo.h | 4 +- Core/EmuSettings.cpp | 24 +- Core/ExpressionEvaluator.cpp | 68 +- Core/ExpressionEvaluator.h | 9 + Core/Gameboy.cpp | 239 +++ Core/Gameboy.h | 68 + Core/GameboyDisUtils.cpp | 134 ++ Core/GameboyDisUtils.h | 18 + Core/GameboyHeader.h | 77 + Core/GbApu.cpp | 272 ++++ Core/GbApu.h | 53 + Core/GbCart.h | 57 + Core/GbCartFactory.h | 36 + Core/GbCpu.cpp | 1352 +++++++++++++++++ Core/GbCpu.h | 146 ++ Core/GbDebugger.cpp | 146 ++ Core/GbDebugger.h | 44 + Core/GbMbc1.h | 62 + Core/GbMbc2.h | 68 + Core/GbMbc3.h | 95 ++ Core/GbMbc5.h | 71 + Core/GbMemoryManager.cpp | 323 ++++ Core/GbMemoryManager.h | 73 + Core/GbNoiseChannel.h | 194 +++ Core/GbPpu.cpp | 378 +++++ Core/GbPpu.h | 50 + Core/GbSquareChannel.h | 241 +++ Core/GbTimer.cpp | 81 + Core/GbTimer.h | 34 + Core/GbTypes.h | 272 ++++ Core/GbWaveChannel.h | 154 ++ Core/GsuDebugger.cpp | 2 +- Core/LabelManager.cpp | 8 + Core/LuaApi.cpp | 87 +- Core/LuaScriptingContext.cpp | 8 +- Core/LuaScriptingContext.h | 5 +- Core/MemoryAccessCounter.cpp | 43 +- Core/MemoryAccessCounter.h | 2 + Core/MemoryDumper.cpp | 97 +- Core/MemoryManager.cpp | 4 +- Core/Multitap.cpp | 6 +- Core/Multitap.h | 2 - Core/NecDspDebugger.cpp | 2 +- Core/PpuTools.cpp | 39 +- Core/PpuTools.h | 2 + Core/Profiler.cpp | 7 +- Core/Profiler.h | 4 +- Core/SPC_DSP.cpp | 10 +- Core/ScriptHost.cpp | 8 +- Core/ScriptHost.h | 2 +- Core/ScriptManager.cpp | 4 +- Core/ScriptManager.h | 3 +- Core/ScriptingContext.cpp | 11 +- Core/ScriptingContext.h | 9 +- Core/SettingTypes.h | 12 +- Core/SnesController.cpp | 6 +- Core/SnesController.h | 3 - Core/SnesMemoryType.h | 34 + Core/SoundMixer.cpp | 4 +- Core/SoundMixer.h | 2 +- Core/SoundResampler.cpp | 12 +- Core/SoundResampler.h | 4 +- Core/Spc.cpp | 2 +- Core/SpcDebugger.cpp | 3 +- Core/TraceLogger.cpp | 53 + Core/TraceLogger.h | 14 +- InteropDLL/DebugApiWrapper.cpp | 2 + Libretro/Makefile.common | 9 + Libretro/libretro.cpp | 29 +- UI/Debugger/Breakpoints/Breakpoint.cs | 8 +- UI/Debugger/Breakpoints/frmBreakpoint.cs | 9 + UI/Debugger/Code/GbDisassemblyManager.cs | 29 + UI/Debugger/Code/GbLineStyleProvider.cs | 64 + UI/Debugger/Config/DebuggerInfo.cs | 2 + UI/Debugger/Config/DebuggerShortcutsConfig.cs | 2 + UI/Debugger/Config/TraceLoggerInfo.cs | 1 + UI/Debugger/Controls/ctrlCallstack.cs | 1 + UI/Debugger/Controls/ctrlDisassemblyView.cs | 1 + .../Controls/ctrlGameboyStatus.Designer.cs | 541 +++++++ UI/Debugger/Controls/ctrlGameboyStatus.cs | 87 ++ UI/Debugger/Controls/ctrlGameboyStatus.resx | 120 ++ UI/Debugger/Controls/ctrlMemoryMapping.cs | 200 +++ UI/Debugger/Controls/ctrlMemoryType.cs | 43 +- UI/Debugger/DebugWindowManager.cs | 5 + UI/Debugger/Labels/CodeLabel.cs | 8 + UI/Debugger/Labels/LabelManager.cs | 6 + UI/Debugger/Labels/ctrlLabelList.cs | 5 + UI/Debugger/Labels/frmEditLabel.cs | 7 + UI/Debugger/PpuViewer/ctrlPaletteViewer.cs | 36 +- .../PpuViewer/ctrlScanlineCycleSelect.cs | 8 +- UI/Debugger/PpuViewer/frmRegisterViewer.cs | 1112 ++++++++------ .../PpuViewer/frmTileViewer.Designer.cs | 84 +- UI/Debugger/PpuViewer/frmTileViewer.cs | 79 +- UI/Debugger/PpuViewer/frmTilemapViewer.cs | 32 +- UI/Debugger/Profiler/ctrlProfiler.cs | 4 + UI/Debugger/Profiler/frmProfiler.cs | 27 +- UI/Debugger/Scripts/frmScript.cs | 44 +- UI/Debugger/frmDbgPreferences.cs | 1 + UI/Debugger/frmDebugger.Designer.cs | 46 +- UI/Debugger/frmDebugger.cs | 48 +- UI/Debugger/frmTraceLogger.Designer.cs | 43 +- UI/Debugger/frmTraceLogger.cs | 23 +- UI/Dependencies/resources.en.xml | 8 +- UI/Forms/frmMain.Designer.cs | 46 +- UI/Forms/frmMain.cs | 29 +- UI/Interop/ConfigApi.cs | 1 + UI/Interop/DebugApi.cs | 29 +- UI/Interop/DebugState.cs | 221 +++ UI/Interop/EmuApi.cs | 3 +- UI/Properties/Resources.Designer.cs | 10 + UI/Properties/Resources.resx | 3 + UI/Resources/GbDebugger.png | Bin 0 -> 643 bytes UI/UI.csproj | 15 + UI/Utilities/FolderHelper.cs | 2 +- Utilities/Utilities.vcxproj | 2 + Utilities/Utilities.vcxproj.filters | 6 + Utilities/VirtualFile.cpp | 2 +- Utilities/blip_buf.cpp | 345 +++++ Utilities/blip_buf.h | 81 + 138 files changed, 8659 insertions(+), 904 deletions(-) create mode 100644 Core/Gameboy.cpp create mode 100644 Core/Gameboy.h create mode 100644 Core/GameboyDisUtils.cpp create mode 100644 Core/GameboyDisUtils.h create mode 100644 Core/GameboyHeader.h create mode 100644 Core/GbApu.cpp create mode 100644 Core/GbApu.h create mode 100644 Core/GbCart.h create mode 100644 Core/GbCartFactory.h create mode 100644 Core/GbCpu.cpp create mode 100644 Core/GbCpu.h create mode 100644 Core/GbDebugger.cpp create mode 100644 Core/GbDebugger.h create mode 100644 Core/GbMbc1.h create mode 100644 Core/GbMbc2.h create mode 100644 Core/GbMbc3.h create mode 100644 Core/GbMbc5.h create mode 100644 Core/GbMemoryManager.cpp create mode 100644 Core/GbMemoryManager.h create mode 100644 Core/GbNoiseChannel.h create mode 100644 Core/GbPpu.cpp create mode 100644 Core/GbPpu.h create mode 100644 Core/GbSquareChannel.h create mode 100644 Core/GbTimer.cpp create mode 100644 Core/GbTimer.h create mode 100644 Core/GbTypes.h create mode 100644 Core/GbWaveChannel.h create mode 100644 Core/SnesMemoryType.h create mode 100644 UI/Debugger/Code/GbDisassemblyManager.cs create mode 100644 UI/Debugger/Code/GbLineStyleProvider.cs create mode 100644 UI/Debugger/Controls/ctrlGameboyStatus.Designer.cs create mode 100644 UI/Debugger/Controls/ctrlGameboyStatus.cs create mode 100644 UI/Debugger/Controls/ctrlGameboyStatus.resx create mode 100644 UI/Debugger/Controls/ctrlMemoryMapping.cs create mode 100644 UI/Resources/GbDebugger.png create mode 100644 Utilities/blip_buf.cpp create mode 100644 Utilities/blip_buf.h diff --git a/Core/BaseCartridge.cpp b/Core/BaseCartridge.cpp index 3d2d49d..a8f10b2 100644 --- a/Core/BaseCartridge.cpp +++ b/Core/BaseCartridge.cpp @@ -21,6 +21,7 @@ #include "BsxMemoryPack.h" #include "FirmwareHelper.h" #include "SpcFileData.h" +#include "Gameboy.h" #include "../Utilities/HexUtilities.h" #include "../Utilities/VirtualFile.h" #include "../Utilities/FolderUtilities.h" @@ -62,6 +63,12 @@ shared_ptr BaseCartridge::CreateCartridge(Console* console, Virtu if(!FirmwareHelper::LoadBsxFirmware(console, &cart->_prgRom, cart->_prgRomSize)) { return nullptr; } + } else if(FolderUtilities::GetExtension(romFile.GetFileName()) == ".gb") { + if(cart->LoadGameboy(romFile)) { + return cart; + } else { + return nullptr; + } } else { cart->_prgRomSize = (uint32_t)romData.size(); if((cart->_prgRomSize & 0xFFF) != 0) { @@ -329,6 +336,10 @@ void BaseCartridge::LoadBattery() if(_coprocessor && _hasBattery) { _coprocessor->LoadBattery(); } + + if(_gameboy) { + _gameboy->LoadBattery(); + } } void BaseCartridge::SaveBattery() @@ -344,6 +355,10 @@ void BaseCartridge::SaveBattery() if(_bsxMemPack) { _bsxMemPack->SaveBattery(); } + + if(_gameboy) { + _gameboy->SaveBattery(); + } } void BaseCartridge::Init(MemoryMappings &mm) @@ -374,7 +389,11 @@ void BaseCartridge::Init(MemoryMappings &mm) } RegisterHandlers(mm); - InitCoprocessor(); + + if(_coprocessorType != CoprocessorType::Gameboy) { + InitCoprocessor(); + } + LoadBattery(); } @@ -555,7 +574,24 @@ void BaseCartridge::ApplyConfigOverrides() void BaseCartridge::LoadSpc() { _spcData.reset(new SpcFileData(_prgRom)); - + SetupCpuHalt(); +} + +bool BaseCartridge::LoadGameboy(VirtualFile &romFile) +{ + _gameboy.reset(Gameboy::Create(_console, romFile)); + if(!_gameboy) { + return false; + } + + _cartInfo = { }; + _coprocessorType = CoprocessorType::Gameboy; + SetupCpuHalt(); + return true; +} + +void BaseCartridge::SetupCpuHalt() +{ //Setup a fake LOROM rom that runs STP right away to disable the main CPU _flags = CartFlags::LoRom; @@ -563,13 +599,16 @@ void BaseCartridge::LoadSpc() _prgRom = new uint8_t[0x8000]; _prgRomSize = 0x8000; memset(_prgRom, 0, 0x8000); - + //Set reset vector to $8000 _prgRom[0x7FFC] = 0x00; _prgRom[0x7FFD] = 0x80; - //STP instruction at $8000 - _prgRom[0] = 0xDB; + //JML $008000 instruction at $8000 + _prgRom[0] = 0x5C; + _prgRom[1] = 0x00; + _prgRom[2] = 0x80; + _prgRom[3] = 0x00; } void BaseCartridge::Serialize(Serializer &s) @@ -581,6 +620,9 @@ void BaseCartridge::Serialize(Serializer &s) if(_bsxMemPack) { s.Stream(_bsxMemPack.get()); } + if(_gameboy) { + s.Stream(_gameboy.get()); + } } string BaseCartridge::GetGameCode() @@ -668,6 +710,7 @@ void BaseCartridge::DisplayCartInfo() case CoprocessorType::ST010: coProcMessage += "ST010"; break; case CoprocessorType::ST011: coProcMessage += "ST011"; break; case CoprocessorType::ST018: coProcMessage += "ST018"; break; + case CoprocessorType::Gameboy: coProcMessage += "Game Boy"; break; } MessageManager::Log(coProcMessage); } @@ -724,6 +767,11 @@ Gsu* BaseCartridge::GetGsu() return _gsu; } +Gameboy* BaseCartridge::GetGameboy() +{ + return _gameboy.get(); +} + void BaseCartridge::RunCoprocessors() { //These coprocessors are run at the end of the frame, or as needed diff --git a/Core/BaseCartridge.h b/Core/BaseCartridge.h index 65a91b4..dc42740 100644 --- a/Core/BaseCartridge.h +++ b/Core/BaseCartridge.h @@ -14,6 +14,7 @@ class Gsu; class Cx4; class BsxCart; class BsxMemoryPack; +class Gameboy; class Console; class SpcFileData; enum class ConsoleRegion; @@ -35,6 +36,7 @@ private: Cx4 *_cx4 = nullptr; BsxCart* _bsx = nullptr; unique_ptr _bsxMemPack; + unique_ptr _gameboy; CartFlags::CartFlags _flags = CartFlags::CartFlags::None; CoprocessorType _coprocessorType = CoprocessorType::None; @@ -68,6 +70,8 @@ private: void LoadRom(); void LoadSpc(); + bool LoadGameboy(VirtualFile& romFile); + void SetupCpuHalt(); void InitCoprocessor(); void LoadEmbeddedFirmware(); @@ -104,6 +108,7 @@ public: Cx4* GetCx4(); BsxCart* GetBsx(); BsxMemoryPack* GetBsxMemoryPack(); + Gameboy* GetGameboy(); void RunCoprocessors(); diff --git a/Core/BaseRenderer.cpp b/Core/BaseRenderer.cpp index 01d85dd..a50eacc 100644 --- a/Core/BaseRenderer.cpp +++ b/Core/BaseRenderer.cpp @@ -3,7 +3,6 @@ #include "BaseRenderer.h" #include "Console.h" #include "EmuSettings.h" -#include "Ppu.h" #include "MessageManager.h" BaseRenderer::BaseRenderer(shared_ptr console, bool registerAsMessageManager) @@ -120,8 +119,7 @@ void BaseRenderer::ShowFpsCounter(int lineNumber) int yPos = 13 + 24 * lineNumber; if(_fpsTimer.GetElapsedMS() > 1000) { //Update fps every sec - shared_ptr ppu = _console->GetPpu(); - uint32_t frameCount = ppu ? ppu->GetFrameCount() : 0; + uint32_t frameCount = _console->GetFrameCount(); if(_lastFrameCount > frameCount) { _currentFPS = 0; } else { @@ -148,7 +146,7 @@ void BaseRenderer::ShowGameTimer(int lineNumber) { int yPos = 13 + 24 * lineNumber; shared_ptr ppu = _console->GetPpu(); - double frameCount = ppu ? ppu->GetFrameCount() : 0; + uint32_t frameCount = _console->GetFrameCount(); bool isPal = _console->GetRegion() == ConsoleRegion::Pal; double frameRate = isPal ? 50.006977968268290848936010226333 : 60.098811862348404716732985230828; uint32_t seconds = (uint32_t)(frameCount / frameRate) % 60; @@ -165,9 +163,9 @@ void BaseRenderer::ShowGameTimer(int lineNumber) void BaseRenderer::ShowFrameCounter(int lineNumber) { int yPos = 13 + 24 * lineNumber; - shared_ptr ppu = _console->GetPpu(); + uint32_t frameCount = _console->GetFrameCount(); - string frameCounter = MessageManager::Localize("Frame") + ": " + std::to_string(ppu ? ppu->GetFrameCount() : 0); + string frameCounter = MessageManager::Localize("Frame") + ": " + std::to_string(frameCount); DrawString(frameCounter, _screenWidth - 146, yPos, 250, 235, 215); } diff --git a/Core/CartTypes.h b/Core/CartTypes.h index 71bf090..887d645 100644 --- a/Core/CartTypes.h +++ b/Core/CartTypes.h @@ -44,7 +44,8 @@ enum class CoprocessorType ST010, ST011, ST018, - CX4 + CX4, + Gameboy }; struct RomInfo diff --git a/Core/Console.cpp b/Core/Console.cpp index 65b676f..a15e4c6 100644 --- a/Core/Console.cpp +++ b/Core/Console.cpp @@ -4,6 +4,7 @@ #include "Ppu.h" #include "Spc.h" #include "NecDsp.h" +#include "Gameboy.h" #include "InternalRegisters.h" #include "ControlManager.h" #include "MemoryManager.h" @@ -94,9 +95,16 @@ void Console::Release() void Console::RunFrame() { - uint32_t frameCount = _ppu->GetFrameCount(); - while(frameCount == _ppu->GetFrameCount()) { - _cpu->Exec(); + _frameRunning = true; + if(_settings->CheckFlag(EmulationFlags::GameboyMode)) { + Gameboy* gameboy = _cart->GetGameboy(); + while(_frameRunning) { + gameboy->Exec(); + } + } else { + while(_frameRunning) { + _cpu->Exec(); + } } } @@ -236,21 +244,19 @@ void Console::ProcessEndOfFrame() _controlManager->UpdateControlDevices(); _internalRegisters->ProcessAutoJoypadRead(); #endif + _frameRunning = false; } void Console::RunSingleFrame() { //Used by Libretro - uint32_t lastFrameNumber = _ppu->GetFrameCount(); _emulationThreadId = std::this_thread::get_id(); _isRunAheadFrame = false; _controlManager->UpdateInputState(); _internalRegisters->ProcessAutoJoypadRead(); - while(_ppu->GetFrameCount() == lastFrameNumber) { - _cpu->Exec(); - } + RunFrame(); _cart->RunCoprocessors(); if(_cart->GetCoprocessor()) { @@ -430,6 +436,13 @@ bool Console::LoadRom(VirtualFile romFile, VirtualFile patchFile, bool stopRom, _ppu->PowerOn(); _cpu->PowerOn(); + if(_cart->GetGameboy()) { + _cart->GetGameboy()->PowerOn(); + _settings->SetFlag(EmulationFlags::GameboyMode); + } else { + _settings->ClearFlag(EmulationFlags::GameboyMode); + } + _rewindManager.reset(new RewindManager(shared_from_this())); _notificationManager->RegisterNotificationListener(_rewindManager); @@ -471,6 +484,15 @@ RomInfo Console::GetRomInfo() } } +uint64_t Console::GetMasterClock() +{ + if(_settings->CheckFlag(EmulationFlags::GameboyMode) && _cart->GetGameboy()) { + return _cart->GetGameboy()->GetCycleCount(); + } else { + return _memoryManager->GetMasterClock(); + } +} + uint32_t Console::GetMasterClockRate() { return _masterClockRate; @@ -496,10 +518,14 @@ void Console::UpdateRegion() double Console::GetFps() { - if(_region == ConsoleRegion::Ntsc) { - return _settings->GetVideoConfig().IntegerFpsMode ? 60.0 : 60.098812; + if(_settings->CheckFlag(EmulationFlags::GameboyMode)) { + return 59.72750056960583; } else { - return _settings->GetVideoConfig().IntegerFpsMode ? 50.0 : 50.006978; + if(_region == ConsoleRegion::Ntsc) { + return _settings->GetVideoConfig().IntegerFpsMode ? 60.0 : 60.0988118623484; + } else { + return _settings->GetVideoConfig().IntegerFpsMode ? 50.0 : 50.00697796826829; + } } } @@ -511,10 +537,14 @@ double Console::GetFrameDelay() frameDelay = 0; } else { UpdateRegion(); - switch(_region) { - default: - case ConsoleRegion::Ntsc: frameDelay = _settings->GetVideoConfig().IntegerFpsMode ? 16.6666666666666666667 : 16.63926405550947; break; - case ConsoleRegion::Pal: frameDelay = _settings->GetVideoConfig().IntegerFpsMode ? 20 : 19.99720882631146; break; + if(_settings->CheckFlag(EmulationFlags::GameboyMode)) { + frameDelay = 16.74270629882813; + } else { + switch(_region) { + default: + case ConsoleRegion::Ntsc: frameDelay = _settings->GetVideoConfig().IntegerFpsMode ? 16.6666666666666666667 : 16.63926405550947; break; + case ConsoleRegion::Pal: frameDelay = _settings->GetVideoConfig().IntegerFpsMode ? 20 : 19.99720882631146; break; + } } frameDelay /= (emulationSpeed / 100.0); } @@ -525,7 +555,11 @@ void Console::PauseOnNextFrame() { shared_ptr debugger = _debugger; if(debugger) { - debugger->Step(CpuType::Cpu, 240, StepType::SpecificScanline); + if(_settings->CheckFlag(EmulationFlags::GameboyMode)) { + debugger->Step(CpuType::Cpu, 144, StepType::SpecificScanline); + } else { + debugger->Step(CpuType::Cpu, 240, StepType::SpecificScanline); + } } else { _pauseOnNextFrame = true; _paused = false; @@ -536,7 +570,11 @@ void Console::Pause() { shared_ptr debugger = _debugger; if(debugger) { - debugger->Step(CpuType::Cpu, 1, StepType::Step); + if(_settings->CheckFlag(EmulationFlags::GameboyMode)) { + debugger->Step(CpuType::Gameboy, 1, StepType::Step); + } else { + debugger->Step(CpuType::Cpu, 1, StepType::Step); + } } else { _paused = true; } @@ -641,16 +679,23 @@ void Console::WaitForLock() void Console::Serialize(ostream &out, int compressionLevel) { Serializer serializer(SaveStateManager::FileFormatVersion); - serializer.Stream(_cpu.get()); - serializer.Stream(_memoryManager.get()); - serializer.Stream(_ppu.get()); - serializer.Stream(_dmaController.get()); - serializer.Stream(_internalRegisters.get()); - serializer.Stream(_cart.get()); - serializer.Stream(_controlManager.get()); - serializer.Stream(_spc.get()); - if(_msu1) { - serializer.Stream(_msu1.get()); + bool isGameboyMode = _settings->CheckFlag(EmulationFlags::GameboyMode); + + if(!isGameboyMode) { + serializer.Stream(_cpu.get()); + serializer.Stream(_memoryManager.get()); + serializer.Stream(_ppu.get()); + serializer.Stream(_dmaController.get()); + serializer.Stream(_internalRegisters.get()); + serializer.Stream(_cart.get()); + serializer.Stream(_controlManager.get()); + serializer.Stream(_spc.get()); + if(_msu1) { + serializer.Stream(_msu1.get()); + } + } else { + serializer.Stream(_cart.get()); + serializer.Stream(_controlManager.get()); } serializer.Save(out, compressionLevel); } @@ -658,16 +703,23 @@ void Console::Serialize(ostream &out, int compressionLevel) void Console::Deserialize(istream &in, uint32_t fileFormatVersion, bool compressed) { Serializer serializer(in, fileFormatVersion, compressed); - serializer.Stream(_cpu.get()); - serializer.Stream(_memoryManager.get()); - serializer.Stream(_ppu.get()); - serializer.Stream(_dmaController.get()); - serializer.Stream(_internalRegisters.get()); - serializer.Stream(_cart.get()); - serializer.Stream(_controlManager.get()); - serializer.Stream(_spc.get()); - if(_msu1) { - serializer.Stream(_msu1.get()); + bool isGameboyMode = _settings->CheckFlag(EmulationFlags::GameboyMode); + + if(!isGameboyMode) { + serializer.Stream(_cpu.get()); + serializer.Stream(_memoryManager.get()); + serializer.Stream(_ppu.get()); + serializer.Stream(_dmaController.get()); + serializer.Stream(_internalRegisters.get()); + serializer.Stream(_cart.get()); + serializer.Stream(_controlManager.get()); + serializer.Stream(_spc.get()); + if(_msu1) { + serializer.Stream(_msu1.get()); + } + } else { + serializer.Stream(_cart.get()); + serializer.Stream(_controlManager.get()); } _notificationManager->SendNotification(ConsoleNotificationType::StateLoaded); } @@ -820,6 +872,17 @@ bool Console::IsRunAheadFrame() return _isRunAheadFrame; } +uint32_t Console::GetFrameCount() +{ + shared_ptr cart = _cart; + if(_settings->CheckFlag(EmulationFlags::GameboyMode) && cart->GetGameboy()) { + return cart->GetGameboy()->GetState().Ppu.FrameCount; + } else { + shared_ptr ppu = _ppu; + return ppu ? ppu->GetFrameCount() : 0; + } +} + template void Console::ProcessMemoryRead(uint32_t addr, uint8_t value, MemoryOperationType opType) { @@ -864,10 +927,10 @@ void Console::ProcessWorkRamWrite(uint32_t addr, uint8_t value) } } -void Console::ProcessPpuCycle() +void Console::ProcessPpuCycle(uint16_t scanline, uint16_t cycle) { if(_debugger) { - _debugger->ProcessPpuCycle(); + _debugger->ProcessPpuCycle(scanline, cycle); } } @@ -882,7 +945,7 @@ void Console::ProcessInterrupt(uint32_t originalPc, uint32_t currentPc, bool for void Console::ProcessEvent(EventType type) { if(type == EventType::EndFrame && _spcHud) { - _spcHud->Draw(_ppu->GetFrameCount()); + _spcHud->Draw(GetFrameCount()); } if(_debugger) { @@ -896,6 +959,7 @@ template void Console::ProcessMemoryRead(uint32_t addr, uint8_t va template void Console::ProcessMemoryRead(uint32_t addr, uint8_t value, MemoryOperationType opType); template void Console::ProcessMemoryRead(uint32_t addr, uint8_t value, MemoryOperationType opType); template void Console::ProcessMemoryRead(uint32_t addr, uint8_t value, MemoryOperationType opType); +template void Console::ProcessMemoryRead(uint32_t addr, uint8_t value, MemoryOperationType opType); template void Console::ProcessMemoryWrite(uint32_t addr, uint8_t value, MemoryOperationType opType); template void Console::ProcessMemoryWrite(uint32_t addr, uint8_t value, MemoryOperationType opType); @@ -903,6 +967,7 @@ template void Console::ProcessMemoryWrite(uint32_t addr, uint8_t v template void Console::ProcessMemoryWrite(uint32_t addr, uint8_t value, MemoryOperationType opType); template void Console::ProcessMemoryWrite(uint32_t addr, uint8_t value, MemoryOperationType opType); template void Console::ProcessMemoryWrite(uint32_t addr, uint8_t value, MemoryOperationType opType); +template void Console::ProcessMemoryWrite(uint32_t addr, uint8_t value, MemoryOperationType opType); template void Console::ProcessInterrupt(uint32_t originalPc, uint32_t currentPc, bool forNmi); template void Console::ProcessInterrupt(uint32_t originalPc, uint32_t currentPc, bool forNmi); \ No newline at end of file diff --git a/Core/Console.h b/Core/Console.h index 592b4d3..ddafeeb 100644 --- a/Core/Console.h +++ b/Core/Console.h @@ -84,6 +84,7 @@ private: uint32_t _masterClockRate; atomic _isRunAheadFrame; + bool _frameRunning = false; unique_ptr _stats; unique_ptr _frameLimiter; @@ -125,6 +126,7 @@ public: bool LoadRom(VirtualFile romFile, VirtualFile patchFile, bool stopRom = true, bool forPowerCycle = false); RomInfo GetRomInfo(); + uint64_t GetMasterClock(); uint32_t GetMasterClockRate(); ConsoleRegion GetRegion(); @@ -166,7 +168,8 @@ public: bool IsRunning(); bool IsRunAheadFrame(); - + + uint32_t GetFrameCount(); double GetFps(); template void ProcessMemoryRead(uint32_t addr, uint8_t value, MemoryOperationType opType); @@ -175,7 +178,7 @@ public: void ProcessPpuWrite(uint32_t addr, uint8_t value, SnesMemoryType memoryType); void ProcessWorkRamRead(uint32_t addr, uint8_t value); void ProcessWorkRamWrite(uint32_t addr, uint8_t value); - void ProcessPpuCycle(); + void ProcessPpuCycle(uint16_t scanline, uint16_t cycle); template void ProcessInterrupt(uint32_t originalPc, uint32_t currentPc, bool forNmi); void ProcessEvent(EventType type); }; diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 1debe64..81e7eb0 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -64,6 +64,25 @@ + + + + + + + + + + + + + + + + + + + @@ -196,6 +215,7 @@ + @@ -253,6 +273,14 @@ + + + + + + + + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index db00338..2578e39 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -521,6 +521,66 @@ Debugger\Debuggers + + GB + + + GB + + + GB + + + GB + + + GB + + + GB + + + Debugger\Disassembler + + + Debugger\Debuggers + + + GB\Carts + + + GB\Carts + + + GB\Carts + + + Debugger + + + GB + + + GB\APU + + + GB\APU + + + GB\APU + + + GB\APU + + + GB\Carts + + + GB\Carts + + + GB\Carts + @@ -839,6 +899,30 @@ Debugger\Debuggers + + GB + + + GB + + + GB + + + GB + + + Debugger\Disassembler + + + Debugger\Debuggers + + + GB\APU + + + GB + @@ -916,5 +1000,14 @@ {c519e61b-e1c6-4182-8a23-ed23b60fbf96} + + {3b255a1a-392b-4005-b21e-4c3f5b6f08d2} + + + {92514dec-b6e7-4085-843b-bcff985f9e41} + + + {c020f128-b5e1-4f6c-9849-ff098fe93d39} + \ No newline at end of file diff --git a/Core/CpuDebugger.cpp b/Core/CpuDebugger.cpp index 66fa310..69418d8 100644 --- a/Core/CpuDebugger.cpp +++ b/Core/CpuDebugger.cpp @@ -20,7 +20,6 @@ #include "Console.h" #include "MemoryAccessCounter.h" #include "ExpressionEvaluator.h" -#include "Profiler.h" CpuDebugger::CpuDebugger(Debugger* debugger, CpuType cpuType) { @@ -35,7 +34,6 @@ CpuDebugger::CpuDebugger(Debugger* debugger, CpuType cpuType) _codeDataLogger = debugger->GetCodeDataLogger().get(); _settings = debugger->GetConsole()->GetSettings().get(); _eventManager = debugger->GetEventManager().get(); - _scriptManager = debugger->GetScriptManager().get(); _memoryManager = debugger->GetConsole()->GetMemoryManager().get(); _callstackManager.reset(new CallstackManager(debugger)); @@ -81,7 +79,7 @@ void CpuDebugger::ProcessRead(uint32_t addr, uint8_t value, MemoryOperationType DebugState debugState; _debugger->GetState(debugState, true); - DisassemblyInfo disInfo = _disassembler->GetDisassemblyInfo(addressInfo); + DisassemblyInfo disInfo = _disassembler->GetDisassemblyInfo(addressInfo, addr, state.PS, _cpuType); _traceLogger->Log(_cpuType, debugState, disInfo); } @@ -153,8 +151,6 @@ void CpuDebugger::ProcessRead(uint32_t addr, uint8_t value, MemoryOperationType } _debugger->ProcessBreakConditions(_step->StepCount == 0, _breakpointManager.get(), operation, addressInfo, breakSource); - - _scriptManager->ProcessMemoryOperation(addr, value, type); } void CpuDebugger::ProcessWrite(uint32_t addr, uint8_t value, MemoryOperationType type) @@ -172,8 +168,6 @@ void CpuDebugger::ProcessWrite(uint32_t addr, uint8_t value, MemoryOperationType _memoryAccessCounter->ProcessMemoryWrite(addressInfo, _memoryManager->GetMasterClock()); _debugger->ProcessBreakConditions(false, _breakpointManager.get(), operation, addressInfo); - - _scriptManager->ProcessMemoryOperation(addr, value, type); } void CpuDebugger::Run() diff --git a/Core/CpuDebugger.h b/Core/CpuDebugger.h index 1bf24f9..d2ee645 100644 --- a/Core/CpuDebugger.h +++ b/Core/CpuDebugger.h @@ -27,7 +27,6 @@ class CpuDebugger final : public IDebugger MemoryManager* _memoryManager; EmuSettings* _settings; CodeDataLogger* _codeDataLogger; - ScriptManager* _scriptManager; EventManager* _eventManager; Cpu* _cpu; Sa1* _sa1; diff --git a/Core/Cx4Debugger.cpp b/Core/Cx4Debugger.cpp index 1315401..5d03430 100644 --- a/Core/Cx4Debugger.cpp +++ b/Core/Cx4Debugger.cpp @@ -58,7 +58,7 @@ void Cx4Debugger::ProcessRead(uint32_t addr, uint8_t value, MemoryOperationType DebugState debugState; _debugger->GetState(debugState, true); - DisassemblyInfo disInfo = _disassembler->GetDisassemblyInfo(addressInfo); + DisassemblyInfo disInfo = _disassembler->GetDisassemblyInfo(addressInfo, addr, 0, CpuType::Cx4); _traceLogger->Log(CpuType::Cx4, debugState, disInfo); } } diff --git a/Core/DebugStats.cpp b/Core/DebugStats.cpp index 1dc2247..39b5dfe 100644 --- a/Core/DebugStats.cpp +++ b/Core/DebugStats.cpp @@ -2,7 +2,6 @@ #include "DebugStats.h" #include "Console.h" #include "SoundMixer.h" -#include "Ppu.h" #include "EmuSettings.h" #include "DebugHud.h" #include "IAudioDevice.h" @@ -16,7 +15,7 @@ void DebugStats::DisplayStats(Console *console, double lastFrameTime) _frameDurations[_frameDurationIndex] = lastFrameTime; _frameDurationIndex = (_frameDurationIndex + 1) % 60; - int startFrame = console->GetPpu()->GetFrameCount(); + int startFrame = console->GetFrameCount(); hud->DrawRectangle(8, 8, 115, 49, 0x40000000, true, 1, startFrame); hud->DrawRectangle(8, 8, 115, 49, 0xFFFFFF, false, 1, startFrame); @@ -50,7 +49,7 @@ void DebugStats::DisplayStats(Console *console, double lastFrameTime) ss << "Last Frame: " << std::fixed << std::setprecision(2) << lastFrameTime << " ms"; hud->DrawString(134, 30, ss.str(), 0xFFFFFF, 0xFF000000, 1, startFrame); - if(console->GetPpu()->GetFrameCount() > 60) { + if(console->GetFrameCount() > 60) { _lastFrameMin = std::min(lastFrameTime, _lastFrameMin); _lastFrameMax = std::max(lastFrameTime, _lastFrameMax); } else { diff --git a/Core/DebugTypes.h b/Core/DebugTypes.h index 9ceaf6a..e3dd3ac 100644 --- a/Core/DebugTypes.h +++ b/Core/DebugTypes.h @@ -7,8 +7,10 @@ #include "GsuTypes.h" #include "Cx4Types.h" #include "Sa1Types.h" +#include "GbTypes.h" #include "InternalRegisterTypes.h" #include "DmaControllerTypes.h" +#include "SnesMemoryType.h" struct DebugState { @@ -22,38 +24,13 @@ struct DebugState GsuState Gsu; Cx4State Cx4; + GbState Gameboy; + DmaChannelConfig DmaChannels[8]; InternalRegisterState InternalRegs; AluState Alu; }; -enum class SnesMemoryType -{ - CpuMemory, - SpcMemory, - Sa1Memory, - NecDspMemory, - GsuMemory, - Cx4Memory, - PrgRom, - WorkRam, - SaveRam, - VideoRam, - SpriteRam, - CGRam, - SpcRam, - SpcRom, - DspProgramRom, - DspDataRom, - DspDataRam, - Sa1InternalRam, - GsuWorkRam, - Cx4DataRam, - BsxPsRam, - BsxMemoryPack, - Register, -}; - struct AddressInfo { int32_t Address; @@ -279,5 +256,6 @@ enum class CpuType : uint8_t NecDsp, Sa1, Gsu, - Cx4 + Cx4, + Gameboy }; diff --git a/Core/DebugUtilities.h b/Core/DebugUtilities.h index 596f668..5c76454 100644 --- a/Core/DebugUtilities.h +++ b/Core/DebugUtilities.h @@ -14,6 +14,7 @@ public: case CpuType::Sa1: return SnesMemoryType::Sa1Memory; case CpuType::Gsu: return SnesMemoryType::GsuMemory; case CpuType::Cx4: return SnesMemoryType::Cx4Memory; + case CpuType::Gameboy: return SnesMemoryType::GameboyMemory; } throw std::runtime_error("Invalid CPU type"); @@ -43,6 +44,13 @@ public: case SnesMemoryType::Cx4DataRam: case SnesMemoryType::Cx4Memory: return CpuType::Cx4; + + case SnesMemoryType::GbPrgRom: + case SnesMemoryType::GbWorkRam: + case SnesMemoryType::GbCartRam: + case SnesMemoryType::GbVideoRam: + case SnesMemoryType::GbHighRam: + return CpuType::Gameboy; default: return CpuType::Cpu; @@ -53,7 +61,7 @@ public: static constexpr SnesMemoryType GetLastCpuMemoryType() { - return SnesMemoryType::Cx4Memory; + return SnesMemoryType::GameboyMemory; } static bool IsPpuMemory(SnesMemoryType memType) @@ -62,6 +70,7 @@ public: case SnesMemoryType::VideoRam: case SnesMemoryType::SpriteRam: case SnesMemoryType::CGRam: + case SnesMemoryType::GbVideoRam: return true; default: @@ -71,6 +80,6 @@ public: static constexpr CpuType GetLastCpuType() { - return CpuType::Cx4; + return CpuType::Gameboy; } }; \ No newline at end of file diff --git a/Core/Debugger.cpp b/Core/Debugger.cpp index 4a7b875..1212893 100644 --- a/Core/Debugger.cpp +++ b/Core/Debugger.cpp @@ -9,11 +9,13 @@ #include "Gsu.h" #include "Cx4.h" #include "NecDsp.h" +#include "Gameboy.h" #include "CpuDebugger.h" #include "SpcDebugger.h" #include "GsuDebugger.h" #include "NecDspDebugger.h" #include "Cx4Debugger.h" +#include "GbDebugger.h" #include "BaseCartridge.h" #include "MemoryManager.h" #include "EmuSettings.h" @@ -62,6 +64,7 @@ Debugger::Debugger(shared_ptr console) _watchExpEval[(int)CpuType::Gsu].reset(new ExpressionEvaluator(this, CpuType::Gsu)); _watchExpEval[(int)CpuType::NecDsp].reset(new ExpressionEvaluator(this, CpuType::NecDsp)); _watchExpEval[(int)CpuType::Cx4].reset(new ExpressionEvaluator(this, CpuType::Cx4)); + _watchExpEval[(int)CpuType::Gameboy].reset(new ExpressionEvaluator(this, CpuType::Gameboy)); _codeDataLogger.reset(new CodeDataLogger(_cart->DebugGetPrgRomSize())); _memoryDumper.reset(new MemoryDumper(this)); @@ -83,6 +86,8 @@ Debugger::Debugger(shared_ptr console) _necDspDebugger.reset(new NecDspDebugger(this)); } else if(_cart->GetCx4()) { _cx4Debugger.reset(new Cx4Debugger(this)); + } else if(_cart->GetGameboy()) { + _gbDebugger.reset(new GbDebugger(this)); } _step.reset(new StepRequest()); @@ -137,7 +142,9 @@ void Debugger::ProcessMemoryRead(uint32_t addr, uint8_t value, MemoryOperationTy case CpuType::Sa1: _sa1Debugger->ProcessRead(addr, value, opType); break; case CpuType::Gsu: _gsuDebugger->ProcessRead(addr, value, opType); break; case CpuType::Cx4: _cx4Debugger->ProcessRead(addr, value, opType); break; + case CpuType::Gameboy: _gbDebugger->ProcessRead(addr, value, opType); break; } + _scriptManager->ProcessMemoryOperation(addr, value, opType, type); } template @@ -150,7 +157,9 @@ void Debugger::ProcessMemoryWrite(uint32_t addr, uint8_t value, MemoryOperationT case CpuType::Sa1: _sa1Debugger->ProcessWrite(addr, value, opType); break; case CpuType::Gsu: _gsuDebugger->ProcessWrite(addr, value, opType); break; case CpuType::Cx4: _cx4Debugger->ProcessWrite(addr, value, opType); break; + case CpuType::Gameboy: _gbDebugger->ProcessWrite(addr, value, opType); break; } + _scriptManager->ProcessMemoryOperation(addr, value, opType, type); } void Debugger::ProcessWorkRamRead(uint32_t addr, uint8_t value) @@ -189,10 +198,8 @@ void Debugger::ProcessPpuWrite(uint16_t addr, uint8_t value, SnesMemoryType memo _memoryAccessCounter->ProcessMemoryWrite(addressInfo, _memoryManager->GetMasterClock()); } -void Debugger::ProcessPpuCycle() +void Debugger::ProcessPpuCycle(uint16_t scanline, uint16_t cycle) { - uint16_t scanline = _ppu->GetScanline(); - uint16_t cycle = _ppu->GetCycle(); _ppuTools->UpdateViewers(scanline, cycle); if(_step->PpuStepCount > 0) { @@ -232,6 +239,8 @@ void Debugger::SleepUntilResume(BreakSource source, MemoryOperationInfo *operati _disassembler->Disassemble(CpuType::NecDsp); } else if(_cart->GetCx4()) { _disassembler->Disassemble(CpuType::Cx4); + } else if(_cart->GetGameboy()) { + _disassembler->RefreshDisassembly(CpuType::Gameboy); } _executionStopped = true; @@ -327,6 +336,9 @@ void Debugger::Run() } if(_cx4Debugger) { _cx4Debugger->Run(); + } + if(_gbDebugger) { + _gbDebugger->Run(); } _waitForBreakResume = false; } @@ -348,6 +360,7 @@ void Debugger::Step(CpuType cpuType, int32_t stepCount, StepType type) case CpuType::Sa1: debugger = _sa1Debugger.get(); break; case CpuType::Gsu: debugger = _gsuDebugger.get(); break; case CpuType::Cx4: debugger = _cx4Debugger.get(); break; + case CpuType::Gameboy: debugger = _gbDebugger.get(); break; } debugger->Step(stepCount, type); break; @@ -374,6 +387,9 @@ void Debugger::Step(CpuType cpuType, int32_t stepCount, StepType type) if(_cx4Debugger && debugger != _cx4Debugger.get()) { _cx4Debugger->Run(); } + if(_gbDebugger && debugger != _gbDebugger.get()) { + _gbDebugger->Run(); + } _waitForBreakResume = false; } @@ -439,6 +455,9 @@ void Debugger::GetState(DebugState &state, bool partialPpuState) if(_cart->GetCx4()) { state.Cx4 = _cart->GetCx4()->GetState(); } + if(_cart->GetGameboy()) { + state.Gameboy = _cart->GetGameboy()->GetState(); + } } AddressInfo Debugger::GetAbsoluteAddress(AddressInfo relAddress) @@ -459,6 +478,8 @@ AddressInfo Debugger::GetAbsoluteAddress(AddressInfo relAddress) return _cart->GetCx4()->GetMemoryMappings()->GetAbsoluteAddress(relAddress.Address); } else if(relAddress.Type == SnesMemoryType::NecDspMemory) { return { relAddress.Address, SnesMemoryType::DspProgramRom }; + } else if(relAddress.Type == SnesMemoryType::GameboyMemory) { + return _cart->GetGameboy()->GetAbsoluteAddress(relAddress.Address); } throw std::runtime_error("Unsupported address type"); @@ -474,6 +495,7 @@ AddressInfo Debugger::GetRelativeAddress(AddressInfo absAddress, CpuType cpuType case CpuType::Sa1: mappings = _cart->GetSa1()->GetMemoryMappings(); break; case CpuType::Gsu: mappings = _cart->GetGsu()->GetMemoryMappings(); break; case CpuType::Cx4: mappings = _cart->GetCx4()->GetMemoryMappings(); break; + case CpuType::Gameboy: break; } switch(absAddress.Type) { @@ -504,7 +526,12 @@ AddressInfo Debugger::GetRelativeAddress(AddressInfo absAddress, CpuType cpuType case SnesMemoryType::SpcRam: case SnesMemoryType::SpcRom: return { _spc->GetRelativeAddress(absAddress), SnesMemoryType::SpcMemory }; - + + case SnesMemoryType::GbPrgRom: + case SnesMemoryType::GbWorkRam: + case SnesMemoryType::GbCartRam: + case SnesMemoryType::GbHighRam: + return { _cart->GetGameboy()->GetRelativeAddress(absAddress), SnesMemoryType::GameboyMemory }; case SnesMemoryType::DspProgramRom: return { absAddress.Address, SnesMemoryType::NecDspMemory }; @@ -583,6 +610,9 @@ void Debugger::SetBreakpoints(Breakpoint breakpoints[], uint32_t length) if(_cx4Debugger) { _cx4Debugger->GetBreakpointManager()->SetBreakpoints(breakpoints, length); } + if(_gbDebugger) { + _gbDebugger->GetBreakpointManager()->SetBreakpoints(breakpoints, length); + } } void Debugger::SaveRomToDisk(string filename, bool saveAsIps, CdlStripOption stripOption) @@ -661,7 +691,8 @@ shared_ptr Debugger::GetCallstackManager(CpuType cpuType) case CpuType::Cpu: return _cpuDebugger->GetCallstackManager(); case CpuType::Spc: return _spcDebugger->GetCallstackManager(); case CpuType::Sa1: return _sa1Debugger->GetCallstackManager(); - + case CpuType::Gameboy: return _gbDebugger->GetCallstackManager(); break; + case CpuType::Gsu: case CpuType::NecDsp: case CpuType::Cx4: @@ -686,6 +717,7 @@ template void Debugger::ProcessMemoryRead(uint32_t addr, uint8_t v template void Debugger::ProcessMemoryRead(uint32_t addr, uint8_t value, MemoryOperationType opType); template void Debugger::ProcessMemoryRead(uint32_t addr, uint8_t value, MemoryOperationType opType); template void Debugger::ProcessMemoryRead(uint32_t addr, uint8_t value, MemoryOperationType opType); +template void Debugger::ProcessMemoryRead(uint32_t addr, uint8_t value, MemoryOperationType opType); template void Debugger::ProcessMemoryWrite(uint32_t addr, uint8_t value, MemoryOperationType opType); template void Debugger::ProcessMemoryWrite(uint32_t addr, uint8_t value, MemoryOperationType opType); @@ -693,6 +725,7 @@ template void Debugger::ProcessMemoryWrite(uint32_t addr, uint8_t template void Debugger::ProcessMemoryWrite(uint32_t addr, uint8_t value, MemoryOperationType opType); template void Debugger::ProcessMemoryWrite(uint32_t addr, uint8_t value, MemoryOperationType opType); template void Debugger::ProcessMemoryWrite(uint32_t addr, uint8_t value, MemoryOperationType opType); +template void Debugger::ProcessMemoryWrite(uint32_t addr, uint8_t value, MemoryOperationType opType); template void Debugger::ProcessInterrupt(uint32_t originalPc, uint32_t currentPc, bool forNmi); template void Debugger::ProcessInterrupt(uint32_t originalPc, uint32_t currentPc, bool forNmi); diff --git a/Core/Debugger.h b/Core/Debugger.h index e1a6886..afdc4e7 100644 --- a/Core/Debugger.h +++ b/Core/Debugger.h @@ -32,6 +32,7 @@ class CpuDebugger; class GsuDebugger; class NecDspDebugger; class Cx4Debugger; +class GbDebugger; class Breakpoint; class Assembler; @@ -57,6 +58,7 @@ private: unique_ptr _gsuDebugger; unique_ptr _necDspDebugger; unique_ptr _cx4Debugger; + unique_ptr _gbDebugger; shared_ptr _scriptManager; shared_ptr _traceLogger; @@ -98,7 +100,7 @@ public: void ProcessPpuRead(uint16_t addr, uint8_t value, SnesMemoryType memoryType); void ProcessPpuWrite(uint16_t addr, uint8_t value, SnesMemoryType memoryType); - void ProcessPpuCycle(); + void ProcessPpuCycle(uint16_t scanline, uint16_t cycle); template void ProcessInterrupt(uint32_t originalPc, uint32_t currentPc, bool forNmi); diff --git a/Core/Disassembler.cpp b/Core/Disassembler.cpp index 43a0168..8742a0a 100644 --- a/Core/Disassembler.cpp +++ b/Core/Disassembler.cpp @@ -10,6 +10,7 @@ #include "Cx4.h" #include "BsxCart.h" #include "BsxMemoryPack.h" +#include "Gameboy.h" #include "Debugger.h" #include "MemoryManager.h" #include "LabelManager.h" @@ -37,6 +38,7 @@ Disassembler::Disassembler(shared_ptr console, shared_ptrGetSpc().get(); _gsu = cart->GetGsu(); _sa1 = cart->GetSa1(); + _gameboy = cart->GetGameboy(); _settings = console->GetSettings().get(); _memoryDumper = _debugger->GetMemoryDumper().get(); _memoryManager = console->GetMemoryManager().get(); @@ -56,17 +58,26 @@ Disassembler::Disassembler(shared_ptr console, shared_ptrGetDsp() ? cart->GetDsp()->DebugGetProgramRom() : nullptr; _necDspProgramRomSize = cart->GetDsp() ? cart->GetDsp()->DebugGetProgramRomSize() : 0; - _sa1InternalRam = cart->GetSa1() ? cart->GetSa1()->DebugGetInternalRam() : nullptr; - _sa1InternalRamSize = cart->GetSa1() ? cart->GetSa1()->DebugGetInternalRamSize() : 0; + _sa1InternalRam = _sa1 ? _sa1->DebugGetInternalRam() : nullptr; + _sa1InternalRamSize = _sa1 ? _sa1->DebugGetInternalRamSize() : 0; - _gsuWorkRam = cart->GetGsu() ? cart->GetGsu()->DebugGetWorkRam() : nullptr; - _gsuWorkRamSize = cart->GetGsu() ? cart->GetGsu()->DebugGetWorkRamSize() : 0; + _gsuWorkRam = _gsu ? _gsu->DebugGetWorkRam() : nullptr; + _gsuWorkRamSize = _gsu ? _gsu->DebugGetWorkRamSize() : 0; _bsxPsRam = cart->GetBsx() ? cart->GetBsx()->DebugGetPsRam() : nullptr; _bsxPsRamSize = cart->GetBsx() ? cart->GetBsx()->DebugGetPsRamSize() : 0; _bsxMemPack = cart->GetBsx() ? cart->GetBsxMemoryPack()->DebugGetMemoryPack() : nullptr; _bsxMemPackSize = cart->GetBsx() ? cart->GetBsxMemoryPack()->DebugGetMemoryPackSize() : 0; + _gbPrgRom = _gameboy ? _gameboy->DebugGetMemory(SnesMemoryType::GbPrgRom) : nullptr; + _gbPrgRomSize = _gameboy ? _gameboy->DebugGetMemorySize(SnesMemoryType::GbPrgRom) : 0; + _gbWorkRam = _gameboy ? _gameboy->DebugGetMemory(SnesMemoryType::GbWorkRam) : nullptr; + _gbWorkRamSize = _gameboy ? _gameboy->DebugGetMemorySize(SnesMemoryType::GbWorkRam) : 0; + _gbCartRam = _gameboy ? _gameboy->DebugGetMemory(SnesMemoryType::GbCartRam) : nullptr; + _gbCartRamSize = _gameboy ? _gameboy->DebugGetMemorySize(SnesMemoryType::GbCartRam) : 0; + _gbHighRam = _gameboy ? _gameboy->DebugGetMemory(SnesMemoryType::GbHighRam) : nullptr; + _gbHighRamSize = _gameboy ? _gameboy->DebugGetMemorySize(SnesMemoryType::GbHighRam) : 0; + _prgCache = vector(_prgRomSize); _sramCache = vector(_sramSize); _wramCache = vector(_wramSize); @@ -77,13 +88,18 @@ Disassembler::Disassembler(shared_ptr console, shared_ptr(_gsuWorkRamSize); _bsxPsRamCache = vector(_bsxPsRamSize); _bsxMemPackCache = vector(_bsxMemPackSize); + + _gbPrgCache = vector(_gbPrgRomSize); + _gbWorkRamCache = vector(_gbWorkRamSize); + _gbCartRamCache = vector(_gbCartRamSize); + _gbHighRamCache = vector(_gbHighRamSize); for(int i = 0; i < (int)DebugUtilities::GetLastCpuType(); i++) { _needDisassemble[i] = true; } _sources[(int)SnesMemoryType::PrgRom] = { _prgRom, &_prgCache, _prgRomSize }; - _sources[(int)SnesMemoryType::WorkRam] = { _wram, &_wramCache, MemoryManager::WorkRamSize }; + _sources[(int)SnesMemoryType::WorkRam] = { _wram, &_wramCache, _wramSize }; _sources[(int)SnesMemoryType::SaveRam] = { _sram, &_sramCache, _sramSize }; _sources[(int)SnesMemoryType::SpcRam] = { _spcRam, &_spcRamCache, _spcRamSize }; _sources[(int)SnesMemoryType::SpcRom] = { _spcRom, &_spcRomCache, _spcRomSize }; @@ -92,6 +108,11 @@ Disassembler::Disassembler(shared_ptr console, shared_ptr 0) { //Build cache for the entire DSP chip (since it only contains instructions) @@ -118,6 +139,7 @@ vector& Disassembler::GetDisassemblyList(CpuType type) case CpuType::Sa1: return _sa1Disassembly; case CpuType::Gsu: return _gsuDisassembly; case CpuType::Cx4: return _cx4Disassembly; + case CpuType::Gameboy: return _gbDisassembly; } throw std::runtime_error("Disassembly::GetDisassemblyList(): Invalid cpu type"); } @@ -134,7 +156,7 @@ uint32_t Disassembler::BuildCache(AddressInfo &addrInfo, uint8_t cpuFlags, CpuTy if(!disInfo.IsInitialized() || !disInfo.IsValid(cpuFlags)) { disInfo.Initialize(src.Data+address, cpuFlags, type); for(int i = 1; i < disInfo.GetOpSize(); i++) { - //Clear any instructions that start in the middle of the once + //Clear any instructions that start in the middle of this one //(can happen when resizing an instruction after X/M updates) (*src.Cache)[address + i] = DisassemblyInfo(); } @@ -145,11 +167,13 @@ uint32_t Disassembler::BuildCache(AddressInfo &addrInfo, uint8_t cpuFlags, CpuTy break; } - if(disInfo.UpdateCpuFlags(cpuFlags)) { - address += disInfo.GetOpSize(); - } else { + if(disInfo.IsUnconditionalJump()) { + //Can't assume what follows is code, stop disassembling break; } + + disInfo.UpdateCpuFlags(cpuFlags); + address += disInfo.GetOpSize(); } if(needDisassemble) { @@ -212,6 +236,7 @@ void Disassembler::Disassemble(CpuType cpuType) auto lock = _disassemblyLock.AcquireSafe(); bool isSpc = cpuType == CpuType::Spc; + bool isGb = cpuType == CpuType::Gameboy; bool isDsp = cpuType == CpuType::NecDsp; MemoryMappings *mappings = nullptr; int32_t maxAddr = 0xFFFFFF; @@ -242,6 +267,7 @@ void Disassembler::Disassemble(CpuType cpuType) maxAddr = _necDspProgramRomSize - 1; break; + case CpuType::Gameboy: case CpuType::Spc: mappings = nullptr; maxAddr = 0xFFFF; @@ -276,7 +302,13 @@ void Disassembler::Disassemble(CpuType cpuType) if(isDsp) { addrInfo = { i, SnesMemoryType::DspProgramRom }; } else { - addrInfo = isSpc ? _spc->GetAbsoluteAddress(i) : mappings->GetAbsoluteAddress(i); + if(isGb) { + addrInfo = _gameboy->GetAbsoluteAddress(i); + } else if(isSpc) { + addrInfo = _spc->GetAbsoluteAddress(i); + } else { + addrInfo = mappings->GetAbsoluteAddress(i); + } } if(addrInfo.Address < 0) { @@ -404,7 +436,7 @@ void Disassembler::Disassemble(CpuType cpuType) } } -DisassemblyInfo Disassembler::GetDisassemblyInfo(AddressInfo &info) +DisassemblyInfo Disassembler::GetDisassemblyInfo(AddressInfo &info, uint32_t cpuAddress, uint8_t cpuFlags, CpuType type) { DisassemblyInfo disassemblyInfo; switch(info.Type) { @@ -416,13 +448,15 @@ DisassemblyInfo Disassembler::GetDisassemblyInfo(AddressInfo &info) case SnesMemoryType::SpcRom: disassemblyInfo = _spcRomCache[info.Address]; break; case SnesMemoryType::DspProgramRom: disassemblyInfo = _necDspRomCache[info.Address]; break; case SnesMemoryType::Sa1InternalRam: disassemblyInfo = _sa1InternalRamCache[info.Address]; break; + case SnesMemoryType::GbPrgRom: disassemblyInfo = _gbPrgCache[info.Address]; break; + case SnesMemoryType::GbWorkRam: disassemblyInfo = _gbWorkRamCache[info.Address]; break; + case SnesMemoryType::GbCartRam: disassemblyInfo = _gbCartRamCache[info.Address]; break; } - if(disassemblyInfo.IsInitialized()) { - return disassemblyInfo; - } else { - return DisassemblyInfo(); + if(!disassemblyInfo.IsInitialized()) { + disassemblyInfo.Initialize(cpuAddress, cpuFlags, type, _memoryDumper); } + return disassemblyInfo; } void Disassembler::RefreshDisassembly(CpuType type) @@ -476,8 +510,13 @@ bool Disassembler::GetLineData(CpuType type, uint32_t lineIndex, CodeLineData &d switch(result.Address.Type) { default: break; + case SnesMemoryType::GbPrgRom: case SnesMemoryType::PrgRom: data.Flags |= (uint8_t)LineFlags::PrgRom; break; + + case SnesMemoryType::GbWorkRam: case SnesMemoryType::WorkRam: data.Flags |= (uint8_t)LineFlags::WorkRam; break; + + case SnesMemoryType::GbCartRam: case SnesMemoryType::SaveRam: data.Flags |= (uint8_t)LineFlags::SaveRam; break; } @@ -581,6 +620,18 @@ bool Disassembler::GetLineData(CpuType type, uint32_t lineIndex, CodeLineData &d data.Flags |= LineFlags::VerifiedCode; } + data.OpSize = disInfo.GetOpSize(); + data.EffectiveAddress = -1; + data.ValueSize = 0; + break; + + case CpuType::Gameboy: + if(!disInfo.IsInitialized()) { + disInfo = DisassemblyInfo(src.Data + result.Address.Address, 0, CpuType::Gameboy); + } else { + data.Flags |= LineFlags::VerifiedCode; + } + data.OpSize = disInfo.GetOpSize(); data.EffectiveAddress = -1; data.ValueSize = 0; diff --git a/Core/Disassembler.h b/Core/Disassembler.h index e311f09..37cce91 100644 --- a/Core/Disassembler.h +++ b/Core/Disassembler.h @@ -11,6 +11,7 @@ class Cpu; class Spc; class Gsu; class Sa1; +class Gameboy; class Debugger; class LabelManager; class CodeDataLogger; @@ -35,6 +36,7 @@ private: Spc* _spc; Gsu* _gsu; Sa1* _sa1; + Gameboy* _gameboy; EmuSettings* _settings; Debugger *_debugger; shared_ptr _cdl; @@ -52,6 +54,11 @@ private: vector _bsxPsRamCache; vector _bsxMemPackCache; + vector _gbPrgCache; + vector _gbWorkRamCache; + vector _gbCartRamCache; + vector _gbHighRamCache; + SimpleLock _disassemblyLock; vector _disassembly; vector _spcDisassembly; @@ -59,6 +66,7 @@ private: vector _gsuDisassembly; vector _necDspDisassembly; vector _cx4Disassembly; + vector _gbDisassembly; DisassemblerSource _sources[(int)SnesMemoryType::Register]; @@ -89,6 +97,15 @@ private: uint8_t* _bsxMemPack; uint32_t _bsxMemPackSize; + uint8_t* _gbPrgRom; + uint32_t _gbPrgRomSize; + uint8_t* _gbWorkRam; + uint32_t _gbWorkRamSize; + uint8_t* _gbCartRam; + uint32_t _gbCartRamSize; + uint8_t* _gbHighRam; + uint32_t _gbHighRamSize; + DisassemblerSource GetSource(SnesMemoryType type); vector& GetDisassemblyList(CpuType type); void SetDisassembleFlag(CpuType type); @@ -101,7 +118,7 @@ public: void InvalidateCache(AddressInfo addrInfo, CpuType type); void Disassemble(CpuType cpuType); - DisassemblyInfo GetDisassemblyInfo(AddressInfo &info); + DisassemblyInfo GetDisassemblyInfo(AddressInfo &info, uint32_t cpuAddress, uint8_t cpuFlags, CpuType type); void RefreshDisassembly(CpuType type); uint32_t GetLineCount(CpuType type); diff --git a/Core/DisassemblyInfo.cpp b/Core/DisassemblyInfo.cpp index d74f581..129cf61 100644 --- a/Core/DisassemblyInfo.cpp +++ b/Core/DisassemblyInfo.cpp @@ -11,6 +11,8 @@ #include "Cx4DisUtils.h" #include "../Utilities/HexUtilities.h" #include "../Utilities/FastString.h" +#include "GameboyDisUtils.h" +#include "DebugUtilities.h" DisassemblyInfo::DisassemblyInfo() { @@ -31,6 +33,23 @@ void DisassemblyInfo::Initialize(uint8_t *opPointer, uint8_t cpuFlags, CpuType t _initialized = true; } +void DisassemblyInfo::Initialize(uint32_t cpuAddress, uint8_t cpuFlags, CpuType type, MemoryDumper* memoryDumper) +{ + _cpuType = type; + _flags = cpuFlags; + + SnesMemoryType cpuMemType = DebugUtilities::GetCpuMemoryType(type); + _byteCode[0] = memoryDumper->GetMemoryValue(cpuMemType, cpuAddress); + + _opSize = GetOpSize(_byteCode[0], _flags, _cpuType); + + for(int i = 1; i < _opSize; i++) { + _byteCode[i] = memoryDumper->GetMemoryValue(cpuMemType, cpuAddress+i); + } + + _initialized = true; +} + bool DisassemblyInfo::IsInitialized() { return _initialized; @@ -58,6 +77,7 @@ void DisassemblyInfo::GetDisassembly(string &out, uint32_t memoryAddr, LabelMana case CpuType::NecDsp: NecDspDisUtils::GetDisassembly(*this, out, memoryAddr, labelManager, settings); break; case CpuType::Gsu: GsuDisUtils::GetDisassembly(*this, out, memoryAddr, labelManager, settings); break; case CpuType::Cx4: Cx4DisUtils::GetDisassembly(*this, out, memoryAddr, labelManager, settings); break; + case CpuType::Gameboy: GameboyDisUtils::GetDisassembly(*this, out, memoryAddr, labelManager, settings); break; } } @@ -73,6 +93,7 @@ int32_t DisassemblyInfo::GetEffectiveAddress(Console *console, void *cpuState, C case CpuType::Cx4: case CpuType::NecDsp: + case CpuType::Gameboy: return -1; } return -1; @@ -141,10 +162,13 @@ uint8_t DisassemblyInfo::GetOpSize(uint8_t opCode, uint8_t flags, CpuType type) case CpuType::NecDsp: return 3; case CpuType::Cx4: return 2; + + case CpuType::Gameboy: return GameboyDisUtils::GetOpSize(opCode); } return 0; } +//TODO: This is never called, removed? bool DisassemblyInfo::IsJumpToSub(uint8_t opCode, CpuType type) { switch(type) { @@ -154,6 +178,8 @@ bool DisassemblyInfo::IsJumpToSub(uint8_t opCode, CpuType type) case CpuType::Spc: return opCode == 0x3F || opCode == 0x0F; //JSR, BRK + case CpuType::Gameboy: return GameboyDisUtils::IsJumpToSub(opCode); + case CpuType::Gsu: case CpuType::NecDsp: case CpuType::Cx4: @@ -171,7 +197,9 @@ bool DisassemblyInfo::IsReturnInstruction(uint8_t opCode, CpuType type) return opCode == 0x60 || opCode == 0x6B || opCode == 0x40; case CpuType::Spc: return opCode == 0x6F || opCode == 0x7F; - + + case CpuType::Gameboy: return GameboyDisUtils::IsReturnInstruction(opCode); + case CpuType::Gsu: case CpuType::NecDsp: case CpuType::Cx4: @@ -181,7 +209,7 @@ bool DisassemblyInfo::IsReturnInstruction(uint8_t opCode, CpuType type) return false; } -bool DisassemblyInfo::UpdateCpuFlags(uint8_t &cpuFlags) +bool DisassemblyInfo::IsUnconditionalJump() { uint8_t opCode = GetOpCode(); switch(_cpuType) { @@ -189,36 +217,50 @@ bool DisassemblyInfo::UpdateCpuFlags(uint8_t &cpuFlags) case CpuType::Cpu: if(opCode == 0x00 || opCode == 0x20 || opCode == 0x40 || opCode == 0x60 || opCode == 0x80 || opCode == 0x22 || opCode == 0xFC || opCode == 0x6B || opCode == 0x4C || opCode == 0x5C || opCode == 0x6C || opCode == 0x7C || opCode == 0x02) { //Jumps, RTI, RTS, BRK, COP, etc., stop disassembling - return false; - } else if(opCode == 0xC2) { - //REP, update the flags and keep disassembling - uint8_t flags = GetByteCode()[1]; - cpuFlags &= ~flags; - } else if(opCode == 0xE2) { - //SEP, update the flags and keep disassembling - uint8_t flags = GetByteCode()[1]; - cpuFlags |= flags; + return true; } else if(opCode == 0x28) { - //PLP, stop disassembling - return false; + //PLP, stop disassembling because the 8-bit/16-bit flags could change + return true; } - return true; - + return false; + + case CpuType::Gameboy: + if(opCode == 0x18 || opCode == 0xC3 || opCode == 0xEA || opCode == 0xCD || opCode == 0xC9 || opCode == 0xD9 || opCode == 0xC7 || opCode == 0xCF || opCode == 0xD7 || opCode == 0xDF || opCode == 0xE7 || opCode == 0xEF || opCode == 0xF7 || opCode == 0xFF) { + return true; + } + return false; + case CpuType::Gsu: case CpuType::Spc: case CpuType::Cx4: - return false; - - case CpuType::NecDsp: return true; + + case CpuType::NecDsp: + return false; } return false; } +void DisassemblyInfo::UpdateCpuFlags(uint8_t& cpuFlags) +{ + if(_cpuType == CpuType::Cpu || _cpuType == CpuType::Sa1) { + uint8_t opCode = GetOpCode(); + if(opCode == 0xC2) { + //REP, update the flags and keep disassembling + uint8_t flags = GetByteCode()[1]; + cpuFlags &= ~flags; + } else if(opCode == 0xE2) { + //SEP, update the flags and keep disassembling + uint8_t flags = GetByteCode()[1]; + cpuFlags |= flags; + } + } +} + uint16_t DisassemblyInfo::GetMemoryValue(uint32_t effectiveAddress, MemoryDumper *memoryDumper, SnesMemoryType memType, uint8_t &valueSize) { - if(_cpuType == CpuType::Spc || (_flags & ProcFlags::MemoryMode8)) { + if((_cpuType == CpuType::Spc || _cpuType == CpuType::Gameboy) || (_flags & ProcFlags::MemoryMode8)) { valueSize = 1; return memoryDumper->GetMemoryValue(memType, effectiveAddress); } else { diff --git a/Core/DisassemblyInfo.h b/Core/DisassemblyInfo.h index 28601fc..2ee0cc9 100644 --- a/Core/DisassemblyInfo.h +++ b/Core/DisassemblyInfo.h @@ -23,6 +23,7 @@ public: DisassemblyInfo(uint8_t *opPointer, uint8_t cpuFlags, CpuType type); void Initialize(uint8_t *opPointer, uint8_t cpuFlags, CpuType type); + void Initialize(uint32_t cpuAddress, uint8_t cpuFlags, CpuType type, MemoryDumper* memoryDumper); bool IsInitialized(); bool IsValid(uint8_t cpuFlags); void Reset(); @@ -42,7 +43,8 @@ public: static bool IsJumpToSub(uint8_t opCode, CpuType type); static bool IsReturnInstruction(uint8_t opCode, CpuType type); - bool UpdateCpuFlags(uint8_t & cpuFlags); + bool IsUnconditionalJump(); + void UpdateCpuFlags(uint8_t& cpuFlags); int32_t GetEffectiveAddress(Console *console, void *cpuState, CpuType type); uint16_t GetMemoryValue(uint32_t effectiveAddress, MemoryDumper *memoryDumper, SnesMemoryType memType, uint8_t &valueSize); diff --git a/Core/EmuSettings.cpp b/Core/EmuSettings.cpp index 39f69c6..3ff8a95 100644 --- a/Core/EmuSettings.cpp +++ b/Core/EmuSettings.cpp @@ -85,6 +85,14 @@ void EmuSettings::SetInputConfig(InputConfig config) InputConfig EmuSettings::GetInputConfig() { + if(CheckFlag(EmulationFlags::GameboyMode)) { + if(_input.Controllers[0].Type != ControllerType::SnesController) { + //Force SNES controller for P1 for gameboy-only mode + InputConfig input = _input; + input.Controllers[0].Type = ControllerType::SnesController; + SetInputConfig(input); + } + } return _input; } @@ -200,10 +208,18 @@ vector EmuSettings::GetShortcutSupersets(EmulatorShortcut shortc OverscanDimensions EmuSettings::GetOverscan() { OverscanDimensions overscan; - overscan.Left = _video.OverscanLeft; - overscan.Right = _video.OverscanRight; - overscan.Top = _video.OverscanTop; - overscan.Bottom = _video.OverscanBottom; + if(CheckFlag(EmulationFlags::GameboyMode)) { + //Force overscan values for gameboy-only mode (not SGB) + overscan.Left = 0; + overscan.Right = 256 - 160; + overscan.Top = 0; + overscan.Bottom = 239 - 144; + } else { + overscan.Left = _video.OverscanLeft; + overscan.Right = _video.OverscanRight; + overscan.Top = _video.OverscanTop; + overscan.Bottom = _video.OverscanBottom; + } return overscan; } diff --git a/Core/ExpressionEvaluator.cpp b/Core/ExpressionEvaluator.cpp index b9e864f..4d57b07 100644 --- a/Core/ExpressionEvaluator.cpp +++ b/Core/ExpressionEvaluator.cpp @@ -71,18 +71,18 @@ bool ExpressionEvaluator::CheckSpecialTokens(string expression, size_t &pos, str } } while(pos < len); + int64_t tokenValue = -1; if(_cpuType == CpuType::Gsu) { - int64_t gsuToken = ProcessGsuTokens(token); - if(gsuToken != -1) { - output += std::to_string(gsuToken); - return true; - } + tokenValue = ProcessGsuTokens(token); + } else if(_cpuType == CpuType::Gameboy) { + tokenValue = ProcessGameboyTokens(token); } else { - int64_t cpuToken = ProcessCpuSpcTokens(token); - if(cpuToken != -1) { - output += std::to_string(cpuToken); - return true; - } + tokenValue = ProcessCpuSpcTokens(token); + } + + if(tokenValue != -1) { + output += std::to_string(tokenValue); + return true; } int64_t sharedToken = ProcessSharedTokens(token); @@ -206,6 +206,33 @@ int64_t ExpressionEvaluator::ProcessGsuTokens(string token) return -1; } + +int64_t ExpressionEvaluator::ProcessGameboyTokens(string token) +{ + if(token == "a") { + return EvalValues::RegA; + } else if(token == "b") { + return EvalValues::RegB; + } else if(token == "c") { + return EvalValues::RegC; + } else if(token == "d") { + return EvalValues::RegD; + } else if(token == "e") { + return EvalValues::RegE; + } else if(token == "f") { + return EvalValues::RegF; + } else if(token == "h") { + return EvalValues::RegH; + } else if(token == "l") { + return EvalValues::RegL; + } else if(token == "sp") { + return EvalValues::RegSP; + } else if(token == "pc") { + return EvalValues::RegPC; + } + return -1; +} + string ExpressionEvaluator::GetNextToken(string expression, size_t &pos, ExpressionData &data, bool &success) { string output; @@ -448,9 +475,9 @@ int32_t ExpressionEvaluator::Evaluate(ExpressionData &data, DebugState &state, E } else { switch(token) { /*case EvalValues::RegOpPC: token = state.Cpu.DebugPC; break;*/ - case EvalValues::PpuFrameCount: token = state.Ppu.FrameCount; break; - case EvalValues::PpuCycle: token = state.Ppu.Cycle; break; - case EvalValues::PpuScanline: token = state.Ppu.Scanline; break; + case EvalValues::PpuFrameCount: token = _cpuType == CpuType::Gameboy ? state.Gameboy.Ppu.FrameCount : state.Ppu.FrameCount; break; + case EvalValues::PpuCycle: token = _cpuType == CpuType::Gameboy ? state.Gameboy.Ppu.Cycle : state.Ppu.Cycle; break; + case EvalValues::PpuScanline: token = _cpuType == CpuType::Gameboy ? state.Gameboy.Ppu.Scanline : state.Ppu.Scanline; break; case EvalValues::Value: token = operationInfo.Value; break; case EvalValues::Address: token = operationInfo.Address; break; //case EvalValues::AbsoluteAddress: token = _debugger->GetAbsoluteAddress(operationInfo.Address); break; @@ -485,6 +512,21 @@ int32_t ExpressionEvaluator::Evaluate(ExpressionData &data, DebugState &state, E } break; + case CpuType::Gameboy: + switch(token) { + case EvalValues::RegA: token = state.Gameboy.Cpu.A; break; + case EvalValues::RegB: token = state.Gameboy.Cpu.B; break; + case EvalValues::RegC: token = state.Gameboy.Cpu.C; break; + case EvalValues::RegD: token = state.Gameboy.Cpu.D; break; + case EvalValues::RegE: token = state.Gameboy.Cpu.E; break; + case EvalValues::RegF: token = state.Gameboy.Cpu.Flags; break; + case EvalValues::RegH: token = state.Gameboy.Cpu.H; break; + case EvalValues::RegL: token = state.Gameboy.Cpu.L; break; + case EvalValues::RegSP: token = state.Gameboy.Cpu.SP; break; + case EvalValues::RegPC: token = state.Gameboy.Cpu.PC; break; + } + break; + case CpuType::Gsu: switch(token) { case EvalValues::R0: token = state.Gsu.R[0]; break; diff --git a/Core/ExpressionEvaluator.h b/Core/ExpressionEvaluator.h index a3bb85c..b324622 100644 --- a/Core/ExpressionEvaluator.h +++ b/Core/ExpressionEvaluator.h @@ -90,6 +90,14 @@ enum EvalValues : int64_t RomBR = 20000000141, RamBR = 20000000142, + RegB = 20000000160, + RegC = 20000000161, + RegD = 20000000162, + RegE = 20000000163, + RegF = 20000000164, + RegH = 20000000165, + RegL = 20000000166, + FirstLabelIndex = 20000002000, }; @@ -142,6 +150,7 @@ private: int64_t ProcessCpuSpcTokens(string token); int64_t ProcessSharedTokens(string token); int64_t ProcessGsuTokens(string token); + int64_t ProcessGameboyTokens(string token); string GetNextToken(string expression, size_t &pos, ExpressionData &data, bool &success); bool ProcessSpecialOperator(EvalOperators evalOp, std::stack &opStack, std::stack &precedenceStack, vector &outputQueue); bool ToRpn(string expression, ExpressionData &data); diff --git a/Core/Gameboy.cpp b/Core/Gameboy.cpp new file mode 100644 index 0000000..cd9e533 --- /dev/null +++ b/Core/Gameboy.cpp @@ -0,0 +1,239 @@ +#include "stdafx.h" +#include "Console.h" +#include "Gameboy.h" +#include "GbCpu.h" +#include "GbPpu.h" +#include "GbApu.h" +#include "GbCart.h" +#include "GbTimer.h" +#include "DebugTypes.h" +#include "GbMemoryManager.h" +#include "GbCartFactory.h" +#include "BatteryManager.h" +#include "GameboyHeader.h" +#include "EmuSettings.h" +#include "MessageManager.h" +#include "../Utilities/VirtualFile.h" +#include "../Utilities/Serializer.h" + +Gameboy* Gameboy::Create(Console* console, VirtualFile &romFile) +{ + vector romData; + romFile.ReadFile(romData); + + GameboyHeader header; + memcpy(&header, romData.data() + 0x134, sizeof(GameboyHeader)); + + MessageManager::Log("-----------------------------"); + MessageManager::Log("File: " + romFile.GetFileName()); + MessageManager::Log("Game: " + header.GetCartName()); + MessageManager::Log("Cart Type: " + std::to_string(header.CartType)); + MessageManager::Log("File size: " + std::to_string(romData.size() / 1024) + " KB"); + if(header.GetCartRamSize() > 0) { + string sizeString = header.GetCartRamSize() > 1024 ? std::to_string(header.GetCartRamSize() / 1024) + " KB" : std::to_string(header.GetCartRamSize()) + " bytes"; + MessageManager::Log("Cart RAM size: " + sizeString + (header.HasBattery() ? " (with battery)" : "")); + } + MessageManager::Log("-----------------------------"); + + GbCart* cart = GbCartFactory::CreateCart(header.CartType); + + if(cart) { + Gameboy* gb = new Gameboy(); + gb->_console = console; + gb->_cart.reset(cart); + gb->_prgRomSize = (uint32_t)romData.size(); + gb->_prgRom = new uint8_t[gb->_prgRomSize]; + memcpy(gb->_prgRom, romData.data(), romData.size()); + + gb->_cartRamSize = header.GetCartRamSize(); + gb->_cartRam = new uint8_t[gb->_cartRamSize]; + gb->_hasBattery = header.HasBattery(); + + gb->_workRam = new uint8_t[Gameboy::WorkRamSize]; + gb->_videoRam = new uint8_t[Gameboy::VideoRamSize]; + gb->_spriteRam = new uint8_t[Gameboy::SpriteRamSize]; + gb->_highRam = new uint8_t[Gameboy::HighRamSize]; + + return gb; + } + + return nullptr; +} + +Gameboy::~Gameboy() +{ + SaveBattery(); + + delete[] _cartRam; + delete[] _prgRom; + + delete[] _spriteRam; + delete[] _videoRam; + + delete[] _highRam; + delete[] _workRam; +} + +void Gameboy::PowerOn() +{ + EmuSettings* settings = _console->GetSettings().get(); + settings->InitializeRam(_cartRam, _cartRamSize); + settings->InitializeRam(_workRam, Gameboy::WorkRamSize); + settings->InitializeRam(_spriteRam, Gameboy::SpriteRamSize); + settings->InitializeRam(_highRam, Gameboy::HighRamSize); + + //VRAM is filled with 0s by the boot rom + memset(_videoRam, 0, Gameboy::VideoRamSize); + + LoadBattery(); + + _ppu.reset(new GbPpu()); + _apu.reset(new GbApu(_console, this)); + _memoryManager.reset(new GbMemoryManager()); + _timer.reset(new GbTimer(_memoryManager.get(), _apu.get())); + _cart->Init(this, _memoryManager.get()); + _memoryManager->Init(_console, this, _cart.get(), _ppu.get(), _apu.get(), _timer.get()); + + _cpu.reset(new GbCpu(_memoryManager.get())); + _ppu->Init(_console, this, _memoryManager.get(), _videoRam, _spriteRam); +} + +void Gameboy::Exec() +{ + _cpu->Exec(); +} + +void Gameboy::Run(uint64_t masterClock) +{ + while(_cpu->GetState().CycleCount < masterClock) { + _cpu->Exec(); + } +} + +void Gameboy::LoadBattery() +{ + if(_hasBattery) { + _console->GetBatteryManager()->LoadBattery(".srm", _cartRam, _cartRamSize); + } +} + +void Gameboy::SaveBattery() +{ + if(_hasBattery) { + _console->GetBatteryManager()->SaveBattery(".srm", _cartRam, _cartRamSize); + } +} + +GbState Gameboy::GetState() +{ + GbState state; + state.Cpu = _cpu->GetState(); + state.Ppu = _ppu->GetState(); + state.Apu = _apu->GetState(); + state.MemoryManager = _memoryManager->GetState(); + state.HasBattery = _hasBattery; + return state; +} + +uint32_t Gameboy::DebugGetMemorySize(SnesMemoryType type) +{ + switch(type) { + case SnesMemoryType::GbPrgRom: return _prgRomSize; + case SnesMemoryType::GbWorkRam: return Gameboy::WorkRamSize; + case SnesMemoryType::GbVideoRam: return Gameboy::VideoRamSize; + case SnesMemoryType::GbCartRam: return _cartRamSize; + case SnesMemoryType::GbHighRam: return Gameboy::HighRamSize; + default: return 0; + } +} + +uint8_t* Gameboy::DebugGetMemory(SnesMemoryType type) +{ + switch(type) { + case SnesMemoryType::GbPrgRom: return _prgRom; + case SnesMemoryType::GbWorkRam: return _workRam; + case SnesMemoryType::GbVideoRam: return _videoRam; + case SnesMemoryType::GbCartRam: return _cartRam; + case SnesMemoryType::GbHighRam: return _highRam; + default: return nullptr; + } +} + +GbMemoryManager* Gameboy::GetMemoryManager() +{ + return _memoryManager.get(); +} + +GbPpu* Gameboy::GetPpu() +{ + return _ppu.get(); +} + +AddressInfo Gameboy::GetAbsoluteAddress(uint16_t addr) +{ + AddressInfo addrInfo = { -1, SnesMemoryType::Register }; + + if(addr >= 0xFF80 && addr <= 0xFFFE) { + addrInfo.Address = addr & 0x7F; + addrInfo.Type = SnesMemoryType::GbHighRam; + return addrInfo; + } + + uint8_t* ptr = _memoryManager->GetMappedBlock(addr); + + if(!ptr) { + return addrInfo; + } + + ptr += (addr & 0xFF); + + if(ptr >= _prgRom && ptr < _prgRom + _prgRomSize) { + addrInfo.Address = (int32_t)(ptr - _prgRom); + addrInfo.Type = SnesMemoryType::GbPrgRom; + } else if(ptr >= _workRam && ptr < _workRam + Gameboy::WorkRamSize) { + addrInfo.Address = (int32_t)(ptr - _workRam); + addrInfo.Type = SnesMemoryType::GbWorkRam; + } else if(ptr >= _cartRam && ptr < _cartRam + _cartRamSize) { + addrInfo.Address = (int32_t)(ptr - _cartRam); + addrInfo.Type = SnesMemoryType::GbCartRam; + } + return addrInfo; +} + +int32_t Gameboy::GetRelativeAddress(AddressInfo& absAddress) +{ + if(absAddress.Type == SnesMemoryType::GbHighRam) { + return 0xFF80 | (absAddress.Address & 0x7F); + } + + for(int32_t i = 0; i < 0x10000; i += 0x100) { + AddressInfo blockAddr = GetAbsoluteAddress(absAddress.Address); + if(blockAddr.Type == absAddress.Type && (blockAddr.Address & ~0xFF) == (absAddress.Address & ~0xFF)) { + return i | (absAddress.Address & 0xFF); + } + } + + return -1; +} + +uint64_t Gameboy::GetCycleCount() +{ + return _cpu->GetCycleCount(); +} + +void Gameboy::Serialize(Serializer& s) +{ + s.Stream(_cpu.get()); + s.Stream(_ppu.get()); + s.Stream(_apu.get()); + s.Stream(_memoryManager.get()); + s.Stream(_cart.get()); + s.Stream(_timer.get()); + s.Stream(_hasBattery); + + s.StreamArray(_cartRam, _cartRamSize); + s.StreamArray(_workRam, Gameboy::WorkRamSize); + s.StreamArray(_videoRam, Gameboy::VideoRamSize); + s.StreamArray(_spriteRam, Gameboy::SpriteRamSize); + s.StreamArray(_highRam, Gameboy::HighRamSize); +} diff --git a/Core/Gameboy.h b/Core/Gameboy.h new file mode 100644 index 0000000..2e1a1b4 --- /dev/null +++ b/Core/Gameboy.h @@ -0,0 +1,68 @@ +#pragma once +#include "stdafx.h" +#include "DebugTypes.h" +#include "../Utilities/ISerializable.h" + +class Console; +class GbPpu; +class GbApu; +class GbCpu; +class GbCart; +class GbTimer; +class GbMemoryManager; +class VirtualFile; + +class Gameboy : public ISerializable +{ +private: + static constexpr int WorkRamSize = 0x2000; + static constexpr int VideoRamSize = 0x2000; + static constexpr int SpriteRamSize = 0xA0; + static constexpr int HighRamSize = 0x7F; + + Console* _console; + + unique_ptr _memoryManager; + unique_ptr _cpu; + unique_ptr _ppu; + unique_ptr _apu; + unique_ptr _cart; + unique_ptr _timer; + + bool _hasBattery; + + uint8_t* _prgRom; + uint32_t _prgRomSize; + + uint8_t* _cartRam; + uint32_t _cartRamSize; + + uint8_t* _workRam; + uint8_t* _videoRam; + uint8_t* _spriteRam; + uint8_t* _highRam; + +public: + static Gameboy* Create(Console* console, VirtualFile& romFile); + virtual ~Gameboy(); + + void PowerOn(); + + void Exec(); + void Run(uint64_t masterClock); + + void LoadBattery(); + void SaveBattery(); + + GbPpu* GetPpu(); + GbState GetState(); + uint32_t DebugGetMemorySize(SnesMemoryType type); + uint8_t* DebugGetMemory(SnesMemoryType type); + GbMemoryManager* GetMemoryManager(); + AddressInfo GetAbsoluteAddress(uint16_t addr); + int32_t GetRelativeAddress(AddressInfo& absAddress); + + uint64_t GetCycleCount(); + + void Serialize(Serializer& s) override; +}; \ No newline at end of file diff --git a/Core/GameboyDisUtils.cpp b/Core/GameboyDisUtils.cpp new file mode 100644 index 0000000..cc7083c --- /dev/null +++ b/Core/GameboyDisUtils.cpp @@ -0,0 +1,134 @@ +#include"stdafx.h" +#include"GameboyDisUtils.h" +#include"Console.h" +#include"DisassemblyInfo.h" +#include"EmuSettings.h" +#include"LabelManager.h" +#include"../Utilities/FastString.h" +#include"../Utilities/HexUtilities.h" + +constexpr const char* _opTemplate[256] = { + "NOP", "LD BC, e", "LD (BC), A", "INC BC", "INC B", "DEC B", "LD B, d", "RLCA", "LD (b), SP", "ADD HL, BC", "LD A, (BC)", "DEC BC", "INC C", "DEC C", "LD C, d", "RRCA", + "STOP", "LD DE, e", "LD (DE), A", "INC DE", "INC D", "DEC D", "LD D, d", "RLA", "JR r", "ADD HL, DE", "LD A, (DE)", "DEC DE", "INC E", "DEC E", "LD E, d", "RRA", + "JR NZ, r", "LD HL, e", "LD (HL), A", "INC HL", "INC H", "DEC H", "LD H, d", "DAA", "JR Z, r", "ADD HL, HL", "LD A, (HL)", "DEC HL", "INC L", "DEC L", "LD L, d", "CPL", + "JR NC, r", "LD SP, e", "LD (HL), A", "INC SP", "INC (HL)", "DEC (HL)", "LD (HL), d", "SCF", "JR C, r", "ADD HL, SP", "LD A, (HL)", "DEC SP", "INC A", "DEC A", "LD A, d", "CCF", + "LD B, B", "LD B, C", "LD B, D", "LD B, E", "LD B, H", "LD B, L", "LD B, (HL)", "LD B, A", "LD C, B", "LD C, C", "LD C, D", "LD C, E", "LD C, H", "LD C, L", "LD C, (HL)", "LD C, A", + "LD D, B", "LD D, C", "LD D, D", "LD D, E", "LD D, H", "LD D, L", "LD D, (HL)", "LD D, A", "LD E, B", "LD E, C", "LD E, D", "LD E, E", "LD E, H", "LD E, L", "LD E, (HL)", "LD E, A", + "LD H, B", "LD H, C", "LD H, D", "LD H, E", "LD H, H", "LD H, L", "LD H, (HL)", "LD H, A", "LD L, B", "LD L, C", "LD L, D", "LD L, E", "LD L, H", "LD L, L", "LD L, (HL)", "LD L, A", + "LD (HL), B", "LD (HL), C", "LD (HL), D", "LD (HL), E","LD (HL), H", "LD (HL), L", "HALT", "LD (HL), A", "LD A, B", "LD A, C", "LD A, D", "LD A, E", "LD A, H", "LD A, L", "LD A, (HL)", "LD A, A", + "ADD A, B", "ADD A, C", "ADD A, D", "ADD A, E", "ADD A, H", "ADD A, L", "ADD A, (HL)", "ADD A, A", "ADC A, B", "ADC A, C", "ADC A, D", "ADC A, E", "ADC A, H", "ADC A, L", "ADC A, (HL)", "ADC A, A", + "SUB B", "SUB C", "SUB D", "SUB E", "SUB H", "SUB L", "SUB (HL)", "SUB A", "SBC A, B", "SBC A, C", "SBC A, D", "SBC A, E", "SBC A, H", "SBC A, L", "SBC A, (HL)", "SBC A, A", + "AND B", "AND C", "AND D", "AND E", "AND H", "AND L", "AND (HL)", "AND A", "XOR B", "XOR C", "XOR D", "XOR E", "XOR H", "XOR L", "XOR (HL)", "XOR A", + "OR B", "OR C", "OR D", "OR E", "OR H", "OR L", "OR (HL)", "OR A", "CP B", "CP C", "CP D", "CP E", "CP H", "CP L", "CP (HL)", "CP A", + "RET NZ", "POP BC", "JP NZ, a", "JP a", "CALL NZ, a", "PUSH BC", "ADD A, d", "RST 00H", "RET Z", "RET", "JP Z, a", "PREFIX", "CALL Z, a","CALL a", "ADC A, d", "RST 08H", + "RET NC", "POP DE", "JP NC, a", "ILL_D3", "CALL NC, a", "PUSH DE", "SUB d", "RST 10H", "RET C", "RETI", "JP C, a", "ILL_DB", "CALL C, a","ILL_DD", "SBC A, d", "RST 18H", + "LDH (c), A", "POP HL", "LD ($FF00+C), A","ILL_E3","ILL_E4", "PUSH HL", "AND d", "RST 20H", "ADD SP, d", "JP HL", "LD (a), A", "ILL_EB", "ILL_EC", "ILL_ED", "XOR d", "RST 28H", + "LDH A, (c)", "POP AF", "LD A, ($FF00+C)","DI", "ILL_F4", "PUSH AF", "OR d", "RST 30H", "LD HL, SP+d", "LD SP, HL", "LD A, (a)", "EI", "ILL_FC", "ILL_FD", "CP d", "RST 38H" +}; + +constexpr const char* _cbTemplate[256] = { + "RLC B", "RLC C", "RLC D", "RLC E", "RLC H", "RLC L", "RLC (HL)", "RLC A", "RRC B", "RRC C", "RRC D", "RRC E", "RRC H", "RRC L", "RRC (HL)", "RRC A", + "RL B", "RL C", "RL D", "RL E", "RL H", "RL L", "RL (HL)", "RL A", "RR B", "RR C", "RR D", "RR E", "RR H", "RR L", "RR (HL)", "RR A", + "SLA B", "SLA C", "SLA D", "SLA E", "SLA H", "SLA L", "SLA (HL)", "SLA A", "SRA B", "SRA C", "SRA D", "SRA E", "SRA H", "SRA L", "SRA (HL)", "SRA A", + "SWAP B", "SWAP C", "SWAP D", "SWAP E", "SWAP H", "SWAP L", "SWAP (HL)", "SWAP A", "SRL B", "SRL C", "SRL D", "SRL E", "SRL H", "SRL L", "SRL (HL)", "SRL A", + "BIT 0, B", "BIT 0, C", "BIT 0, D", "BIT 0, E", "BIT 0, H", "BIT 0, L", "BIT 0, (HL)", "BIT 0, A", "BIT 1, B", "BIT 1, C", "BIT 1, D", "BIT 1, E", "BIT 1, H", "BIT 1, L", "BIT 1, (HL)", "BIT 1, A", + "BIT 2, B", "BIT 2, C", "BIT 2, D", "BIT 2, E", "BIT 2, H", "BIT 2, L", "BIT 2, (HL)", "BIT 2, A", "BIT 3, B", "BIT 3, C", "BIT 3, D", "BIT 3, E", "BIT 3, H", "BIT 3, L", "BIT 3, (HL)", "BIT 3, A", + "BIT 4, B", "BIT 4, C", "BIT 4, D", "BIT 4, E", "BIT 4, H", "BIT 4, L", "BIT 4, (HL)", "BIT 4, A", "BIT 5, B", "BIT 5, C", "BIT 5, D", "BIT 5, E", "BIT 5, H", "BIT 5, L", "BIT 5, (HL)", "BIT 5, A", + "BIT 6, B", "BIT 6, C", "BIT 6, D", "BIT 6, E", "BIT 6, H", "BIT 6, L", "BIT 6, (HL)", "BIT 6, A", "BIT 7, B", "BIT 7, C", "BIT 7, D", "BIT 7, E", "BIT 7, H", "BIT 7, L", "BIT 7, (HL)", "BIT 7, A", + "RES 0, B", "RES 0, C", "RES 0, D", "RES 0, E", "RES 0, H", "RES 0, L", "RES 0, (HL)", "RES 0, A", "RES 1, B", "RES 1, C", "RES 1, D", "RES 1, E", "RES 1, H", "RES 1, L", "RES 1, (HL)", "RES 1, A", + "RES 2, B", "RES 2, C", "RES 2, D", "RES 2, E", "RES 2, H", "RES 2, L", "RES 2, (HL)", "RES 2, A", "RES 3, B", "RES 3, C", "RES 3, D", "RES 3, E", "RES 3, H", "RES 3, L", "RES 3, (HL)", "RES 3, A", + "RES 4, B", "RES 4, C", "RES 4, D", "RES 4, E", "RES 4, H", "RES 4, L", "RES 4, (HL)", "RES 4, A", "RES 5, B", "RES 5, C", "RES 5, D", "RES 5, E", "RES 5, H", "RES 5, L", "RES 5, (HL)", "RES 5, A", + "RES 6, B", "RES 6, C", "RES 6, D", "RES 6, E", "RES 6, H", "RES 6, L", "RES 6, (HL)", "RES 6, A", "RES 7, B", "RES 7, C", "RES 7, D", "RES 7, E", "RES 7, H", "RES 7, L", "RES 7, (HL)", "RES 7, A", + "SET 0, B", "SET 0, C", "SET 0, D", "SET 0, E", "SET 0, H", "SET 0, L", "SET 0, (HL)", "SET 0, A", "SET 1, B", "SET 1, C", "SET 1, D", "SET 1, E", "SET 1, H", "SET 1, L", "SET 1, (HL)", "SET 1, A", + "SET 2, B", "SET 2, C", "SET 2, D", "SET 2, E", "SET 2, H", "SET 2, L", "SET 2, (HL)", "SET 2, A", "SET 3, B", "SET 3, C", "SET 3, D", "SET 3, E", "SET 3, H", "SET 3, L", "SET 3, (HL)", "SET 3, A", + "SET 4, B", "SET 4, C", "SET 4, D", "SET 4, E", "SET 4, H", "SET 4, L", "SET 4, (HL)", "SET 4, A", "SET 5, B", "SET 5, C", "SET 5, D", "SET 5, E", "SET 5, H", "SET 5, L", "SET 5, (HL)", "SET 5, A", + "SET 6, B", "SET 6, C", "SET 6, D", "SET 6, E", "SET 6, H", "SET 6, L", "SET 6, (HL)", "SET 6, A", "SET 7, B", "SET 7, C", "SET 7, D", "SET 7, E", "SET 7, H", "SET 7, L", "SET 7, (HL)", "SET 7, A", +}; + +constexpr const uint8_t _opSize[256] = { + 1,3,1,1,1,1,2,1,3,1,1,1,1,1,2,1, + 1,3,1,1,1,1,2,1,2,1,1,1,1,1,2,1, + 2,3,1,1,1,1,2,1,2,1,1,1,1,1,2,1, + 2,3,1,1,1,1,2,1,2,1,1,1,1,1,2,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,3,3,3,1,2,1,1,1,3,1,3,3,2,1, + 1,1,3,1,3,1,2,1,1,1,3,1,3,1,2,1, + 2,1,1,1,1,1,2,1,2,1,3,1,1,1,2,1, + 2,1,1,1,1,1,2,1,2,1,3,1,1,1,2,1, +}; + +void GameboyDisUtils::GetDisassembly(DisassemblyInfo& info, string& out, uint32_t memoryAddr, LabelManager* labelManager, EmuSettings* settings) +{ + FastString str(settings->CheckDebuggerFlag(DebuggerFlags::UseLowerCaseDisassembly)); + + AddressInfo addrInfo { 0, SnesMemoryType::GameboyMemory }; + auto getOperand = [&str, &addrInfo, labelManager](uint16_t addr) { + addrInfo.Address = addr; + string label = labelManager ? labelManager->GetLabel(addrInfo) :""; + if(label.empty()) { + str.WriteAll('$', HexUtilities::ToHex(addr)); + } else { + str.Write(label, true); + } + }; + + uint8_t* byteCode = info.GetByteCode(); + const char* op = byteCode[0] == 0xCB ? _cbTemplate[byteCode[0]] : _opTemplate[byteCode[0]]; + if(byteCode[0] == 0xCB) { + byteCode++; + } + + int i = 0; + while(op[i]) { + switch(op[i]) { + case 'r': getOperand((uint16_t)(memoryAddr + (int8_t)byteCode[1] + GetOpSize(byteCode[0]))); break; + + case 'a': getOperand((uint16_t)(byteCode[1] | (byteCode[2] << 8))); break; + case 'b': str.WriteAll('$', HexUtilities::ToHex(byteCode[1])); break; + case 'c': str.WriteAll('$', HexUtilities::ToHex((uint16_t)(0xFF00 | byteCode[1]))); break; + + case 'd': str.WriteAll("#$", HexUtilities::ToHex(byteCode[1])); break; + case 'e': str.WriteAll("#$", HexUtilities::ToHex((uint16_t)(byteCode[1] | (byteCode[2] << 8)))); break; + + default: str.Write(op[i]); break; + } + i++; + } + + out += str.ToString(); +} + +int32_t GameboyDisUtils::GetEffectiveAddress(DisassemblyInfo& info, Console* console, GbCpuState& state) +{ + return -1; +} + + +uint8_t GameboyDisUtils::GetOpSize(uint8_t opCode) +{ + return _opSize[opCode]; +} + +bool GameboyDisUtils::IsJumpToSub(uint8_t opCode) +{ + return ( + opCode == 0xC4 || opCode == 0xCC || opCode == 0xD4 || opCode == 0xDC || //CALL conditional + opCode == 0xCD ||//Unconditional CALL + opCode == 0xC7 || opCode == 0xCF || opCode == 0xD7 || opCode == 0xDF || opCode == 0xE7 || opCode == 0xEF || opCode == 0xF7 || opCode == 0xFF //RST unconditional + ); +} + +bool GameboyDisUtils::IsReturnInstruction(uint8_t opCode) +{ + return ( + opCode == 0xC0 || opCode == 0xC8 || opCode == 0xD0 || opCode == 0xD8 || //Conditional RET + opCode == 0xC9 || opCode == 0xD9 //Unconditional RET/RETI + ); +} \ No newline at end of file diff --git a/Core/GameboyDisUtils.h b/Core/GameboyDisUtils.h new file mode 100644 index 0000000..f9e83fb --- /dev/null +++ b/Core/GameboyDisUtils.h @@ -0,0 +1,18 @@ +#pragma once +#include "stdafx.h" + +class DisassemblyInfo; +class Console; +class LabelManager; +class EmuSettings; +struct GbCpuState; + +class GameboyDisUtils +{ +public: + static void GetDisassembly(DisassemblyInfo& info, string& out, uint32_t memoryAddr, LabelManager* labelManager, EmuSettings* settings); + static int32_t GetEffectiveAddress(DisassemblyInfo& info, Console* console, GbCpuState& state); + static uint8_t GetOpSize(uint8_t opCode); + static bool IsJumpToSub(uint8_t opCode); + static bool IsReturnInstruction(uint8_t opCode); +}; diff --git a/Core/GameboyHeader.h b/Core/GameboyHeader.h new file mode 100644 index 0000000..108eb76 --- /dev/null +++ b/Core/GameboyHeader.h @@ -0,0 +1,77 @@ +#pragma once +#include "stdafx.h" + +struct GameboyHeader +{ + //Starts at 0x134 + char Title[11]; + char ManufacturerCode[4]; + uint8_t CgbFlag; + char LicenseeCode[2]; + uint8_t SgbFlag; + uint8_t CartType; + uint8_t PrgRomSize; + uint8_t CartRamSize; + uint8_t DestCode; + uint8_t OldLicenseeCode; + uint8_t MaskRomVersion; + uint8_t HeaderChecksum; + uint8_t GlobalChecksum[2]; + + uint32_t GetPrgRomSize() + { + if(PrgRomSize < 16) { + return 0x8000 << PrgRomSize; + } + return 0x8000; + } + + uint32_t GetCartRamSize() + { + if(CartType == 5 || CartType == 6) { + //MBC2 has 512x4bits of cart ram + return 0x200; + } + + switch(CartRamSize) { + case 0: return 0; + case 1: return 0x800; + case 2: return 0x2000; + case 3: return 0x8000; + case 4: return 0x20000; + case 5: return 0x10000; + } + return 0; + } + + bool HasBattery() + { + switch(CartType) { + case 0x03: case 0x06: case 0x09: case 0x0D: + case 0x0F: case 0x10: case 0x13: case 0x1B: + case 0x1E: case 0x22: case 0xFF: + return true; + } + + return false; + } + + string GetCartName() + { + int nameLength = 11; + for(int i = 0; i < 11; i++) { + if(Title[i] == 0) { + nameLength = i; + break; + } + } + string name = string(Title, nameLength); + + size_t lastNonSpace = name.find_last_not_of(' '); + if(lastNonSpace != string::npos) { + return name.substr(0, lastNonSpace + 1); + } else { + return name; + } + } +}; diff --git a/Core/GbApu.cpp b/Core/GbApu.cpp new file mode 100644 index 0000000..07a4c35 --- /dev/null +++ b/Core/GbApu.cpp @@ -0,0 +1,272 @@ +#include "stdafx.h" +#include "GbApu.h" +#include "Console.h" +#include "Gameboy.h" +#include "SoundMixer.h" +#include "EmuSettings.h" +#include "../Utilities/Serializer.h" + +GbApu::GbApu(Console* console, Gameboy* gameboy) +{ + _console = console; + _soundMixer = console->GetSoundMixer().get(); + _gameboy = gameboy; + + _soundBuffer = new int16_t[GbApu::MaxSamples*2]; + memset(_soundBuffer, 0, GbApu::MaxSamples*2*sizeof(int16_t)); + + _leftChannel = blip_new(GbApu::MaxSamples); + _rightChannel = blip_new(GbApu::MaxSamples); + + blip_set_rates(_leftChannel, GbApu::ApuFrequency, GbApu::SampleRate); + blip_set_rates(_rightChannel, GbApu::ApuFrequency, GbApu::SampleRate); +} + +GbApu::~GbApu() +{ + blip_delete(_leftChannel); + blip_delete(_rightChannel); + delete[] _soundBuffer; +} + +GbApuDebugState GbApu::GetState() +{ + GbApuDebugState state; + state.Common = _state; + state.Square1 = _square1.GetState(); + state.Square2 = _square2.GetState(); + state.Wave = _wave.GetState(); + state.Noise = _noise.GetState(); + return state; +} + +void GbApu::Run() +{ + uint64_t clockCount = _gameboy->GetCycleCount(); + uint32_t clocksToRun = (uint32_t)(clockCount - _prevClockCount); + _prevClockCount = clockCount; + + if(!_state.ApuEnabled) { + return; + } + + while(clocksToRun > 0) { + uint32_t minTimer = std::min({ clocksToRun, _square1.GetState().Timer, _square2.GetState().Timer, _wave.GetState().Timer, _noise.GetState().Timer }); + + clocksToRun -= minTimer; + _square1.Exec(minTimer); + _square2.Exec(minTimer); + _wave.Exec(minTimer); + _noise.Exec(minTimer); + + int16_t leftOutput = ( + (_square1.GetOutput() & _state.EnableLeftSq1) + + (_square2.GetOutput() & _state.EnableLeftSq2) + + (_wave.GetOutput() & _state.EnableLeftWave) + + (_noise.GetOutput() & _state.EnableLeftNoise) + ) * (_state.LeftVolume + 1) * 40; + + if(_prevLeftOutput != leftOutput) { + blip_add_delta(_leftChannel, _clockCounter, leftOutput - _prevLeftOutput); + _prevLeftOutput = leftOutput; + } + + int16_t rightOutput = ( + (_square1.GetOutput() & _state.EnableRightSq1) + + (_square2.GetOutput() & _state.EnableRightSq2) + + (_wave.GetOutput() & _state.EnableRightWave) + + (_noise.GetOutput() & _state.EnableRightNoise) + ) * (_state.RightVolume + 1) * 40; + + if(_prevRightOutput != rightOutput) { + blip_add_delta(_rightChannel, _clockCounter, rightOutput - _prevRightOutput); + _prevRightOutput = rightOutput; + } + + _clockCounter += minTimer; + + if(_clockCounter >= 20000) { + blip_end_frame(_leftChannel, _clockCounter); + blip_end_frame(_rightChannel, _clockCounter); + + uint32_t sampleCount = (uint32_t)blip_read_samples(_leftChannel, _soundBuffer, GbApu::MaxSamples, 1); + blip_read_samples(_rightChannel, _soundBuffer + 1, GbApu::MaxSamples, 1); + _console->GetSoundMixer()->PlayAudioBuffer(_soundBuffer, sampleCount, GbApu::SampleRate); + _clockCounter = 0; + } + } +} + +void GbApu::ClockFrameSequencer() +{ + Run(); + + if(!_state.ApuEnabled) { + return; + } + + if((_state.FrameSequenceStep & 0x01) == 0) { + _square1.ClockLengthCounter(); + _square2.ClockLengthCounter(); + _wave.ClockLengthCounter(); + _noise.ClockLengthCounter(); + + if((_state.FrameSequenceStep & 0x03) == 2) { + _square1.ClockSweepUnit(); + } + } else if(_state.FrameSequenceStep == 7) { + _square1.ClockEnvelope(); + _square2.ClockEnvelope(); + _noise.ClockEnvelope(); + } + + _state.FrameSequenceStep = (_state.FrameSequenceStep + 1) & 0x07; +} + +uint8_t GbApu::Read(uint16_t addr) +{ + Run(); + + switch(addr) { + case 0xFF10: case 0xFF11: case 0xFF12: case 0xFF13: case 0xFF14: + return _square1.Read(addr - 0xFF10); + + case 0xFF16: case 0xFF17: case 0xFF18: case 0xFF19: + return _square2.Read(addr - 0xFF15); + + case 0xFF1A: case 0xFF1B: case 0xFF1C: case 0xFF1D: case 0xFF1E: + return _wave.Read(addr - 0xFF1A); + + case 0xFF20: case 0xFF21: case 0xFF22: case 0xFF23: + return _noise.Read(addr - 0xFF1F); + + case 0xFF24: + return ( + (_state.ExtAudioLeftEnabled ? 0x80 : 0) | + (_state.LeftVolume << 4) | + (_state.ExtAudioRightEnabled ? 0x08 : 0) | + _state.RightVolume + ); + + case 0xFF25: + return ( + (_state.EnableLeftNoise ? 0x80 : 0) | + (_state.EnableLeftWave ? 0x40 : 0) | + (_state.EnableLeftSq2 ? 0x20 : 0) | + (_state.EnableLeftSq1 ? 0x10 : 0) | + (_state.EnableRightNoise ? 0x08 : 0) | + (_state.EnableRightWave ? 0x04 : 0) | + (_state.EnableRightSq2 ? 0x02 : 0) | + (_state.EnableRightSq1 ? 0x01 : 0) + ); + + case 0xFF26: + return ( + (_state.ApuEnabled ? 0x80 : 0) | + 0x70 | //open bus + ((_state.ApuEnabled && _noise.Enabled()) ? 0x08 : 0) | + ((_state.ApuEnabled && _wave.Enabled()) ? 0x04 : 0) | + ((_state.ApuEnabled && _square2.Enabled()) ? 0x02 : 0) | + ((_state.ApuEnabled && _square1.Enabled()) ? 0x01 : 0) + ); + + case 0xFF30: case 0xFF31: case 0xFF32: case 0xFF33: case 0xFF34: case 0xFF35: case 0xFF36: case 0xFF37: + case 0xFF38: case 0xFF39: case 0xFF3A: case 0xFF3B: case 0xFF3C: case 0xFF3D: case 0xFF3E: case 0xFF3F: + return _wave.ReadRam(addr); + } + + //Open bus + return 0xFF; +} + +void GbApu::Write(uint16_t addr, uint8_t value) +{ + Run(); + + if(!_state.ApuEnabled) { + if(addr == 0xFF11 || addr == 0xFF16 || addr == 0xFF20) { + //Allow writes to length counter, but not the upper 2 bits (square duty) + value &= 0x3F; + } else if(addr < 0xFF26 && addr != 0xFF1B) { + //Ignore all writes to these registers when APU is disabled + return; + } + } + + switch(addr) { + case 0xFF10: case 0xFF11: case 0xFF12: case 0xFF13: case 0xFF14: + _square1.Write(addr - 0xFF10, value); + break; + + case 0xFF16: case 0xFF17: case 0xFF18: case 0xFF19: + _square2.Write(addr - 0xFF15, value); //Same as square1, but without a sweep unit + break; + + case 0xFF1A: case 0xFF1B: case 0xFF1C: case 0xFF1D: case 0xFF1E: + _wave.Write(addr - 0xFF1A, value); + break; + + case 0xFF20: case 0xFF21: case 0xFF22: case 0xFF23: + _noise.Write(addr - 0xFF1F, value); + break; + + case 0xFF24: + _state.ExtAudioLeftEnabled = (value & 0x80) != 0; + _state.LeftVolume = (value & 0x70) >> 4; + _state.ExtAudioRightEnabled = (value & 0x08) != 0; + _state.RightVolume = (value & 0x07); + break; + + case 0xFF25: + _state.EnableLeftNoise = (value & 0x80) ? 0xFF : 0; + _state.EnableLeftWave = (value & 0x40) ? 0xFF : 0; + _state.EnableLeftSq2 = (value & 0x20) ? 0xFF : 0; + _state.EnableLeftSq1 = (value & 0x10) ? 0xFF : 0; + + _state.EnableRightNoise = (value & 0x08) ? 0xFF : 0; + _state.EnableRightWave = (value & 0x04) ? 0xFF : 0; + _state.EnableRightSq2 = (value & 0x02) ? 0xFF : 0; + _state.EnableRightSq1 = (value & 0x01) ? 0xFF : 0; + break; + + case 0xFF26: { + bool apuEnabled = (value & 0x80) != 0; + if(_state.ApuEnabled != apuEnabled) { + if(!apuEnabled) { + _square1.Disable(); + _square2.Disable(); + _wave.Disable(); + _noise.Disable(); + Write(0xFF24, 0); + Write(0xFF25, 0); + } else { + //When powered on, the frame sequencer is reset so that the next step will be 0, + //the square duty units are reset to the first step of the waveform, and the wave channel's sample buffer is reset to 0. + _state.FrameSequenceStep = 0; + } + _state.ApuEnabled = apuEnabled; + } + break; + } + case 0xFF30: case 0xFF31: case 0xFF32: case 0xFF33: case 0xFF34: case 0xFF35: case 0xFF36: case 0xFF37: + case 0xFF38: case 0xFF39: case 0xFF3A: case 0xFF3B: case 0xFF3C: case 0xFF3D: case 0xFF3E: case 0xFF3F: + _wave.WriteRam(addr, value); + break; + } +} + +void GbApu::Serialize(Serializer& s) +{ + s.Stream( + _state.ApuEnabled, _state.FrameSequenceStep, + _state.EnableLeftSq1, _state.EnableLeftSq2, _state.EnableLeftWave, _state.EnableLeftNoise, + _state.EnableRightSq1, _state.EnableRightSq2, _state.EnableRightWave, _state.EnableRightNoise, + _state.LeftVolume, _state.RightVolume, _state.ExtAudioLeftEnabled, _state.ExtAudioRightEnabled, + _prevLeftOutput, _prevRightOutput, _clockCounter, _prevClockCount + ); + + s.Stream(&_square1); + s.Stream(&_square2); + s.Stream(&_wave); + s.Stream(&_noise); +} diff --git a/Core/GbApu.h b/Core/GbApu.h new file mode 100644 index 0000000..5f931e6 --- /dev/null +++ b/Core/GbApu.h @@ -0,0 +1,53 @@ +#pragma once +#include "stdafx.h" +#include "GbSquareChannel.h" +#include "GbWaveChannel.h" +#include "GbNoiseChannel.h" +#include "../Utilities/blip_buf.h" +#include "../Utilities/ISerializable.h" + +class Console; +class Gameboy; +class SoundMixer; + +class GbApu : public ISerializable +{ +private: + static constexpr int ApuFrequency = 1024 * 1024 * 4; //4mhz + static constexpr int MaxSamples = 4000; + static constexpr int SampleRate = 96000; + Console* _console = nullptr; + Gameboy* _gameboy = nullptr; + SoundMixer* _soundMixer = nullptr; + + GbSquareChannel _square1; + GbSquareChannel _square2; + GbWaveChannel _wave; + GbNoiseChannel _noise; + + int16_t* _soundBuffer = nullptr; + blip_t* _leftChannel = nullptr; + blip_t* _rightChannel = nullptr; + + int16_t _prevLeftOutput = 0; + int16_t _prevRightOutput = 0; + uint32_t _clockCounter = 0; + uint64_t _prevClockCount = 0; + + GbApuState _state = {}; + +public: + GbApu(Console* console, Gameboy* gameboy); + virtual ~GbApu(); + + GbApuDebugState GetState(); + + void Run(); + + void ClockFrameSequencer(); + + uint8_t Read(uint16_t addr); + void Write(uint16_t addr, uint8_t value); + + void Serialize(Serializer& s) override; +}; \ No newline at end of file diff --git a/Core/GbCart.h b/Core/GbCart.h new file mode 100644 index 0000000..e16d73d --- /dev/null +++ b/Core/GbCart.h @@ -0,0 +1,57 @@ +#pragma once +#include "stdafx.h" +#include "Gameboy.h" +#include "GbMemoryManager.h" +#include "../Utilities/ISerializable.h" + +class GbCart : public ISerializable +{ +protected: + Gameboy* _gameboy = nullptr; + GbMemoryManager* _memoryManager = nullptr; + uint8_t* _cartRam = nullptr; + + void Map(uint16_t start, uint16_t end, GbMemoryType type, uint32_t offset, bool readonly) + { + _memoryManager->Map(start, end, type, offset, readonly); + } + + void Unmap(uint16_t start, uint16_t end) + { + _memoryManager->Unmap(start, end); + } + +public: + virtual ~GbCart() + { + } + + void Init(Gameboy* gameboy, GbMemoryManager* memoryManager) + { + _gameboy = gameboy; + _memoryManager = memoryManager; + _cartRam = gameboy->DebugGetMemory(SnesMemoryType::GbCartRam); + } + + virtual void InitCart() + { + } + + virtual void RefreshMappings() + { + Map(0x0000, 0x7FFF, GbMemoryType::PrgRom, 0, true); + } + + virtual uint8_t ReadRegister(uint16_t addr) + { + return 0; + } + + virtual void WriteRegister(uint16_t addr, uint8_t value) + { + } + + void Serialize(Serializer& s) override + { + } +}; diff --git a/Core/GbCartFactory.h b/Core/GbCartFactory.h new file mode 100644 index 0000000..0eeeb86 --- /dev/null +++ b/Core/GbCartFactory.h @@ -0,0 +1,36 @@ +#pragma once +#include "stdafx.h" +#include "GbCart.h" +#include "GbMbc1.h" +#include "GbMbc2.h" +#include "GbMbc3.h" +#include "GbMbc5.h" + +class GbCartFactory +{ +public: + static GbCart* CreateCart(uint8_t cartType) + { + switch(cartType) { + case 0: + return new GbCart(); + + case 1: case 2: case 3: + return new GbMbc1(); + + case 5: case 6: + return new GbMbc2(); + + case 15: case 16: + return new GbMbc3(true); + + case 17: case 18: case 19: + return new GbMbc3(false); + + case 25: case 26: case 27: case 28: case 29: case 30: + return new GbMbc5(); + }; + + return nullptr; + } +}; diff --git a/Core/GbCpu.cpp b/Core/GbCpu.cpp new file mode 100644 index 0000000..a6ca207 --- /dev/null +++ b/Core/GbCpu.cpp @@ -0,0 +1,1352 @@ +#include "stdafx.h" +#include "GbCpu.h" +#include "GbMemoryManager.h" +#include "../Utilities/Serializer.h" + +GbCpu::GbCpu(GbMemoryManager* memoryManager) +{ + _memoryManager = memoryManager; + _state = {}; + +#ifdef USEBOOTROM + _state.PC = 0; + _state.SP = 0xFFFF; +#else + _state.PC = 0x100; + _state.SP = 0xFFFE; + _state.A = 0x01; + _state.B = 0x00; + _state.C = 0x13; + _state.D = 0x00; + _state.E = 0xD8; + _state.H = 0x01; + _state.L = 0x4D; + _state.Flags = 0xB0; +#endif +} + +GbCpu::~GbCpu() +{ +} + +GbCpuState GbCpu::GetState() +{ + return _state; +} + +uint64_t GbCpu::GetCycleCount() +{ + return _state.CycleCount; +} + +void GbCpu::Exec() +{ + uint8_t irqVector = _memoryManager->ProcessIrqRequests(); + if(irqVector) { + if(_state.IME) { + _memoryManager->ClearIrqRequest(irqVector); + IncCycleCount(); + IncCycleCount(); + PushWord(_state.PC); + IncCycleCount(); + switch(irqVector) { + case GbIrqSource::VerticalBlank: _state.PC = 0x40; break; + case GbIrqSource::LcdStat: _state.PC = 0x48; break; + case GbIrqSource::Timer: _state.PC = 0x50; break; + case GbIrqSource::Serial: _state.PC = 0x58; break; + case GbIrqSource::Joypad: _state.PC = 0x60; break; + } + _state.IME = false; + } + _state.Halted = false; + } + + if(_state.Halted) { + IncCycleCount(); + return; + } + + ExecOpCode(ReadOpCode()); +} + +void GbCpu::ExecOpCode(uint8_t opCode) +{ + switch(opCode) { + case 0x00: NOP(); break; + case 0x01: LD(_regBC, ReadCodeWord()); break; + case 0x02: LD_Indirect(_regBC, _state.A); break; + case 0x03: INC(_regBC); break; + case 0x04: INC(_state.B); break; + case 0x05: DEC(_state.B); break; + case 0x06: LD(_state.B, ReadCode()); break; + case 0x07: RLCA(); break; + case 0x08: LD_Indirect16(ReadCodeWord(), _state.SP); break; + case 0x09: ADD(_regHL, _regBC); break; + case 0x0A: LD(_state.A, Read(_regBC)); break; + case 0x0B: DEC(_regBC); break; + case 0x0C: INC(_state.C); break; + case 0x0D: DEC(_state.C); break; + case 0x0E: LD(_state.C, ReadCode()); break; + case 0x0F: RRCA(); break; + case 0x10: STOP(); break; + case 0x11: LD(_regDE, ReadCodeWord()); break; + case 0x12: LD_Indirect(_regDE, _state.A); break; + case 0x13: INC(_regDE); break; + case 0x14: INC(_state.D); break; + case 0x15: DEC(_state.D); break; + case 0x16: LD(_state.D, ReadCode()); break; + case 0x17: RLA(); break; + case 0x18: JR(ReadCode()); break; + case 0x19: ADD(_regHL, _regDE); break; + case 0x1A: LD(_state.A, Read(_regDE)); break; + case 0x1B: DEC(_regDE); break; + case 0x1C: INC(_state.E); break; + case 0x1D: DEC(_state.E); break; + case 0x1E: LD(_state.E, ReadCode()); break; + case 0x1F: RRA(); break; + case 0x20: JR((_state.Flags & GbCpuFlags::Zero) == 0, ReadCode()); break; + case 0x21: LD(_regHL, ReadCodeWord()); break; + case 0x22: LD_Indirect(_regHL, _state.A); _regHL.Inc(); break; + case 0x23: INC(_regHL); break; + case 0x24: INC(_state.H); break; + case 0x25: DEC(_state.H); break; + case 0x26: LD(_state.H, ReadCode()); break; + case 0x27: DAA(); break; + case 0x28: JR((_state.Flags & GbCpuFlags::Zero) != 0, ReadCode()); break; + case 0x29: ADD(_regHL, _regHL); break; + case 0x2A: LD(_state.A, Read(_regHL)); _regHL.Inc(); break; + case 0x2B: DEC(_regHL); break; + case 0x2C: INC(_state.L); break; + case 0x2D: DEC(_state.L); break; + case 0x2E: LD(_state.L, ReadCode()); break; + case 0x2F: CPL(); break; + case 0x30: JR((_state.Flags & GbCpuFlags::Carry) == 0, ReadCode()); break; + case 0x31: LD(_state.SP, ReadCodeWord()); break; + case 0x32: LD_Indirect(_regHL, _state.A); _regHL.Dec(); break; + case 0x33: INC_SP(); break; + case 0x34: INC_Indirect(_regHL); break; + case 0x35: DEC_Indirect(_regHL); break; + case 0x36: LD_Indirect(_regHL, ReadCode()); break; + case 0x37: SCF(); break; + case 0x38: JR((_state.Flags & GbCpuFlags::Carry) != 0, ReadCode()); break; + case 0x39: ADD(_regHL, _state.SP); break; + case 0x3A: LD(_state.A, Read(_regHL)); _regHL.Dec(); break; + case 0x3B: DEC_SP(); break; + case 0x3C: INC(_state.A); break; + case 0x3D: DEC(_state.A); break; + case 0x3E: LD(_state.A, ReadCode()); break; + case 0x3F: CCF(); break; + case 0x40: LD(_state.B, _state.B); break; + case 0x41: LD(_state.B, _state.C); break; + case 0x42: LD(_state.B, _state.D); break; + case 0x43: LD(_state.B, _state.E); break; + case 0x44: LD(_state.B, _state.H); break; + case 0x45: LD(_state.B, _state.L); break; + case 0x46: LD(_state.B, Read(_regHL)); break; + case 0x47: LD(_state.B, _state.A); break; + case 0x48: LD(_state.C, _state.B); break; + case 0x49: LD(_state.C, _state.C); break; + case 0x4A: LD(_state.C, _state.D); break; + case 0x4B: LD(_state.C, _state.E); break; + case 0x4C: LD(_state.C, _state.H); break; + case 0x4D: LD(_state.C, _state.L); break; + case 0x4E: LD(_state.C, Read(_regHL)); break; + case 0x4F: LD(_state.C, _state.A); break; + case 0x50: LD(_state.D, _state.B); break; + case 0x51: LD(_state.D, _state.C); break; + case 0x52: LD(_state.D, _state.D); break; + case 0x53: LD(_state.D, _state.E); break; + case 0x54: LD(_state.D, _state.H); break; + case 0x55: LD(_state.D, _state.L); break; + case 0x56: LD(_state.D, Read(_regHL)); break; + case 0x57: LD(_state.D, _state.A); break; + case 0x58: LD(_state.E, _state.B); break; + case 0x59: LD(_state.E, _state.C); break; + case 0x5A: LD(_state.E, _state.D); break; + case 0x5B: LD(_state.E, _state.E); break; + case 0x5C: LD(_state.E, _state.H); break; + case 0x5D: LD(_state.E, _state.L); break; + case 0x5E: LD(_state.E, Read(_regHL)); break; + case 0x5F: LD(_state.E, _state.A); break; + case 0x60: LD(_state.H, _state.B); break; + case 0x61: LD(_state.H, _state.C); break; + case 0x62: LD(_state.H, _state.D); break; + case 0x63: LD(_state.H, _state.E); break; + case 0x64: LD(_state.H, _state.H); break; + case 0x65: LD(_state.H, _state.L); break; + case 0x66: LD(_state.H, Read(_regHL)); break; + case 0x67: LD(_state.H, _state.A); break; + case 0x68: LD(_state.L, _state.B); break; + case 0x69: LD(_state.L, _state.C); break; + case 0x6A: LD(_state.L, _state.D); break; + case 0x6B: LD(_state.L, _state.E); break; + case 0x6C: LD(_state.L, _state.H); break; + case 0x6D: LD(_state.L, _state.L); break; + case 0x6E: LD(_state.L, Read(_regHL)); break; + case 0x6F: LD(_state.L, _state.A); break; + case 0x70: LD_Indirect(_regHL, _state.B); break; + case 0x71: LD_Indirect(_regHL, _state.C); break; + case 0x72: LD_Indirect(_regHL, _state.D); break; + case 0x73: LD_Indirect(_regHL, _state.E); break; + case 0x74: LD_Indirect(_regHL, _state.H); break; + case 0x75: LD_Indirect(_regHL, _state.L); break; + case 0x76: HALT(); break; + case 0x77: LD_Indirect(_regHL, _state.A); break; + case 0x78: LD(_state.A, _state.B); break; + case 0x79: LD(_state.A, _state.C); break; + case 0x7A: LD(_state.A, _state.D); break; + case 0x7B: LD(_state.A, _state.E); break; + case 0x7C: LD(_state.A, _state.H); break; + case 0x7D: LD(_state.A, _state.L); break; + case 0x7E: LD(_state.A, Read(_regHL)); break; + case 0x7F: LD(_state.A, _state.A); break; + case 0x80: ADD(_state.B); break; + case 0x81: ADD(_state.C); break; + case 0x82: ADD(_state.D); break; + case 0x83: ADD(_state.E); break; + case 0x84: ADD(_state.H); break; + case 0x85: ADD(_state.L); break; + case 0x86: ADD(Read(_regHL)); break; + case 0x87: ADD(_state.A); break; + case 0x88: ADC(_state.B); break; + case 0x89: ADC(_state.C); break; + case 0x8A: ADC(_state.D); break; + case 0x8B: ADC(_state.E); break; + case 0x8C: ADC(_state.H); break; + case 0x8D: ADC(_state.L); break; + case 0x8E: ADC(Read(_regHL)); break; + case 0x8F: ADC(_state.A); break; + case 0x90: SUB(_state.B); break; + case 0x91: SUB(_state.C); break; + case 0x92: SUB(_state.D); break; + case 0x93: SUB(_state.E); break; + case 0x94: SUB(_state.H); break; + case 0x95: SUB(_state.L); break; + case 0x96: SUB(Read(_regHL)); break; + case 0x97: SUB(_state.A); break; + case 0x98: SBC(_state.B); break; + case 0x99: SBC(_state.C); break; + case 0x9A: SBC(_state.D); break; + case 0x9B: SBC(_state.E); break; + case 0x9C: SBC(_state.H); break; + case 0x9D: SBC(_state.L); break; + case 0x9E: SBC(Read(_regHL)); break; + case 0x9F: SBC(_state.A); break; + case 0xA0: AND(_state.B); break; + case 0xA1: AND(_state.C); break; + case 0xA2: AND(_state.D); break; + case 0xA3: AND(_state.E); break; + case 0xA4: AND(_state.H); break; + case 0xA5: AND(_state.L); break; + case 0xA6: AND(Read(_regHL)); break; + case 0xA7: AND(_state.A); break; + case 0xA8: XOR(_state.B); break; + case 0xA9: XOR(_state.C); break; + case 0xAA: XOR(_state.D); break; + case 0xAB: XOR(_state.E); break; + case 0xAC: XOR(_state.H); break; + case 0xAD: XOR(_state.L); break; + case 0xAE: XOR(Read(_regHL)); break; + case 0xAF: XOR(_state.A); break; + case 0xB0: OR(_state.B); break; + case 0xB1: OR(_state.C); break; + case 0xB2: OR(_state.D); break; + case 0xB3: OR(_state.E); break; + case 0xB4: OR(_state.H); break; + case 0xB5: OR(_state.L); break; + case 0xB6: OR(Read(_regHL)); break; + case 0xB7: OR(_state.A); break; + case 0xB8: CP(_state.B); break; + case 0xB9: CP(_state.C); break; + case 0xBA: CP(_state.D); break; + case 0xBB: CP(_state.E); break; + case 0xBC: CP(_state.H); break; + case 0xBD: CP(_state.L); break; + case 0xBE: CP(Read(_regHL)); break; + case 0xBF: CP(_state.A); break; + case 0xC0: RET((_state.Flags & GbCpuFlags::Zero) == 0); break; + case 0xC1: POP(_regBC); break; + case 0xC2: JP((_state.Flags & GbCpuFlags::Zero) == 0, ReadCodeWord()); break; + case 0xC3: JP(ReadCodeWord()); break; + case 0xC4: CALL((_state.Flags & GbCpuFlags::Zero) == 0, ReadCodeWord()); break; + case 0xC5: PUSH(_regBC); break; + case 0xC6: ADD(ReadCode()); break; + case 0xC7: RST(0x00); break; + case 0xC8: RET((_state.Flags & GbCpuFlags::Zero) != 0); break; + case 0xC9: RET(); break; + case 0xCA: JP((_state.Flags & GbCpuFlags::Zero) != 0, ReadCodeWord()); break; + case 0xCB: PREFIX(); break; + case 0xCC: CALL((_state.Flags & GbCpuFlags::Zero) != 0, ReadCodeWord()); break; + case 0xCD: CALL(ReadCodeWord()); break; + case 0xCE: ADC(ReadCode()); break; + case 0xCF: RST(0x08); break; + case 0xD0: RET((_state.Flags & GbCpuFlags::Carry) == 0); break; + case 0xD1: POP(_regDE); break; + case 0xD2: JP((_state.Flags & GbCpuFlags::Carry) == 0, ReadCodeWord()); break; + case 0xD3: InvalidOp(); break; + case 0xD4: CALL((_state.Flags & GbCpuFlags::Carry) == 0, ReadCodeWord()); break; + case 0xD5: PUSH(_regDE); break; + case 0xD6: SUB(ReadCode()); break; + case 0xD7: RST(0x10); break; + case 0xD8: RET((_state.Flags & GbCpuFlags::Carry) != 0); break; + case 0xD9: RETI(); break; + case 0xDA: JP((_state.Flags & GbCpuFlags::Carry) != 0, ReadCodeWord()); break; + case 0xDB: InvalidOp(); break; + case 0xDC: CALL((_state.Flags & GbCpuFlags::Carry) != 0, ReadCodeWord()); break; + case 0xDD: InvalidOp(); break; + case 0xDE: SBC(ReadCode()); break; + case 0xDF: RST(0x18); break; + case 0xE0: LD_Indirect(0xFF00 | ReadCode(), _state.A); break; + case 0xE1: POP(_regHL); break; + case 0xE2: LD_Indirect(0xFF00 | _state.C, _state.A); break; + case 0xE3: InvalidOp(); break; + case 0xE4: InvalidOp(); break; + case 0xE5: PUSH(_regHL); break; + case 0xE6: AND(ReadCode()); break; + case 0xE7: RST(0x20); break; + case 0xE8: ADD_SP(ReadCode()); break; + case 0xE9: JP_HL(); break; + case 0xEA: LD_Indirect(ReadCodeWord(), _state.A); break; + case 0xEB: InvalidOp(); break; + case 0xEC: InvalidOp(); break; + case 0xED: InvalidOp(); break; + case 0xEE: XOR(ReadCode()); break; + case 0xEF: RST(0x28); break; + case 0xF0: LD(_state.A, Read(0xFF00 | ReadCode())); break; + case 0xF1: POP_AF(); break; + case 0xF2: LD(_state.A, Read(0xFF00 | _state.C)); break; + case 0xF3: DI(); break; + case 0xF4: InvalidOp(); break; + case 0xF5: PUSH(_regAF); break; + case 0xF6: OR(ReadCode()); break; + case 0xF7: RST(0x30); break; + case 0xF8: LD_HL(ReadCode()); break; + case 0xF9: LD(_state.SP, _regHL); IncCycleCount(); break; + case 0xFA: LD(_state.A, Read(ReadCodeWord())); break; + case 0xFB: EI(); break; + case 0xFC: InvalidOp(); break; + case 0xFD: InvalidOp(); break; + case 0xFE: CP(ReadCode()); break; + case 0xFF: RST(0x38); break; + } +} + +void GbCpu::IncCycleCount() +{ + _state.CycleCount += 4; + _memoryManager->Exec(); +} + +uint8_t GbCpu::ReadOpCode() +{ + IncCycleCount(); + uint8_t value = _memoryManager->Read(_state.PC, MemoryOperationType::ExecOpCode); + _state.PC++; + return value; +} + +uint8_t GbCpu::ReadCode() +{ + IncCycleCount(); + uint8_t value = _memoryManager->Read(_state.PC, MemoryOperationType::ExecOperand); + _state.PC++; + return value; +} + +uint16_t GbCpu::ReadCodeWord() +{ + uint8_t low = ReadCode(); + uint8_t high = ReadCode(); + return (high << 8) | low; +} + +uint8_t GbCpu::Read(uint16_t addr) +{ + IncCycleCount(); + return _memoryManager->Read(addr, MemoryOperationType::Read); +} + +void GbCpu::Write(uint16_t addr, uint8_t value) +{ + IncCycleCount(); + _memoryManager->Write(addr, value); +} + +bool GbCpu::CheckFlag(uint8_t flag) +{ + return (_state.Flags & flag) != 0; +} + +void GbCpu::SetFlag(uint8_t flag) +{ + _state.Flags |= flag; +} + +void GbCpu::ClearFlag(uint8_t flag) +{ + _state.Flags &= ~flag; +} + +void GbCpu::SetFlagState(uint8_t flag, bool state) +{ + if(state) { + SetFlag(flag); + } else { + ClearFlag(flag); + } +} + +void GbCpu::PushByte(uint8_t value) +{ + _state.SP--; + Write(_state.SP, value); +} + +void GbCpu::PushWord(uint16_t value) +{ + PushByte(value >> 8); + PushByte((uint8_t)value); +} + +uint8_t GbCpu::PopByte() +{ + uint8_t val = Read(_state.SP); + _state.SP++; + return val; +} + +uint16_t GbCpu::PopWord() +{ + uint8_t low = PopByte(); + uint8_t high = PopByte(); + return (high << 8) | low; +} + +void GbCpu::LD(uint8_t& dst, uint8_t value) +{ + dst = value; +} + +void GbCpu::LD(uint16_t& dst, uint16_t value) +{ + dst = value; +} + +void GbCpu::LD(Register16& dst, uint16_t value) +{ + dst.Write(value); +} + +void GbCpu::LD_Indirect(uint16_t dst, uint8_t value) +{ + Write(dst, value); +} + +void GbCpu::LD_Indirect16(uint16_t dst, uint16_t value) +{ + Write(dst, (uint8_t)value); + Write(dst + 1, value >> 8); +} + +//ld HL,SP+dd F8 12 00hc HL = SP +/- dd ;dd is 8bit signed number +void GbCpu::LD_HL(int8_t value) +{ + int result = (uint8_t)_state.SP + (uint8_t)value; + + //"Both of these set carry and half-carry based on the low byte of SP added to the UNSIGNED immediate byte. The Negative and Zero flags are always cleared." + SetFlagState(GbCpuFlags::HalfCarry, (((uint8_t)_state.SP ^ result ^ value) & 0x10) != 0); + SetFlagState(GbCpuFlags::Carry, result > 0xFF); + + _regHL.Write(_state.SP + value); + + ClearFlag(GbCpuFlags::AddSub); + ClearFlag(GbCpuFlags::Zero); + + IncCycleCount(); +} + +// inc r xx 4 z0h- r=r+1 +void GbCpu::INC(uint8_t& dst) +{ + SetFlagState(GbCpuFlags::HalfCarry, ((dst ^ 1 ^ (dst + 1)) & 0x10) != 0); + dst++; + + SetFlagState(GbCpuFlags::Zero, dst == 0); + ClearFlag(GbCpuFlags::AddSub); +} + +// inc rr x3 8 ---- rr = rr+1 ;rr may be BC,DE,HL,SP +void GbCpu::INC(Register16& dst) +{ + //16-bit inc does not alter flags + IncCycleCount(); + dst.Write(dst.Read() + 1); +} + +// inc rr x3 8 ---- rr = rr+1 ;rr may be BC,DE,HL,SP +void GbCpu::INC_SP() +{ + IncCycleCount(); + _state.SP++; +} + +// inc (HL) 34 12 z0h- (HL)=(HL)+1 +void GbCpu::INC_Indirect(uint16_t addr) +{ + uint8_t val = Read(addr); + INC(val); + Write(addr, val); +} + +// dec r xx 4 z1h- r=r-1 +void GbCpu::DEC(uint8_t& dst) +{ + SetFlagState(GbCpuFlags::HalfCarry, (dst & 0x0F) == 0x00); + dst--; + + SetFlagState(GbCpuFlags::Zero, dst == 0); + SetFlag(GbCpuFlags::AddSub); +} + +// dec rr xB 8 ---- rr = rr-1 ;rr may be BC,DE,HL,SP +void GbCpu::DEC(Register16& dst) +{ + //16-bit dec does not alter flags + IncCycleCount(); + dst.Write(dst.Read() - 1); +} + +// dec (HL) 35 12 z1h- (HL)=(HL)-1 +void GbCpu::DEC_Indirect(uint16_t addr) +{ + uint8_t val = Read(addr); + DEC(val); + Write(addr, val); +} + +// dec rr xB 8 ---- rr = rr-1 ;rr may be BC,DE,HL,SP +void GbCpu::DEC_SP() +{ + _state.SP--; + IncCycleCount(); +} + +// add A,r 8x 4 z0hc A=A+r +// add A,n C6 nn 8 z0hc A=A+n +// add A,(HL) 86 8 z0hc A=A+(HL) +void GbCpu::ADD(uint8_t value) +{ + int result = _state.A + value; + + //SetFlagState(GbCpuFlags::HalfCarry, (_state.A & 0xF) + (value & 0xF) > 0xF); + SetFlagState(GbCpuFlags::HalfCarry, ((_state.A ^ value ^ result) & 0x10) != 0); + + _state.A = (uint8_t)result; + + SetFlagState(GbCpuFlags::Carry, result > 0xFF); + SetFlagState(GbCpuFlags::Zero, _state.A == 0); + ClearFlag(GbCpuFlags::AddSub); +} + +//add SP,dd E8 16 00hc SP = SP +/- dd ;dd is 8bit signed number +void GbCpu::ADD_SP(int8_t value) +{ + int result = (uint8_t)_state.SP + (uint8_t)value; + + //"Both of these set carry and half-carry based on the low byte of SP added to the UNSIGNED immediate byte. The Negative and Zero flags are always cleared." + SetFlagState(GbCpuFlags::HalfCarry, (((uint8_t)_state.SP ^ result ^ value) & 0x10) != 0); + SetFlagState(GbCpuFlags::Carry, result > 0xFF); + + _state.SP += value; + + ClearFlag(GbCpuFlags::AddSub); + ClearFlag(GbCpuFlags::Zero); + + IncCycleCount(); + IncCycleCount(); +} + +// add HL,rr x9 8 -0hc HL = HL+rr ;rr may be BC,DE,HL,SP +void GbCpu::ADD(Register16& reg, uint16_t value) +{ + int result = reg.Read() + value; + + SetFlagState(GbCpuFlags::HalfCarry, ((reg.Read() ^ value ^ result) & 0x1000) != 0); + + reg.Write((uint16_t)result); + + SetFlagState(GbCpuFlags::Carry, result > 0xFFFF); + ClearFlag(GbCpuFlags::AddSub); + IncCycleCount(); +} + +// adc A,r 8x 4 z0hc A=A+r+cy +// adc A,n CE nn 8 z0hc A=A+n+cy +// adc A,(HL) 8E 8 z0hc A=A+(HL)+cy +void GbCpu::ADC(uint8_t value) +{ + uint8_t carry = ((_state.Flags & GbCpuFlags::Carry) >> 4); + int result = _state.A + value + carry; + + SetFlagState(GbCpuFlags::HalfCarry, (_state.A & 0x0F) + (value & 0x0F) + carry > 0x0F); + + _state.A = (uint8_t)result; + + SetFlagState(GbCpuFlags::Carry, result > 0xFF); + SetFlagState(GbCpuFlags::Zero, _state.A == 0); + ClearFlag(GbCpuFlags::AddSub); +} + +//sub r 9x 4 z1hc A = A - r +//sub n D6 nn 8 z1hc A = A - n +//sub(HL) 96 8 z1hc A = A - (HL) +void GbCpu::SUB(uint8_t value) +{ + int result = (int)_state.A - value; + + SetFlagState(GbCpuFlags::HalfCarry, (_state.A & 0x0F) < (value & 0x0F)); + + _state.A = (uint8_t)result; + + SetFlagState(GbCpuFlags::Carry, result < 0); + SetFlagState(GbCpuFlags::Zero, _state.A == 0); + SetFlag(GbCpuFlags::AddSub); +} + +// sbc A,r 9x 4 z1hc A=A-r-cy +// sbc A,n DE nn 8 z1hc A=A-n-cy +// sbc A,(HL) 9E 8 z1hc A=A-(HL)-cy +void GbCpu::SBC(uint8_t value) +{ + uint8_t carry = ((_state.Flags & GbCpuFlags::Carry) >> 4); + int result = (int)_state.A - value - carry; + + SetFlagState(GbCpuFlags::HalfCarry, (_state.A & 0x0F) < (value & 0x0F) + carry); + + _state.A = (uint8_t)result; + + SetFlagState(GbCpuFlags::Carry, result < 0); + SetFlagState(GbCpuFlags::Zero, _state.A == 0); + SetFlag(GbCpuFlags::AddSub); +} + +// and r Ax 4 z010 A = A & r +// and n E6 nn 8 z010 A = A & n +// and (HL) A6 8 z010 A = A & (HL) +void GbCpu::AND(uint8_t value) +{ + _state.A &= value; + SetFlagState(GbCpuFlags::Zero, _state.A == 0); + ClearFlag(GbCpuFlags::AddSub); + ClearFlag(GbCpuFlags::Carry); + SetFlag(GbCpuFlags::HalfCarry); +} + +//or r Bx 4 z000 A = A | r +//or n F6 nn 8 z000 A = A | n +//or (HL) B6 8 z000 A = A | (HL) +void GbCpu::OR(uint8_t value) +{ + _state.A |= value; + SetFlagState(GbCpuFlags::Zero, _state.A == 0); + ClearFlag(GbCpuFlags::AddSub); + ClearFlag(GbCpuFlags::Carry); + ClearFlag(GbCpuFlags::HalfCarry); +} + +// xor r Ax 4 z000 +// xor n EE nn 8 z000 +// xor (HL) AE 8 z000 +void GbCpu::XOR(uint8_t value) +{ + _state.A ^= value; + SetFlagState(GbCpuFlags::Zero, _state.A == 0); + ClearFlag(GbCpuFlags::AddSub); + ClearFlag(GbCpuFlags::Carry); + ClearFlag(GbCpuFlags::HalfCarry); +} + +// cp r Bx 4 z1hc compare A-r +// cp n FE nn 8 z1hc compare A-n +// cp (HL) BE 8 z1hc compare A-(HL) +void GbCpu::CP(uint8_t value) +{ + int result = (int)_state.A - value; + + SetFlagState(GbCpuFlags::Carry, result < 0); + SetFlagState(GbCpuFlags::HalfCarry, ((_state.A ^ value ^ result) & 0x10) != 0); + SetFlagState(GbCpuFlags::Zero, (uint8_t)result == 0); + SetFlag(GbCpuFlags::AddSub); +} + +void GbCpu::NOP() +{ + +} + +void GbCpu::InvalidOp() +{ + STOP(); +} + +void GbCpu::STOP() +{ + +} + +void GbCpu::HALT() +{ + //TODO: HALT BUG emulation + _state.Halted = true; +} + +// cpl 2F 4 -11- A = A xor FF +void GbCpu::CPL() +{ + _state.A ^= 0xFF; + SetFlag(GbCpuFlags::AddSub); + SetFlag(GbCpuFlags::HalfCarry); +} + +//rl r CB 1x 8 z00c rotate left through carry +void GbCpu::RL(uint8_t& dst) +{ + uint8_t carry = (uint8_t)CheckFlag(GbCpuFlags::Carry); + SetFlagState(GbCpuFlags::Carry, (dst & 0x80) != 0); + dst = (dst << 1) | carry; + + SetFlagState(GbCpuFlags::Zero, dst == 0); + ClearFlag(GbCpuFlags::AddSub); + ClearFlag(GbCpuFlags::HalfCarry); +} + +//rl (HL) CB 16 16 z00c rotate left through carry +void GbCpu::RL_Indirect(uint16_t addr) +{ + uint8_t val = Read(addr); + RL(val); + Write(addr, val); +} + +//rlc r CB 0x 8 z00c rotate left +void GbCpu::RLC(uint8_t& dst) +{ + SetFlagState(GbCpuFlags::Carry, (dst & 0x80) != 0); + dst = (dst << 1) | ((dst & 0x80) >> 7); + + SetFlagState(GbCpuFlags::Zero, dst == 0); + ClearFlag(GbCpuFlags::AddSub); + ClearFlag(GbCpuFlags::HalfCarry); +} + +//rlc (HL) CB 06 16 z00c rotate left +void GbCpu::RLC_Indirect(uint16_t addr) +{ + uint8_t val = Read(addr); + RLC(val); + Write(addr, val); +} + +//rr r CB 1x 8 z00c rotate right through carry +void GbCpu::RR(uint8_t& dst) +{ + uint8_t carry = (uint8_t)CheckFlag(GbCpuFlags::Carry) << 7; + SetFlagState(GbCpuFlags::Carry, (dst & 0x01) != 0); + dst = (dst >> 1) | carry; + + SetFlagState(GbCpuFlags::Zero, dst == 0); + ClearFlag(GbCpuFlags::AddSub); + ClearFlag(GbCpuFlags::HalfCarry); +} + +//rr (HL) CB 1E 16 z00c rotate right through carry +void GbCpu::RR_Indirect(uint16_t addr) +{ + uint8_t val = Read(addr); + RR(val); + Write(addr, val); +} + +//rrc r CB 0x 8 z00c rotate right +void GbCpu::RRC(uint8_t& dst) +{ + SetFlagState(GbCpuFlags::Carry, (dst & 0x01) != 0); + dst = (dst >> 1) | ((dst & 0x01) << 7); + + SetFlagState(GbCpuFlags::Zero, dst == 0); + ClearFlag(GbCpuFlags::AddSub); + ClearFlag(GbCpuFlags::HalfCarry); +} + +//rrc (HL) CB 0E 16 z00c rotate right +void GbCpu::RRC_Indirect(uint16_t addr) +{ + uint8_t val = Read(addr); + RRC(val); + Write(addr, val); +} + +//rra 1F 4 000c rotate akku right through carry +void GbCpu::RRA() +{ + RR(_state.A); + ClearFlag(GbCpuFlags::Zero); +} + +//rrca 0F 4 000c rotate akku right +void GbCpu::RRCA() +{ + RRC(_state.A); + ClearFlag(GbCpuFlags::Zero); +} + +//rlca 07 4 000c rotate akku left +void GbCpu::RLCA() +{ + RLC(_state.A); + ClearFlag(GbCpuFlags::Zero); +} + +//rla 17 4 000c rotate akku left through carry +void GbCpu::RLA() +{ + RL(_state.A); + ClearFlag(GbCpuFlags::Zero); +} + +//srl r CB 3x 8 z00c shift right logical (b7=0) +void GbCpu::SRL(uint8_t& dst) +{ + SetFlagState(GbCpuFlags::Carry, (dst & 0x01) != 0); + dst = (dst >> 1); + + SetFlagState(GbCpuFlags::Zero, dst == 0); + ClearFlag(GbCpuFlags::AddSub); + ClearFlag(GbCpuFlags::HalfCarry); +} + +//srl (HL) CB 3E 16 z00c shift right logical (b7=0) +void GbCpu::SRL_Indirect(uint16_t addr) +{ + uint8_t val = Read(addr); + SRL(val); + Write(addr, val); +} + +//sra r CB 2x 8 z00c shift right arithmetic (b7=b7) +void GbCpu::SRA(uint8_t& dst) +{ + SetFlagState(GbCpuFlags::Carry, (dst & 0x01) != 0); + dst = (dst & 0x80) | (dst >> 1); + + SetFlagState(GbCpuFlags::Zero, dst == 0); + ClearFlag(GbCpuFlags::AddSub); + ClearFlag(GbCpuFlags::HalfCarry); +} + +//sra (HL) CB 2E 16 z00c shift right arithmetic (b7=b7) +void GbCpu::SRA_Indirect(uint16_t addr) +{ + uint8_t val = Read(addr); + SRA(val); + Write(addr, val); +} + +//sla r CB 2x 8 z00c shift left arithmetic (b0=0) +void GbCpu::SLA(uint8_t& dst) +{ + SetFlagState(GbCpuFlags::Carry, (dst & 0x80) != 0); + dst = (dst << 1); + + SetFlagState(GbCpuFlags::Zero, dst == 0); + ClearFlag(GbCpuFlags::AddSub); + ClearFlag(GbCpuFlags::HalfCarry); +} + +//sla (HL) CB 26 16 z00c shift left arithmetic (b0=0) +void GbCpu::SLA_Indirect(uint16_t addr) +{ + uint8_t val = Read(addr); + SLA(val); + Write(addr, val); +} + +//swap r CB 3x 8 z000 exchange low/hi-nibble +void GbCpu::SWAP(uint8_t& dst) +{ + dst = ((dst & 0x0F) << 4) | (dst >> 4); + + SetFlagState(GbCpuFlags::Zero, dst == 0); + ClearFlag(GbCpuFlags::AddSub); + ClearFlag(GbCpuFlags::Carry); + ClearFlag(GbCpuFlags::HalfCarry); +} + +//swap (HL) CB 36 16 z000 exchange low/hi-nibble +void GbCpu::SWAP_Indirect(uint16_t addr) +{ + uint8_t val = Read(addr); + SWAP(val); + Write(addr, val); +} + +template +void GbCpu::BIT(uint8_t src) +{ + SetFlagState(GbCpuFlags::Zero, (src & (1 << bit)) == 0); + ClearFlag(GbCpuFlags::AddSub); + SetFlag(GbCpuFlags::HalfCarry); +} + +template +void GbCpu::RES(uint8_t& dst) +{ + dst &= ~(1 << bit); +} + +template +void GbCpu::RES_Indirect(uint16_t addr) +{ + uint8_t val = Read(addr); + RES(val); + Write(addr, val); +} + +template +void GbCpu::SET(uint8_t& dst) +{ + dst |= (1 << bit); +} + +template +void GbCpu::SET_Indirect(uint16_t addr) +{ + uint8_t val = Read(addr); + SET(val); + Write(addr, val); +} + +// daa 27 4 z-0x decimal adjust akku +void GbCpu::DAA() +{ + if(CheckFlag(GbCpuFlags::AddSub)) { + if(CheckFlag(GbCpuFlags::Carry)) { + _state.A -= 0x60; + } + if(CheckFlag(GbCpuFlags::HalfCarry)) { + _state.A -= 0x06; + } + } else { + if(CheckFlag(GbCpuFlags::Carry) || _state.A > 0x99) { + _state.A += 0x60; + SetFlag(GbCpuFlags::Carry); + } + if(CheckFlag(GbCpuFlags::HalfCarry) || (_state.A & 0x0F) > 0x09) { + _state.A += 0x6; + } + } + + SetFlagState(GbCpuFlags::Zero, _state.A == 0); + ClearFlag(GbCpuFlags::HalfCarry); +} + +//jp nn C3 nn nn 16 ---- jump to nn, PC=nn +void GbCpu::JP(uint16_t dstAddr) +{ + _state.PC = dstAddr; + IncCycleCount(); +} + +//jp HL E9 4 ---- jump to HL, PC=HL +void GbCpu::JP_HL() +{ + _state.PC = _regHL; +} + +//jp f,nn xx nn nn 16;12 ---- conditional jump if nz,z,nc,c +void GbCpu::JP(bool condition, uint16_t dstAddr) +{ + if(condition) { + _state.PC = dstAddr; + IncCycleCount(); + } +} + +//jr PC+dd 18 dd 12 ---- relative jump to nn (PC=PC+/-7bit) +void GbCpu::JR(int8_t offset) +{ + _state.PC += offset; + IncCycleCount(); +} + +//jr f,PC+dd xx dd 12;8 ---- conditional relative jump if nz,z,nc,c +void GbCpu::JR(bool condition, int8_t offset) +{ + if(condition) { + _state.PC += offset; + IncCycleCount(); + } +} + +//call nn CD nn nn 24 ---- call to nn, SP=SP-2, (SP)=PC, PC=nn +void GbCpu::CALL(uint16_t dstAddr) +{ + PushWord(_state.PC); + _state.PC = dstAddr; + IncCycleCount(); +} + +//call f,nn xx nn nn 24;12 ---- conditional call if nz,z,nc,c +void GbCpu::CALL(bool condition, uint16_t dstAddr) +{ + if(condition) { + PushWord(_state.PC); + _state.PC = dstAddr; + IncCycleCount(); + } +} + +//ret C9 16 ---- return, PC=(SP), SP=SP+2 +void GbCpu::RET() +{ + _state.PC = PopWord(); + IncCycleCount(); +} + +//ret f xx 20;8 ---- conditional return if nz,z,nc,c +void GbCpu::RET(bool condition) +{ + IncCycleCount(); + if(condition) { + _state.PC = PopWord(); + IncCycleCount(); + } +} + +//reti D9 16 ---- return and enable interrupts (IME=1) +void GbCpu::RETI() +{ + _state.PC = PopWord(); + _state.IME = true; + IncCycleCount(); +} + +//rst n xx 16 ---- call to 00,08,10,18,20,28,30,38 +void GbCpu::RST(uint8_t value) +{ + PushWord(_state.PC); + _state.PC = value; + IncCycleCount(); +} + +void GbCpu::POP(Register16& reg) +{ + reg.Write(PopWord()); +} + +void GbCpu::PUSH(Register16& reg) +{ + PushWord(reg); + IncCycleCount(); +} + +void GbCpu::POP_AF() +{ + _regAF.Write(PopWord() & 0xFFF0); +} + +//scf 37 4 -001 cy=1 +void GbCpu::SCF() +{ + SetFlag(GbCpuFlags::Carry); + ClearFlag(GbCpuFlags::AddSub); + ClearFlag(GbCpuFlags::HalfCarry); +} + +//ccf 3F 4 -00c cy=cy xor 1 +void GbCpu::CCF() +{ + _state.Flags ^= GbCpuFlags::Carry; + ClearFlag(GbCpuFlags::AddSub); + ClearFlag(GbCpuFlags::HalfCarry); +} + +void GbCpu::EI() +{ + _state.IME = true; +} + +void GbCpu::DI() +{ + _state.IME = false; +} + +void GbCpu::PREFIX() +{ + switch(ReadCode()) { + case 0x00: RLC(_state.B); break; + case 0x01: RLC(_state.C); break; + case 0x02: RLC(_state.D); break; + case 0x03: RLC(_state.E); break; + case 0x04: RLC(_state.H); break; + case 0x05: RLC(_state.L); break; + case 0x06: RLC_Indirect(_regHL); break; + case 0x07: RLC(_state.A); break; + case 0x08: RRC(_state.B); break; + case 0x09: RRC(_state.C); break; + case 0x0A: RRC(_state.D); break; + case 0x0B: RRC(_state.E); break; + case 0x0C: RRC(_state.H); break; + case 0x0D: RRC(_state.L); break; + case 0x0E: RRC_Indirect(_regHL); break; + case 0x0F: RRC(_state.A); break; + case 0x10: RL(_state.B); break; + case 0x11: RL(_state.C); break; + case 0x12: RL(_state.D); break; + case 0x13: RL(_state.E); break; + case 0x14: RL(_state.H); break; + case 0x15: RL(_state.L); break; + case 0x16: RL_Indirect(_regHL); break; + case 0x17: RL(_state.A); break; + case 0x18: RR(_state.B); break; + case 0x19: RR(_state.C); break; + case 0x1A: RR(_state.D); break; + case 0x1B: RR(_state.E); break; + case 0x1C: RR(_state.H); break; + case 0x1D: RR(_state.L); break; + case 0x1E: RR_Indirect(_regHL); break; + case 0x1F: RR(_state.A); break; + case 0x20: SLA(_state.B); break; + case 0x21: SLA(_state.C); break; + case 0x22: SLA(_state.D); break; + case 0x23: SLA(_state.E); break; + case 0x24: SLA(_state.H); break; + case 0x25: SLA(_state.L); break; + case 0x26: SLA_Indirect(_regHL); break; + case 0x27: SLA(_state.A); break; + case 0x28: SRA(_state.B); break; + case 0x29: SRA(_state.C); break; + case 0x2A: SRA(_state.D); break; + case 0x2B: SRA(_state.E); break; + case 0x2C: SRA(_state.H); break; + case 0x2D: SRA(_state.L); break; + case 0x2E: SRA_Indirect(_regHL); break; + case 0x2F: SRA(_state.A); break; + case 0x30: SWAP(_state.B); break; + case 0x31: SWAP(_state.C); break; + case 0x32: SWAP(_state.D); break; + case 0x33: SWAP(_state.E); break; + case 0x34: SWAP(_state.H); break; + case 0x35: SWAP(_state.L); break; + case 0x36: SWAP_Indirect(_regHL); break; + case 0x37: SWAP(_state.A); break; + case 0x38: SRL(_state.B); break; + case 0x39: SRL(_state.C); break; + case 0x3A: SRL(_state.D); break; + case 0x3B: SRL(_state.E); break; + case 0x3C: SRL(_state.H); break; + case 0x3D: SRL(_state.L); break; + case 0x3E: SRL_Indirect(_regHL); break; + case 0x3F: SRL(_state.A); break; + case 0x40: BIT<0>(_state.B); break; + case 0x41: BIT<0>(_state.C); break; + case 0x42: BIT<0>(_state.D); break; + case 0x43: BIT<0>(_state.E); break; + case 0x44: BIT<0>(_state.H); break; + case 0x45: BIT<0>(_state.L); break; + case 0x46: BIT<0>(Read(_regHL)); break; + case 0x47: BIT<0>(_state.A); break; + case 0x48: BIT<1>(_state.B); break; + case 0x49: BIT<1>(_state.C); break; + case 0x4A: BIT<1>(_state.D); break; + case 0x4B: BIT<1>(_state.E); break; + case 0x4C: BIT<1>(_state.H); break; + case 0x4D: BIT<1>(_state.L); break; + case 0x4E: BIT<1>(Read(_regHL)); break; + case 0x4F: BIT<1>(_state.A); break; + case 0x50: BIT<2>(_state.B); break; + case 0x51: BIT<2>(_state.C); break; + case 0x52: BIT<2>(_state.D); break; + case 0x53: BIT<2>(_state.E); break; + case 0x54: BIT<2>(_state.H); break; + case 0x55: BIT<2>(_state.L); break; + case 0x56: BIT<2>(Read(_regHL)); break; + case 0x57: BIT<2>(_state.A); break; + case 0x58: BIT<3>(_state.B); break; + case 0x59: BIT<3>(_state.C); break; + case 0x5A: BIT<3>(_state.D); break; + case 0x5B: BIT<3>(_state.E); break; + case 0x5C: BIT<3>(_state.H); break; + case 0x5D: BIT<3>(_state.L); break; + case 0x5E: BIT<3>(Read(_regHL)); break; + case 0x5F: BIT<3>(_state.A); break; + case 0x60: BIT<4>(_state.B); break; + case 0x61: BIT<4>(_state.C); break; + case 0x62: BIT<4>(_state.D); break; + case 0x63: BIT<4>(_state.E); break; + case 0x64: BIT<4>(_state.H); break; + case 0x65: BIT<4>(_state.L); break; + case 0x66: BIT<4>(Read(_regHL)); break; + case 0x67: BIT<4>(_state.A); break; + case 0x68: BIT<5>(_state.B); break; + case 0x69: BIT<5>(_state.C); break; + case 0x6A: BIT<5>(_state.D); break; + case 0x6B: BIT<5>(_state.E); break; + case 0x6C: BIT<5>(_state.H); break; + case 0x6D: BIT<5>(_state.L); break; + case 0x6E: BIT<5>(Read(_regHL)); break; + case 0x6F: BIT<5>(_state.A); break; + case 0x70: BIT<6>(_state.B); break; + case 0x71: BIT<6>(_state.C); break; + case 0x72: BIT<6>(_state.D); break; + case 0x73: BIT<6>(_state.E); break; + case 0x74: BIT<6>(_state.H); break; + case 0x75: BIT<6>(_state.L); break; + case 0x76: BIT<6>(Read(_regHL)); break; + case 0x77: BIT<6>(_state.A); break; + case 0x78: BIT<7>(_state.B); break; + case 0x79: BIT<7>(_state.C); break; + case 0x7A: BIT<7>(_state.D); break; + case 0x7B: BIT<7>(_state.E); break; + case 0x7C: BIT<7>(_state.H); break; + case 0x7D: BIT<7>(_state.L); break; + case 0x7E: BIT<7>(Read(_regHL)); break; + case 0x7F: BIT<7>(_state.A); break; + case 0x80: RES<0>(_state.B); break; + case 0x81: RES<0>(_state.C); break; + case 0x82: RES<0>(_state.D); break; + case 0x83: RES<0>(_state.E); break; + case 0x84: RES<0>(_state.H); break; + case 0x85: RES<0>(_state.L); break; + case 0x86: RES_Indirect<0>(_regHL); break; + case 0x87: RES<0>(_state.A); break; + case 0x88: RES<1>(_state.B); break; + case 0x89: RES<1>(_state.C); break; + case 0x8A: RES<1>(_state.D); break; + case 0x8B: RES<1>(_state.E); break; + case 0x8C: RES<1>(_state.H); break; + case 0x8D: RES<1>(_state.L); break; + case 0x8E: RES_Indirect<1>(_regHL); break; + case 0x8F: RES<1>(_state.A); break; + case 0x90: RES<2>(_state.B); break; + case 0x91: RES<2>(_state.C); break; + case 0x92: RES<2>(_state.D); break; + case 0x93: RES<2>(_state.E); break; + case 0x94: RES<2>(_state.H); break; + case 0x95: RES<2>(_state.L); break; + case 0x96: RES_Indirect<2>(_regHL); break; + case 0x97: RES<2>(_state.A); break; + case 0x98: RES<3>(_state.B); break; + case 0x99: RES<3>(_state.C); break; + case 0x9A: RES<3>(_state.D); break; + case 0x9B: RES<3>(_state.E); break; + case 0x9C: RES<3>(_state.H); break; + case 0x9D: RES<3>(_state.L); break; + case 0x9E: RES_Indirect<3>(_regHL); break; + case 0x9F: RES<3>(_state.A); break; + case 0xA0: RES<4>(_state.B); break; + case 0xA1: RES<4>(_state.C); break; + case 0xA2: RES<4>(_state.D); break; + case 0xA3: RES<4>(_state.E); break; + case 0xA4: RES<4>(_state.H); break; + case 0xA5: RES<4>(_state.L); break; + case 0xA6: RES_Indirect<4>(_regHL); break; + case 0xA7: RES<4>(_state.A); break; + case 0xA8: RES<5>(_state.B); break; + case 0xA9: RES<5>(_state.C); break; + case 0xAA: RES<5>(_state.D); break; + case 0xAB: RES<5>(_state.E); break; + case 0xAC: RES<5>(_state.H); break; + case 0xAD: RES<5>(_state.L); break; + case 0xAE: RES_Indirect<5>(_regHL); break; + case 0xAF: RES<5>(_state.A); break; + case 0xB0: RES<6>(_state.B); break; + case 0xB1: RES<6>(_state.C); break; + case 0xB2: RES<6>(_state.D); break; + case 0xB3: RES<6>(_state.E); break; + case 0xB4: RES<6>(_state.H); break; + case 0xB5: RES<6>(_state.L); break; + case 0xB6: RES_Indirect<6>(_regHL); break; + case 0xB7: RES<6>(_state.A); break; + case 0xB8: RES<7>(_state.B); break; + case 0xB9: RES<7>(_state.C); break; + case 0xBA: RES<7>(_state.D); break; + case 0xBB: RES<7>(_state.E); break; + case 0xBC: RES<7>(_state.H); break; + case 0xBD: RES<7>(_state.L); break; + case 0xBE: RES_Indirect<7>(_regHL); break; + case 0xBF: RES<7>(_state.A); break; + case 0xC0: SET<0>(_state.B); break; + case 0xC1: SET<0>(_state.C); break; + case 0xC2: SET<0>(_state.D); break; + case 0xC3: SET<0>(_state.E); break; + case 0xC4: SET<0>(_state.H); break; + case 0xC5: SET<0>(_state.L); break; + case 0xC6: SET_Indirect<0>(_regHL); break; + case 0xC7: SET<0>(_state.A); break; + case 0xC8: SET<1>(_state.B); break; + case 0xC9: SET<1>(_state.C); break; + case 0xCA: SET<1>(_state.D); break; + case 0xCB: SET<1>(_state.E); break; + case 0xCC: SET<1>(_state.H); break; + case 0xCD: SET<1>(_state.L); break; + case 0xCE: SET_Indirect<1>(_regHL); break; + case 0xCF: SET<1>(_state.A); break; + case 0xD0: SET<2>(_state.B); break; + case 0xD1: SET<2>(_state.C); break; + case 0xD2: SET<2>(_state.D); break; + case 0xD3: SET<2>(_state.E); break; + case 0xD4: SET<2>(_state.H); break; + case 0xD5: SET<2>(_state.L); break; + case 0xD6: SET_Indirect<2>(_regHL); break; + case 0xD7: SET<2>(_state.A); break; + case 0xD8: SET<3>(_state.B); break; + case 0xD9: SET<3>(_state.C); break; + case 0xDA: SET<3>(_state.D); break; + case 0xDB: SET<3>(_state.E); break; + case 0xDC: SET<3>(_state.H); break; + case 0xDD: SET<3>(_state.L); break; + case 0xDE: SET_Indirect<3>(_regHL); break; + case 0xDF: SET<3>(_state.A); break; + case 0xE0: SET<4>(_state.B); break; + case 0xE1: SET<4>(_state.C); break; + case 0xE2: SET<4>(_state.D); break; + case 0xE3: SET<4>(_state.E); break; + case 0xE4: SET<4>(_state.H); break; + case 0xE5: SET<4>(_state.L); break; + case 0xE6: SET_Indirect<4>(_regHL); break; + case 0xE7: SET<4>(_state.A); break; + case 0xE8: SET<5>(_state.B); break; + case 0xE9: SET<5>(_state.C); break; + case 0xEA: SET<5>(_state.D); break; + case 0xEB: SET<5>(_state.E); break; + case 0xEC: SET<5>(_state.H); break; + case 0xED: SET<5>(_state.L); break; + case 0xEE: SET_Indirect<5>(_regHL); break; + case 0xEF: SET<5>(_state.A); break; + case 0xF0: SET<6>(_state.B); break; + case 0xF1: SET<6>(_state.C); break; + case 0xF2: SET<6>(_state.D); break; + case 0xF3: SET<6>(_state.E); break; + case 0xF4: SET<6>(_state.H); break; + case 0xF5: SET<6>(_state.L); break; + case 0xF6: SET_Indirect<6>(_regHL); break; + case 0xF7: SET<6>(_state.A); break; + case 0xF8: SET<7>(_state.B); break; + case 0xF9: SET<7>(_state.C); break; + case 0xFA: SET<7>(_state.D); break; + case 0xFB: SET<7>(_state.E); break; + case 0xFC: SET<7>(_state.H); break; + case 0xFD: SET<7>(_state.L); break; + case 0xFE: SET_Indirect<7>(_regHL); break; + case 0xFF: SET<7>(_state.A); break; + } +} + +void GbCpu::Serialize(Serializer& s) +{ + s.Stream( + _state.CycleCount, _state.PC, _state.SP, _state.A, _state.Flags, _state.B, + _state.C, _state.D, _state.E, _state.H, _state.L, _state.IME, _state.Halted + ); +} diff --git a/Core/GbCpu.h b/Core/GbCpu.h new file mode 100644 index 0000000..51f1802 --- /dev/null +++ b/Core/GbCpu.h @@ -0,0 +1,146 @@ +#pragma once +#include "stdafx.h" +#include "GbTypes.h" +#include "../Utilities/ISerializable.h" + +class GbMemoryManager; + +class GbCpu : public ISerializable +{ +private: + GbCpuState _state = {}; + Register16 _regAF = Register16(&_state.A, &_state.Flags); + Register16 _regBC = Register16(&_state.B, &_state.C); + Register16 _regDE = Register16(&_state.D, &_state.E); + Register16 _regHL = Register16(&_state.H, &_state.L); + + GbMemoryManager* _memoryManager; + +public: + GbCpu(GbMemoryManager* memoryManager); + virtual ~GbCpu(); + + GbCpuState GetState(); + uint64_t GetCycleCount(); + + void Exec(); + void ExecOpCode(uint8_t opCode); + + __forceinline void IncCycleCount(); + __forceinline uint8_t ReadOpCode(); + __forceinline uint8_t ReadCode(); + __forceinline uint16_t ReadCodeWord(); + __forceinline uint8_t Read(uint16_t addr); + __forceinline void Write(uint16_t addr, uint8_t value); + + bool CheckFlag(uint8_t flag); + void SetFlag(uint8_t flag); + void ClearFlag(uint8_t flag); + void SetFlagState(uint8_t flag, bool state); + + void PushByte(uint8_t value); + void PushWord(uint16_t value); + uint8_t PopByte(); + uint16_t PopWord(); + + void LD(uint8_t& dst, uint8_t value); + void LD(uint16_t& dst, uint16_t value); + void LD(Register16& dst, uint16_t value); + void LD_Indirect(uint16_t dst, uint8_t value); + void LD_Indirect16(uint16_t dst, uint16_t value); + void LD_HL(int8_t value); + + void INC(uint8_t& dst); + void INC(Register16& dst); + void INC_SP(); + void INC_Indirect(uint16_t addr); + void DEC(uint8_t& dst); + void DEC(Register16& dst); + void DEC_Indirect(uint16_t addr); + void DEC_SP(); + + void ADD(uint8_t value); + void ADD_SP(int8_t value); + void ADD(Register16& reg, uint16_t value); + void ADC(uint8_t value); + void SUB(uint8_t value); + void SBC(uint8_t value); + + void AND(uint8_t value); + void OR(uint8_t value); + void XOR(uint8_t value); + + void CP(uint8_t value); + + void NOP(); + void InvalidOp(); + void STOP(); + void HALT(); + + void CPL(); + + void RL(uint8_t& dst); + void RL_Indirect(uint16_t addr); + void RLC(uint8_t& dst); + void RLC_Indirect(uint16_t addr); + void RR(uint8_t& dst); + void RR_Indirect(uint16_t addr); + void RRC(uint8_t& dst); + void RRC_Indirect(uint16_t addr); + void RRA(); + void RRCA(); + void RLCA(); + void RLA(); + void SRL(uint8_t& dst); + void SRL_Indirect(uint16_t addr); + void SRA(uint8_t& dst); + void SRA_Indirect(uint16_t addr); + void SLA(uint8_t& dst); + void SLA_Indirect(uint16_t addr); + + void SWAP(uint8_t& dst); + void SWAP_Indirect(uint16_t addr); + + template + void BIT(uint8_t src); + + template + void RES(uint8_t& dst); + + template + void RES_Indirect(uint16_t addr); + + template + void SET(uint8_t& dst); + + template + void SET_Indirect(uint16_t addr); + + void DAA(); + + void JP(uint16_t dstAddr); + void JP_HL(); + void JP(bool condition, uint16_t dstAddr); + void JR(int8_t offset); + void JR(bool condition, int8_t offset); + + void CALL(uint16_t dstAddr); + void CALL(bool condition, uint16_t dstAddr); + void RET(); + void RET(bool condition); + void RETI(); + void RST(uint8_t value); + + void POP(Register16& reg); + void PUSH(Register16& reg); + void POP_AF(); + + void SCF(); + void CCF(); + + void EI(); + void DI(); + void PREFIX(); + + void Serialize(Serializer& s) override; +}; diff --git a/Core/GbDebugger.cpp b/Core/GbDebugger.cpp new file mode 100644 index 0000000..9ee00d8 --- /dev/null +++ b/Core/GbDebugger.cpp @@ -0,0 +1,146 @@ +#include "stdafx.h" +#include "GbDebugger.h" +#include "DisassemblyInfo.h" +#include "Disassembler.h" +#include "Gameboy.h" +#include "TraceLogger.h" +#include "CallstackManager.h" +#include "BreakpointManager.h" +#include "MemoryManager.h" +#include "Debugger.h" +#include "Console.h" +#include "MemoryAccessCounter.h" +#include "ExpressionEvaluator.h" +#include "EmuSettings.h" +#include "BaseCartridge.h" +#include "GameboyDisUtils.h" + +GbDebugger::GbDebugger(Debugger* debugger) +{ + _debugger = debugger; + _traceLogger = debugger->GetTraceLogger().get(); + _disassembler = debugger->GetDisassembler().get(); + _memoryAccessCounter = debugger->GetMemoryAccessCounter().get(); + _gameboy = debugger->GetConsole()->GetCartridge()->GetGameboy(); + _memoryManager = debugger->GetConsole()->GetMemoryManager().get(); + _settings = debugger->GetConsole()->GetSettings().get(); + + _callstackManager.reset(new CallstackManager(debugger)); + _breakpointManager.reset(new BreakpointManager(debugger, CpuType::Gameboy)); + _step.reset(new StepRequest()); +} + +void GbDebugger::Reset() +{ + _callstackManager.reset(new CallstackManager(_debugger)); + _prevOpCode = 0; +} + +void GbDebugger::ProcessRead(uint16_t addr, uint8_t value, MemoryOperationType type) +{ + AddressInfo addressInfo = _gameboy->GetAbsoluteAddress(addr); + MemoryOperationInfo operation { addr, value, type }; + BreakSource breakSource = BreakSource::Unspecified; + + if(type == MemoryOperationType::ExecOpCode) { + GbCpuState gbState = _gameboy->GetState().Cpu; + + if(_traceLogger->IsCpuLogged(CpuType::Gameboy) || _settings->CheckDebuggerFlag(DebuggerFlags::GbDebuggerEnabled)) { + if(addressInfo.Address >= 0) { + _disassembler->BuildCache(addressInfo, 0, CpuType::Gameboy); + } + + if(_traceLogger->IsCpuLogged(CpuType::Gameboy)) { + DebugState debugState; + _debugger->GetState(debugState, true); + + DisassemblyInfo disInfo = _disassembler->GetDisassemblyInfo(addressInfo, addr, 0, CpuType::Gameboy); + _traceLogger->Log(CpuType::Gameboy, debugState, disInfo); + } + } + + if(GameboyDisUtils::IsJumpToSub(_prevOpCode) && gbState.PC != _prevProgramCounter + GameboyDisUtils::GetOpSize(_prevOpCode)) { + //CALL and RST, and PC doesn't match the next instruction, so the call was (probably) done + uint8_t opSize = DisassemblyInfo::GetOpSize(_prevOpCode, 0, CpuType::Gameboy); + uint16_t returnPc = _prevProgramCounter + opSize; + AddressInfo src = _gameboy->GetAbsoluteAddress(_prevProgramCounter); + AddressInfo ret = _gameboy->GetAbsoluteAddress(returnPc); + _callstackManager->Push(src, _prevProgramCounter, addressInfo, gbState.PC, ret, returnPc, StackFrameFlags::None); + } else if(GameboyDisUtils::IsReturnInstruction(_prevOpCode) && gbState.PC != _prevProgramCounter + GameboyDisUtils::GetOpSize(_prevOpCode)) { + //RET used, and PC doesn't match the next instruction, so the ret was (probably) taken + _callstackManager->Pop(addressInfo, gbState.PC); + } + + if(_step->BreakAddress == (int32_t)gbState.PC && GameboyDisUtils::IsReturnInstruction(_prevOpCode)) { + //RET/RETI found, if we're on the expected return address, break immediately (for step over/step out) + _step->StepCount = 0; + } + + _prevOpCode = value; + _prevProgramCounter = gbState.PC; + + if(_step->StepCount > 0) { + _step->StepCount--; + } + + _memoryAccessCounter->ProcessMemoryExec(addressInfo, _memoryManager->GetMasterClock()); + } else if(type == MemoryOperationType::ExecOperand) { + _memoryAccessCounter->ProcessMemoryExec(addressInfo, _memoryManager->GetMasterClock()); + } else { + _memoryAccessCounter->ProcessMemoryRead(addressInfo, _memoryManager->GetMasterClock()); + } + + _debugger->ProcessBreakConditions(_step->StepCount == 0, GetBreakpointManager(), operation, addressInfo, breakSource); +} + +void GbDebugger::ProcessWrite(uint16_t addr, uint8_t value, MemoryOperationType type) +{ + AddressInfo addressInfo = _gameboy->GetAbsoluteAddress(addr); + MemoryOperationInfo operation { addr, value, type }; + _debugger->ProcessBreakConditions(false, GetBreakpointManager(), operation, addressInfo); + + if(addressInfo.Type == SnesMemoryType::GbWorkRam || addressInfo.Type == SnesMemoryType::GbCartRam || addressInfo.Type == SnesMemoryType::GbHighRam) { + _disassembler->InvalidateCache(addressInfo, CpuType::Gameboy); + } + + _memoryAccessCounter->ProcessMemoryWrite(addressInfo, _memoryManager->GetMasterClock()); +} + +void GbDebugger::Run() +{ + _step.reset(new StepRequest()); +} + +void GbDebugger::Step(int32_t stepCount, StepType type) +{ + StepRequest step; + + switch(type) { + case StepType::Step: step.StepCount = stepCount; break; + case StepType::StepOut: step.BreakAddress = _callstackManager->GetReturnAddress(); break; + case StepType::StepOver: + if(GameboyDisUtils::IsJumpToSub(_prevOpCode)) { + step.BreakAddress = _prevProgramCounter + DisassemblyInfo::GetOpSize(_prevOpCode, 0, CpuType::Gameboy); + } else { + //For any other instruction, step over is the same as step into + step.StepCount = 1; + } + break; + + case StepType::SpecificScanline: + case StepType::PpuStep: + break; + } + + _step.reset(new StepRequest(step)); +} + +shared_ptr GbDebugger::GetCallstackManager() +{ + return _callstackManager; +} + +BreakpointManager* GbDebugger::GetBreakpointManager() +{ + return _breakpointManager.get(); +} \ No newline at end of file diff --git a/Core/GbDebugger.h b/Core/GbDebugger.h new file mode 100644 index 0000000..42c2812 --- /dev/null +++ b/Core/GbDebugger.h @@ -0,0 +1,44 @@ +#pragma once +#include "stdafx.h" +#include "DebugTypes.h" +#include "IDebugger.h" + +class Disassembler; +class Debugger; +class TraceLogger; +class Gameboy; +class CallstackManager; +class MemoryAccessCounter; +class MemoryManager; +class BreakpointManager; +class EmuSettings; + +class GbDebugger final : public IDebugger +{ + Debugger* _debugger; + Disassembler* _disassembler; + TraceLogger* _traceLogger; + MemoryAccessCounter* _memoryAccessCounter; + MemoryManager* _memoryManager; + Gameboy* _gameboy; + EmuSettings* _settings; + + shared_ptr _callstackManager; + unique_ptr _breakpointManager; + unique_ptr _step; + + uint8_t _prevOpCode = 0xFF; + uint32_t _prevProgramCounter = 0; + +public: + GbDebugger(Debugger* debugger); + + void Reset(); + + void ProcessRead(uint16_t addr, uint8_t value, MemoryOperationType type); + void ProcessWrite(uint16_t addr, uint8_t value, MemoryOperationType type); + void Run(); + void Step(int32_t stepCount, StepType type); + shared_ptr GetCallstackManager(); + BreakpointManager* GetBreakpointManager(); +}; \ No newline at end of file diff --git a/Core/GbMbc1.h b/Core/GbMbc1.h new file mode 100644 index 0000000..23c75d2 --- /dev/null +++ b/Core/GbMbc1.h @@ -0,0 +1,62 @@ +#pragma once +#include "stdafx.h" +#include "GbCart.h" +#include "GbMemoryManager.h" +#include "../Utilities/Serializer.h" + +class GbMbc1 : public GbCart +{ +private: + bool _ramEnabled = false; + uint8_t _prgBank = 1; + uint8_t _ramBank = 0; + bool _mode = false; + +public: + void InitCart() override + { + _memoryManager->MapRegisters(0x0000, 0x7FFF, RegisterAccess::Write); + } + + void RefreshMappings() override + { + constexpr int prgBankSize = 0x4000; + constexpr int ramBankSize = 0x2000; + + Map(0x0000, 0x3FFF, GbMemoryType::PrgRom, (_mode ? (_ramBank << 5) : 0) * prgBankSize, true); + + uint8_t prgBank = _prgBank | (_ramBank << 5); + Map(0x4000, 0x7FFF, GbMemoryType::PrgRom, prgBank*prgBankSize, true); + + if(_ramEnabled) { + uint8_t ramBank = _mode ? _ramBank : 0; + Map(0xA000, 0xBFFF, GbMemoryType::CartRam, ramBank*ramBankSize, false); + _memoryManager->MapRegisters(0xA000, 0xBFFF, RegisterAccess::None); + } else { + Unmap(0xA000, 0xBFFF); + _memoryManager->MapRegisters(0xA000, 0xBFFF, RegisterAccess::Read); + } + } + + uint8_t ReadRegister(uint16_t addr) override + { + //Disabled RAM returns 0xFF on reads + return 0xFF; + } + + void WriteRegister(uint16_t addr, uint8_t value) override + { + switch(addr & 0x6000) { + case 0x0000: _ramEnabled = ((value & 0x0F) == 0x0A); break; + case 0x2000: _prgBank = std::max(1, value & 0x1F); break; + case 0x4000: _ramBank = value & 0x03; break; + case 0x6000: _mode = (value & 0x01) != 0; break; + } + RefreshMappings(); + } + + void Serialize(Serializer& s) override + { + s.Stream(_ramEnabled, _prgBank, _ramBank, _mode); + } +}; \ No newline at end of file diff --git a/Core/GbMbc2.h b/Core/GbMbc2.h new file mode 100644 index 0000000..c1677c1 --- /dev/null +++ b/Core/GbMbc2.h @@ -0,0 +1,68 @@ +#pragma once +#include "stdafx.h" +#include "GbCart.h" +#include "GbMemoryManager.h" +#include "../Utilities/Serializer.h" + +class GbMbc2 : public GbCart +{ +private: + bool _ramEnabled = false; + uint8_t _prgBank = 1; + +public: + void InitCart() override + { + _memoryManager->MapRegisters(0x0000, 0x3FFF, RegisterAccess::Write); + + for(int i = 0; i < 512; i++) { + //Ensure cart RAM contains $F in the upper nibble, no matter the contents of save ram + _cartRam[i] |= 0xF0; + } + } + + void RefreshMappings() override + { + constexpr int prgBankSize = 0x4000; + + Map(0x0000, 0x3FFF, GbMemoryType::PrgRom, 0, true); + + Map(0x4000, 0x7FFF, GbMemoryType::PrgRom, _prgBank * prgBankSize, true); + + if(_ramEnabled) { + for(int i = 0; i < 16; i++) { + Map(0xA000+0x200*i, 0xA1FF+0x200*i, GbMemoryType::CartRam, 0, false); + } + _memoryManager->MapRegisters(0xA000, 0xBFFF, RegisterAccess::Write); + } else { + Unmap(0xA000, 0xBFFF); + _memoryManager->MapRegisters(0xA000, 0xBFFF, RegisterAccess::Read); + } + } + + uint8_t ReadRegister(uint16_t addr) override + { + //Disabled RAM returns 0xFF on reads + return 0xFF; + } + + void WriteRegister(uint16_t addr, uint8_t value) override + { + if(addr >= 0xA000 && addr <= 0xBFFF) { + //Cut off the top 4 bits for all cart ram writes + //Set top nibble to $F to mimic open bus + _cartRam[addr & 0x1FF] = (value & 0x0F) | 0xF0; + } else { + switch(addr & 0x100) { + case 0x000: _ramEnabled = ((value & 0x0F) == 0x0A); break; + case 0x100: _prgBank = std::max(1, value & 0x0F); break; + } + RefreshMappings(); + } + } + + void Serialize(Serializer& s) override + { + s.Stream(_ramEnabled, _prgBank); + } +}; \ No newline at end of file diff --git a/Core/GbMbc3.h b/Core/GbMbc3.h new file mode 100644 index 0000000..75535df --- /dev/null +++ b/Core/GbMbc3.h @@ -0,0 +1,95 @@ +#pragma once +#include "stdafx.h" +#include "GbCart.h" +#include "GbMemoryManager.h" +#include "../Utilities/Serializer.h" + +class GbMbc3 : public GbCart +{ +private: + bool _hasRtcTimer = false; + + bool _ramRtcEnabled = false; + uint8_t _prgBank = 1; + uint8_t _ramBank = 0; + uint8_t _rtcRegisters[5] = {}; + +public: + GbMbc3(bool hasRtcTimer) + { + _hasRtcTimer = hasRtcTimer; + } + + void InitCart() override + { + _memoryManager->MapRegisters(0x0000, 0x7FFF, RegisterAccess::Write); + } + + void RefreshMappings() override + { + constexpr int prgBankSize = 0x4000; + + Map(0x0000, 0x3FFF, GbMemoryType::PrgRom, 0, true); + Map(0x4000, 0x7FFF, GbMemoryType::PrgRom, _prgBank * prgBankSize, true); + + if(_ramRtcEnabled && _ramBank <= 3) { + Map(0xA000, 0xBFFF, GbMemoryType::CartRam, _ramBank, false); + _memoryManager->MapRegisters(0xA000, 0xBFFF, RegisterAccess::None); + } else if(_hasRtcTimer && _ramRtcEnabled && _ramBank >= 0x08 && _ramBank <= 0x0C) { + _memoryManager->MapRegisters(0xA000, 0xBFFF, RegisterAccess::ReadWrite); + } else { + Unmap(0xA000, 0xBFFF); + _memoryManager->MapRegisters(0xA000, 0xBFFF, RegisterAccess::Read); + } + } + + uint8_t ReadRegister(uint16_t addr) override + { + if(_ramRtcEnabled) { + //Disabled RAM/RTC registers returns 0xFF on reads (?) + return 0xFF; + } + + //RTC register read (TODO) + switch(_ramBank) { + case 0x08: return _rtcRegisters[0]; //Seconds + case 0x09: return _rtcRegisters[1]; //Minutes + case 0x0A: return _rtcRegisters[2]; //Hours + case 0x0B: return _rtcRegisters[3]; //Day counter + case 0x0C: return _rtcRegisters[4]; //Day counter (upper bit) + carry/halt flags + } + + //Not reached + return 0xFF; + } + + void WriteRegister(uint16_t addr, uint8_t value) override + { + if(addr <= 0x7FFF) { + switch(addr & 0x6000) { + case 0x0000: _ramRtcEnabled = ((value & 0x0F) == 0x0A); break; + case 0x2000: _prgBank = std::max(1, value); break; + case 0x4000: _ramBank = value & 0x0F; break; + case 0x6000: + //RTC register read latch + break; + } + RefreshMappings(); + } else if(addr >= 0xA000 && addr <= 0xBFFF) { + //RTC register write (TODO) + switch(_ramBank) { + case 0x08: _rtcRegisters[0] = value; break; //Seconds + case 0x09: _rtcRegisters[1] = value; break; //Minutes + case 0x0A: _rtcRegisters[2] = value; break; //Hours + case 0x0B: _rtcRegisters[3] = value; break; //Day counter + case 0x0C: _rtcRegisters[4] = value; break; //Day counter (upper bit) + carry/halt flags + } + } + } + + void Serialize(Serializer& s) override + { + s.Stream(_ramRtcEnabled, _prgBank, _ramBank); + s.StreamArray(_rtcRegisters, 5); + } +}; \ No newline at end of file diff --git a/Core/GbMbc5.h b/Core/GbMbc5.h new file mode 100644 index 0000000..163eb8e --- /dev/null +++ b/Core/GbMbc5.h @@ -0,0 +1,71 @@ +#pragma once +#include "stdafx.h" +#include "GbCart.h" +#include "GbMemoryManager.h" +#include "../Utilities/Serializer.h" + +class GbMbc5 : public GbCart +{ +private: + bool _ramEnabled = false; + uint16_t _prgBank = 1; + uint8_t _ramBank = 0; + +public: + void InitCart() override + { + _memoryManager->MapRegisters(0x0000, 0x5FFF, RegisterAccess::Write); + } + + void RefreshMappings() override + { + constexpr int prgBankSize = 0x4000; + constexpr int ramBankSize = 0x2000; + + Map(0x0000, 0x3FFF, GbMemoryType::PrgRom, 0, true); + Map(0x4000, 0x7FFF, GbMemoryType::PrgRom, _prgBank * prgBankSize, true); + + if(_ramEnabled) { + Map(0xA000, 0xBFFF, GbMemoryType::CartRam, _ramBank * ramBankSize, false); + _memoryManager->MapRegisters(0xA000, 0xBFFF, RegisterAccess::None); + } else { + Unmap(0xA000, 0xBFFF); + _memoryManager->MapRegisters(0xA000, 0xBFFF, RegisterAccess::Read); + } + } + + uint8_t ReadRegister(uint16_t addr) override + { + //Disabled RAM returns 0xFF on reads + return 0xFF; + } + + void WriteRegister(uint16_t addr, uint8_t value) override + { + switch(addr & 0x7000) { + case 0x0000: + case 0x1000: + _ramEnabled = (value == 0x0A); + break; + + case 0x2000: + _prgBank = (value & 0xFF) | (_prgBank & 0x100); + break; + + case 0x3000: + _prgBank = (_prgBank & 0xFF) | ((value & 0x01) << 8); + break; + + case 0x4000: + case 0x5000: + _ramBank = value & 0x0F; + break; + } + RefreshMappings(); + } + + void Serialize(Serializer& s) override + { + s.Stream(_ramEnabled, _prgBank, _ramBank); + } +}; \ No newline at end of file diff --git a/Core/GbMemoryManager.cpp b/Core/GbMemoryManager.cpp new file mode 100644 index 0000000..c48e982 --- /dev/null +++ b/Core/GbMemoryManager.cpp @@ -0,0 +1,323 @@ +#include "stdafx.h" +#include "Console.h" +#include "Gameboy.h" +#include "GbMemoryManager.h" +#include "GbPpu.h" +#include "GbApu.h" +#include "GbTimer.h" +#include "GbTypes.h" +#include "GbCart.h" +#include "EmuSettings.h" +#include "ControlManager.h" +#include "../Utilities/VirtualFile.h" +#include "../Utilities/Serializer.h" +#include "SnesController.h" + +void GbMemoryManager::Init(Console* console, Gameboy* gameboy, GbCart* cart, GbPpu* ppu, GbApu* apu, GbTimer* timer) +{ + _state = {}; + _state.DisableBootRom = true; + + _prgRom = gameboy->DebugGetMemory(SnesMemoryType::GbPrgRom); + _prgRomSize = gameboy->DebugGetMemorySize(SnesMemoryType::GbPrgRom); + _cartRam = gameboy->DebugGetMemory(SnesMemoryType::GbCartRam); + _cartRamSize = gameboy->DebugGetMemorySize(SnesMemoryType::GbCartRam); + _workRam = gameboy->DebugGetMemory(SnesMemoryType::GbWorkRam); + _workRamSize = gameboy->DebugGetMemorySize(SnesMemoryType::GbWorkRam); + _highRam = gameboy->DebugGetMemory(SnesMemoryType::GbHighRam); + + _apu = apu; + _ppu = ppu; + _gameboy = gameboy; + _cart = cart; + _timer = timer; + _console = console; + _controlManager = console->GetControlManager().get(); + _settings = console->GetSettings().get(); + + MapRegisters(0x8000, 0x9FFF, RegisterAccess::ReadWrite); + MapRegisters(0xFE00, 0xFFFF, RegisterAccess::ReadWrite); + + _cart->InitCart(); + RefreshMappings(); +} + +GbMemoryManager::~GbMemoryManager() +{ +} + +GbMemoryManagerState GbMemoryManager::GetState() +{ + return _state; +} + +void GbMemoryManager::RefreshMappings() +{ + if(!_state.DisableBootRom) { + //TODO + //Map(0x0000, 0x00FF, GbMemoryType::WorkRam, true); + } + + Map(0xC000, 0xDFFF, GbMemoryType::WorkRam, 0, false); + Map(0xE000, 0xFCFF, GbMemoryType::WorkRam, 0, false); + + _cart->RefreshMappings(); +} + +void GbMemoryManager::Exec() +{ + _timer->Exec(); + _ppu->Exec(); +} + +void GbMemoryManager::MapRegisters(uint16_t start, uint16_t end, RegisterAccess access) +{ + for(int i = start; i < end; i += 0x100) { + _state.IsReadRegister[i >> 8] = ((int)access & (int)RegisterAccess::Read) != 0; + _state.IsWriteRegister[i >> 8] = ((int)access & (int)RegisterAccess::Write) != 0; + } +} + +void GbMemoryManager::Map(uint16_t start, uint16_t end, GbMemoryType type, uint32_t offset, bool readonly) +{ + uint8_t* src = _gameboy->DebugGetMemory((SnesMemoryType)type); + uint32_t size = _gameboy->DebugGetMemorySize((SnesMemoryType)type); + + if(size > 0) { + while(offset >= size) { + offset -= size; + } + + src += offset; + for(int i = start; i < end; i += 0x100) { + _reads[i >> 8] = src; + _writes[i >> 8] = readonly ? nullptr : src; + + _state.MemoryType[i >> 8] = type; + _state.MemoryOffset[i >> 8] = offset; + _state.MemoryAccessType[i >> 8] = readonly ? RegisterAccess::Read : RegisterAccess::ReadWrite; + + if(src) { + src += 0x100; + offset = (offset + 0x100) & (size - 1); + } + } + } else { + Unmap(start, end); + } +} + +void GbMemoryManager::Unmap(uint16_t start, uint16_t end) +{ + for(int i = start; i < end; i += 0x100) { + _reads[i >> 8] = nullptr; + _writes[i >> 8] = nullptr; + + _state.MemoryType[i >> 8] = GbMemoryType::None; + _state.MemoryOffset[i >> 8] = 0; + _state.MemoryAccessType[i >> 8] = RegisterAccess::None; + } +} + +uint8_t GbMemoryManager::Read(uint16_t addr, MemoryOperationType opType) +{ + uint8_t value = 0; + if(_state.IsReadRegister[addr >> 8]) { + value = ReadRegister(addr); + } else if(_reads[addr >> 8]) { + value = _reads[addr >> 8][(uint8_t)addr]; + } + _console->ProcessMemoryRead(addr, value, opType); + return value; +} + +void GbMemoryManager::Write(uint16_t addr, uint8_t value) +{ + _console->ProcessMemoryWrite(addr, value, MemoryOperationType::Write); + if(_state.IsWriteRegister[addr >> 8]) { + WriteRegister(addr, value); + } else if(_writes[addr >> 8]) { + _writes[addr >> 8][(uint8_t)addr] = value; + } +} + +uint8_t GbMemoryManager::DebugRead(uint16_t addr) +{ + if(_state.IsReadRegister[addr >> 8]) { + if(addr >= 0xFE00) { + return ReadRegister(addr); + } else { + //Avoid potential read side effects + return 0xFF; + } + } else if(_reads[addr >> 8]) { + return _reads[addr >> 8][(uint8_t)addr]; + } + return 0; +} + +void GbMemoryManager::DebugWrite(uint16_t addr, uint8_t value) +{ + if(_state.IsWriteRegister[addr >> 8]) { + //Do not write to registers via debug tools + } else if(_writes[addr >> 8]) { + _writes[addr >> 8][(uint8_t)addr] = value; + } +} + +uint8_t* GbMemoryManager::GetMappedBlock(uint16_t addr) +{ + if(_reads[addr >> 8]) { + return _reads[addr >> 8]; + } + return nullptr; +} + +uint8_t GbMemoryManager::ReadRegister(uint16_t addr) +{ + if(addr >= 0xFF00) { + if(addr == 0xFFFF) { + return _state.IrqEnabled; //IE - Interrupt Enable (R/W) + } else if(addr >= 0xFF80) { + return _highRam[addr & 0x7F]; //80-FE + } else if(addr >= 0xFF50) { + return 0; //50-7F + } else if(addr >= 0xFF40) { + return _ppu->Read(addr); //40-4F + } else if(addr >= 0xFF10) { + return _apu->Read(addr); //10-3F + } else { + //00-0F + switch(addr) { + case 0xFF00: return ReadInputPort(); break; + case 0xFF01: break; //Serial + + case 0xFF04: case 0xFF05: case 0xFF06: case 0xFF07: + return _timer->Read(addr); + + case 0xFF0F: return _state.IrqRequests; break; //IF - Interrupt flags (R/W) + } + } + } else if(addr >= 0xFE00) { + return _ppu->ReadOam((uint8_t)addr); + } else if(addr >= 0x8000 && addr <= 0x9FFF) { + return _ppu->ReadVram(addr); + } + + return _cart->ReadRegister(addr); +} + +void GbMemoryManager::WriteRegister(uint16_t addr, uint8_t value) +{ + if(addr >= 0xFF00) { + if(addr == 0xFFFF) { + _state.IrqEnabled = value; //IE register + } else if(addr >= 0xFF80) { + _highRam[addr & 0x7F] = value; //80-FE + } else if(addr >= 0xFF50) { + //50-7F + if(addr == 0xFF50 && (value & 0x01)) { + _state.DisableBootRom = true; + RefreshMappings(); + } + } else if(addr >= 0xFF40) { + _ppu->Write(addr, value); //40-4F + } else if(addr >= 0xFF10) { + _apu->Write(addr, value); //10-3F + } else { + //00-0F + switch(addr) { + case 0xFF00: _state.InputSelect = value; break; + case 0xFF01: break; //Serial + + case 0xFF04: case 0xFF05: case 0xFF06: case 0xFF07: + _timer->Write(addr, value); + break; + + case 0xFF0F: _state.IrqRequests = value; break; //IF - Interrupt flags (R/W) + } + } + } else if(addr >= 0xFE00) { + _ppu->WriteOam((uint8_t)addr, value); + } else if(addr >= 0x8000 && addr <= 0x9FFF) { + _ppu->WriteVram(addr, value); + } else { + _cart->WriteRegister(addr, value); + } +} + +void GbMemoryManager::RequestIrq(uint8_t source) +{ + _state.IrqRequests |= source; +} + +void GbMemoryManager::ClearIrqRequest(uint8_t source) +{ + _state.IrqRequests &= ~source; +} + +uint8_t GbMemoryManager::ProcessIrqRequests() +{ + uint8_t irqsToProcess = _state.IrqRequests & _state.IrqEnabled; + if(irqsToProcess) { + if(irqsToProcess & GbIrqSource::VerticalBlank) { + return GbIrqSource::VerticalBlank; + } else if(irqsToProcess & GbIrqSource::LcdStat) { + return GbIrqSource::LcdStat; + } else if(irqsToProcess & GbIrqSource::Timer) { + return GbIrqSource::Timer; + } else if(irqsToProcess & GbIrqSource::Serial) { + return GbIrqSource::Serial; + } else if(irqsToProcess & GbIrqSource::Joypad) { + return GbIrqSource::Joypad; + } + } + return 0; +} + +uint8_t GbMemoryManager::ReadInputPort() +{ + //Bit 7 - Not used + //Bit 6 - Not used + //Bit 5 - P15 Select Button Keys (0=Select) + //Bit 4 - P14 Select Direction Keys (0=Select) + //Bit 3 - P13 Input Down or Start (0=Pressed) (Read Only) + //Bit 2 - P12 Input Up or Select (0=Pressed) (Read Only) + //Bit 1 - P11 Input Left or Button B (0=Pressed) (Read Only) + //Bit 0 - P10 Input Right or Button A (0=Pressed) (Read Only) + BaseControlDevice* controller = (SnesController*)_controlManager->GetControlDevice(0).get(); + uint8_t result = 0x0F; + if(controller && controller->GetControllerType() == ControllerType::SnesController) { + if(!(_state.InputSelect & 0x20)) { + result &= ~(controller->IsPressed(SnesController::A) ? 0x01 : 0); + result &= ~(controller->IsPressed(SnesController::B) ? 0x02 : 0); + result &= ~(controller->IsPressed(SnesController::Select) ? 0x04 : 0); + result &= ~(controller->IsPressed(SnesController::Start) ? 0x08 : 0); + } + if(!(_state.InputSelect & 0x10)) { + result &= ~(controller->IsPressed(SnesController::Right) ? 0x01 : 0); + result &= ~(controller->IsPressed(SnesController::Left) ? 0x02 : 0); + result &= ~(controller->IsPressed(SnesController::Up) ? 0x04 : 0); + result &= ~(controller->IsPressed(SnesController::Down) ? 0x08 : 0); + } + } + + return result | (_state.InputSelect & 0x30); +} + +void GbMemoryManager::Serialize(Serializer& s) +{ + s.Stream(_state.DisableBootRom, _state.IrqEnabled, _state.IrqRequests, _state.InputSelect); + s.StreamArray(_state.MemoryType, 0x100); + s.StreamArray(_state.MemoryOffset, 0x100); + s.StreamArray(_state.MemoryAccessType, 0x100); + s.StreamArray(_state.IsReadRegister, 0x100); + s.StreamArray(_state.IsWriteRegister, 0x100); + + if(!s.IsSaving()) { + //Restore mappings based on state + for(int i = 0; i < 0x100; i++) { + Map(i*0x100, i*0x100+0xFF, _state.MemoryType[i], _state.MemoryOffset[i], _state.MemoryAccessType[i] == RegisterAccess::ReadWrite ? false : true); + } + } +} diff --git a/Core/GbMemoryManager.h b/Core/GbMemoryManager.h new file mode 100644 index 0000000..be5aff8 --- /dev/null +++ b/Core/GbMemoryManager.h @@ -0,0 +1,73 @@ +#pragma once +#include "stdafx.h" +#include "DebugTypes.h" +#include "../Utilities/ISerializable.h" + +class Gameboy; +class GbCart; +class GbPpu; +class GbApu; +class GbTimer; + +class EmuSettings; +class Console; +class ControlManager; + +class GbMemoryManager : public ISerializable +{ +private: + Console* _console = nullptr; + EmuSettings* _settings = nullptr; + ControlManager* _controlManager = nullptr; + + Gameboy* _gameboy = nullptr; + GbCart* _cart = nullptr; + GbApu* _apu = nullptr; + GbPpu* _ppu = nullptr; + GbTimer* _timer; + + uint8_t* _prgRom = nullptr; + uint32_t _prgRomSize = 0; + uint8_t* _workRam = nullptr; + uint32_t _workRamSize = 0; + uint8_t* _cartRam = nullptr; + uint32_t _cartRamSize = 0; + uint8_t* _highRam = nullptr; + + uint8_t* _reads[0x100] = {}; + uint8_t* _writes[0x100] = {}; + + GbMemoryManagerState _state; + +public: + virtual ~GbMemoryManager(); + + GbMemoryManagerState GetState(); + + void Init(Console* console, Gameboy* gameboy, GbCart* cart, GbPpu* ppu, GbApu* apu, GbTimer* timer); + void MapRegisters(uint16_t start, uint16_t end, RegisterAccess access); + void Map(uint16_t start, uint16_t end, GbMemoryType type, uint32_t offset, bool readonly); + void Unmap(uint16_t start, uint16_t end); + void RefreshMappings(); + + void Exec(); + + uint8_t Read(uint16_t addr, MemoryOperationType opType); + void Write(uint16_t addr, uint8_t value); + + uint8_t ReadRegister(uint16_t addr); + void WriteRegister(uint16_t addr, uint8_t value); + + void RequestIrq(uint8_t source); + void ClearIrqRequest(uint8_t source); + uint8_t ProcessIrqRequests(); + + uint8_t ReadInputPort(); + + uint8_t DebugRead(uint16_t addr); + void DebugWrite(uint16_t addr, uint8_t value); + + uint8_t* GetMappedBlock(uint16_t addr); + + void Serialize(Serializer& s) override; +}; diff --git a/Core/GbNoiseChannel.h b/Core/GbNoiseChannel.h new file mode 100644 index 0000000..c746877 --- /dev/null +++ b/Core/GbNoiseChannel.h @@ -0,0 +1,194 @@ +#pragma once +#include "stdafx.h" +#include "GbTypes.h" +#include "../Utilities/ISerializable.h" +#include "../Utilities/Serializer.h" + +class GbNoiseChannel : public ISerializable +{ +private: + GbNoiseState _state = {}; + +public: + GbNoiseState GetState() + { + return _state; + } + + bool Enabled() + { + return _state.Enabled; + } + + void Disable() + { + uint8_t len = _state.Length; + _state = {}; + _state.Length = len; + } + + void ClockLengthCounter() + { + if(_state.LengthEnabled && _state.Length > 0) { + _state.Length--; + if(_state.Length == 0) { + //"Length becoming 0 should clear status" + _state.Enabled = false; + } + } + } + + void ClockEnvelope() + { + if(_state.EnvTimer > 0) { + _state.EnvTimer--; + + if(_state.EnvTimer == 0) { + if(_state.EnvRaiseVolume && _state.Volume < 0x0F) { + _state.Volume++; + } else if(!_state.EnvRaiseVolume && _state.Volume > 0) { + _state.Volume--; + } + + _state.EnvTimer = _state.EnvPeriod; + } + } + } + + uint8_t GetOutput() + { + return _state.Output; + } + + uint32_t GetPeriod() + { + if(_state.Divisor == 0) { + return 8 << _state.PeriodShift; + } else { + return (16 * _state.Divisor) << _state.PeriodShift; + } + } + + void Exec(uint32_t clocksToRun) + { + if(_state.PeriodShift >= 14) { + //Using a noise channel clock shift of 14 or 15 results in the LFSR receiving no clocks. + return; + } + + _state.Timer -= clocksToRun; + + if(_state.Enabled) { + _state.Output = ((_state.ShiftRegister & 0x01) ^ 0x01) * _state.Volume; + } else { + _state.Output = 0; + } + + if(_state.Timer == 0) { + _state.Timer = GetPeriod(); + + //When clocked by the frequency timer, the low two bits (0 and 1) are XORed, all bits are shifted right by one, + //and the result of the XOR is put into the now-empty high bit. + uint16_t shiftedValue = _state.ShiftRegister >> 1; + uint8_t xorResult = (_state.ShiftRegister & 0x01) ^ (shiftedValue & 0x01); + _state.ShiftRegister = (xorResult << 14) | shiftedValue; + + if(_state.ShortWidthMode) { + //If width mode is 1 (NR43), the XOR result is ALSO put into bit 6 AFTER the shift, resulting in a 7-bit LFSR. + _state.ShiftRegister &= ~0x40; + _state.ShiftRegister |= (xorResult << 6); + } + } + } + + uint8_t Read(uint16_t addr) + { + constexpr uint8_t openBusBits[5] = { 0xFF, 0xFF, 0x00, 0x00, 0xBF }; + + uint8_t value = 0; + switch(addr) { + case 2: + value = ( + (_state.EnvVolume << 4) | + (_state.EnvRaiseVolume ? 0x08 : 0) | + _state.EnvPeriod + ); + break; + + case 3: + value = ( + (_state.PeriodShift << 4) | + (_state.ShortWidthMode ? 0x08 : 0) | + _state.Divisor + ); + break; + + case 4: value = _state.LengthEnabled ? 0x40 : 0; break; + } + + return value | openBusBits[addr]; + } + + void Write(uint16_t addr, uint8_t value) + { + switch(addr) { + case 0: break; + case 1: + _state.Length = 64 - (value & 0x3F); + break; + + case 2: + _state.EnvPeriod = value & 0x07; + _state.EnvRaiseVolume = (value & 0x08) != 0; + _state.EnvVolume = (value & 0xF0) >> 4; + + if(!(value & 0xF8)) { + _state.Enabled = false; + } + break; + + case 3: + _state.PeriodShift = (value & 0xF0) >> 4; + _state.ShortWidthMode = (value & 0x08) != 0; + _state.Divisor = value & 0x07; + break; + + case 4: + _state.LengthEnabled = (value & 0x40) != 0; + + if(value & 0x80) { + //Writing a value to NRx4 with bit 7 set causes the following things to occur : + + //Channel is enabled, if volume is not 0 or raise volume flag is set + _state.Enabled = _state.EnvRaiseVolume || _state.EnvVolume > 0; + + //Frequency timer is reloaded with period. + _state.Timer = GetPeriod(); + + //Noise channel's LFSR bits are all set to 1. + _state.ShiftRegister = 0x7FFF; + + //If length counter is zero, it is set to 64 (256 for wave channel). + if(_state.Length == 0) { + _state.Length = 64; + } + + //Volume envelope timer is reloaded with period. + _state.EnvTimer = _state.EnvPeriod; + + //Channel volume is reloaded from NRx2. + _state.Volume = _state.EnvVolume; + } + break; + } + } + + void Serialize(Serializer& s) override + { + s.Stream( + _state.Volume, _state.EnvVolume, _state.EnvRaiseVolume, _state.EnvPeriod, _state.EnvTimer, + _state.ShiftRegister, _state.PeriodShift, _state.Divisor, _state.ShortWidthMode, + _state.Length, _state.LengthEnabled, _state.Enabled, _state.Timer, _state.Output + ); + } +}; \ No newline at end of file diff --git a/Core/GbPpu.cpp b/Core/GbPpu.cpp new file mode 100644 index 0000000..f5b669d --- /dev/null +++ b/Core/GbPpu.cpp @@ -0,0 +1,378 @@ +#include "stdafx.h" +#include "GbPpu.h" +#include "GbTypes.h" +#include "EventType.h" +#include "Console.h" +#include "Gameboy.h" +#include "VideoDecoder.h" +#include "RewindManager.h" +#include "GbMemoryManager.h" +#include "NotificationManager.h" +#include "../Utilities/Serializer.h" + +void GbPpu::Init(Console* console, Gameboy* gameboy, GbMemoryManager* memoryManager, uint8_t* vram, uint8_t* oam) +{ + _console = console; + _gameboy = gameboy; + _memoryManager = memoryManager; + _vram = vram; + _oam = oam; + + _state = {}; + _state.Mode = PpuMode::HBlank; + _drawModeLength = (_state.ScrollX & 0x07) + 160 + 8 * 5; + _lastFrameTime = 0; + + _outputBuffers[0] = new uint16_t[256 * 240]; + _outputBuffers[1] = new uint16_t[256 * 240]; + memset(_outputBuffers[0], 0, 256 * 240 * sizeof(uint16_t)); + memset(_outputBuffers[1], 0, 256 * 240 * sizeof(uint16_t)); + _currentBuffer = _outputBuffers[0]; + +#ifndef USEBOOTROM + Write(0xFF40, 0x91); + Write(0xFF42, 0x00); + Write(0xFF43, 0x00); + Write(0xFF45, 0x00); + Write(0xFF47, 0xFC); + Write(0xFF48, 0xFF); + Write(0xFF49, 0xFF); + Write(0xFF4A, 0); + Write(0xFF4B, 0); +#endif +} + +GbPpu::~GbPpu() +{ +} + +GbPpuState GbPpu::GetState() +{ + return _state; +} + +void GbPpu::Exec() +{ + if(!_state.LcdEnabled) { + //LCD is disabled, prevent IRQs, etc. + //Not quite correct in terms of frame pacing + if(_gameboy->GetCycleCount() - _lastFrameTime > 70224) { + //More than a full frame's worth of time has passed since the last frame, send another blank frame + _lastFrameTime = _gameboy->GetCycleCount(); + SendFrame(); + } + return; + } + + ExecCycle(); + ExecCycle(); + ExecCycle(); + ExecCycle(); +} + +void GbPpu::ExecCycle() +{ + _state.Cycle++; + if(_state.Cycle == 456) { + _state.Cycle = 0; + + _state.Scanline++; + + if(_state.Scanline == 144) { + _state.Mode = PpuMode::VBlank; + _memoryManager->RequestIrq(GbIrqSource::VerticalBlank); + + if(_state.Status & GbPpuStatusFlags::VBlankIrq) { + _memoryManager->RequestIrq(GbIrqSource::LcdStat); + } + + SendFrame(); + } else if(_state.Scanline == 154) { + _console->ProcessEvent(EventType::StartFrame); + _state.Scanline = 0; + } + + if(_state.Scanline < 144) { + _state.Mode = PpuMode::OamEvaluation; + _drawModeLength = (_state.ScrollX & 0x07) + 160 + 8 * 5; + + if(_state.Status & GbPpuStatusFlags::OamIrq) { + _memoryManager->RequestIrq(GbIrqSource::LcdStat); + } + } + + if(_state.LyCompare == _state.Scanline && (_state.Status & GbPpuStatusFlags::CoincidenceIrq)) { + _memoryManager->RequestIrq(GbIrqSource::LcdStat); + } + } + + _console->ProcessPpuCycle(_state.Scanline, _state.Cycle); + + //TODO: Dot-based renderer, currently draws at the end of the scanline + if(_state.Scanline < 144) { + if(_state.Cycle < 80) { + if(_state.Cycle == 79) { + _state.Mode = PpuMode::Drawing; + } + } else if(_state.Mode == PpuMode::Drawing) { + _drawModeLength--; + if(_drawModeLength == 0) { + _state.Mode = PpuMode::HBlank; + if(_state.Status & GbPpuStatusFlags::HBlankIrq) { + _memoryManager->RequestIrq(GbIrqSource::LcdStat); + } + RenderScanline(); + } + } + } +} + +void GbPpu::GetPalette(uint16_t out[4], uint8_t palCfg) +{ + constexpr uint16_t rgbPalette[4] = { 0x7FFF, 0x6318, 0x318C, 0x0000 }; + out[0] = rgbPalette[palCfg & 0x03]; + out[1] = rgbPalette[(palCfg >> 2) & 0x03]; + out[2] = rgbPalette[(palCfg >> 4) & 0x03]; + out[3] = rgbPalette[(palCfg >> 6) & 0x03]; +} + +void GbPpu::RenderScanline() +{ + uint16_t bgColors[4]; + uint16_t oamColors[2][4]; + GetPalette(bgColors, _state.BgPalette); + GetPalette(oamColors[0], _state.ObjPalette0); + GetPalette(oamColors[1], _state.ObjPalette1); + + uint8_t visibleSprites[10] = {}; + uint8_t spriteCount = 0; + for(uint8_t i = 0; i < 0xA0; i += 4) { + int16_t sprY = (int16_t)_oam[i] - 16; + if(_state.Scanline >= sprY && _state.Scanline < sprY + (_state.LargeSprites ? 16 : 8)) { + visibleSprites[spriteCount] = i; + spriteCount++; + if(spriteCount == 10) { + break; + } + } + } + + if(spriteCount > 1) { + //Sort sprites by their X position first, and then by their index when X values are equal + std::sort(visibleSprites, visibleSprites + spriteCount, [=](uint8_t a, uint8_t b) { + if(_oam[a + 1] == _oam[b + 1]) { + return a < b; + } else { + return _oam[a + 1] < _oam[b + 1]; + } + }); + } + + uint8_t xOffset; + uint8_t yOffset; + uint16_t tilemapAddr; + uint16_t baseTile = _state.BgTileSelect ? 0 : 0x1000; + + for(int x = 0; x < 160; x++) { + uint8_t bgColor = 0; + uint16_t outOffset = _state.Scanline * 256 + x; + if(_state.BgEnabled) { + + if(_state.WindowEnabled && x >= _state.WindowX - 7 && _state.Scanline >= _state.WindowY) { + //Draw window content instead + tilemapAddr = _state.WindowTilemapSelect ? 0x1C00 : 0x1800; + xOffset = x - (_state.WindowX - 7); + yOffset = _state.Scanline - _state.WindowY; + } else { + //Draw regular tilemap + tilemapAddr = _state.BgTilemapSelect ? 0x1C00 : 0x1800; + xOffset = _state.ScrollX + x; + yOffset = _state.ScrollY + _state.Scanline; + } + + uint8_t row = yOffset >> 3; + uint8_t tileY = yOffset & 0x07; + uint8_t column = xOffset >> 3; + uint8_t tileIndex = _vram[tilemapAddr + column + row * 32]; + + uint16_t tileRowAddr = baseTile + (baseTile ? (int8_t)tileIndex * 16 : tileIndex * 16) + tileY * 2; + uint8_t shift = 7 - (xOffset & 0x07); + bgColor = ((_vram[tileRowAddr] >> shift) & 0x01) | (((_vram[tileRowAddr + 1] >> shift) & 0x01) << 1); + } + _currentBuffer[outOffset] = bgColors[bgColor]; + + if(_state.SpritesEnabled && spriteCount) { + for(int i = 0; i < spriteCount; i++) { + uint8_t sprIndex = visibleSprites[i]; + int16_t sprX = (int16_t)_oam[sprIndex + 1] - 8; + if(x >= sprX && x < sprX + 8) { + int16_t sprY = (int16_t)_oam[sprIndex] - 16; + uint8_t sprTile = _oam[sprIndex + 2]; + bool bgPriority = (_oam[sprIndex + 3] & 0x80); + bool vMirror = (_oam[sprIndex + 3] & 0x40); + bool hMirror = (_oam[sprIndex + 3] & 0x20); + bool palette = (_oam[sprIndex + 3] & 0x10); + + uint8_t sprOffsetY = vMirror ? (_state.LargeSprites ? 15 : 7) - (_state.Scanline - sprY) : (_state.Scanline - sprY); + if(_state.LargeSprites) { + sprTile &= 0xFE; + } + uint8_t sprShiftX = hMirror ? (x - sprX) : 7 - (x - sprX); + + uint16_t sprTileAddr = sprTile * 16 + sprOffsetY * 2; + uint8_t sprColor = ((_vram[sprTileAddr] >> sprShiftX) & 0x01) | (((_vram[sprTileAddr + 1] >> sprShiftX) & 0x01) << 1); + if(sprColor > 0 && (bgColor == 0 || !bgPriority)) { + _currentBuffer[outOffset] = oamColors[(int)palette][sprColor]; + break; + } + } + } + } + } +} + +void GbPpu::SendFrame() +{ + _console->ProcessEvent(EventType::EndFrame); + _state.FrameCount++; + _console->GetNotificationManager()->SendNotification(ConsoleNotificationType::PpuFrameDone); + +#ifdef LIBRETRO + _console->GetVideoDecoder()->UpdateFrameSync(_currentBuffer, 256, 239, _state.FrameCount, false); +#else + if(_console->GetRewindManager()->IsRewinding()) { + _console->GetVideoDecoder()->UpdateFrameSync(_currentBuffer, 256, 239, _state.FrameCount, true); + } else { + _console->GetVideoDecoder()->UpdateFrame(_currentBuffer, 256, 239, _state.FrameCount); + } +#endif + + //TODO move this somewhere that makes more sense + uint8_t prevInput = _memoryManager->ReadInputPort(); + _console->ProcessEndOfFrame(); + uint8_t newInput = _memoryManager->ReadInputPort(); + if(prevInput != newInput) { + _memoryManager->RequestIrq(GbIrqSource::Joypad); + } + + _currentBuffer = _currentBuffer == _outputBuffers[0] ? _outputBuffers[1] : _outputBuffers[0]; +} + +uint8_t GbPpu::Read(uint16_t addr) +{ + switch(addr) { + case 0xFF40: return _state.Control; + case 0xFF41: + //FF41 - STAT - LCDC Status (R/W) + return ( + (_state.Status & 0xF8) | + ((_state.LyCompare == _state.Scanline) ? 0x04 : 0x00) | + (int)_state.Mode + ); + + case 0xFF42: return _state.ScrollY; //FF42 - SCY - Scroll Y (R/W) + case 0xFF43: return _state.ScrollX; //FF43 - SCX - Scroll X (R/W) + case 0xFF44: return _state.Scanline; //FF44 - LY - LCDC Y-Coordinate (R) + case 0xFF45: return _state.LyCompare; //FF45 - LYC - LY Compare (R/W) + case 0xFF47: return _state.BgPalette; //FF47 - BGP - BG Palette Data (R/W) - Non CGB Mode Only + case 0xFF48: return _state.ObjPalette0; //FF48 - OBP0 - Object Palette 0 Data (R/W) - Non CGB Mode Only + case 0xFF49: return _state.ObjPalette1; //FF49 - OBP1 - Object Palette 1 Data (R/W) - Non CGB Mode Only + case 0xFF4A: return _state.WindowY; //FF4A - WY - Window Y Position (R/W) + case 0xFF4B: return _state.WindowX; //FF4B - WX - Window X Position minus 7 (R/W) + } + return 0; +} + +void GbPpu::Write(uint16_t addr, uint8_t value) +{ + switch(addr) { + case 0xFF40: + _state.Control = value; + if(_state.LcdEnabled != ((value & 0x80) != 0)) { + _state.LcdEnabled = (value & 0x80) != 0; + + if(!_state.LcdEnabled) { + //Reset LCD to top of screen when it gets turned on + _state.Cycle = 0; + _state.Scanline = 0; + _state.Mode = PpuMode::HBlank; + + //Send a blank (white) frame + _lastFrameTime = _gameboy->GetCycleCount(); + std::fill(_currentBuffer, _currentBuffer + 256 * 239, 0x7FFF); + SendFrame(); + } + } + _state.WindowTilemapSelect = (value & 0x40) != 0; + _state.WindowEnabled = (value & 0x20) != 0; + _state.BgTileSelect = (value & 0x10) != 0; + _state.BgTilemapSelect = (value & 0x08) != 0; + _state.LargeSprites = (value & 0x04) != 0; + _state.SpritesEnabled = (value & 0x02) != 0; + _state.BgEnabled = (value & 0x01) != 0; + break; + + case 0xFF41: _state.Status = value & 0xF8; break; + case 0xFF42: _state.ScrollY = value; break; + case 0xFF43: _state.ScrollX = value; break; + case 0xFF45: _state.LyCompare = value; break; + + case 0xFF46: + //OAM DMA - TODO, restrict CPU accesses to high ram during this? + for(int i = 0; i < 0xA0; i++) { + WriteOam(i, _memoryManager->Read((value << 8) | i, MemoryOperationType::DmaRead)); + } + break; + + case 0xFF47: _state.BgPalette = value; break; + case 0xFF48: _state.ObjPalette0 = value; break; + case 0xFF49: _state.ObjPalette1 = value; break; + case 0xFF4A: _state.WindowY = value; break; + case 0xFF4B: _state.WindowX = value; break; + } +} + +uint8_t GbPpu::ReadVram(uint16_t addr) +{ + if((int)_state.Mode <= (int)PpuMode::OamEvaluation) { + return _vram[addr & 0x1FFF]; + } else { + return 0xFF; + } +} + +void GbPpu::WriteVram(uint16_t addr, uint8_t value) +{ + if((int)_state.Mode <= (int)PpuMode::OamEvaluation) { + _vram[addr & 0x1FFF] = value; + } +} + +uint8_t GbPpu::ReadOam(uint8_t addr) +{ + if(addr < 0xA0) { + if((int)_state.Mode <= (int)PpuMode::VBlank) { + return _oam[addr]; + } else { + return 0xFF; + } + } + return 0; +} + +void GbPpu::WriteOam(uint8_t addr, uint8_t value) +{ + if(addr < 0xA0 && (int)_state.Mode <= (int)PpuMode::VBlank) { + _oam[addr] = value; + } +} + +void GbPpu::Serialize(Serializer& s) +{ + s.Stream( + _state.Scanline, _state.Cycle, _state.Mode, _state.LyCompare, _state.BgPalette, _state.ObjPalette0, _state.ObjPalette1, + _state.ScrollX, _state.ScrollY, _state.WindowX, _state.WindowY, _state.Control, _state.LcdEnabled, _state.WindowTilemapSelect, + _state.WindowEnabled, _state.BgTileSelect, _state.BgTilemapSelect, _state.LargeSprites, _state.SpritesEnabled, _state.BgEnabled, + _state.Status, _state.FrameCount, _lastFrameTime + ); +} diff --git a/Core/GbPpu.h b/Core/GbPpu.h new file mode 100644 index 0000000..dde1d1c --- /dev/null +++ b/Core/GbPpu.h @@ -0,0 +1,50 @@ +#pragma once +#include "stdafx.h" +#include "GbTypes.h" +#include "../Utilities/ISerializable.h" + +class Console; +class Gameboy; +class GbMemoryManager; + +class GbPpu : public ISerializable +{ +private: + Console* _console = nullptr; + Gameboy* _gameboy = nullptr; + GbPpuState _state = {}; + GbMemoryManager* _memoryManager = nullptr; + uint16_t* _outputBuffers[2] = {}; + uint16_t* _currentBuffer; + uint8_t* _vram = nullptr; + uint8_t* _oam = nullptr; + + uint64_t _lastFrameTime = 0; + uint16_t _drawModeLength = 140; + + void ExecCycle(); + void RenderScanline(); + +public: + virtual ~GbPpu(); + + void Init(Console* console, Gameboy* gameboy, GbMemoryManager* memoryManager, uint8_t* vram, uint8_t* oam); + + GbPpuState GetState(); + void GetPalette(uint16_t out[4], uint8_t palCfg); + + void Exec(); + + void SendFrame(); + + uint8_t Read(uint16_t addr); + void Write(uint16_t addr, uint8_t value); + + uint8_t ReadVram(uint16_t addr); + void WriteVram(uint16_t addr, uint8_t value); + + uint8_t ReadOam(uint8_t addr); + void WriteOam(uint8_t addr, uint8_t value); + + void Serialize(Serializer& s) override; +}; diff --git a/Core/GbSquareChannel.h b/Core/GbSquareChannel.h new file mode 100644 index 0000000..68eadae --- /dev/null +++ b/Core/GbSquareChannel.h @@ -0,0 +1,241 @@ +#pragma once +#include "stdafx.h" +#include "GbTypes.h" +#include "../Utilities/ISerializable.h" +#include "../Utilities/Serializer.h" + +class GbSquareChannel : public ISerializable +{ +private: + const uint8_t _dutySequences[4][8] = { + { 0, 1, 1, 1, 1, 1, 1, 1 }, + { 0, 0, 1, 1, 1, 1, 1, 1 }, + { 0, 0, 0, 0, 1, 1, 1, 1 }, + { 0, 0, 0, 0, 0, 0, 1, 1 } + }; + + GbSquareState _state = {}; + +public: + GbSquareState GetState() + { + return _state; + } + + bool Enabled() + { + return _state.Enabled; + } + + void Disable() + { + uint8_t len = _state.Length; + _state = {}; + _state.Length = len; + } + + void ClockSweepUnit() + { + if(!_state.SweepEnabled) { + return; + } + + if(_state.SweepTimer > 0 && _state.SweepPeriod > 0) { + _state.SweepTimer--; + if(_state.SweepTimer == 0) { + _state.SweepTimer = _state.SweepPeriod; + + //When it generates a clock and the sweep's internal enabled flag is set and the sweep period is not zero, a new frequency is calculated and the overflow + uint16_t newFreq = GetSweepTargetFrequency(); + + if(_state.SweepShift > 0 && newFreq < 2048) { + //If the new frequency is 2047 or less and the sweep shift is not zero, this new frequency is written back to the shadow frequency and square 1's frequency in NR13 and NR14, + _state.Frequency = _state.SweepFreq; + _state.SweepFreq = newFreq; + + newFreq = GetSweepTargetFrequency(); + if(newFreq >= 2048) { + //then frequency calculation and overflow check are run AGAIN immediately using this new value, but this second new frequency is not written back. + _state.SweepEnabled = false; + _state.Enabled = false; + } + } else { + _state.SweepEnabled = false; + _state.Enabled = false; + } + } + } + } + + uint16_t GetSweepTargetFrequency() + { + uint16_t shiftResult = (_state.SweepFreq >> _state.SweepShift); + if(_state.SweepNegate) { + return _state.SweepFreq - shiftResult; + } else { + return _state.SweepFreq + shiftResult; + } + } + + void ClockLengthCounter() + { + if(_state.LengthEnabled && _state.Length > 0) { + _state.Length--; + if(_state.Length == 0) { + //"Length becoming 0 should clear status" + _state.Enabled = false; + } + } + } + + void ClockEnvelope() + { + if(_state.EnvTimer > 0) { + _state.EnvTimer--; + + if(_state.EnvTimer == 0) { + if(_state.EnvRaiseVolume && _state.Volume < 0x0F) { + _state.Volume++; + } else if(!_state.EnvRaiseVolume && _state.Volume > 0) { + _state.Volume--; + } + + _state.EnvTimer = _state.EnvPeriod; + } + } + } + + uint8_t GetOutput() + { + return _state.Output; + } + + void Exec(uint32_t clocksToRun) + { + _state.Timer -= clocksToRun; + if(_state.Enabled) { + _state.Output = _dutySequences[_state.Duty][_state.DutyPos] * _state.Volume; + } else { + _state.Output = 0; + } + + + if(_state.Timer == 0) { + _state.Timer = (2048 - _state.Frequency) * 4; + _state.DutyPos = (_state.DutyPos + 1) & 0x07; + } + } + + uint8_t Read(uint16_t addr) + { + constexpr uint8_t openBusBits[5] = { 0x80, 0x3F, 0x00, 0xFF, 0xBF }; + + uint8_t value = 0; + switch(addr) { + case 0: + value = ( + (_state.SweepPeriod << 4) | + (_state.SweepNegate ? 0x08 : 0) | + _state.SweepShift + ); + break; + + case 1: value = _state.Duty << 6; break; + + case 2: + value = ( + (_state.EnvVolume << 4) | + (_state.EnvRaiseVolume ? 0x08 : 0) | + _state.EnvPeriod + ); + break; + + case 4: value = _state.LengthEnabled ? 0x40 : 0; break; + } + + return value | openBusBits[addr]; + } + + void Write(uint16_t addr, uint8_t value) + { + switch(addr) { + case 0: + _state.SweepShift = value & 0x07; + _state.SweepNegate = (value & 0x08) != 0; + _state.SweepPeriod = (value & 0x70) >> 4; + break; + + case 1: + _state.Length = 64 - (value & 0x3F); + _state.Duty = (value & 0xC0) >> 6; + break; + + case 2: + _state.EnvPeriod = value & 0x07; + _state.EnvRaiseVolume = (value & 0x08) != 0; + _state.EnvVolume = (value & 0xF0) >> 4; + + if(!(value & 0xF8)) { + _state.Enabled = false; + } + break; + + case 3: + _state.Frequency = (_state.Frequency & 0x700) | value; + break; + + case 4: + _state.Frequency = (_state.Frequency & 0xFF) | ((value & 0x07) << 8); + _state.LengthEnabled = (value & 0x40) != 0; + + if(value & 0x80) { + //Writing a value to NRx4 with bit 7 set causes the following things to occur : + + //Channel is enabled, if volume is not 0 or raise volume flag is set + _state.Enabled = _state.EnvRaiseVolume || _state.EnvVolume > 0; + + //Frequency timer is reloaded with period. + _state.Timer = (2048 - _state.Frequency) * 4; + + //If length counter is zero, it is set to 64 (256 for wave channel). + if(_state.Length == 0) { + _state.Length = 64; + } + + //Volume envelope timer is reloaded with period. + _state.EnvTimer = _state.EnvPeriod; + + //Channel volume is reloaded from NRx2. + _state.Volume = _state.EnvVolume; + + //Sweep-related + //During a trigger event, several things occur: + //Square 1's frequency is copied to the shadow register. + //The sweep timer is reloaded. + //The internal enabled flag is set if either the sweep period or shift are non-zero, cleared otherwise. + //If the sweep shift is non-zero, frequency calculation and the overflow check are performed immediately. + _state.SweepFreq = _state.Frequency; + _state.SweepTimer = _state.SweepPeriod; + _state.SweepEnabled = _state.SweepPeriod > 0 || _state.SweepShift > 0; + + if(_state.SweepShift > 0) { + _state.SweepFreq = GetSweepTargetFrequency(); + if(_state.SweepFreq > 2047) { + _state.SweepEnabled = false; + _state.Enabled = false; + } + } + } + break; + } + } + + void Serialize(Serializer& s) override + { + s.Stream( + _state.SweepPeriod, _state.SweepNegate, _state.SweepShift, _state.SweepTimer, _state.SweepEnabled, _state.SweepFreq, + _state.Volume, _state.EnvVolume, _state.EnvRaiseVolume, _state.EnvPeriod, _state.EnvTimer, _state.Duty, _state.Frequency, + _state.Length, _state.LengthEnabled, _state.Enabled, _state.Timer, _state.DutyPos, _state.Output + ); + } +}; \ No newline at end of file diff --git a/Core/GbTimer.cpp b/Core/GbTimer.cpp new file mode 100644 index 0000000..6882b02 --- /dev/null +++ b/Core/GbTimer.cpp @@ -0,0 +1,81 @@ +#include "stdafx.h" +#include "GbTimer.h" +#include "GbTypes.h" +#include "GbMemoryManager.h" +#include "GbApu.h" + +GbTimer::GbTimer(GbMemoryManager* memoryManager, GbApu* apu) +{ + _apu = apu; + _memoryManager = memoryManager; +} + +GbTimer::~GbTimer() +{ +} + +void GbTimer::Exec() +{ + uint16_t newValue = _divider + 4; + if(_timerEnabled && !(newValue & _timerDivider) && (_divider & _timerDivider)) { + _counter++; + if(_counter == 0) { + _counter = _modulo; + _memoryManager->RequestIrq(GbIrqSource::Timer); + } + } + + if(!(newValue & 0x1000) && (_divider & 0x1000)) { + _apu->ClockFrameSequencer(); + } + + _divider = newValue; +} + +uint8_t GbTimer::Read(uint16_t addr) +{ + switch(addr) { + case 0xFF04: return _divider >> 8; + case 0xFF05: return _counter; //FF05 - TIMA - Timer counter (R/W) + case 0xFF06: return _modulo; //FF06 - TMA - Timer Modulo (R/W) + case 0xFF07: return _control; //FF07 - TAC - Timer Control (R/W) + } + return 0; +} + +void GbTimer::Write(uint16_t addr, uint8_t value) +{ + //TODO properly detect edges when setting new values to registers or disabling timer, etc. + switch(addr) { + case 0xFF04: + _divider = 0; + break; + + case 0xFF05: + //FF05 - TIMA - Timer counter (R/W) + _counter = value; + break; + + case 0xFF06: + //FF06 - TMA - Timer Modulo (R/W) + _modulo = value; + break; + + case 0xFF07: + //FF07 - TAC - Timer Control (R/W) + _control = value; + _timerEnabled = (value & 0x04) != 0; + switch(value & 0x03) { + case 0: _timerDivider = 1 << 9; break; + case 1: _timerDivider = 1 << 3; break; + case 2: _timerDivider = 1 << 5; break; + case 3: _timerDivider = 1 << 7; break; + } + break; + } +} + +void GbTimer::Serialize(Serializer& s) +{ + s.Stream(_divider, _counter, _modulo, _control, _timerEnabled, _timerDivider); +} diff --git a/Core/GbTimer.h b/Core/GbTimer.h new file mode 100644 index 0000000..39c2f25 --- /dev/null +++ b/Core/GbTimer.h @@ -0,0 +1,34 @@ +#pragma once +#include "stdafx.h" +#include "../Utilities/ISerializable.h" +#include "../Utilities/Serializer.h" + +class GbMemoryManager; +class GbApu; + +class GbTimer : public ISerializable +{ +private: + GbMemoryManager* _memoryManager; + GbApu* _apu; + + uint16_t _divider = 0; + + uint8_t _counter = 0; + uint8_t _modulo = 0; + + uint8_t _control = 0; + bool _timerEnabled = false; + uint16_t _timerDivider = 1024; + +public: + GbTimer(GbMemoryManager* memoryManager, GbApu* apu); + virtual ~GbTimer(); + + void Exec(); + + uint8_t Read(uint16_t addr); + void Write(uint16_t addr, uint8_t value); + + void Serialize(Serializer& s) override; +}; \ No newline at end of file diff --git a/Core/GbTypes.h b/Core/GbTypes.h new file mode 100644 index 0000000..5455b51 --- /dev/null +++ b/Core/GbTypes.h @@ -0,0 +1,272 @@ +#pragma once +#include "stdafx.h" +#include "SnesMemoryType.h" + +struct GbCpuState +{ + uint64_t CycleCount; + uint16_t PC; + uint16_t SP; + + uint8_t A; + uint8_t Flags; + + uint8_t B; + uint8_t C; + uint8_t D; + uint8_t E; + + uint8_t H; + uint8_t L; + + bool IME; + bool Halted; +}; + +namespace GbCpuFlags +{ + enum GbCpuFlags + { + Zero = 0x80, + AddSub = 0x40, + HalfCarry = 0x20, + Carry = 0x10 + }; +} + +namespace GbIrqSource +{ + enum GbIrqSource + { + VerticalBlank = 0x01, + LcdStat = 0x02, + Timer = 0x04, + Serial = 0x08, + Joypad = 0x10 + }; +} + +class Register16 +{ + uint8_t* _low; + uint8_t* _high; + +public: + Register16(uint8_t* high, uint8_t* low) + { + _high = high; + _low = low; + } + + uint16_t Read() + { + return (*_high << 8) | *_low; + } + + void Write(uint16_t value) + { + *_high = (uint8_t)(value >> 8); + *_low = (uint8_t)value; + } + + void Inc() + { + Write(Read() + 1); + } + + void Dec() + { + Write(Read() - 1); + } + + operator uint16_t() { return Read(); } +}; + +enum class PpuMode +{ + HBlank, + VBlank, + OamEvaluation, + Drawing +}; + +namespace GbPpuStatusFlags +{ + enum GbPpuStatusFlags + { + CoincidenceIrq = 0x40, + OamIrq = 0x20, + VBlankIrq = 0x10, + HBlankIrq = 0x08 + }; +} + +struct GbPpuState +{ + uint8_t Scanline; + uint16_t Cycle; + PpuMode Mode; + + uint8_t LyCompare; + uint8_t BgPalette; + uint8_t ObjPalette0; + uint8_t ObjPalette1; + uint8_t ScrollX; + uint8_t ScrollY; + uint8_t WindowX; + uint8_t WindowY; + + uint8_t Control; + bool LcdEnabled; + bool WindowTilemapSelect; + bool WindowEnabled; + bool BgTileSelect; + bool BgTilemapSelect; + bool LargeSprites; + bool SpritesEnabled; + bool BgEnabled; + + uint8_t Status; + uint32_t FrameCount; +}; + +struct GbSquareState +{ + uint16_t SweepPeriod; + bool SweepNegate; + uint8_t SweepShift; + + uint16_t SweepTimer; + bool SweepEnabled; + uint16_t SweepFreq; + + uint8_t Volume; + uint8_t EnvVolume; + bool EnvRaiseVolume; + uint8_t EnvPeriod; + uint8_t EnvTimer; + + uint8_t Duty; + uint16_t Frequency; + + uint8_t Length; + bool LengthEnabled; + + bool Enabled; + uint16_t Timer; + uint8_t DutyPos; + uint8_t Output; +}; + +struct GbNoiseState +{ + uint8_t Volume; + uint8_t EnvVolume; + bool EnvRaiseVolume; + uint8_t EnvPeriod; + uint8_t EnvTimer; + + uint8_t Length; + bool LengthEnabled; + + uint16_t ShiftRegister; + + uint8_t PeriodShift; + uint8_t Divisor; + bool ShortWidthMode; + + bool Enabled; + uint32_t Timer; + uint8_t Output; +}; + +struct GbWaveState +{ + bool DacEnabled; + + uint8_t SampleBuffer; + uint8_t Ram[0x10]; + uint8_t Position; + + uint8_t Volume; + uint16_t Frequency; + + uint16_t Length; + bool LengthEnabled; + + bool Enabled; + uint16_t Timer; + uint8_t Output; +}; + +struct GbApuState +{ + bool ApuEnabled; + + uint8_t EnableLeftSq1; + uint8_t EnableLeftSq2; + uint8_t EnableLeftWave; + uint8_t EnableLeftNoise; + + uint8_t EnableRightSq1; + uint8_t EnableRightSq2; + uint8_t EnableRightWave; + uint8_t EnableRightNoise; + + uint8_t LeftVolume; + uint8_t RightVolume; + + bool ExtAudioLeftEnabled; + bool ExtAudioRightEnabled; + + uint8_t FrameSequenceStep; +}; + +struct GbApuDebugState +{ + GbApuState Common; + GbSquareState Square1; + GbSquareState Square2; + GbWaveState Wave; + GbNoiseState Noise; +}; + +enum class RegisterAccess +{ + None = 0, + Read = 1, + Write = 2, + ReadWrite = 3 +}; + +enum class GbMemoryType +{ + None = 0, + PrgRom = (int)SnesMemoryType::GbPrgRom, + WorkRam = (int)SnesMemoryType::GbWorkRam, + CartRam = (int)SnesMemoryType::GbCartRam, +}; + +struct GbMemoryManagerState +{ + bool DisableBootRom; + uint8_t IrqRequests; + uint8_t IrqEnabled; + uint8_t InputSelect; + + bool IsReadRegister[0x100]; + bool IsWriteRegister[0x100]; + + GbMemoryType MemoryType[0x100]; + uint32_t MemoryOffset[0x100]; + RegisterAccess MemoryAccessType[0x100]; +}; + +struct GbState +{ + GbCpuState Cpu; + GbPpuState Ppu; + GbApuDebugState Apu; + GbMemoryManagerState MemoryManager; + bool HasBattery; +}; diff --git a/Core/GbWaveChannel.h b/Core/GbWaveChannel.h new file mode 100644 index 0000000..5670130 --- /dev/null +++ b/Core/GbWaveChannel.h @@ -0,0 +1,154 @@ +#pragma once +#include "stdafx.h" +#include "GbTypes.h" +#include "../Utilities/ISerializable.h" +#include "../Utilities/Serializer.h" + +class GbWaveChannel : public ISerializable +{ +private: + GbWaveState _state = {}; + +public: + GbWaveState GetState() + { + return _state; + } + + bool Enabled() + { + return _state.Enabled; + } + + void Disable() + { + uint16_t len = _state.Length; + uint8_t ram[0x10]; + memcpy(ram, _state.Ram, sizeof(ram)); + _state = {}; + _state.Length = len; + memcpy(_state.Ram, ram, sizeof(ram)); + } + + void ClockLengthCounter() + { + if(_state.LengthEnabled && _state.Length > 0) { + _state.Length--; + if(_state.Length == 0) { + //"Length becoming 0 should clear status" + _state.Enabled = false; + } + } + } + + uint8_t GetOutput() + { + return _state.Output; + } + + void Exec(uint32_t clocksToRun) + { + _state.Timer -= clocksToRun; + + //The DAC receives the current value from the upper/lower nibble of the sample buffer, shifted right by the volume control. + if(_state.Volume && _state.Enabled) { + _state.Output = _state.SampleBuffer >> (_state.Volume - 1); + } else { + _state.Output = 0; + } + + if(_state.Timer == 0) { + //The wave channel's frequency timer period is set to (2048-frequency)*2. + _state.Timer = (2048 - _state.Frequency) * 2; + + //When the timer generates a clock, the position counter is advanced one sample in the wave table, + //looping back to the beginning when it goes past the end, + _state.Position = (_state.Position + 1) & 0x1F; + + //then a sample is read into the sample buffer from this NEW position. + if(_state.Position & 0x01) { + _state.SampleBuffer = _state.Ram[_state.Position >> 1] & 0x0F; + } else { + _state.SampleBuffer = _state.Ram[_state.Position >> 1] >> 4; + } + } + } + + uint8_t Read(uint16_t addr) + { + constexpr uint8_t openBusBits[5] = { 0x7F, 0xFF, 0x9F, 0xFF, 0xBF }; + + uint8_t value = 0; + switch(addr) { + case 0: value = _state.DacEnabled ? 0x80 : 0; break; + case 2: value = _state.Volume << 5; break; + case 4: value = _state.LengthEnabled ? 0x40 : 0; break; + } + + return value | openBusBits[addr]; + } + + void Write(uint16_t addr, uint8_t value) + { + switch(addr) { + case 0: + _state.DacEnabled = (value & 0x80) != 0; + _state.Enabled &= _state.DacEnabled; + break; + + case 1: + _state.Length = 256 - value; + break; + + case 2: + _state.Volume = (value & 0x60) >> 5; + break; + + case 3: + _state.Frequency = (_state.Frequency & 0x700) | value; + break; + + case 4: + _state.Frequency = (_state.Frequency & 0xFF) | ((value & 0x07) << 8); + _state.LengthEnabled = (value & 0x40) != 0; + + if(value & 0x80) { + //Start playback + + //Channel is enabled, if DAC is enabled + _state.Enabled = _state.DacEnabled; + + //Frequency timer is reloaded with period. + _state.Timer = (2048 - _state.Frequency) * 2; + + //If length counter is zero, it is set to 64 (256 for wave channel). + if(_state.Length == 0) { + _state.Length = 256; + } + + //Wave channel's position is set to 0 but sample buffer is NOT refilled. + _state.Position = 0; + } + break; + } + } + + void WriteRam(uint16_t addr, uint8_t value) + { + _state.Ram[addr & 0x0F] = value; + } + + uint8_t ReadRam(uint16_t addr) + { + return _state.Ram[addr & 0x0F]; + } + + void Serialize(Serializer& s) override + { + s.Stream( + _state.DacEnabled, _state.SampleBuffer, _state.Position, _state.Volume, _state.Frequency, + _state.Length, _state.LengthEnabled, _state.Enabled, _state.Timer, _state.Output + ); + s.StreamArray(_state.Ram, 0x10); + } +}; \ No newline at end of file diff --git a/Core/GsuDebugger.cpp b/Core/GsuDebugger.cpp index 917f47a..12fd5c1 100644 --- a/Core/GsuDebugger.cpp +++ b/Core/GsuDebugger.cpp @@ -59,7 +59,7 @@ void GsuDebugger::ProcessRead(uint32_t addr, uint8_t value, MemoryOperationType _debugger->GetState(debugState, true); debugState.Gsu.R[15] = addr; - DisassemblyInfo disInfo = _disassembler->GetDisassemblyInfo(addressInfo); + DisassemblyInfo disInfo = _disassembler->GetDisassemblyInfo(addressInfo, addr, 0, CpuType::Gsu); _traceLogger->Log(CpuType::Gsu, debugState, disInfo); } } diff --git a/Core/LabelManager.cpp b/Core/LabelManager.cpp index 7c624f5..17c1193 100644 --- a/Core/LabelManager.cpp +++ b/Core/LabelManager.cpp @@ -56,6 +56,10 @@ int64_t LabelManager::GetLabelKey(uint32_t absoluteAddr, SnesMemoryType memType) case SnesMemoryType::BsxPsRam: return absoluteAddr | ((uint64_t)9 << 32); case SnesMemoryType::BsxMemoryPack: return absoluteAddr | ((uint64_t)10 << 32); case SnesMemoryType::DspProgramRom: return absoluteAddr | ((uint64_t)11 << 32); + case SnesMemoryType::GbPrgRom: return absoluteAddr | ((uint64_t)12 << 32); + case SnesMemoryType::GbWorkRam: return absoluteAddr | ((uint64_t)13 << 32); + case SnesMemoryType::GbCartRam: return absoluteAddr | ((uint64_t)14 << 32); + case SnesMemoryType::GbHighRam: return absoluteAddr | ((uint64_t)15 << 32); default: return -1; } } @@ -74,6 +78,10 @@ SnesMemoryType LabelManager::GetKeyMemoryType(uint64_t key) case ((uint64_t)9 << 32): return SnesMemoryType::BsxPsRam; break; case ((uint64_t)10 << 32): return SnesMemoryType::BsxMemoryPack; break; case ((uint64_t)11 << 32): return SnesMemoryType::DspProgramRom; break; + case ((uint64_t)12 << 32): return SnesMemoryType::GbPrgRom; break; + case ((uint64_t)13 << 32): return SnesMemoryType::GbWorkRam; break; + case ((uint64_t)14 << 32): return SnesMemoryType::GbCartRam; break; + case ((uint64_t)15 << 32): return SnesMemoryType::GbHighRam; break; } throw std::runtime_error("Invalid label key"); diff --git a/Core/LuaApi.cpp b/Core/LuaApi.cpp index bcff914..4d4085b 100644 --- a/Core/LuaApi.cpp +++ b/Core/LuaApi.cpp @@ -108,16 +108,24 @@ int LuaApi::GetLibrary(lua_State *lua) lua_pushintvalue(spc, SnesMemoryType::SpcMemory); lua_pushintvalue(sa1, SnesMemoryType::Sa1Memory); lua_pushintvalue(gsu, SnesMemoryType::GsuMemory); + lua_pushintvalue(cx4, SnesMemoryType::Cx4Memory); + lua_pushintvalue(gameboy, SnesMemoryType::GameboyMemory); lua_pushintvalue(cgram, SnesMemoryType::CGRam); lua_pushintvalue(vram, SnesMemoryType::VideoRam); lua_pushintvalue(oam, SnesMemoryType::SpriteRam); lua_pushintvalue(prgRom, SnesMemoryType::PrgRom); lua_pushintvalue(workRam, SnesMemoryType::WorkRam); lua_pushintvalue(saveRam, SnesMemoryType::SaveRam); + lua_pushintvalue(gbPrgRom, SnesMemoryType::GbPrgRom); + lua_pushintvalue(gbWorkRam, SnesMemoryType::GbWorkRam); + lua_pushintvalue(gbCartRam, SnesMemoryType::GbCartRam); + lua_pushintvalue(gbVideoRam, SnesMemoryType::GbVideoRam); lua_pushintvalue(cpuDebug, SnesMemoryType::CpuMemory | 0x100); lua_pushintvalue(spcDebug, SnesMemoryType::SpcMemory | 0x100); lua_pushintvalue(sa1Debug, SnesMemoryType::Sa1Memory | 0x100); lua_pushintvalue(gsuDebug, SnesMemoryType::GsuMemory | 0x100); + lua_pushintvalue(cx4Debug, SnesMemoryType::Cx4Memory | 0x100); + lua_pushintvalue(gameboyDebug, SnesMemoryType::GameboyMemory | 0x100); lua_settable(lua, -3); lua_pushliteral(lua, "memCallbackType"); @@ -132,7 +140,24 @@ int LuaApi::GetLibrary(lua_State *lua) lua_pushintvalue(prgRom, SnesMemoryType::PrgRom); lua_pushintvalue(workRam, SnesMemoryType::WorkRam); lua_pushintvalue(saveRam, SnesMemoryType::SaveRam); - //TODO add more + lua_pushintvalue(videoRam, SnesMemoryType::VideoRam); + lua_pushintvalue(spriteRam, SnesMemoryType::SpriteRam); + lua_pushintvalue(cgRam, SnesMemoryType::CGRam); + lua_pushintvalue(spcRam, SnesMemoryType::SpcRam); + lua_pushintvalue(spcRom, SnesMemoryType::SpcRom); + lua_pushintvalue(dspProgramRom, SnesMemoryType::DspProgramRom); + lua_pushintvalue(dspDataRom, SnesMemoryType::DspDataRom); + lua_pushintvalue(dspDataRam, SnesMemoryType::DspDataRam); + lua_pushintvalue(sa1InternalRam, SnesMemoryType::Sa1InternalRam); + lua_pushintvalue(gsuWorkRam, SnesMemoryType::GsuWorkRam); + lua_pushintvalue(cx4DataRam, SnesMemoryType::Cx4DataRam); + lua_pushintvalue(bsxPsRam, SnesMemoryType::BsxPsRam); + lua_pushintvalue(bsxMemoryPack, SnesMemoryType::BsxMemoryPack); + lua_pushintvalue(gbPrgRom, SnesMemoryType::GbPrgRom); + lua_pushintvalue(gbWorkRam, SnesMemoryType::GbWorkRam); + lua_pushintvalue(gbCartRam, SnesMemoryType::GbCartRam); + lua_pushintvalue(gbVideoRam, SnesMemoryType::GbVideoRam); + lua_pushintvalue(gbHighRam, SnesMemoryType::GbHighRam); lua_settable(lua, -3); lua_pushliteral(lua, "counterOpType"); @@ -164,6 +189,17 @@ int LuaApi::GetLibrary(lua_State *lua) lua_pushintvalue(ppuCycles, StepType::PpuStep); lua_settable(lua, -3); + lua_pushliteral(lua, "cpuType"); + lua_newtable(lua); + lua_pushintvalue(cpu, CpuType::Cpu); + lua_pushintvalue(spc, CpuType::Spc); + lua_pushintvalue(dsp, CpuType::NecDsp); + lua_pushintvalue(sa1, CpuType::Sa1); + lua_pushintvalue(gsu, CpuType::Gsu); + lua_pushintvalue(cx4, CpuType::Cx4); + lua_pushintvalue(gameboy, CpuType::Gameboy); + lua_settable(lua, -3); + return 1; } @@ -266,10 +302,11 @@ int LuaApi::GetPrgRomOffset(lua_State *lua) int LuaApi::RegisterMemoryCallback(lua_State *lua) { LuaCallHelper l(lua); - l.ForceParamCount(4); + l.ForceParamCount(5); + CpuType cpuType = (CpuType)l.ReadInteger((int)CpuType::Cpu); int32_t endAddr = l.ReadInteger(-1); int32_t startAddr = l.ReadInteger(); - CallbackType type = (CallbackType)l.ReadInteger(); + CallbackType callbackType = (CallbackType)l.ReadInteger(); int reference = l.GetReference(); checkminparams(3); @@ -278,9 +315,10 @@ int LuaApi::RegisterMemoryCallback(lua_State *lua) } errorCond(startAddr > endAddr, "start address must be <= end address"); - errorCond(type < CallbackType::CpuRead || type > CallbackType::CpuExec, "the specified type is invalid"); + errorCond(callbackType < CallbackType::CpuRead || callbackType > CallbackType::CpuExec, "the specified type is invalid"); + errorCond(cpuType < CpuType::Cpu || cpuType > CpuType::Gameboy, "the cpu type is invalid"); errorCond(reference == LUA_NOREF, "the specified function could not be found"); - _context->RegisterMemoryCallback(type, startAddr, endAddr, reference); + _context->RegisterMemoryCallback(callbackType, startAddr, endAddr, cpuType, reference); _context->Log("Registered memory callback from $" + HexUtilities::ToHex((uint32_t)startAddr) + " to $" + HexUtilities::ToHex((uint32_t)endAddr)); l.Return(reference); return l.ReturnCount(); @@ -289,8 +327,9 @@ int LuaApi::RegisterMemoryCallback(lua_State *lua) int LuaApi::UnregisterMemoryCallback(lua_State *lua) { LuaCallHelper l(lua); - l.ForceParamCount(4); + l.ForceParamCount(5); + CpuType cpuType = (CpuType)l.ReadInteger((int)CpuType::Cpu); int endAddr = l.ReadInteger(-1); int startAddr = l.ReadInteger(); CallbackType type = (CallbackType)l.ReadInteger(); @@ -305,7 +344,7 @@ int LuaApi::UnregisterMemoryCallback(lua_State *lua) errorCond(startAddr > endAddr, "start address must be <= end address"); errorCond(type < CallbackType::CpuRead || type > CallbackType::CpuExec, "the specified type is invalid"); errorCond(reference == LUA_NOREF, "function reference is invalid"); - _context->UnregisterMemoryCallback(type, startAddr, endAddr, reference); + _context->UnregisterMemoryCallback(type, startAddr, endAddr, cpuType, reference); return l.ReturnCount(); } @@ -347,7 +386,7 @@ int LuaApi::DrawString(lua_State *lua) int x = l.ReadInteger(); checkminparams(3); - int startFrame = _ppu->GetFrameCount() + displayDelay; + int startFrame = _console->GetFrameCount() + displayDelay; _console->GetDebugHud()->DrawString(x, y, text, color, backColor, frameCount, startFrame); return l.ReturnCount(); @@ -366,7 +405,7 @@ int LuaApi::DrawLine(lua_State *lua) int x = l.ReadInteger(); checkminparams(4); - int startFrame = _ppu->GetFrameCount() + displayDelay; + int startFrame = _console->GetFrameCount() + displayDelay; _console->GetDebugHud()->DrawLine(x, y, x2, y2, color, frameCount, startFrame); return l.ReturnCount(); @@ -383,7 +422,7 @@ int LuaApi::DrawPixel(lua_State *lua) int x = l.ReadInteger(); checkminparams(3); - int startFrame = _ppu->GetFrameCount() + displayDelay; + int startFrame = _console->GetFrameCount() + displayDelay; _console->GetDebugHud()->DrawPixel(x, y, color, frameCount, startFrame); return l.ReturnCount(); @@ -403,7 +442,7 @@ int LuaApi::DrawRectangle(lua_State *lua) int x = l.ReadInteger(); checkminparams(4); - int startFrame = _ppu->GetFrameCount() + displayDelay; + int startFrame = _console->GetFrameCount() + displayDelay; _console->GetDebugHud()->DrawRectangle(x, y, width, height, color, fill, frameCount, startFrame); return l.ReturnCount(); @@ -445,7 +484,7 @@ int LuaApi::SetScreenBuffer(lua_State *lua) pixels[i] = l.ReadInteger() ^ 0xFF000000; } - int startFrame = _ppu->GetFrameCount(); + int startFrame = _console->GetFrameCount(); _console->GetDebugHud()->DrawScreenBuffer(pixels, startFrame); return l.ReturnCount(); @@ -777,21 +816,21 @@ int LuaApi::GetState(lua_State *lua) lua_newtable(lua); lua_pushintvalue(activeLayers, ( - state.Ppu.Window[i].ActiveLayers[0] | - (state.Ppu.Window[i].ActiveLayers[1] << 1) | - (state.Ppu.Window[i].ActiveLayers[2] << 2) | - (state.Ppu.Window[i].ActiveLayers[3] << 3) | - (state.Ppu.Window[i].ActiveLayers[4] << 4) | - (state.Ppu.Window[i].ActiveLayers[5] << 5) + (uint8_t)state.Ppu.Window[i].ActiveLayers[0] | + ((uint8_t)state.Ppu.Window[i].ActiveLayers[1] << 1) | + ((uint8_t)state.Ppu.Window[i].ActiveLayers[2] << 2) | + ((uint8_t)state.Ppu.Window[i].ActiveLayers[3] << 3) | + ((uint8_t)state.Ppu.Window[i].ActiveLayers[4] << 4) | + ((uint8_t)state.Ppu.Window[i].ActiveLayers[5] << 5) )); lua_pushintvalue(invertedLayers, ( - state.Ppu.Window[i].InvertedLayers[0] | - (state.Ppu.Window[i].InvertedLayers[1] << 1) | - (state.Ppu.Window[i].InvertedLayers[2] << 2) | - (state.Ppu.Window[i].InvertedLayers[3] << 3) | - (state.Ppu.Window[i].InvertedLayers[4] << 4) | - (state.Ppu.Window[i].InvertedLayers[5] << 5) + (uint8_t)state.Ppu.Window[i].InvertedLayers[0] | + ((uint8_t)state.Ppu.Window[i].InvertedLayers[1] << 1) | + ((uint8_t)state.Ppu.Window[i].InvertedLayers[2] << 2) | + ((uint8_t)state.Ppu.Window[i].InvertedLayers[3] << 3) | + ((uint8_t)state.Ppu.Window[i].InvertedLayers[4] << 4) | + ((uint8_t)state.Ppu.Window[i].InvertedLayers[5] << 5) )); lua_pushintvalue(left, state.Ppu.Window[i].Left); diff --git a/Core/LuaScriptingContext.cpp b/Core/LuaScriptingContext.cpp index 04a43c3..e7cbae9 100644 --- a/Core/LuaScriptingContext.cpp +++ b/Core/LuaScriptingContext.cpp @@ -95,9 +95,9 @@ bool LuaScriptingContext::LoadScript(string scriptName, string scriptContent, De return false; } -void LuaScriptingContext::UnregisterMemoryCallback(CallbackType type, int startAddr, int endAddr, int reference) +void LuaScriptingContext::UnregisterMemoryCallback(CallbackType type, int startAddr, int endAddr, CpuType cpuType, int reference) { - ScriptingContext::UnregisterMemoryCallback(type, startAddr, endAddr, reference); + ScriptingContext::UnregisterMemoryCallback(type, startAddr, endAddr, cpuType, reference); luaL_unref(_lua, LUA_REGISTRYINDEX, reference); } @@ -107,7 +107,7 @@ void LuaScriptingContext::UnregisterEventCallback(EventType type, int reference) luaL_unref(_lua, LUA_REGISTRYINDEX, reference); } -void LuaScriptingContext::InternalCallMemoryCallback(uint32_t addr, uint8_t &value, CallbackType type) +void LuaScriptingContext::InternalCallMemoryCallback(uint32_t addr, uint8_t &value, CallbackType type, CpuType cpuType) { if(_callbacks[(int)type].empty()) { return; @@ -118,7 +118,7 @@ void LuaScriptingContext::InternalCallMemoryCallback(uint32_t addr, uint8_t &val lua_sethook(_lua, LuaScriptingContext::ExecutionCountHook, LUA_MASKCOUNT, 1000); LuaApi::SetContext(this); for(MemoryCallback &callback: _callbacks[(int)type]) { - if(addr < callback.StartAddress || addr > callback.EndAddress) { + if(callback.Type != cpuType || addr < callback.StartAddress || addr > callback.EndAddress) { continue; } diff --git a/Core/LuaScriptingContext.h b/Core/LuaScriptingContext.h index 0eef96a..acc148e 100644 --- a/Core/LuaScriptingContext.h +++ b/Core/LuaScriptingContext.h @@ -1,5 +1,6 @@ #pragma once #include "stdafx.h" +#include "DebugTypes.h" #include "ScriptingContext.h" #include "EventType.h" #include "../Utilities/Timer.h" @@ -19,7 +20,7 @@ private: static void ExecutionCountHook(lua_State* lua, lua_Debug* ar); protected: - void InternalCallMemoryCallback(uint32_t addr, uint8_t &value, CallbackType type) override; + void InternalCallMemoryCallback(uint32_t addr, uint8_t &value, CallbackType type, CpuType cpuType) override; int InternalCallEventCallback(EventType type) override; public: @@ -30,6 +31,6 @@ public: bool LoadScript(string scriptName, string scriptContent, Debugger* debugger) override; - void UnregisterMemoryCallback(CallbackType type, int startAddr, int endAddr, int reference) override; + void UnregisterMemoryCallback(CallbackType type, int startAddr, int endAddr, CpuType cpuType, int reference) override; void UnregisterEventCallback(EventType type, int reference) override; }; diff --git a/Core/MemoryAccessCounter.cpp b/Core/MemoryAccessCounter.cpp index 64f5890..b234fcf 100644 --- a/Core/MemoryAccessCounter.cpp +++ b/Core/MemoryAccessCounter.cpp @@ -8,6 +8,7 @@ #include "Sa1.h" #include "Gsu.h" #include "Cx4.h" +#include "Gameboy.h" #include "BaseCartridge.h" MemoryAccessCounter::MemoryAccessCounter(Debugger* debugger, Console *console) @@ -18,6 +19,7 @@ MemoryAccessCounter::MemoryAccessCounter(Debugger* debugger, Console *console) _sa1 = console->GetCartridge()->GetSa1(); _gsu = console->GetCartridge()->GetGsu(); _cx4 = console->GetCartridge()->GetCx4(); + _gameboy = console->GetCartridge()->GetGameboy(); for(int i = (int)SnesMemoryType::PrgRom; i < (int)SnesMemoryType::Register; i++) { uint32_t memSize = _debugger->GetMemoryDumper()->GetMemorySize((SnesMemoryType)i); @@ -107,28 +109,45 @@ void MemoryAccessCounter::GetAccessCounts(uint32_t offset, uint32_t length, Snes break; case SnesMemoryType::Sa1Memory: - for(uint32_t i = 0; i < length; i++) { - AddressInfo info = _sa1->GetMemoryMappings()->GetAbsoluteAddress(offset + i); - if(info.Address >= 0) { - counts[i] = _counters[(int)info.Type][info.Address]; + if(_sa1) { + for(uint32_t i = 0; i < length; i++) { + AddressInfo info = _sa1->GetMemoryMappings()->GetAbsoluteAddress(offset + i); + if(info.Address >= 0) { + counts[i] = _counters[(int)info.Type][info.Address]; + } } } break; case SnesMemoryType::GsuMemory: - for(uint32_t i = 0; i < length; i++) { - AddressInfo info = _gsu->GetMemoryMappings()->GetAbsoluteAddress(offset + i); - if(info.Address >= 0) { - counts[i] = _counters[(int)info.Type][info.Address]; + if(_gsu) { + for(uint32_t i = 0; i < length; i++) { + AddressInfo info = _gsu->GetMemoryMappings()->GetAbsoluteAddress(offset + i); + if(info.Address >= 0) { + counts[i] = _counters[(int)info.Type][info.Address]; + } } } break; case SnesMemoryType::Cx4Memory: - for(uint32_t i = 0; i < length; i++) { - AddressInfo info = _cx4->GetMemoryMappings()->GetAbsoluteAddress(offset + i); - if(info.Address >= 0) { - counts[i] = _counters[(int)info.Type][info.Address]; + if(_cx4) { + for(uint32_t i = 0; i < length; i++) { + AddressInfo info = _cx4->GetMemoryMappings()->GetAbsoluteAddress(offset + i); + if(info.Address >= 0) { + counts[i] = _counters[(int)info.Type][info.Address]; + } + } + } + break; + + case SnesMemoryType::GameboyMemory: + if(_gameboy) { + for(uint32_t i = 0; i < length; i++) { + AddressInfo info = _gameboy->GetAbsoluteAddress(offset + i); + if(info.Address >= 0) { + counts[i] = _counters[(int)info.Type][info.Address]; + } } } break; diff --git a/Core/MemoryAccessCounter.h b/Core/MemoryAccessCounter.h index 01e6e54..30c3453 100644 --- a/Core/MemoryAccessCounter.h +++ b/Core/MemoryAccessCounter.h @@ -9,6 +9,7 @@ class Console; class Sa1; class Gsu; class Cx4; +class Gameboy; struct AddressCounters { @@ -35,6 +36,7 @@ private: Sa1* _sa1; Gsu* _gsu; Cx4* _cx4; + Gameboy* _gameboy; bool IsAddressUninitialized(AddressInfo &addressInfo); diff --git a/Core/MemoryDumper.cpp b/Core/MemoryDumper.cpp index cf436f1..32a9920 100644 --- a/Core/MemoryDumper.cpp +++ b/Core/MemoryDumper.cpp @@ -7,6 +7,8 @@ #include "Sa1.h" #include "Cx4.h" #include "Gsu.h" +#include "Gameboy.h" +#include "GbMemoryManager.h" #include "BsxCart.h" #include "BsxMemoryPack.h" #include "Console.h" @@ -40,6 +42,7 @@ void MemoryDumper::SetMemoryState(SnesMemoryType type, uint8_t *buffer, uint32_t case SnesMemoryType::Sa1Memory: case SnesMemoryType::GsuMemory: case SnesMemoryType::Cx4Memory: + case SnesMemoryType::GameboyMemory: break; case SnesMemoryType::PrgRom: memcpy(_cartridge->DebugGetPrgRom(), buffer, length); break; @@ -56,10 +59,21 @@ void MemoryDumper::SetMemoryState(SnesMemoryType type, uint8_t *buffer, uint32_t case SnesMemoryType::DspDataRam: memcpy(_cartridge->GetDsp()->DebugGetDataRam(), buffer, length); break; case SnesMemoryType::Sa1InternalRam: memcpy(_cartridge->GetSa1()->DebugGetInternalRam(), buffer, length); break; - case SnesMemoryType::GsuWorkRam: memcpy(_cartridge->GetGsu()->DebugGetWorkRam(), buffer, length); break; - case SnesMemoryType::Cx4DataRam: memcpy(_cartridge->GetCx4()->DebugGetDataRam(), buffer, length); break; - case SnesMemoryType::BsxPsRam: memcpy(_cartridge->GetBsx()->DebugGetPsRam(), buffer, length); break; - case SnesMemoryType::BsxMemoryPack: memcpy(_cartridge->GetBsxMemoryPack()->DebugGetMemoryPack(), buffer, length); break; + case SnesMemoryType::GsuWorkRam: memcpy(_cartridge->GetGsu()->DebugGetWorkRam(), buffer, length); break; + case SnesMemoryType::Cx4DataRam: memcpy(_cartridge->GetCx4()->DebugGetDataRam(), buffer, length); break; + case SnesMemoryType::BsxPsRam: memcpy(_cartridge->GetBsx()->DebugGetPsRam(), buffer, length); break; + case SnesMemoryType::BsxMemoryPack: memcpy(_cartridge->GetBsxMemoryPack()->DebugGetMemoryPack(), buffer, length); break; + + case SnesMemoryType::GbPrgRom: + case SnesMemoryType::GbWorkRam: + case SnesMemoryType::GbVideoRam: + case SnesMemoryType::GbCartRam: + case SnesMemoryType::GbHighRam: + if(_cartridge->GetGameboy()) { + memcpy(_cartridge->GetGameboy()->DebugGetMemory(type), buffer, length); + } + break; + } } @@ -72,6 +86,7 @@ uint32_t MemoryDumper::GetMemorySize(SnesMemoryType type) case SnesMemoryType::Sa1Memory: return 0x1000000; case SnesMemoryType::GsuMemory: return 0x1000000; case SnesMemoryType::Cx4Memory: return 0x1000000; + case SnesMemoryType::GameboyMemory: return 0x10000; case SnesMemoryType::PrgRom: return _cartridge->DebugGetPrgRomSize(); case SnesMemoryType::WorkRam: return MemoryManager::WorkRamSize; case SnesMemoryType::SaveRam: return _cartridge->DebugGetSaveRamSize(); @@ -86,11 +101,18 @@ uint32_t MemoryDumper::GetMemorySize(SnesMemoryType type) case SnesMemoryType::DspDataRom: return _cartridge->GetDsp() ? _cartridge->GetDsp()->DebugGetDataRomSize() : 0; case SnesMemoryType::DspDataRam: return _cartridge->GetDsp() ? _cartridge->GetDsp()->DebugGetDataRamSize() : 0; - case SnesMemoryType::Sa1InternalRam: return _cartridge->GetSa1() ? _cartridge->GetSa1()->DebugGetInternalRamSize() : 0;; - case SnesMemoryType::GsuWorkRam: return _cartridge->GetGsu() ? _cartridge->GetGsu()->DebugGetWorkRamSize() : 0;; - case SnesMemoryType::Cx4DataRam: return _cartridge->GetCx4() ? _cartridge->GetCx4()->DebugGetDataRamSize() : 0;; - case SnesMemoryType::BsxPsRam: return _cartridge->GetBsx() ? _cartridge->GetBsx()->DebugGetPsRamSize() : 0;; - case SnesMemoryType::BsxMemoryPack: return _cartridge->GetBsxMemoryPack() ? _cartridge->GetBsxMemoryPack()->DebugGetMemoryPackSize() : 0;; + case SnesMemoryType::Sa1InternalRam: return _cartridge->GetSa1() ? _cartridge->GetSa1()->DebugGetInternalRamSize() : 0; + case SnesMemoryType::GsuWorkRam: return _cartridge->GetGsu() ? _cartridge->GetGsu()->DebugGetWorkRamSize() : 0; + case SnesMemoryType::Cx4DataRam: return _cartridge->GetCx4() ? _cartridge->GetCx4()->DebugGetDataRamSize() : 0; + case SnesMemoryType::BsxPsRam: return _cartridge->GetBsx() ? _cartridge->GetBsx()->DebugGetPsRamSize() : 0; + case SnesMemoryType::BsxMemoryPack: return _cartridge->GetBsxMemoryPack() ? _cartridge->GetBsxMemoryPack()->DebugGetMemoryPackSize() : 0; + + case SnesMemoryType::GbPrgRom: + case SnesMemoryType::GbWorkRam: + case SnesMemoryType::GbVideoRam: + case SnesMemoryType::GbCartRam: + case SnesMemoryType::GbHighRam: + return _cartridge->GetGameboy() ? _cartridge->GetGameboy()->DebugGetMemorySize(type) : 0; } } @@ -112,23 +134,39 @@ void MemoryDumper::GetMemoryState(SnesMemoryType type, uint8_t *buffer) break; case SnesMemoryType::Sa1Memory: - for(int i = 0; i <= 0xFFFFFF; i+=0x1000) { - _cartridge->GetSa1()->GetMemoryMappings()->PeekBlock(i, buffer + i); + if(_cartridge->GetSa1()) { + for(int i = 0; i <= 0xFFFFFF; i += 0x1000) { + _cartridge->GetSa1()->GetMemoryMappings()->PeekBlock(i, buffer + i); + } } break; case SnesMemoryType::GsuMemory: - for(int i = 0; i <= 0xFFFFFF; i += 0x1000) { - _cartridge->GetGsu()->GetMemoryMappings()->PeekBlock(i, buffer + i); + if(_cartridge->GetGsu()) { + for(int i = 0; i <= 0xFFFFFF; i += 0x1000) { + _cartridge->GetGsu()->GetMemoryMappings()->PeekBlock(i, buffer + i); + } } break; case SnesMemoryType::Cx4Memory: - for(int i = 0; i <= 0xFFFFFF; i += 0x1000) { - _cartridge->GetCx4()->GetMemoryMappings()->PeekBlock(i, buffer + i); + if(_cartridge->GetCx4()) { + for(int i = 0; i <= 0xFFFFFF; i += 0x1000) { + _cartridge->GetCx4()->GetMemoryMappings()->PeekBlock(i, buffer + i); + } } break; + case SnesMemoryType::GameboyMemory: { + if(_cartridge->GetGameboy()) { + GbMemoryManager* memManager = _cartridge->GetGameboy()->GetMemoryManager(); + for(int i = 0; i <= 0xFFFF; i++) { + buffer[i] = memManager->DebugRead(i); + } + } + break; + } + case SnesMemoryType::PrgRom: memcpy(buffer, _cartridge->DebugGetPrgRom(), _cartridge->DebugGetPrgRomSize()); break; case SnesMemoryType::WorkRam: memcpy(buffer, _memoryManager->DebugGetWorkRam(), MemoryManager::WorkRamSize); break; case SnesMemoryType::SaveRam: memcpy(buffer, _cartridge->DebugGetSaveRam(), _cartridge->DebugGetSaveRamSize()); break; @@ -147,6 +185,16 @@ void MemoryDumper::GetMemoryState(SnesMemoryType type, uint8_t *buffer) case SnesMemoryType::Cx4DataRam: memcpy(buffer, _cartridge->GetCx4()->DebugGetDataRam(), _cartridge->GetCx4()->DebugGetDataRamSize()); break; case SnesMemoryType::BsxPsRam: memcpy(buffer, _cartridge->GetBsx()->DebugGetPsRam(), _cartridge->GetBsx()->DebugGetPsRamSize()); break; case SnesMemoryType::BsxMemoryPack: memcpy(buffer, _cartridge->GetBsxMemoryPack()->DebugGetMemoryPack(), _cartridge->GetBsxMemoryPack()->DebugGetMemoryPackSize()); break; + + case SnesMemoryType::GbPrgRom: + case SnesMemoryType::GbWorkRam: + case SnesMemoryType::GbVideoRam: + case SnesMemoryType::GbCartRam: + case SnesMemoryType::GbHighRam: + if(_cartridge->GetGameboy()) { + memcpy(buffer, _cartridge->GetGameboy()->DebugGetMemory(type), _cartridge->GetGameboy()->DebugGetMemorySize(type)); + } + break; } } @@ -186,6 +234,7 @@ void MemoryDumper::SetMemoryValue(SnesMemoryType memoryType, uint32_t address, u case SnesMemoryType::Sa1Memory: _cartridge->GetSa1()->GetMemoryMappings()->DebugWrite(address, value); break; case SnesMemoryType::GsuMemory: _cartridge->GetGsu()->GetMemoryMappings()->DebugWrite(address, value); break; case SnesMemoryType::Cx4Memory: _cartridge->GetCx4()->GetMemoryMappings()->DebugWrite(address, value); break; + case SnesMemoryType::GameboyMemory: _cartridge->GetGameboy()->GetMemoryManager()->DebugWrite(address, value); break; case SnesMemoryType::PrgRom: _cartridge->DebugGetPrgRom()[address] = value; invalidateCache(); break; case SnesMemoryType::WorkRam: _memoryManager->DebugGetWorkRam()[address] = value; invalidateCache(); break; @@ -206,6 +255,16 @@ void MemoryDumper::SetMemoryValue(SnesMemoryType memoryType, uint32_t address, u case SnesMemoryType::Cx4DataRam: _cartridge->GetCx4()->DebugGetDataRam()[address] = value; break; case SnesMemoryType::BsxPsRam: _cartridge->GetBsx()->DebugGetPsRam()[address] = value; break; case SnesMemoryType::BsxMemoryPack: _cartridge->GetBsxMemoryPack()->DebugGetMemoryPack()[address] = value; break; + + case SnesMemoryType::GbPrgRom: + case SnesMemoryType::GbWorkRam: + case SnesMemoryType::GbVideoRam: + case SnesMemoryType::GbCartRam: + case SnesMemoryType::GbHighRam: + if(_cartridge->GetGameboy()) { + _cartridge->GetGameboy()->DebugGetMemory(memoryType)[address] = value; + } + break; } } @@ -223,6 +282,7 @@ uint8_t MemoryDumper::GetMemoryValue(SnesMemoryType memoryType, uint32_t address case SnesMemoryType::Sa1Memory: return _cartridge->GetSa1()->GetMemoryMappings()->Peek(address); case SnesMemoryType::GsuMemory: return _cartridge->GetGsu()->GetMemoryMappings()->Peek(address); case SnesMemoryType::Cx4Memory: return _cartridge->GetCx4()->GetMemoryMappings()->Peek(address); + case SnesMemoryType::GameboyMemory: return _cartridge->GetGameboy()->GetMemoryManager()->DebugRead(address); case SnesMemoryType::PrgRom: return _cartridge->DebugGetPrgRom()[address]; case SnesMemoryType::WorkRam: return _memoryManager->DebugGetWorkRam()[address]; @@ -243,6 +303,13 @@ uint8_t MemoryDumper::GetMemoryValue(SnesMemoryType memoryType, uint32_t address case SnesMemoryType::Cx4DataRam: return _cartridge->GetCx4()->DebugGetDataRam()[address]; case SnesMemoryType::BsxPsRam: return _cartridge->GetBsx()->DebugGetPsRam()[address]; case SnesMemoryType::BsxMemoryPack: return _cartridge->GetBsxMemoryPack()->DebugGetMemoryPack()[address]; + + case SnesMemoryType::GbPrgRom: + case SnesMemoryType::GbWorkRam: + case SnesMemoryType::GbVideoRam: + case SnesMemoryType::GbCartRam: + case SnesMemoryType::GbHighRam: + return _cartridge->GetGameboy() ? _cartridge->GetGameboy()->DebugGetMemory(memoryType)[address] : 0; } } diff --git a/Core/MemoryManager.cpp b/Core/MemoryManager.cpp index 29bcd2c..84f8226 100644 --- a/Core/MemoryManager.cpp +++ b/Core/MemoryManager.cpp @@ -219,7 +219,7 @@ void MemoryManager::Exec() } if((_hClock & 0x03) == 0) { - _console->ProcessPpuCycle(); + _console->ProcessPpuCycle(_ppu->GetScanline(), _hClock); _regs->ProcessIrqCounters(); if(_hClock == 276 * 4 && _ppu->GetScanline() < _ppu->GetVblankStart()) { @@ -236,7 +236,7 @@ void MemoryManager::Exec() _cpu->IncreaseCycleCount<5>(); } } else if((_hClock & 0x03) == 0) { - _console->ProcessPpuCycle(); + _console->ProcessPpuCycle(_ppu->GetScanline(), _hClock); _regs->ProcessIrqCounters(); } diff --git a/Core/Multitap.cpp b/Core/Multitap.cpp index 63042f5..5f1b8df 100644 --- a/Core/Multitap.cpp +++ b/Core/Multitap.cpp @@ -1,7 +1,6 @@ #include "stdafx.h" #include "Multitap.h" #include "InternalRegisters.h" -#include "Ppu.h" #include "SnesController.h" string Multitap::GetKeyNames() @@ -29,7 +28,7 @@ void Multitap::InternalSetStateFromInput() SetPressedState(Buttons::Right + offset, keyMapping.Right); uint8_t turboFreq = 1 << (4 - _turboSpeed[i]); - bool turboOn = (uint8_t)(_ppu->GetFrameCount() % turboFreq) < turboFreq / 2; + bool turboOn = (uint8_t)(_console->GetFrameCount() % turboFreq) < turboFreq / 2; if(turboOn) { SetPressedState(Buttons::A + offset, keyMapping.TurboA); SetPressedState(Buttons::B + offset, keyMapping.TurboB); @@ -93,9 +92,8 @@ void Multitap::RefreshStateBuffer() } } -Multitap::Multitap(Console * console, uint8_t port, KeyMappingSet keyMappings1, KeyMappingSet keyMappings2, KeyMappingSet keyMappings3, KeyMappingSet keyMappings4) : BaseControlDevice(console, port, keyMappings1) +Multitap::Multitap(Console* console, uint8_t port, KeyMappingSet keyMappings1, KeyMappingSet keyMappings2, KeyMappingSet keyMappings3, KeyMappingSet keyMappings4) : BaseControlDevice(console, port, keyMappings1) { - _ppu = console->GetPpu().get(); _turboSpeed[0] = keyMappings1.TurboSpeed; _turboSpeed[1] = keyMappings2.TurboSpeed; _turboSpeed[2] = keyMappings3.TurboSpeed; diff --git a/Core/Multitap.h b/Core/Multitap.h index 2eecca8..e84b8c8 100644 --- a/Core/Multitap.h +++ b/Core/Multitap.h @@ -3,7 +3,6 @@ #include "BaseControlDevice.h" #include "../Utilities/Serializer.h" -class Ppu; class InternalRegisters; class SnesController; @@ -13,7 +12,6 @@ private: enum Buttons { A = 0, B, X, Y, L, R, Select, Start, Up, Down, Left, Right }; static constexpr int ButtonCount = 12; - Ppu *_ppu; vector _mappings[4]; uint8_t _turboSpeed[4] = {}; uint16_t _stateBuffer[4] = {}; diff --git a/Core/NecDspDebugger.cpp b/Core/NecDspDebugger.cpp index e510571..f8768c1 100644 --- a/Core/NecDspDebugger.cpp +++ b/Core/NecDspDebugger.cpp @@ -43,7 +43,7 @@ void NecDspDebugger::ProcessRead(uint16_t addr, uint8_t value, MemoryOperationTy DebugState debugState; _debugger->GetState(debugState, true); - DisassemblyInfo disInfo = _disassembler->GetDisassemblyInfo(addressInfo); + DisassemblyInfo disInfo = _disassembler->GetDisassemblyInfo(addressInfo, addr, 0, CpuType::NecDsp); _traceLogger->Log(CpuType::NecDsp, debugState, disInfo); } } diff --git a/Core/PpuTools.cpp b/Core/PpuTools.cpp index 66c6dd9..a838db0 100644 --- a/Core/PpuTools.cpp +++ b/Core/PpuTools.cpp @@ -7,6 +7,9 @@ #include "MemoryManager.h" #include "NotificationManager.h" #include "DefaultVideoFilter.h" +#include "Gameboy.h" +#include "GbTypes.h" +#include "GbPpu.h" PpuTools::PpuTools(Console *console, Ppu *ppu) { @@ -371,4 +374,38 @@ void PpuTools::UpdateViewers(uint16_t scanline, uint16_t cycle) _console->GetNotificationManager()->SendNotification(ConsoleNotificationType::ViewerRefresh, (void*)(uint64_t)updateTiming.first); } } -} \ No newline at end of file +} + +void PpuTools::GetGameboyTilemap(uint8_t* vram, uint16_t offset, uint32_t* outBuffer) +{ + GbPpu* ppu = _console->GetCartridge()->GetGameboy()->GetPpu(); + GbPpuState state = ppu->GetState(); + + uint16_t palette[4]; + ppu->GetPalette(palette, state.BgPalette); + + uint16_t baseTile = state.BgTileSelect ? 0 : 0x1000; + + std::fill(outBuffer, outBuffer + 1024*256, 0xFFFFFFFF); + + for(int row = 0; row < 32; row++) { + uint16_t baseOffset = offset + ((row & 0x1F) << 5); + + for(int column = 0; column < 32; column++) { + uint16_t addr = (baseOffset + column); + uint8_t tileIndex = vram[addr]; + uint16_t tileStart = baseTile + (baseTile ? (int8_t)tileIndex*16 : tileIndex*16); + for(int y = 0; y < 8; y++) { + uint16_t pixelStart = tileStart + y * 2; + for(int x = 0; x < 8; x++) { + uint8_t shift = 7 - (x & 0x07); + uint8_t color = GetTilePixelColor(vram, 0x1FFF, 2, pixelStart, shift); + + if(color != 0) { + outBuffer[((row * 8) + y) * 1024 + column * 8 + x] = DefaultVideoFilter::ToArgb(palette[color]); + } + } + } + } + } +} diff --git a/Core/PpuTools.h b/Core/PpuTools.h index dd1be43..5b32577 100644 --- a/Core/PpuTools.h +++ b/Core/PpuTools.h @@ -28,4 +28,6 @@ public: void SetViewerUpdateTiming(uint32_t viewerId, uint16_t scanline, uint16_t cycle); void RemoveViewer(uint32_t viewerId); void UpdateViewers(uint16_t scanline, uint16_t cycle); + + void GetGameboyTilemap(uint8_t* vram, uint16_t offset, uint32_t* outBuffer); }; \ No newline at end of file diff --git a/Core/Profiler.cpp b/Core/Profiler.cpp index a295edb..bcd2a3e 100644 --- a/Core/Profiler.cpp +++ b/Core/Profiler.cpp @@ -4,7 +4,6 @@ #include "DebugBreakHelper.h" #include "Debugger.h" #include "Console.h" -#include "MemoryManager.h" #include "MemoryDumper.h" #include "DebugTypes.h" @@ -13,7 +12,7 @@ static constexpr int32_t ResetFunctionIndex = -1; Profiler::Profiler(Debugger* debugger) { _debugger = debugger; - _memoryManager = debugger->GetConsole()->GetMemoryManager().get(); + _console = debugger->GetConsole().get(); InternalReset(); } @@ -46,7 +45,7 @@ void Profiler::StackFunction(AddressInfo &addr, StackFrameFlags stackFlag) void Profiler::UpdateCycles() { - uint64_t masterClock = _memoryManager->GetMasterClock(); + uint64_t masterClock = _console->GetMasterClock(); ProfiledFunction& func = _functions[_currentFunction]; uint64_t clockGap = masterClock - _prevMasterClock; @@ -94,7 +93,7 @@ void Profiler::Reset() void Profiler::InternalReset() { - _prevMasterClock = _memoryManager->GetMasterClock(); + _prevMasterClock = _console->GetMasterClock(); _currentCycleCount = 0; _currentFunction = ResetFunctionIndex; _functionStack.clear(); diff --git a/Core/Profiler.h b/Core/Profiler.h index f50fece..4cbb0cb 100644 --- a/Core/Profiler.h +++ b/Core/Profiler.h @@ -3,7 +3,7 @@ #include "DebugTypes.h" class Debugger; -class MemoryManager; +class Console; struct ProfiledFunction { @@ -19,7 +19,7 @@ class Profiler { private: Debugger* _debugger; - MemoryManager* _memoryManager; + Console* _console; unordered_map _functions; diff --git a/Core/SPC_DSP.cpp b/Core/SPC_DSP.cpp index b56e6f1..c0f6889 100644 --- a/Core/SPC_DSP.cpp +++ b/Core/SPC_DSP.cpp @@ -136,17 +136,17 @@ inline int SPC_DSP::interpolate_cubic(voice_t const* v) { int const* in = &v->buf[(v->interp_pos >> 12) + v->buf_pos]; - float v0 = in[0] / 32768.0; - float v1 = in[1] / 32768.0; - float v2 = in[2] / 32768.0; - float v3 = in[3] / 32768.0; + float v0 = in[0] / 32768.0f; + float v1 = in[1] / 32768.0f; + float v2 = in[2] / 32768.0f; + float v3 = in[3] / 32768.0f; float a = (v3 - v2) - (v0 - v1); float b = (v0 - v1) - a; float c = v2 - v0; float d = v1; - float ratio = (double)(v->interp_pos & 0xFFF) / 0x1000; + float ratio = (float)(v->interp_pos & 0xFFF) / 0x1000; return (int)((d + ratio * (c + ratio * (b + ratio * a))) * 32768); } diff --git a/Core/ScriptHost.cpp b/Core/ScriptHost.cpp index 8aa8498..4e9e3c9 100644 --- a/Core/ScriptHost.cpp +++ b/Core/ScriptHost.cpp @@ -35,13 +35,13 @@ bool ScriptHost::LoadScript(string scriptName, string scriptContent, Debugger* d #endif } -void ScriptHost::ProcessMemoryOperation(uint32_t addr, uint8_t &value, MemoryOperationType type) +void ScriptHost::ProcessMemoryOperation(uint32_t addr, uint8_t &value, MemoryOperationType type, CpuType cpuType) { if(_context) { switch(type) { - case MemoryOperationType::Read: _context->CallMemoryCallback(addr, value, CallbackType::CpuRead); break; - case MemoryOperationType::Write: _context->CallMemoryCallback(addr, value, CallbackType::CpuWrite); break; - case MemoryOperationType::ExecOpCode: _context->CallMemoryCallback(addr, value, CallbackType::CpuExec); break; + case MemoryOperationType::Read: _context->CallMemoryCallback(addr, value, CallbackType::CpuRead, cpuType); break; + case MemoryOperationType::Write: _context->CallMemoryCallback(addr, value, CallbackType::CpuWrite, cpuType); break; + case MemoryOperationType::ExecOpCode: _context->CallMemoryCallback(addr, value, CallbackType::CpuExec, cpuType); break; default: break; } } diff --git a/Core/ScriptHost.h b/Core/ScriptHost.h index 8e436df..ce52c89 100644 --- a/Core/ScriptHost.h +++ b/Core/ScriptHost.h @@ -20,7 +20,7 @@ public: bool LoadScript(string scriptName, string scriptContent, Debugger* debugger); - void ProcessMemoryOperation(uint32_t addr, uint8_t &value, MemoryOperationType type); + void ProcessMemoryOperation(uint32_t addr, uint8_t &value, MemoryOperationType type, CpuType cpuType); void ProcessEvent(EventType eventType); bool ProcessSavestate(); diff --git a/Core/ScriptManager.cpp b/Core/ScriptManager.cpp index 6d7777d..c330d9c 100644 --- a/Core/ScriptManager.cpp +++ b/Core/ScriptManager.cpp @@ -77,11 +77,11 @@ void ScriptManager::ProcessEvent(EventType type) } } -void ScriptManager::ProcessMemoryOperation(uint32_t address, uint8_t &value, MemoryOperationType type) +void ScriptManager::ProcessMemoryOperation(uint32_t address, uint8_t &value, MemoryOperationType type, CpuType cpuType) { if(_hasScript) { for(shared_ptr &script : _scripts) { - script->ProcessMemoryOperation(address, value, type); + script->ProcessMemoryOperation(address, value, type, cpuType); } } } diff --git a/Core/ScriptManager.h b/Core/ScriptManager.h index b5b571f..ebaaccc 100644 --- a/Core/ScriptManager.h +++ b/Core/ScriptManager.h @@ -2,6 +2,7 @@ #include "stdafx.h" #include "../Utilities/SimpleLock.h" #include "EventType.h" +#include "DebugTypes.h" class Debugger; class ScriptHost; @@ -23,5 +24,5 @@ public: void RemoveScript(int32_t scriptId); const char* GetScriptLog(int32_t scriptId); void ProcessEvent(EventType type); - void ProcessMemoryOperation(uint32_t address, uint8_t &value, MemoryOperationType type); + void ProcessMemoryOperation(uint32_t address, uint8_t &value, MemoryOperationType type, CpuType cpuType); }; \ No newline at end of file diff --git a/Core/ScriptingContext.cpp b/Core/ScriptingContext.cpp index abc86ae..f8bc5e4 100644 --- a/Core/ScriptingContext.cpp +++ b/Core/ScriptingContext.cpp @@ -43,10 +43,10 @@ string ScriptingContext::GetScriptName() return _scriptName; } -void ScriptingContext::CallMemoryCallback(uint32_t addr, uint8_t &value, CallbackType type) +void ScriptingContext::CallMemoryCallback(uint32_t addr, uint8_t &value, CallbackType type, CpuType cpuType) { _inExecOpEvent = type == CallbackType::CpuExec; - InternalCallMemoryCallback(addr, value, type); + InternalCallMemoryCallback(addr, value, type, cpuType); _inExecOpEvent = false; } @@ -81,7 +81,7 @@ bool ScriptingContext::CheckStateLoadedFlag() return stateLoaded; } -void ScriptingContext::RegisterMemoryCallback(CallbackType type, int startAddr, int endAddr, int reference) +void ScriptingContext::RegisterMemoryCallback(CallbackType type, int startAddr, int endAddr, CpuType cpuType, int reference) { if(endAddr < startAddr) { return; @@ -95,10 +95,11 @@ void ScriptingContext::RegisterMemoryCallback(CallbackType type, int startAddr, callback.StartAddress = (uint32_t)startAddr; callback.EndAddress = (uint32_t)endAddr; callback.Reference = reference; + callback.Type = cpuType; _callbacks[(int)type].push_back(callback); } -void ScriptingContext::UnregisterMemoryCallback(CallbackType type, int startAddr, int endAddr, int reference) +void ScriptingContext::UnregisterMemoryCallback(CallbackType type, int startAddr, int endAddr, CpuType cpuType, int reference) { if(endAddr < startAddr) { return; @@ -110,7 +111,7 @@ void ScriptingContext::UnregisterMemoryCallback(CallbackType type, int startAddr for(size_t i = 0; i < _callbacks[(int)type].size(); i++) { MemoryCallback &callback = _callbacks[(int)type][i]; - if(callback.Reference == reference && (int)callback.StartAddress == startAddr && (int)callback.EndAddress == endAddr) { + if(callback.Reference == reference && callback.Type == cpuType && (int)callback.StartAddress == startAddr && (int)callback.EndAddress == endAddr) { _callbacks[(int)type].erase(_callbacks[(int)type].begin() + i); break; } diff --git a/Core/ScriptingContext.h b/Core/ScriptingContext.h index 6bf5e33..934dda9 100644 --- a/Core/ScriptingContext.h +++ b/Core/ScriptingContext.h @@ -18,6 +18,7 @@ struct MemoryCallback { uint32_t StartAddress; uint32_t EndAddress; + CpuType Type; int Reference; }; @@ -47,7 +48,7 @@ protected: vector _callbacks[3]; vector _eventCallbacks[(int)EventType::EventTypeSize]; - virtual void InternalCallMemoryCallback(uint32_t addr, uint8_t &value, CallbackType type) = 0; + virtual void InternalCallMemoryCallback(uint32_t addr, uint8_t &value, CallbackType type, CpuType cpuType) = 0; virtual int InternalCallEventCallback(EventType type) = 0; public: @@ -70,15 +71,15 @@ public: void ClearSavestateData(int slot); bool ProcessSavestate(); - void CallMemoryCallback(uint32_t addr, uint8_t &value, CallbackType type); + void CallMemoryCallback(uint32_t addr, uint8_t &value, CallbackType type, CpuType cpuType); int CallEventCallback(EventType type); bool CheckInitDone(); bool CheckInStartFrameEvent(); bool CheckInExecOpEvent(); bool CheckStateLoadedFlag(); - void RegisterMemoryCallback(CallbackType type, int startAddr, int endAddr, int reference); - virtual void UnregisterMemoryCallback(CallbackType type, int startAddr, int endAddr, int reference); + void RegisterMemoryCallback(CallbackType type, int startAddr, int endAddr, CpuType cpuType, int reference); + virtual void UnregisterMemoryCallback(CallbackType type, int startAddr, int endAddr, CpuType cpuType, int reference); void RegisterEventCallback(EventType type, int reference); virtual void UnregisterEventCallback(EventType type, int reference); }; diff --git a/Core/SettingTypes.h b/Core/SettingTypes.h index 23c77eb..67acde7 100644 --- a/Core/SettingTypes.h +++ b/Core/SettingTypes.h @@ -4,11 +4,12 @@ enum class EmulationFlags { - Turbo = 1, - Rewind = 2, - TurboOrRewind = 3, - MaximumSpeed = 4, - InBackground = 8 + Turbo = 0x01, + Rewind = 0x02, + TurboOrRewind = 0x03, + MaximumSpeed = 0x04, + InBackground = 0x08, + GameboyMode = 0x10, }; enum class ScaleFilterType @@ -493,6 +494,7 @@ enum class DebuggerFlags : uint32_t AutoResetCdl = 0x4000, + GbDebuggerEnabled = 0x02000000, Cx4DebuggerEnabled = 0x04000000, NecDspDebuggerEnabled = 0x08000000, GsuDebuggerEnabled = 0x10000000, diff --git a/Core/SnesController.cpp b/Core/SnesController.cpp index e1c23e8..12a2b62 100644 --- a/Core/SnesController.cpp +++ b/Core/SnesController.cpp @@ -1,11 +1,9 @@ #include "stdafx.h" #include "SnesController.h" #include "Console.h" -#include "Ppu.h" -SnesController::SnesController(Console * console, uint8_t port, KeyMappingSet keyMappings) : BaseControlDevice(console, port, keyMappings) +SnesController::SnesController(Console* console, uint8_t port, KeyMappingSet keyMappings) : BaseControlDevice(console, port, keyMappings) { - _ppu = console->GetPpu().get(); _turboSpeed = keyMappings.TurboSpeed; } @@ -31,7 +29,7 @@ void SnesController::InternalSetStateFromInput() SetPressedState(Buttons::Right, keyMapping.Right); uint8_t turboFreq = 1 << (4 - _turboSpeed); - bool turboOn = (uint8_t)(_ppu->GetFrameCount() % turboFreq) < turboFreq / 2; + bool turboOn = (uint8_t)(_console->GetFrameCount() % turboFreq) < turboFreq / 2; if(turboOn) { SetPressedState(Buttons::A, keyMapping.TurboA); SetPressedState(Buttons::B, keyMapping.TurboB); diff --git a/Core/SnesController.h b/Core/SnesController.h index cd90fca..f2572de 100644 --- a/Core/SnesController.h +++ b/Core/SnesController.h @@ -3,14 +3,11 @@ #include "BaseControlDevice.h" #include "../Utilities/Serializer.h" -class Ppu; - class SnesController : public BaseControlDevice { private: uint32_t _stateBuffer = 0; uint8_t _turboSpeed = 0; - Ppu *_ppu; protected: string GetKeyNames() override; diff --git a/Core/SnesMemoryType.h b/Core/SnesMemoryType.h new file mode 100644 index 0000000..2d559a9 --- /dev/null +++ b/Core/SnesMemoryType.h @@ -0,0 +1,34 @@ +#pragma once + +enum class SnesMemoryType +{ + CpuMemory, + SpcMemory, + Sa1Memory, + NecDspMemory, + GsuMemory, + Cx4Memory, + GameboyMemory, + PrgRom, + WorkRam, + SaveRam, + VideoRam, + SpriteRam, + CGRam, + SpcRam, + SpcRom, + DspProgramRom, + DspDataRom, + DspDataRam, + Sa1InternalRam, + GsuWorkRam, + Cx4DataRam, + BsxPsRam, + BsxMemoryPack, + GbPrgRom, + GbWorkRam, + GbCartRam, + GbVideoRam, + GbHighRam, + Register +}; \ No newline at end of file diff --git a/Core/SoundMixer.cpp b/Core/SoundMixer.cpp index 8bcc557..745d002 100644 --- a/Core/SoundMixer.cpp +++ b/Core/SoundMixer.cpp @@ -48,7 +48,7 @@ void SoundMixer::StopAudio(bool clearBuffer) } } -void SoundMixer::PlayAudioBuffer(int16_t* samples, uint32_t sampleCount) +void SoundMixer::PlayAudioBuffer(int16_t* samples, uint32_t sampleCount, uint32_t sourceRate) { AudioConfig cfg = _console->GetSettings()->GetAudioConfig(); @@ -71,7 +71,7 @@ void SoundMixer::PlayAudioBuffer(int16_t* samples, uint32_t sampleCount) _rightSample = samples[1]; int16_t *out = _sampleBuffer; - uint32_t count = _resampler->Resample(samples, sampleCount, cfg.SampleRate, out); + uint32_t count = _resampler->Resample(samples, sampleCount, sourceRate, cfg.SampleRate, out); shared_ptr msu1 = _console->GetMsu1(); if(msu1) { diff --git a/Core/SoundMixer.h b/Core/SoundMixer.h index 8ae15d0..e8c2e8b 100644 --- a/Core/SoundMixer.h +++ b/Core/SoundMixer.h @@ -26,7 +26,7 @@ public: SoundMixer(Console *console); ~SoundMixer(); - void PlayAudioBuffer(int16_t *samples, uint32_t sampleCount); + void PlayAudioBuffer(int16_t *samples, uint32_t sampleCount, uint32_t sourceRate); void StopAudio(bool clearBuffer = false); void RegisterAudioDevice(IAudioDevice *audioDevice); diff --git a/Core/SoundResampler.cpp b/Core/SoundResampler.cpp index fca9607..57adcf4 100644 --- a/Core/SoundResampler.cpp +++ b/Core/SoundResampler.cpp @@ -70,15 +70,15 @@ double SoundResampler::GetTargetRateAdjustment() return _rateAdjustment; } -void SoundResampler::UpdateTargetSampleRate(uint32_t sampleRate) +void SoundResampler::UpdateTargetSampleRate(uint32_t sourceRate, uint32_t sampleRate) { - double spcSampleRate = Spc::SpcSampleRate; + double spcSampleRate = sourceRate; if(_console->GetSettings()->GetVideoConfig().IntegerFpsMode) { //Adjust sample rate when running at 60.0 fps instead of 60.1 switch(_console->GetRegion()) { default: - case ConsoleRegion::Ntsc: spcSampleRate = Spc::SpcSampleRate * (60.0 / 60.0988118623484); break; - case ConsoleRegion::Pal: spcSampleRate = Spc::SpcSampleRate * (50.0 / 50.00697796826829); break; + case ConsoleRegion::Ntsc: spcSampleRate = sourceRate * (60.0 / _console->GetFps()); break; + case ConsoleRegion::Pal: spcSampleRate = sourceRate * (50.0 / _console->GetFps()); break; } } @@ -90,8 +90,8 @@ void SoundResampler::UpdateTargetSampleRate(uint32_t sampleRate) } } -uint32_t SoundResampler::Resample(int16_t *inSamples, uint32_t sampleCount, uint32_t sampleRate, int16_t *outSamples) +uint32_t SoundResampler::Resample(int16_t *inSamples, uint32_t sampleCount, uint32_t sourceRate, uint32_t sampleRate, int16_t *outSamples) { - UpdateTargetSampleRate(sampleRate); + UpdateTargetSampleRate(sourceRate, sampleRate); return _resampler.Resample(inSamples, sampleCount, outSamples); } \ No newline at end of file diff --git a/Core/SoundResampler.h b/Core/SoundResampler.h index a2dbbbe..2c7bed8 100644 --- a/Core/SoundResampler.h +++ b/Core/SoundResampler.h @@ -17,7 +17,7 @@ private: HermiteResampler _resampler; double GetTargetRateAdjustment(); - void UpdateTargetSampleRate(uint32_t sampleRate); + void UpdateTargetSampleRate(uint32_t sourceRate, uint32_t sampleRate); public: SoundResampler(Console *console); @@ -25,5 +25,5 @@ public: double GetRateAdjustment(); - uint32_t Resample(int16_t *inSamples, uint32_t sampleCount, uint32_t sampleRate, int16_t *outSamples); + uint32_t Resample(int16_t *inSamples, uint32_t sampleCount, uint32_t sourceRate, uint32_t sampleRate, int16_t *outSamples); }; \ No newline at end of file diff --git a/Core/Spc.cpp b/Core/Spc.cpp index 178f419..d9110eb 100644 --- a/Core/Spc.cpp +++ b/Core/Spc.cpp @@ -376,7 +376,7 @@ void Spc::ProcessEndFrame() int sampleCount = _dsp->sample_count(); if(sampleCount != 0) { - _console->GetSoundMixer()->PlayAudioBuffer(_soundBuffer, sampleCount / 2); + _console->GetSoundMixer()->PlayAudioBuffer(_soundBuffer, sampleCount / 2, Spc::SpcSampleRate); } _dsp->set_output(_soundBuffer, Spc::SampleBufferSize >> 1); } diff --git a/Core/SpcDebugger.cpp b/Core/SpcDebugger.cpp index d9321b5..3a68f97 100644 --- a/Core/SpcDebugger.cpp +++ b/Core/SpcDebugger.cpp @@ -12,7 +12,6 @@ #include "MemoryAccessCounter.h" #include "ExpressionEvaluator.h" #include "EmuSettings.h" -#include "Profiler.h" SpcDebugger::SpcDebugger(Debugger* debugger) { @@ -56,7 +55,7 @@ void SpcDebugger::ProcessRead(uint16_t addr, uint8_t value, MemoryOperationType DebugState debugState; _debugger->GetState(debugState, true); - DisassemblyInfo disInfo = _disassembler->GetDisassemblyInfo(addressInfo); + DisassemblyInfo disInfo = _disassembler->GetDisassemblyInfo(addressInfo, addr, 0, CpuType::Spc); _traceLogger->Log(CpuType::Spc, debugState, disInfo); } } diff --git a/Core/TraceLogger.cpp b/Core/TraceLogger.cpp index c6fbf75..dc4c827 100644 --- a/Core/TraceLogger.cpp +++ b/Core/TraceLogger.cpp @@ -22,6 +22,7 @@ TraceLogger::TraceLogger(Debugger* debugger, shared_ptr console) _settings = console->GetSettings().get(); _labelManager = debugger->GetLabelManager().get(); _memoryDumper = debugger->GetMemoryDumper().get(); + _options = {}; _currentPos = 0; _logCount = 0; _logToFile = false; @@ -76,6 +77,7 @@ void TraceLogger::SetOptions(TraceLoggerOptions options) _logCpu[(int)CpuType::Sa1] = options.LogSa1; _logCpu[(int)CpuType::Gsu] = options.LogGsu; _logCpu[(int)CpuType::Cx4] = options.LogCx4; + _logCpu[(int)CpuType::Gameboy] = options.LogGameboy; string condition = _options.Condition; string format = _options.Format; @@ -95,6 +97,7 @@ void TraceLogger::SetOptions(TraceLoggerOptions options) ParseFormatString(_dspRowParts, "[PC,4h] [ByteCode,11h] [Disassembly] [Align,65] [A,2h] S:[SP,2h] H:[Cycle,3] V:[Scanline,3]"); ParseFormatString(_gsuRowParts, "[PC,6h] [ByteCode,11h] [Disassembly] [Align,50] SRC:[X,2] DST:[Y,2] R0:[A,2h] H:[Cycle,3] V:[Scanline,3]"); ParseFormatString(_cx4RowParts, "[PC,6h] [ByteCode,11h] [Disassembly] [Align,45] [A,2h] H:[Cycle,3] V:[Scanline,3]"); + ParseFormatString(_gbRowParts, "[PC,6h] [ByteCode,11h] [Disassembly] [Align,45] A:[A,2h] B:[B,2h] C:[C,2h] D:[D,2h] E:[E,2h] HL:[H,2h][L,2h] F:[F,2h] SP:[SP,4h] CYC:[Cycle,3] LY:[Scanline,3]"); } void TraceLogger::ParseFormatString(vector &rowParts, string format) @@ -131,6 +134,20 @@ void TraceLogger::ParseFormatString(vector &rowParts, string format) part.DataType = RowDataType::PC; } else if(dataType == "A") { part.DataType = RowDataType::A; + } else if(dataType == "B") { + part.DataType = RowDataType::B; + } else if(dataType == "C") { + part.DataType = RowDataType::C; + } else if(dataType == "D") { + part.DataType = RowDataType::D; + } else if(dataType == "E") { + part.DataType = RowDataType::E; + } else if(dataType == "F") { + part.DataType = RowDataType::F; + } else if(dataType == "H") { + part.DataType = RowDataType::H; + } else if(dataType == "L") { + part.DataType = RowDataType::L; } else if(dataType == "X") { part.DataType = RowDataType::X; } else if(dataType == "Y") { @@ -323,6 +340,7 @@ void TraceLogger::GetTraceRow(string &output, CpuState &cpuState, PpuState &ppuS case RowDataType::HClock: WriteValue(output, ppuState.HClock, rowPart); break; case RowDataType::FrameCount: WriteValue(output, ppuState.FrameCount, rowPart); break; case RowDataType::CycleCount: WriteValue(output, (uint32_t)cpuState.CycleCount, rowPart); break; + default: break; } } output += _options.UseWindowsEol ? "\r\n" : "\n"; @@ -467,6 +485,39 @@ void TraceLogger::GetTraceRow(string &output, Cx4State &cx4State, PpuState &ppuS output += _options.UseWindowsEol ? "\r\n" : "\n"; } +void TraceLogger::GetTraceRow(string& output, GbCpuState& cpuState, GbPpuState& ppuState, DisassemblyInfo& disassemblyInfo) +{ + int originalSize = (int)output.size(); + uint32_t pcAddress = cpuState.PC; + for(RowPart& rowPart : _gbRowParts) { + switch(rowPart.DataType) { + case RowDataType::Text: output += rowPart.Text; break; + case RowDataType::ByteCode: WriteByteCode(disassemblyInfo, rowPart, output); break; + case RowDataType::Disassembly: WriteDisassembly(disassemblyInfo, rowPart, (uint8_t)cpuState.SP, pcAddress, output); break; + case RowDataType::EffectiveAddress: WriteEffectiveAddress(disassemblyInfo, rowPart, &cpuState, output, SnesMemoryType::GameboyMemory, CpuType::Gameboy); break; + case RowDataType::MemoryValue: WriteMemoryValue(disassemblyInfo, rowPart, &cpuState, output, SnesMemoryType::GameboyMemory, CpuType::Gameboy); break; + case RowDataType::Align: WriteAlign(originalSize, rowPart, output); break; + + case RowDataType::PC: WriteValue(output, HexUtilities::ToHex((uint16_t)pcAddress), rowPart); break; + case RowDataType::A: WriteValue(output, cpuState.A, rowPart); break; + case RowDataType::B: WriteValue(output, cpuState.B, rowPart); break; + case RowDataType::C: WriteValue(output, cpuState.C, rowPart); break; + case RowDataType::D: WriteValue(output, cpuState.D, rowPart); break; + case RowDataType::E: WriteValue(output, cpuState.E, rowPart); break; + case RowDataType::F: WriteValue(output, cpuState.Flags, rowPart); break; + case RowDataType::H: WriteValue(output, cpuState.H, rowPart); break; + case RowDataType::L: WriteValue(output, cpuState.L, rowPart); break; + case RowDataType::SP: WriteValue(output, cpuState.SP, rowPart); break; + case RowDataType::Cycle: WriteValue(output, ppuState.Cycle, rowPart); break; + case RowDataType::Scanline: WriteValue(output, ppuState.Scanline, rowPart); break; + case RowDataType::FrameCount: WriteValue(output, ppuState.FrameCount, rowPart); break; + + default: break; + } + } + output += _options.UseWindowsEol ? "\r\n" : "\n"; +} + /* bool TraceLogger::ConditionMatches(DebugState &state, DisassemblyInfo &disassemblyInfo, OperationInfo &operationInfo) { @@ -495,6 +546,7 @@ void TraceLogger::GetTraceRow(string &output, CpuType cpuType, DisassemblyInfo & case CpuType::Sa1: GetTraceRow(output, state.Sa1.Cpu, state.Ppu, disassemblyInfo, SnesMemoryType::Sa1Memory, cpuType); break; case CpuType::Gsu: GetTraceRow(output, state.Gsu, state.Ppu, disassemblyInfo); break; case CpuType::Cx4: GetTraceRow(output, state.Cx4, state.Ppu, disassemblyInfo); break; + case CpuType::Gameboy: GetTraceRow(output, state.Gameboy.Cpu, state.Gameboy.Ppu, disassemblyInfo); break; } } @@ -591,6 +643,7 @@ const char* TraceLogger::GetExecutionTrace(uint32_t lineCount) case CpuType::Sa1: _executionTrace += "\x4\x1" + HexUtilities::ToHex24((state.Sa1.Cpu.K << 16) | state.Sa1.Cpu.PC) + "\x1"; break; case CpuType::Gsu: _executionTrace += "\x4\x1" + HexUtilities::ToHex24((state.Gsu.ProgramBank << 16) | state.Gsu.R[15]) + "\x1"; break; case CpuType::Cx4: _executionTrace += "\x4\x1" + HexUtilities::ToHex24((state.Cx4.Cache.Address[state.Cx4.Cache.Page] + (state.Cx4.PC * 2)) & 0xFFFFFF) + "\x1"; break; + case CpuType::Gameboy: _executionTrace += "\x4\x1" + HexUtilities::ToHex(state.Gameboy.Cpu.PC) + "\x1"; break; } string byteCode; diff --git a/Core/TraceLogger.h b/Core/TraceLogger.h index 40f29b2..5558019 100644 --- a/Core/TraceLogger.h +++ b/Core/TraceLogger.h @@ -3,6 +3,7 @@ #include "CpuTypes.h" #include "PpuTypes.h" #include "SpcTypes.h" +#include "GbTypes.h" #include "DebugTypes.h" #include "DisassemblyInfo.h" #include "DebugUtilities.h" @@ -23,6 +24,7 @@ struct TraceLoggerOptions bool LogSa1; bool LogGsu; bool LogCx4; + bool LogGameboy; bool ShowExtraInfo; bool IndentCode; @@ -44,9 +46,15 @@ enum class RowDataType Align, PC, A, + B, + C, + D, + E, + F, + H, + L, X, Y, - D, DB, SP, PS, @@ -87,6 +95,7 @@ private: vector _dspRowParts; vector _gsuRowParts; vector _cx4RowParts; + vector _gbRowParts; bool _logCpu[(int)DebugUtilities::GetLastCpuType() + 1] = {}; @@ -124,7 +133,8 @@ private: void GetTraceRow(string &output, SpcState &cpuState, PpuState &ppuState, DisassemblyInfo &disassemblyInfo); void GetTraceRow(string &output, NecDspState &cpuState, PpuState &ppuState, DisassemblyInfo &disassemblyInfo); void GetTraceRow(string &output, GsuState &gsuState, PpuState &ppuState, DisassemblyInfo &disassemblyInfo); - void GetTraceRow(string &output, Cx4State &cx4State, PpuState &ppuState, DisassemblyInfo &disassemblyInfo); + void GetTraceRow(string& output, Cx4State& cx4State, PpuState& ppuState, DisassemblyInfo& disassemblyInfo); + void GetTraceRow(string &output, GbCpuState &gbState, GbPpuState &gbPpuState, DisassemblyInfo &disassemblyInfo); template void WriteValue(string &output, T value, RowPart& rowPart); diff --git a/InteropDLL/DebugApiWrapper.cpp b/InteropDLL/DebugApiWrapper.cpp index 6940d7b..8fe196d 100644 --- a/InteropDLL/DebugApiWrapper.cpp +++ b/InteropDLL/DebugApiWrapper.cpp @@ -91,6 +91,8 @@ extern "C" DllExport void __stdcall GetSpritePreview(GetSpritePreviewOptions options, PpuState state, uint8_t* vram, uint8_t *oamRam, uint8_t *cgram, uint32_t *buffer) { GetDebugger()->GetPpuTools()->GetSpritePreview(options, state, vram, oamRam, cgram, buffer); } DllExport void __stdcall SetViewerUpdateTiming(uint32_t viewerId, uint16_t scanline, uint16_t cycle) { GetDebugger()->GetPpuTools()->SetViewerUpdateTiming(viewerId, scanline, cycle); } + DllExport void __stdcall GetGameboyTilemap(uint8_t* vram, uint16_t offset, uint32_t* buffer) { GetDebugger()->GetPpuTools()->GetGameboyTilemap(vram, offset, buffer); } + DllExport void __stdcall GetDebugEvents(DebugEventInfo *infoArray, uint32_t &maxEventCount) { GetDebugger()->GetEventManager()->GetEvents(infoArray, maxEventCount); } DllExport uint32_t __stdcall GetDebugEventCount(EventViewerDisplayOptions options) { return GetDebugger()->GetEventManager()->GetEventCount(options); } DllExport void __stdcall GetEventViewerOutput(uint32_t *buffer, EventViewerDisplayOptions options) { GetDebugger()->GetEventManager()->GetDisplayBuffer(buffer, options); } diff --git a/Libretro/Makefile.common b/Libretro/Makefile.common index 2bf146d..e99aacd 100644 --- a/Libretro/Makefile.common +++ b/Libretro/Makefile.common @@ -62,6 +62,14 @@ SOURCES_CXX := $(LIBRETRO_DIR)/libretro.cpp \ $(CORE_DIR)/GameConnection.cpp \ $(CORE_DIR)/GameServer.cpp \ $(CORE_DIR)/GameServerConnection.cpp \ + $(CORE_DIR)/Gameboy.cpp \ + $(CORE_DIR)/GbCpu.cpp \ + $(CORE_DIR)/GbPpu.cpp \ + $(CORE_DIR)/GbApu.cpp \ + $(CORE_DIR)/GbTimer.cpp \ + $(CORE_DIR)/GbMemoryManager.cpp \ + $(CORE_DIR)/GbDebugger.cpp \ + $(CORE_DIR)/GameboyDisUtils.cpp \ $(CORE_DIR)/Gsu.cpp \ $(CORE_DIR)/Gsu.Instructions.cpp \ $(CORE_DIR)/GsuDisUtils.cpp \ @@ -126,6 +134,7 @@ SOURCES_CXX := $(LIBRETRO_DIR)/libretro.cpp \ $(UTIL_DIR)/AutoResetEvent.cpp \ $(UTIL_DIR)/AviRecorder.cpp \ $(UTIL_DIR)/AviWriter.cpp \ + $(UTIL_DIR)/blip_buf.cpp \ $(UTIL_DIR)/BpsPatcher.cpp \ $(UTIL_DIR)/CamstudioCodec.cpp \ $(UTIL_DIR)/CRC32.cpp \ diff --git a/Libretro/libretro.cpp b/Libretro/libretro.cpp index e6fa853..b554f38 100644 --- a/Libretro/libretro.cpp +++ b/Libretro/libretro.cpp @@ -8,6 +8,7 @@ #include "libretro.h" #include "../Core/Console.h" #include "../Core/Spc.h" +#include "../Core/Gameboy.h" #include "../Core/BaseCartridge.h" #include "../Core/MemoryManager.h" #include "../Core/VideoDecoder.h" @@ -667,18 +668,34 @@ extern "C" { RETRO_API void *retro_get_memory_data(unsigned id) { - switch(id) { - case RETRO_MEMORY_SAVE_RAM: return _console->GetCartridge()->DebugGetSaveRam(); - case RETRO_MEMORY_SYSTEM_RAM: return _console->GetMemoryManager()->DebugGetWorkRam(); + shared_ptr cart = _console->GetCartridge(); + if(_console->GetSettings()->CheckFlag(EmulationFlags::GameboyMode)) { + switch(id) { + case RETRO_MEMORY_SAVE_RAM: return cart->GetGameboy()->DebugGetMemory(SnesMemoryType::GbCartRam); + case RETRO_MEMORY_SYSTEM_RAM: return cart->GetGameboy()->DebugGetMemory(SnesMemoryType::GbWorkRam); + } + } else { + switch(id) { + case RETRO_MEMORY_SAVE_RAM: return cart->DebugGetSaveRam(); + case RETRO_MEMORY_SYSTEM_RAM: return _console->GetMemoryManager()->DebugGetWorkRam(); + } } return nullptr; } RETRO_API size_t retro_get_memory_size(unsigned id) { - switch(id) { - case RETRO_MEMORY_SAVE_RAM: return _console->GetCartridge()->DebugGetSaveRamSize(); break; - case RETRO_MEMORY_SYSTEM_RAM: return MemoryManager::WorkRamSize; + shared_ptr cart = _console->GetCartridge(); + if(_console->GetSettings()->CheckFlag(EmulationFlags::GameboyMode)) { + switch(id) { + case RETRO_MEMORY_SAVE_RAM: return cart->GetGameboy()->DebugGetMemorySize(SnesMemoryType::GbCartRam); + case RETRO_MEMORY_SYSTEM_RAM: return cart->GetGameboy()->DebugGetMemorySize(SnesMemoryType::GbWorkRam); + } + } else { + switch(id) { + case RETRO_MEMORY_SAVE_RAM: return cart->DebugGetSaveRamSize(); break; + case RETRO_MEMORY_SYSTEM_RAM: return MemoryManager::WorkRamSize; + } } return 0; } diff --git a/UI/Debugger/Breakpoints/Breakpoint.cs b/UI/Debugger/Breakpoints/Breakpoint.cs index 5dd7eb9..303efe8 100644 --- a/UI/Debugger/Breakpoints/Breakpoint.cs +++ b/UI/Debugger/Breakpoints/Breakpoint.cs @@ -36,7 +36,7 @@ namespace Mesen.GUI.Debugger public string GetAddressString(bool showLabel) { string addr = ""; - string format = _memoryType == SnesMemoryType.SpcMemory ? "X4" : "X6"; + string format = (_memoryType == SnesMemoryType.SpcMemory || _memoryType == SnesMemoryType.GameboyMemory) ? "X4" : "X6"; switch(AddressType) { case BreakpointAddressType.AnyAddress: return ""; @@ -129,6 +129,12 @@ namespace Mesen.GUI.Debugger case SnesMemoryType.BsxPsRam: type = "PSRAM"; break; case SnesMemoryType.BsxMemoryPack: type = "MPACK"; break; + + case SnesMemoryType.GameboyMemory: type = "CPU"; break; + case SnesMemoryType.GbPrgRom: type = "PRG"; break; + case SnesMemoryType.GbWorkRam: type = "WRAM"; break; + case SnesMemoryType.GbCartRam: type = "SRAM"; break; + case SnesMemoryType.GbHighRam: type = "HRAM"; break; case SnesMemoryType.Register: type = "REG"; break; } diff --git a/UI/Debugger/Breakpoints/frmBreakpoint.cs b/UI/Debugger/Breakpoints/frmBreakpoint.cs index 7180cd1..bfdbf28 100644 --- a/UI/Debugger/Breakpoints/frmBreakpoint.cs +++ b/UI/Debugger/Breakpoints/frmBreakpoint.cs @@ -80,6 +80,15 @@ namespace Mesen.GUI.Debugger cboBreakpointType.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.DspProgramRom)); cboBreakpointType.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.DspDataRom)); cboBreakpointType.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.DspDataRam)); + } else if(_cpuType == CpuType.Gameboy) { + cboBreakpointType.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.GameboyMemory)); + cboBreakpointType.Items.Add("-"); + cboBreakpointType.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.GbPrgRom)); + cboBreakpointType.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.GbWorkRam)); + cboBreakpointType.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.GbHighRam)); + if(DebugApi.GetMemorySize(SnesMemoryType.GbCartRam) > 0) { + cboBreakpointType.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.GbCartRam)); + } } this.toolTip.SetToolTip(this.picExpressionWarning, "Condition contains invalid syntax or symbols."); diff --git a/UI/Debugger/Code/GbDisassemblyManager.cs b/UI/Debugger/Code/GbDisassemblyManager.cs new file mode 100644 index 0000000..8c79a76 --- /dev/null +++ b/UI/Debugger/Code/GbDisassemblyManager.cs @@ -0,0 +1,29 @@ +using Mesen.GUI.Debugger.Controls; +using Mesen.GUI.Debugger.Integration; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Mesen.GUI.Debugger.Code +{ + public class GbDisassemblyManager : CpuDisassemblyManager + { + public override CpuType CpuType { get { return CpuType.Gameboy; } } + public override SnesMemoryType RelativeMemoryType { get { return SnesMemoryType.GameboyMemory; } } + public override int AddressSize { get { return 4; } } + public override int ByteCodeSize { get { return 3; } } + public override bool AllowSourceView { get { return false; } } + + public override void RefreshCode(ISymbolProvider symbolProvider, SourceFileInfo file) + { + this._provider = new CodeDataProvider(CpuType.Gameboy); + } + + protected override int GetFullAddress(int address, int length) + { + return address; + } + } +} diff --git a/UI/Debugger/Code/GbLineStyleProvider.cs b/UI/Debugger/Code/GbLineStyleProvider.cs new file mode 100644 index 0000000..89ed228 --- /dev/null +++ b/UI/Debugger/Code/GbLineStyleProvider.cs @@ -0,0 +1,64 @@ +using Mesen.GUI.Config; +using Mesen.GUI.Debugger.Controls; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Mesen.GUI.Debugger.Code +{ + public class GbLineStyleProvider : BaseStyleProvider + { + public GbLineStyleProvider() + { + } + + public override string GetLineComment(int lineNumber) + { + return null; + } + + public static void ConfigureActiveStatement(LineProperties props) + { + props.FgColor = Color.Black; + props.TextBgColor = ConfigManager.Config.Debug.Debugger.CodeActiveStatementColor; + props.Symbol |= LineSymbol.Arrow; + } + + public override LineProperties GetLineStyle(CodeLineData lineData, int lineIndex) + { + DebuggerInfo cfg = ConfigManager.Config.Debug.Debugger; + LineProperties props = new LineProperties(); + + if(lineData.Address >= 0) { + GetBreakpointLineProperties(props, lineData.Address); + } + + bool isActiveStatement = ActiveAddress.HasValue && ActiveAddress.Value == lineData.Address; + if(isActiveStatement) { + ConfigureActiveStatement(props); + } + + if(lineData.Flags.HasFlag(LineFlags.VerifiedData)) { + props.LineBgColor = cfg.CodeVerifiedDataColor; + } else if(!lineData.Flags.HasFlag(LineFlags.VerifiedCode)) { + props.LineBgColor = cfg.CodeUnidentifiedDataColor; + } + + return props; + } + + private void GetBreakpointLineProperties(LineProperties props, int cpuAddress) + { + AddressInfo absAddress = DebugApi.GetAbsoluteAddress(new AddressInfo() { Address = cpuAddress, Type = SnesMemoryType.GameboyMemory }); + foreach(Breakpoint breakpoint in BreakpointManager.Breakpoints) { + if(breakpoint.Matches((uint)cpuAddress, SnesMemoryType.GameboyMemory, CpuType.Gameboy) || (absAddress.Address >= 0 && breakpoint.Matches((uint)absAddress.Address, absAddress.Type, CpuType.Gameboy))) { + SetBreakpointLineProperties(props, breakpoint); + return; + } + } + } + } +} diff --git a/UI/Debugger/Config/DebuggerInfo.cs b/UI/Debugger/Config/DebuggerInfo.cs index 7fbc460..94345a6 100644 --- a/UI/Debugger/Config/DebuggerInfo.cs +++ b/UI/Debugger/Config/DebuggerInfo.cs @@ -30,6 +30,8 @@ namespace Mesen.GUI.Config public bool AutoResetCdl = true; + public bool ShowMemoryMappings = true; + public bool BringToFrontOnBreak = true; public bool BringToFrontOnPause = false; diff --git a/UI/Debugger/Config/DebuggerShortcutsConfig.cs b/UI/Debugger/Config/DebuggerShortcutsConfig.cs index a267727..7466ce2 100644 --- a/UI/Debugger/Config/DebuggerShortcutsConfig.cs +++ b/UI/Debugger/Config/DebuggerShortcutsConfig.cs @@ -81,6 +81,8 @@ namespace Mesen.GUI.Config public XmlKeys OpenNecDspDebugger = Keys.None; [ShortcutName("Open CX4 Debugger")] public XmlKeys OpenCx4Debugger = Keys.None; + [ShortcutName("Open Game Boy Debugger")] + public XmlKeys OpenGameboyDebugger = Keys.None; [ShortcutName("Open Event Viewer")] public XmlKeys OpenEventViewer = Keys.Control | Keys.E; [ShortcutName("Open Memory Tools")] diff --git a/UI/Debugger/Config/TraceLoggerInfo.cs b/UI/Debugger/Config/TraceLoggerInfo.cs index 56456c2..eda8444 100644 --- a/UI/Debugger/Config/TraceLoggerInfo.cs +++ b/UI/Debugger/Config/TraceLoggerInfo.cs @@ -51,6 +51,7 @@ namespace Mesen.GUI.Config public bool LogSa1; public bool LogGsu; public bool LogCx4; + public bool LogGameboy; public bool ShowByteCode; public bool ShowRegisters; diff --git a/UI/Debugger/Controls/ctrlCallstack.cs b/UI/Debugger/Controls/ctrlCallstack.cs index 734e2e5..d91eaf0 100644 --- a/UI/Debugger/Controls/ctrlCallstack.cs +++ b/UI/Debugger/Controls/ctrlCallstack.cs @@ -44,6 +44,7 @@ namespace Mesen.GUI.Debugger.Controls case CpuType.Cpu: _programCounter = (uint)(state.Cpu.K << 16) | state.Cpu.PC; break; case CpuType.Sa1: _programCounter = (uint)(state.Sa1.Cpu.K << 16) | state.Sa1.Cpu.PC; break; case CpuType.Spc: _programCounter = (uint)state.Spc.PC; break; + case CpuType.Gameboy: _programCounter = (uint)state.Gameboy.Cpu.PC; break; default: throw new Exception("Invalid cpu type"); } diff --git a/UI/Debugger/Controls/ctrlDisassemblyView.cs b/UI/Debugger/Controls/ctrlDisassemblyView.cs index 465c782..6b82f2c 100644 --- a/UI/Debugger/Controls/ctrlDisassemblyView.cs +++ b/UI/Debugger/Controls/ctrlDisassemblyView.cs @@ -351,6 +351,7 @@ namespace Mesen.GUI.Debugger.Controls DebugApi.RefreshDisassembly(CpuType.Gsu); DebugApi.RefreshDisassembly(CpuType.NecDsp); DebugApi.RefreshDisassembly(CpuType.Cx4); + DebugApi.RefreshDisassembly(CpuType.Gameboy); } _manager.RefreshCode(_inSourceView ? _symbolProvider : null, _inSourceView ? cboSourceFile.SelectedItem as SourceFileInfo : null); diff --git a/UI/Debugger/Controls/ctrlGameboyStatus.Designer.cs b/UI/Debugger/Controls/ctrlGameboyStatus.Designer.cs new file mode 100644 index 0000000..3552c7d --- /dev/null +++ b/UI/Debugger/Controls/ctrlGameboyStatus.Designer.cs @@ -0,0 +1,541 @@ +namespace Mesen.GUI.Debugger.Controls +{ + partial class ctrlGameboyStatus + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if(disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.grpCpu = new System.Windows.Forms.GroupBox(); + this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); + this.lblA = new System.Windows.Forms.Label(); + this.txtA = new System.Windows.Forms.TextBox(); + this.txtF = new System.Windows.Forms.TextBox(); + this.txtStack = new System.Windows.Forms.TextBox(); + this.txtPC = new System.Windows.Forms.TextBox(); + this.label5 = new System.Windows.Forms.Label(); + this.txtB = new System.Windows.Forms.TextBox(); + this.txtC = new System.Windows.Forms.TextBox(); + this.label1 = new System.Windows.Forms.Label(); + this.label8 = new System.Windows.Forms.Label(); + this.label2 = new System.Windows.Forms.Label(); + this.label3 = new System.Windows.Forms.Label(); + this.label4 = new System.Windows.Forms.Label(); + this.txtE = new System.Windows.Forms.TextBox(); + this.txtD = new System.Windows.Forms.TextBox(); + this.txtSP = new System.Windows.Forms.TextBox(); + this.label6 = new System.Windows.Forms.Label(); + this.label7 = new System.Windows.Forms.Label(); + this.txtHL = new System.Windows.Forms.TextBox(); + this.tableLayoutPanel2 = new System.Windows.Forms.TableLayoutPanel(); + this.chkCarry = new System.Windows.Forms.CheckBox(); + this.chkNegative = new System.Windows.Forms.CheckBox(); + this.chkZero = new System.Windows.Forms.CheckBox(); + this.chkHalfCarry = new System.Windows.Forms.CheckBox(); + this.chkIme = new System.Windows.Forms.CheckBox(); + this.chkHalted = new System.Windows.Forms.CheckBox(); + this.label12 = new System.Windows.Forms.Label(); + this.txtCycleCount = new System.Windows.Forms.TextBox(); + this.grpPpu = new System.Windows.Forms.GroupBox(); + this.tableLayoutPanel3 = new System.Windows.Forms.TableLayoutPanel(); + this.txtScanline = new System.Windows.Forms.TextBox(); + this.label9 = new System.Windows.Forms.Label(); + this.label10 = new System.Windows.Forms.Label(); + this.txtCycle = new System.Windows.Forms.TextBox(); + this.grpCpu.SuspendLayout(); + this.tableLayoutPanel1.SuspendLayout(); + this.tableLayoutPanel2.SuspendLayout(); + this.grpPpu.SuspendLayout(); + this.tableLayoutPanel3.SuspendLayout(); + this.SuspendLayout(); + // + // grpCpu + // + this.grpCpu.Controls.Add(this.tableLayoutPanel1); + this.grpCpu.Dock = System.Windows.Forms.DockStyle.Top; + this.grpCpu.Location = new System.Drawing.Point(0, 0); + this.grpCpu.Name = "grpCpu"; + this.grpCpu.Size = new System.Drawing.Size(342, 141); + this.grpCpu.TabIndex = 0; + this.grpCpu.TabStop = false; + this.grpCpu.Text = "CPU"; + // + // tableLayoutPanel1 + // + this.tableLayoutPanel1.ColumnCount = 10; + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20F)); + this.tableLayoutPanel1.Controls.Add(this.lblA, 0, 0); + this.tableLayoutPanel1.Controls.Add(this.txtA, 1, 0); + this.tableLayoutPanel1.Controls.Add(this.txtF, 3, 0); + this.tableLayoutPanel1.Controls.Add(this.txtStack, 7, 1); + this.tableLayoutPanel1.Controls.Add(this.label5, 7, 0); + this.tableLayoutPanel1.Controls.Add(this.txtB, 1, 1); + this.tableLayoutPanel1.Controls.Add(this.txtC, 3, 1); + this.tableLayoutPanel1.Controls.Add(this.label1, 0, 1); + this.tableLayoutPanel1.Controls.Add(this.label8, 2, 0); + this.tableLayoutPanel1.Controls.Add(this.label2, 2, 1); + this.tableLayoutPanel1.Controls.Add(this.label3, 0, 2); + this.tableLayoutPanel1.Controls.Add(this.label4, 2, 2); + this.tableLayoutPanel1.Controls.Add(this.txtE, 3, 2); + this.tableLayoutPanel1.Controls.Add(this.txtD, 1, 2); + this.tableLayoutPanel1.Controls.Add(this.txtSP, 8, 0); + this.tableLayoutPanel1.Controls.Add(this.label7, 4, 1); + this.tableLayoutPanel1.Controls.Add(this.txtHL, 5, 1); + this.tableLayoutPanel1.Controls.Add(this.tableLayoutPanel2, 0, 3); + this.tableLayoutPanel1.Controls.Add(this.label6, 4, 2); + this.tableLayoutPanel1.Controls.Add(this.label12, 4, 0); + this.tableLayoutPanel1.Controls.Add(this.txtPC, 5, 2); + this.tableLayoutPanel1.Controls.Add(this.txtCycleCount, 5, 0); + this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill; + this.tableLayoutPanel1.Location = new System.Drawing.Point(3, 16); + this.tableLayoutPanel1.Name = "tableLayoutPanel1"; + this.tableLayoutPanel1.RowCount = 7; + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel1.Size = new System.Drawing.Size(336, 122); + this.tableLayoutPanel1.TabIndex = 0; + // + // lblA + // + this.lblA.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.lblA.AutoSize = true; + this.lblA.Location = new System.Drawing.Point(3, 6); + this.lblA.Name = "lblA"; + this.lblA.Size = new System.Drawing.Size(17, 13); + this.lblA.TabIndex = 0; + this.lblA.Text = "A:"; + // + // txtA + // + this.txtA.Location = new System.Drawing.Point(27, 3); + this.txtA.Name = "txtA"; + this.txtA.Size = new System.Drawing.Size(25, 20); + this.txtA.TabIndex = 1; + this.txtA.Text = "DDDD"; + // + // txtF + // + this.txtF.Location = new System.Drawing.Point(81, 3); + this.txtF.Name = "txtF"; + this.txtF.Size = new System.Drawing.Size(25, 20); + this.txtF.TabIndex = 4; + // + // txtStack + // + this.txtStack.BackColor = System.Drawing.SystemColors.Window; + this.tableLayoutPanel1.SetColumnSpan(this.txtStack, 2); + this.txtStack.Dock = System.Windows.Forms.DockStyle.Fill; + this.txtStack.Location = new System.Drawing.Point(244, 29); + this.txtStack.Multiline = true; + this.txtStack.Name = "txtStack"; + this.txtStack.ReadOnly = true; + this.tableLayoutPanel1.SetRowSpan(this.txtStack, 3); + this.txtStack.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; + this.txtStack.Size = new System.Drawing.Size(88, 69); + this.txtStack.TabIndex = 23; + // + // txtPC + // + this.txtPC.Location = new System.Drawing.Point(154, 55); + this.txtPC.Name = "txtPC"; + this.txtPC.Size = new System.Drawing.Size(40, 20); + this.txtPC.TabIndex = 13; + this.txtPC.Text = "DDDD"; + // + // label5 + // + this.label5.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.label5.AutoSize = true; + this.label5.Location = new System.Drawing.Point(244, 6); + this.label5.Name = "label5"; + this.label5.Size = new System.Drawing.Size(24, 13); + this.label5.TabIndex = 10; + this.label5.Text = "SP:"; + // + // txtB + // + this.txtB.Location = new System.Drawing.Point(27, 29); + this.txtB.Name = "txtB"; + this.txtB.Size = new System.Drawing.Size(25, 20); + this.txtB.TabIndex = 25; + this.txtB.Text = "DDDD"; + // + // txtC + // + this.txtC.Location = new System.Drawing.Point(81, 29); + this.txtC.Name = "txtC"; + this.txtC.Size = new System.Drawing.Size(25, 20); + this.txtC.TabIndex = 26; + this.txtC.Text = "DDDD"; + // + // label1 + // + this.label1.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(3, 32); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(17, 13); + this.label1.TabIndex = 2; + this.label1.Text = "B:"; + // + // label8 + // + this.label8.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.label8.AutoSize = true; + this.label8.Location = new System.Drawing.Point(58, 6); + this.label8.Name = "label8"; + this.label8.Size = new System.Drawing.Size(16, 13); + this.label8.TabIndex = 29; + this.label8.Text = "F:"; + // + // label2 + // + this.label2.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(58, 32); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(17, 13); + this.label2.TabIndex = 3; + this.label2.Text = "C:"; + // + // label3 + // + this.label3.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(3, 58); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(18, 13); + this.label3.TabIndex = 24; + this.label3.Text = "D:"; + // + // label4 + // + this.label4.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.label4.AutoSize = true; + this.label4.Location = new System.Drawing.Point(58, 58); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(17, 13); + this.label4.TabIndex = 28; + this.label4.Text = "E:"; + // + // txtE + // + this.txtE.Location = new System.Drawing.Point(81, 55); + this.txtE.Name = "txtE"; + this.txtE.Size = new System.Drawing.Size(25, 20); + this.txtE.TabIndex = 27; + this.txtE.Text = "DDDD"; + // + // txtD + // + this.txtD.Location = new System.Drawing.Point(27, 55); + this.txtD.Name = "txtD"; + this.txtD.Size = new System.Drawing.Size(25, 20); + this.txtD.TabIndex = 5; + // + // txtSP + // + this.txtSP.Location = new System.Drawing.Point(274, 3); + this.txtSP.Name = "txtSP"; + this.txtSP.Size = new System.Drawing.Size(40, 20); + this.txtSP.TabIndex = 11; + // + // label6 + // + this.label6.Anchor = System.Windows.Forms.AnchorStyles.Right; + this.label6.AutoSize = true; + this.label6.Location = new System.Drawing.Point(124, 58); + this.label6.Name = "label6"; + this.label6.Size = new System.Drawing.Size(24, 13); + this.label6.TabIndex = 12; + this.label6.Text = "PC:"; + // + // label7 + // + this.label7.Anchor = System.Windows.Forms.AnchorStyles.Right; + this.label7.AutoSize = true; + this.label7.Location = new System.Drawing.Point(124, 32); + this.label7.Name = "label7"; + this.label7.Size = new System.Drawing.Size(24, 13); + this.label7.TabIndex = 14; + this.label7.Text = "HL:"; + // + // txtHL + // + this.txtHL.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.txtHL.Location = new System.Drawing.Point(154, 29); + this.txtHL.Name = "txtHL"; + this.txtHL.Size = new System.Drawing.Size(40, 20); + this.txtHL.TabIndex = 15; + this.txtHL.Text = "DD"; + // + // tableLayoutPanel2 + // + this.tableLayoutPanel2.ColumnCount = 4; + this.tableLayoutPanel1.SetColumnSpan(this.tableLayoutPanel2, 5); + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20F)); + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20F)); + this.tableLayoutPanel2.Controls.Add(this.chkCarry, 0, 0); + this.tableLayoutPanel2.Controls.Add(this.chkHalfCarry, 1, 0); + this.tableLayoutPanel2.Controls.Add(this.chkIme, 2, 1); + this.tableLayoutPanel2.Controls.Add(this.chkNegative, 0, 1); + this.tableLayoutPanel2.Controls.Add(this.chkZero, 1, 1); + this.tableLayoutPanel2.Controls.Add(this.chkHalted, 2, 0); + this.tableLayoutPanel2.Location = new System.Drawing.Point(0, 78); + this.tableLayoutPanel2.Margin = new System.Windows.Forms.Padding(0); + this.tableLayoutPanel2.Name = "tableLayoutPanel2"; + this.tableLayoutPanel2.RowCount = 2; + this.tableLayoutPanel1.SetRowSpan(this.tableLayoutPanel2, 2); + this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F)); + this.tableLayoutPanel2.Size = new System.Drawing.Size(135, 44); + this.tableLayoutPanel2.TabIndex = 16; + // + // chkCarry + // + this.chkCarry.AutoSize = true; + this.chkCarry.Location = new System.Drawing.Point(3, 3); + this.chkCarry.Name = "chkCarry"; + this.chkCarry.Size = new System.Drawing.Size(33, 17); + this.chkCarry.TabIndex = 23; + this.chkCarry.Text = "C"; + this.chkCarry.UseVisualStyleBackColor = true; + // + // chkNegative + // + this.chkNegative.AutoSize = true; + this.chkNegative.Location = new System.Drawing.Point(3, 26); + this.chkNegative.Name = "chkNegative"; + this.chkNegative.Size = new System.Drawing.Size(34, 15); + this.chkNegative.TabIndex = 17; + this.chkNegative.Text = "N"; + this.chkNegative.UseVisualStyleBackColor = true; + // + // chkZero + // + this.chkZero.AutoSize = true; + this.chkZero.Location = new System.Drawing.Point(43, 26); + this.chkZero.Name = "chkZero"; + this.chkZero.Size = new System.Drawing.Size(33, 15); + this.chkZero.TabIndex = 21; + this.chkZero.Text = "Z"; + this.chkZero.UseVisualStyleBackColor = true; + // + // chkHalfCarry + // + this.chkHalfCarry.AutoSize = true; + this.chkHalfCarry.Location = new System.Drawing.Point(43, 3); + this.chkHalfCarry.Name = "chkHalfCarry"; + this.chkHalfCarry.Size = new System.Drawing.Size(34, 17); + this.chkHalfCarry.TabIndex = 25; + this.chkHalfCarry.Text = "H"; + this.chkHalfCarry.UseVisualStyleBackColor = true; + // + // chkIme + // + this.chkIme.AutoSize = true; + this.chkIme.Location = new System.Drawing.Point(83, 26); + this.chkIme.Name = "chkIme"; + this.chkIme.Size = new System.Drawing.Size(45, 15); + this.chkIme.TabIndex = 26; + this.chkIme.Text = "IME"; + this.chkIme.UseVisualStyleBackColor = true; + // + // chkHalted + // + this.chkHalted.AutoSize = true; + this.chkHalted.Location = new System.Drawing.Point(83, 3); + this.chkHalted.Name = "chkHalted"; + this.chkHalted.Size = new System.Drawing.Size(45, 17); + this.chkHalted.TabIndex = 27; + this.chkHalted.Text = "Halt"; + this.chkHalted.UseVisualStyleBackColor = true; + // + // label12 + // + this.label12.Anchor = System.Windows.Forms.AnchorStyles.Right; + this.label12.AutoSize = true; + this.label12.Location = new System.Drawing.Point(112, 6); + this.label12.Name = "label12"; + this.label12.Size = new System.Drawing.Size(36, 13); + this.label12.TabIndex = 31; + this.label12.Text = "Cycle:"; + // + // txtCycleCount + // + this.tableLayoutPanel1.SetColumnSpan(this.txtCycleCount, 2); + this.txtCycleCount.Location = new System.Drawing.Point(154, 3); + this.txtCycleCount.Name = "txtCycleCount"; + this.txtCycleCount.Size = new System.Drawing.Size(84, 20); + this.txtCycleCount.TabIndex = 32; + this.txtCycleCount.Text = "DDDD"; + // + // grpPpu + // + this.grpPpu.Controls.Add(this.tableLayoutPanel3); + this.grpPpu.Dock = System.Windows.Forms.DockStyle.Top; + this.grpPpu.Location = new System.Drawing.Point(0, 141); + this.grpPpu.Name = "grpPpu"; + this.grpPpu.Size = new System.Drawing.Size(342, 47); + this.grpPpu.TabIndex = 2; + this.grpPpu.TabStop = false; + this.grpPpu.Text = "PPU"; + // + // tableLayoutPanel3 + // + this.tableLayoutPanel3.ColumnCount = 6; + this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel3.Controls.Add(this.txtScanline, 0, 0); + this.tableLayoutPanel3.Controls.Add(this.label9, 0, 0); + this.tableLayoutPanel3.Controls.Add(this.label10, 2, 0); + this.tableLayoutPanel3.Controls.Add(this.txtCycle, 3, 0); + this.tableLayoutPanel3.Dock = System.Windows.Forms.DockStyle.Fill; + this.tableLayoutPanel3.Location = new System.Drawing.Point(3, 16); + this.tableLayoutPanel3.Name = "tableLayoutPanel3"; + this.tableLayoutPanel3.RowCount = 2; + this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel3.Size = new System.Drawing.Size(336, 28); + this.tableLayoutPanel3.TabIndex = 0; + // + // txtScanline + // + this.txtScanline.Location = new System.Drawing.Point(60, 3); + this.txtScanline.Name = "txtScanline"; + this.txtScanline.Size = new System.Drawing.Size(33, 20); + this.txtScanline.TabIndex = 3; + this.txtScanline.Text = "555"; + // + // label9 + // + this.label9.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.label9.AutoSize = true; + this.label9.Location = new System.Drawing.Point(3, 6); + this.label9.Name = "label9"; + this.label9.Size = new System.Drawing.Size(51, 13); + this.label9.TabIndex = 1; + this.label9.Text = "Scanline:"; + // + // label10 + // + this.label10.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.label10.AutoSize = true; + this.label10.Location = new System.Drawing.Point(99, 6); + this.label10.Name = "label10"; + this.label10.Size = new System.Drawing.Size(36, 13); + this.label10.TabIndex = 2; + this.label10.Text = "Cycle:"; + // + // txtCycle + // + this.txtCycle.Location = new System.Drawing.Point(141, 3); + this.txtCycle.Name = "txtCycle"; + this.txtCycle.Size = new System.Drawing.Size(33, 20); + this.txtCycle.TabIndex = 4; + this.txtCycle.Text = "555"; + // + // ctrlGameboyStatus + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this.grpPpu); + this.Controls.Add(this.grpCpu); + this.Name = "ctrlGameboyStatus"; + this.Size = new System.Drawing.Size(342, 187); + this.grpCpu.ResumeLayout(false); + this.tableLayoutPanel1.ResumeLayout(false); + this.tableLayoutPanel1.PerformLayout(); + this.tableLayoutPanel2.ResumeLayout(false); + this.tableLayoutPanel2.PerformLayout(); + this.grpPpu.ResumeLayout(false); + this.tableLayoutPanel3.ResumeLayout(false); + this.tableLayoutPanel3.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.GroupBox grpCpu; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.Label lblA; + private System.Windows.Forms.TextBox txtA; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.TextBox txtF; + private System.Windows.Forms.TextBox txtD; + private System.Windows.Forms.Label label5; + private System.Windows.Forms.TextBox txtSP; + private System.Windows.Forms.TextBox txtPC; + private System.Windows.Forms.TextBox txtHL; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel2; + private System.Windows.Forms.CheckBox chkHalfCarry; + private System.Windows.Forms.CheckBox chkCarry; + private System.Windows.Forms.CheckBox chkZero; + private System.Windows.Forms.CheckBox chkNegative; + private System.Windows.Forms.TextBox txtStack; + private System.Windows.Forms.TextBox txtB; + private System.Windows.Forms.TextBox txtC; + private System.Windows.Forms.Label label8; + private System.Windows.Forms.TextBox txtE; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.Label label6; + private System.Windows.Forms.Label label7; + private System.Windows.Forms.GroupBox grpPpu; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel3; + private System.Windows.Forms.TextBox txtScanline; + private System.Windows.Forms.Label label9; + private System.Windows.Forms.Label label10; + private System.Windows.Forms.TextBox txtCycle; + private System.Windows.Forms.CheckBox chkIme; + private System.Windows.Forms.CheckBox chkHalted; + private System.Windows.Forms.Label label12; + private System.Windows.Forms.TextBox txtCycleCount; + } +} diff --git a/UI/Debugger/Controls/ctrlGameboyStatus.cs b/UI/Debugger/Controls/ctrlGameboyStatus.cs new file mode 100644 index 0000000..a1f28ac --- /dev/null +++ b/UI/Debugger/Controls/ctrlGameboyStatus.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using Mesen.GUI.Controls; +using Mesen.GUI.Forms; + +namespace Mesen.GUI.Debugger.Controls +{ + public partial class ctrlGameboyStatus : BaseControl + { + private EntityBinder _cpuBinder = new EntityBinder(); + private EntityBinder _ppuBinder = new EntityBinder(); + private GbState _lastState; + + public ctrlGameboyStatus() + { + InitializeComponent(); + if(IsDesignMode) { + return; + } + + _cpuBinder.Entity = new GbCpuState(); + _cpuBinder.AddBinding(nameof(GbCpuState.A), txtA); + _cpuBinder.AddBinding(nameof(GbCpuState.B), txtB); + _cpuBinder.AddBinding(nameof(GbCpuState.C), txtC); + _cpuBinder.AddBinding(nameof(GbCpuState.D), txtD); + _cpuBinder.AddBinding(nameof(GbCpuState.E), txtE); + _cpuBinder.AddBinding(nameof(GbCpuState.Flags), txtF); + _cpuBinder.AddBinding(nameof(GbCpuState.PC), txtPC); + _cpuBinder.AddBinding(nameof(GbCpuState.SP), txtSP); + + _cpuBinder.AddBinding(nameof(GbCpuState.Halted), chkHalted); + _cpuBinder.AddBinding(nameof(GbCpuState.IME), chkIme); + _cpuBinder.AddBinding(nameof(GbCpuState.CycleCount), txtCycleCount, eNumberFormat.Decimal); + + _ppuBinder.Entity = new GbPpuState(); + _ppuBinder.AddBinding(nameof(GbPpuState.Cycle), txtCycle, eNumberFormat.Decimal); + _ppuBinder.AddBinding(nameof(GbPpuState.Scanline), txtScanline, eNumberFormat.Decimal); + } + + public void UpdateStatus(GbState state) + { + _lastState = state; + + _cpuBinder.Entity = state.Cpu; + _cpuBinder.UpdateUI(); + + txtHL.Text = ((state.Cpu.H << 8) | state.Cpu.L).ToString("X4"); + + _ppuBinder.Entity = state.Ppu; + _ppuBinder.UpdateUI(); + + UpdateCpuFlags(); + UpdateStack(); + } + + private void UpdateCpuFlags() + { + GameboyFlags flags = (GameboyFlags)_lastState.Cpu.Flags; + chkNegative.Checked = flags.HasFlag(GameboyFlags.AddSub); + chkHalfCarry.Checked = flags.HasFlag(GameboyFlags.HalfCarry); + chkZero.Checked = flags.HasFlag(GameboyFlags.Zero); + chkCarry.Checked = flags.HasFlag(GameboyFlags.Carry); + } + + private void UpdateStack() + { + StringBuilder sb = new StringBuilder(); + for(UInt32 i = (uint)_lastState.Cpu.SP + 1; (i & 0xFF) != 0; i++) { + sb.Append("$"); + sb.Append(DebugApi.GetMemoryValue(SnesMemoryType.GameboyMemory, i).ToString("X2")); + sb.Append(", "); + } + string stack = sb.ToString(); + if(stack.Length > 2) { + stack = stack.Substring(0, stack.Length - 2); + } + txtStack.Text = stack; + } + } +} diff --git a/UI/Debugger/Controls/ctrlGameboyStatus.resx b/UI/Debugger/Controls/ctrlGameboyStatus.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/UI/Debugger/Controls/ctrlGameboyStatus.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/UI/Debugger/Controls/ctrlMemoryMapping.cs b/UI/Debugger/Controls/ctrlMemoryMapping.cs new file mode 100644 index 0000000..461796e --- /dev/null +++ b/UI/Debugger/Controls/ctrlMemoryMapping.cs @@ -0,0 +1,200 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace Mesen.GUI.Debugger.Controls +{ + class ctrlMemoryMapping : Control + { + Font _largeFont = new Font("Arial", 9); + Font _mediumFont = new Font("Arial", 8); + Font _smallFont = new Font("Arial", 7); + + List _regions = new List(); + + public ctrlMemoryMapping() + { + this.DoubleBuffered = true; + this.ResizeRedraw = true; + } + + private void UpdateRegionArray(List regions) + { + if(regions.Count != _regions.Count) { + _regions = regions; + this.Invalidate(); + } else { + for(int i = 0; i < regions.Count; i++) { + if(_regions[i].Color != regions[i].Color || _regions[i].Name != regions[i].Name || _regions[i].Size != regions[i].Size) { + _regions = regions; + this.Invalidate(); + return; + } + } + } + } + + public void UpdateCpuRegions(GbState gbState) + { + GbMemoryManagerState state = gbState.MemoryManager; + List regions = new List(); + + Action addEmpty = (int size) => { regions.Add(new MemoryRegionInfo() { Name = "N/A", Size = size, Color = Color.FromArgb(222, 222, 222) }); }; + + Action addWorkRam = (int page, int size, RegisterAccess type) => { + string name = size >= 0x1000 ? ("WRAM ($" + page.ToString("X2") + ")") : (size >= 0x800 ? ("$" + page.ToString("X2")) : ""); + regions.Add(new MemoryRegionInfo() { Name = name, Size = size, Color = Color.FromArgb(0xCD, 0xDC, 0xFA), AccessType = type }); + }; + + Action addSaveRam = (int page, int size, RegisterAccess type) => { + string name = size >= 0x2000 ? ("Save RAM ($" + page.ToString("X2") + ")") : (size >= 0x800 ? ("$" + page.ToString("X2")) : ""); + regions.Add(new MemoryRegionInfo() { Name = name, Size = size, Color = Color.FromArgb(0xFA, 0xDC, 0xCD), AccessType = type }); + }; + + Action addCartRam = (int page, int size, RegisterAccess type) => { + string name = size >= 0x2000 ? ("Cart RAM ($" + page.ToString("X2") + ")") : (size >= 0x800 ? ("$" + page.ToString("X2")) : ""); + regions.Add(new MemoryRegionInfo() { Name = name, Size = size, Color = Color.FromArgb(0xFA, 0xDC, 0xCD), AccessType = type }); + }; + + Action addPrgRom = (int page, int size, Color color) => { regions.Add(new MemoryRegionInfo() { Name = "$" + page.ToString("X2"), Size = size, Color = color }); }; + + GbMemoryType memoryType = GbMemoryType.None; + RegisterAccess accessType = RegisterAccess.None; + int currentSize = 0; + int startIndex = 0; + bool alternateColor = true; + + const int prgBankSize = 0x4000; + const int otherBankSize = 0x2000; + + Action addSection = (int i) => { + if(currentSize == 0) { + return; + } + + if(memoryType == GbMemoryType.None) { + addEmpty(currentSize); + } else if(memoryType == GbMemoryType.PrgRom) { + addPrgRom((int)(state.MemoryOffset[startIndex] / prgBankSize), currentSize, alternateColor ? Color.FromArgb(0xC4, 0xE7, 0xD4) : Color.FromArgb(0xA4, 0xD7, 0xB4)); + alternateColor = !alternateColor; + } else if(memoryType == GbMemoryType.WorkRam) { + addWorkRam((int)(state.MemoryOffset[startIndex] / otherBankSize), currentSize, accessType); + } else if(memoryType == GbMemoryType.CartRam) { + if(gbState.HasBattery) { + addSaveRam((int)(state.MemoryOffset[startIndex] / otherBankSize), currentSize, accessType); + } else { + addCartRam((int)(state.MemoryOffset[startIndex] / otherBankSize), currentSize, accessType); + } + } + currentSize = 0; + startIndex = i; + }; + + for(int i = 0; i < 0xFE; i++) { + if(i == 0x80) { + addSection(i); + regions.Add(new MemoryRegionInfo() { Name = "VRAM", Size = 0x2000, Color = Color.FromArgb(0xFA, 0xDC, 0xCD), AccessType = RegisterAccess.ReadWrite }); + addSection(i); + memoryType = GbMemoryType.None; + accessType = RegisterAccess.None; + currentSize = 0; + i += 0x20; + } + + if(state.MemoryAccessType[i] != RegisterAccess.None) { + bool forceNewBlock = ( + (memoryType == GbMemoryType.PrgRom && state.MemoryOffset[i] % prgBankSize == 0) || + (memoryType == GbMemoryType.WorkRam && state.MemoryOffset[i] % otherBankSize == 0) || + (memoryType == GbMemoryType.CartRam && state.MemoryOffset[i] % otherBankSize == 0) + ); + + if(forceNewBlock || memoryType != state.MemoryType[i] || state.MemoryOffset[i] - state.MemoryOffset[i-1] != 0x100) { + addSection(i); + } + memoryType = state.MemoryType[i]; + accessType = state.MemoryAccessType[i]; + } else { + if(memoryType != GbMemoryType.None) { + addSection(i); + } + memoryType = GbMemoryType.None; + accessType = RegisterAccess.None; + } + currentSize += 0x100; + } + + regions.Add(new MemoryRegionInfo() { Name = "", Size = 0x200, Color = Color.FromArgb(222, 222, 222) }); + + UpdateRegionArray(regions); + } + + protected override void OnPaint(PaintEventArgs e) + { + base.OnPaint(e); + + e.Graphics.Clear(Color.LightGray); + + if(_regions.Count > 0) { + Rectangle rect = Rectangle.Inflate(this.ClientRectangle, -2, -1); + + int totalSize = 0; + foreach(MemoryRegionInfo region in _regions) { + totalSize += region.Size; + } + + float pixelsPerByte = (float)rect.Width / totalSize; + + float currentPosition = 1; + int byteOffset = 0; + foreach(MemoryRegionInfo region in _regions) { + float length = pixelsPerByte * region.Size; + using(Brush brush = new SolidBrush(region.Color)) { + e.Graphics.FillRectangle(brush, currentPosition, 0, length, rect.Height); + } + e.Graphics.DrawRectangle(Pens.Black, currentPosition, 0, length, rect.Height); + + if(region.Size > 0x200) { + e.Graphics.RotateTransform(-90); + SizeF textSize = e.Graphics.MeasureString(byteOffset.ToString("X4"), _mediumFont); + e.Graphics.DrawString(byteOffset.ToString("X4"), _mediumFont, Brushes.Black, -rect.Height + (rect.Height - textSize.Width) / 2, currentPosition + 3); + e.Graphics.ResetTransform(); + + textSize = e.Graphics.MeasureString(region.Name, _largeFont); + e.Graphics.DrawString(region.Name, _largeFont, Brushes.Black, currentPosition + 12 + ((length - 12) / 2 - textSize.Width / 2), rect.Height / 2 - 7); + + if(region.AccessType != RegisterAccess.None) { + string accessTypeString = ""; + if(((int)region.AccessType & (int)RegisterAccess.Read) != 0) { + accessTypeString += "R"; + } + if(((int)region.AccessType & (int)RegisterAccess.Write) != 0) { + accessTypeString += "W"; + } + if(string.IsNullOrWhiteSpace(accessTypeString)) { + //Mark it as "open bus" if it cannot be written/read + accessTypeString = "OB"; + } + + SizeF size = e.Graphics.MeasureString(accessTypeString, _smallFont); + e.Graphics.DrawString(accessTypeString, _smallFont, Brushes.Black, currentPosition + length - size.Width - 1, rect.Height - size.Height + 2); + } + } + currentPosition += length; + byteOffset += region.Size; + } + } + } + } + + class MemoryRegionInfo + { + public string Name { get; set; } + public int Size { get; set; } + public Color Color { get; set; } + public RegisterAccess AccessType { get; set; } = RegisterAccess.None; + } +} diff --git a/UI/Debugger/Controls/ctrlMemoryType.cs b/UI/Debugger/Controls/ctrlMemoryType.cs index 9a3d0f3..a8d8845 100644 --- a/UI/Debugger/Controls/ctrlMemoryType.cs +++ b/UI/Debugger/Controls/ctrlMemoryType.cs @@ -28,6 +28,41 @@ namespace Mesen.GUI.Debugger.Controls this.BeginUpdate(); this.Items.Clear(); + if(EmuApi.GetRomInfo().CoprocessorType == CoprocessorType.Gameboy) { + this.AddGameboyTypes(excludeCpuMemory); + } else { + this.AddSnesMemoryTypes(excludeCpuMemory); + this.AddGameboyTypes(excludeCpuMemory); + } + + this.SelectedIndex = 0; + this.SetEnumValue(originalValue); + this._disableEvent = false; + + this.EndUpdate(); + } + + private void AddGameboyTypes(bool excludeCpuMemory) + { + if(DebugApi.GetMemorySize(SnesMemoryType.GbPrgRom) > 0) { + if(this.Items.Count > 0) { + this.Items.Add("-"); + } + + if(!excludeCpuMemory) { + this.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.GameboyMemory)); + } + this.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.GbPrgRom)); + this.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.GbWorkRam)); + if(DebugApi.GetMemorySize(SnesMemoryType.GbCartRam) > 0) { + this.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.GbCartRam)); + } + this.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.GbVideoRam)); + } + } + + private void AddSnesMemoryTypes(bool excludeCpuMemory) + { if(!excludeCpuMemory) { this.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.CpuMemory)); this.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.SpcMemory)); @@ -78,17 +113,11 @@ namespace Mesen.GUI.Debugger.Controls this.Items.Add("-"); this.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.BsxPsRam)); } - + if(DebugApi.GetMemorySize(SnesMemoryType.BsxMemoryPack) > 0) { this.Items.Add("-"); this.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.BsxMemoryPack)); } - - this.SelectedIndex = 0; - this.SetEnumValue(originalValue); - this._disableEvent = false; - - this.EndUpdate(); } } } diff --git a/UI/Debugger/DebugWindowManager.cs b/UI/Debugger/DebugWindowManager.cs index fd09fe3..a1f3c19 100644 --- a/UI/Debugger/DebugWindowManager.cs +++ b/UI/Debugger/DebugWindowManager.cs @@ -32,6 +32,7 @@ namespace Mesen.GUI.Debugger case DebugWindow.GsuDebugger: frm = new frmDebugger(CpuType.Gsu); frm.Icon = Properties.Resources.GsuDebugger; break; case DebugWindow.NecDspDebugger: frm = new frmDebugger(CpuType.NecDsp); frm.Icon = Properties.Resources.NecDspDebugger; break; case DebugWindow.Cx4Debugger: frm = new frmDebugger(CpuType.Cx4); frm.Icon = Properties.Resources.Cx4Debugger; break; + case DebugWindow.GbDebugger: frm = new frmDebugger(CpuType.Gameboy); frm.Icon = Properties.Resources.GbDebugger; break; case DebugWindow.TraceLogger: frm = new frmTraceLogger(); frm.Icon = Properties.Resources.LogWindow; break; case DebugWindow.MemoryTools: frm = new frmMemoryTools(); frm.Icon = Properties.Resources.CheatCode; break; case DebugWindow.TileViewer: frm = new frmTileViewer(); frm.Icon = Properties.Resources.VerticalLayout; break; @@ -91,6 +92,7 @@ namespace Mesen.GUI.Debugger case CpuType.Sa1: return (frmDebugger)OpenDebugWindow(DebugWindow.Sa1Debugger); case CpuType.Gsu: return (frmDebugger)OpenDebugWindow(DebugWindow.GsuDebugger); case CpuType.Cx4: return (frmDebugger)OpenDebugWindow(DebugWindow.Cx4Debugger); + case CpuType.Gameboy: return (frmDebugger)OpenDebugWindow(DebugWindow.GbDebugger); } throw new Exception("Invalid CPU type"); } @@ -104,6 +106,7 @@ namespace Mesen.GUI.Debugger case CpuType.Sa1: return (frmDebugger)GetExistingSingleInstanceWindow(DebugWindow.Sa1Debugger); case CpuType.Gsu: return (frmDebugger)GetExistingSingleInstanceWindow(DebugWindow.GsuDebugger); case CpuType.Cx4: return (frmDebugger)GetExistingSingleInstanceWindow(DebugWindow.Cx4Debugger); + case CpuType.Gameboy: return (frmDebugger)GetExistingSingleInstanceWindow(DebugWindow.GbDebugger); } throw new Exception("Invalid CPU type"); } @@ -152,6 +155,7 @@ namespace Mesen.GUI.Debugger case DebugWindow.GsuDebugger: return _openedWindows.ToList().Find((form) => form.GetType() == typeof(frmDebugger) && ((frmDebugger)form).CpuType == CpuType.Gsu); case DebugWindow.NecDspDebugger: return _openedWindows.ToList().Find((form) => form.GetType() == typeof(frmDebugger) && ((frmDebugger)form).CpuType == CpuType.NecDsp); case DebugWindow.Cx4Debugger: return _openedWindows.ToList().Find((form) => form.GetType() == typeof(frmDebugger) && ((frmDebugger)form).CpuType == CpuType.Cx4); + case DebugWindow.GbDebugger: return _openedWindows.ToList().Find((form) => form.GetType() == typeof(frmDebugger) && ((frmDebugger)form).CpuType == CpuType.Gameboy); case DebugWindow.TraceLogger: return _openedWindows.ToList().Find((form) => form.GetType() == typeof(frmTraceLogger)); case DebugWindow.EventViewer: return _openedWindows.ToList().Find((form) => form.GetType() == typeof(frmEventViewer)); case DebugWindow.Profiler: return _openedWindows.ToList().Find((form) => form.GetType() == typeof(frmProfiler)); @@ -195,6 +199,7 @@ namespace Mesen.GUI.Debugger GsuDebugger, NecDspDebugger, Cx4Debugger, + GbDebugger, MemoryTools, TraceLogger, TileViewer, diff --git a/UI/Debugger/Labels/CodeLabel.cs b/UI/Debugger/Labels/CodeLabel.cs index 6b5cf75..cda4f90 100644 --- a/UI/Debugger/Labels/CodeLabel.cs +++ b/UI/Debugger/Labels/CodeLabel.cs @@ -30,6 +30,10 @@ namespace Mesen.GUI.Debugger.Labels case SnesMemoryType.BsxPsRam: sb.Append("PSRAM:"); break; case SnesMemoryType.BsxMemoryPack: sb.Append("MPACK:"); break; case SnesMemoryType.DspProgramRom: sb.Append("DSPPRG:"); break; + case SnesMemoryType.GbPrgRom: sb.Append("GBPRG:"); break; + case SnesMemoryType.GbWorkRam: sb.Append("GBWRAM:"); break; + case SnesMemoryType.GbCartRam: sb.Append("GBSRAM:"); break; + case SnesMemoryType.GbHighRam: sb.Append("GBHRAM:"); break; } sb.Append(Address.ToString("X4")); @@ -66,6 +70,10 @@ namespace Mesen.GUI.Debugger.Labels case "PSRAM": type = SnesMemoryType.BsxPsRam; break; case "MPACK": type = SnesMemoryType.BsxMemoryPack; break; case "DSPPRG": type = SnesMemoryType.DspProgramRom; break; + case "GBPRG": type = SnesMemoryType.GbPrgRom; break; + case "GBWRAM": type = SnesMemoryType.GsuWorkRam; break; + case "GBSRAM": type = SnesMemoryType.GbCartRam; break; + case "GBHRAM": type = SnesMemoryType.GbHighRam; break; default: return null; } diff --git a/UI/Debugger/Labels/LabelManager.cs b/UI/Debugger/Labels/LabelManager.cs index db2ccaa..8811886 100644 --- a/UI/Debugger/Labels/LabelManager.cs +++ b/UI/Debugger/Labels/LabelManager.cs @@ -88,6 +88,10 @@ namespace Mesen.GUI.Debugger.Labels case SnesMemoryType.BsxPsRam: return address | ((ulong)9 << 32); case SnesMemoryType.BsxMemoryPack: return address | ((ulong)10 << 32); case SnesMemoryType.DspProgramRom: return address | ((ulong)11 << 32); + case SnesMemoryType.GbPrgRom: return address | ((ulong)12 << 32); + case SnesMemoryType.GbWorkRam: return address | ((ulong)13 << 32); + case SnesMemoryType.GbCartRam: return address | ((ulong)14 << 32); + case SnesMemoryType.GbHighRam: return address | ((ulong)15 << 32); } throw new Exception("Invalid type"); } @@ -188,6 +192,8 @@ namespace Mesen.GUI.Debugger.Labels DebugApi.RefreshDisassembly(CpuType.Spc); } else if(label.MemoryType.ToCpuType() == CpuType.NecDsp) { DebugApi.RefreshDisassembly(CpuType.NecDsp); + } else if(label.MemoryType.ToCpuType() == CpuType.Gameboy) { + DebugApi.RefreshDisassembly(CpuType.Gameboy); } else { DebugApi.RefreshDisassembly(CpuType.Cpu); DebugApi.RefreshDisassembly(CpuType.Sa1); diff --git a/UI/Debugger/Labels/ctrlLabelList.cs b/UI/Debugger/Labels/ctrlLabelList.cs index c8bf0e6..ae91eb2 100644 --- a/UI/Debugger/Labels/ctrlLabelList.cs +++ b/UI/Debugger/Labels/ctrlLabelList.cs @@ -137,6 +137,10 @@ namespace Mesen.GUI.Debugger.Controls case SnesMemoryType.BsxPsRam: prefix = "PSRAM: $"; break; case SnesMemoryType.BsxMemoryPack: prefix = "MPACK: $"; break; case SnesMemoryType.DspProgramRom: prefix = "DSPPRG: $"; break; + case SnesMemoryType.GbPrgRom: prefix = "PRG: $"; break; + case SnesMemoryType.GbWorkRam: prefix = "WRAM: $"; break; + case SnesMemoryType.GbCartRam: prefix = "SRAM: $"; break; + case SnesMemoryType.GbHighRam: prefix = "HRAM: $"; break; default: throw new Exception("Unsupported type"); } int relAddress = label.GetRelativeAddress(_cpuType).Address; @@ -251,6 +255,7 @@ namespace Mesen.GUI.Debugger.Controls case CpuType.Spc: defaultMemType = SnesMemoryType.SpcRam; break; case CpuType.NecDsp: defaultMemType = SnesMemoryType.DspProgramRom; break; + case CpuType.Gameboy: defaultMemType = SnesMemoryType.GbPrgRom; break; default: throw new Exception("Unsupported CPU type"); } diff --git a/UI/Debugger/Labels/frmEditLabel.cs b/UI/Debugger/Labels/frmEditLabel.cs index ef45125..b799666 100644 --- a/UI/Debugger/Labels/frmEditLabel.cs +++ b/UI/Debugger/Labels/frmEditLabel.cs @@ -57,6 +57,13 @@ namespace Mesen.GUI.Debugger cboType.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.SpcRom)); } else if(cpuType == CpuType.NecDsp) { cboType.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.DspProgramRom)); + } else if(cpuType == CpuType.Gameboy) { + cboType.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.GbPrgRom)); + cboType.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.GbWorkRam)); + cboType.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.GbHighRam)); + if(DebugApi.GetMemorySize(SnesMemoryType.GbCartRam) > 0) { + cboType.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.GbCartRam)); + } } } diff --git a/UI/Debugger/PpuViewer/ctrlPaletteViewer.cs b/UI/Debugger/PpuViewer/ctrlPaletteViewer.cs index 5443d48..225d76a 100644 --- a/UI/Debugger/PpuViewer/ctrlPaletteViewer.cs +++ b/UI/Debugger/PpuViewer/ctrlPaletteViewer.cs @@ -114,7 +114,41 @@ namespace Mesen.GUI.Debugger public void RefreshData() { - _cgRam = DebugApi.GetMemoryState(SnesMemoryType.CGRam); + if(EmuApi.GetRomInfo().CoprocessorType == CoprocessorType.Gameboy) { + _cgRam = GetGameboyPalette(); + } else { + _cgRam = DebugApi.GetMemoryState(SnesMemoryType.CGRam); + } + } + + public static byte[] GetGameboyPalette() + { + byte[] cgRam = new byte[512]; + + //Generate a fake SNES-like palette based on the gameboy PPU state + GbPpuState state = DebugApi.GetState().Gameboy.Ppu; + + byte[,] paletteBytes = new byte[4,2] { + { 0xFF, 0x7F}, {0x18,0x63}, {0x8C, 0x31}, {0,0} + }; + + Action setPalette = (byte pal, UInt16 offset) => { + cgRam[offset] = paletteBytes[pal & 0x03, 0]; + cgRam[offset+1] = paletteBytes[pal & 0x03, 1]; + cgRam[offset+2] = paletteBytes[(pal >> 2) & 0x03, 0]; + cgRam[offset+3] = paletteBytes[(pal >> 2) & 0x03, 1]; + cgRam[offset+4] = paletteBytes[(pal >> 4) & 0x03, 0]; + cgRam[offset+5] = paletteBytes[(pal >> 4) & 0x03, 1]; + cgRam[offset+6] = paletteBytes[(pal >> 6) & 0x03, 0]; + cgRam[offset+7] = paletteBytes[(pal >> 6) & 0x03, 1]; + }; + + setPalette(state.BgPalette, 0); + setPalette(state.ObjPalette0, 32); + setPalette(state.ObjPalette1, 64); + setPalette(0xE4, 96); + + return cgRam; } private uint To8Bit(int color) diff --git a/UI/Debugger/PpuViewer/ctrlScanlineCycleSelect.cs b/UI/Debugger/PpuViewer/ctrlScanlineCycleSelect.cs index 0a67421..6126530 100644 --- a/UI/Debugger/PpuViewer/ctrlScanlineCycleSelect.cs +++ b/UI/Debugger/PpuViewer/ctrlScanlineCycleSelect.cs @@ -42,12 +42,13 @@ namespace Mesen.GUI.Debugger.Controls this.nudScanline.Value = _scanline; this.nudCycle.Value = _cycle; - DebugApi.SetViewerUpdateTiming(_viewerId, _scanline, _cycle); + RefreshSettings(); } public void RefreshSettings() { - DebugApi.SetViewerUpdateTiming(_viewerId, _scanline, _cycle); + bool isGameboy = EmuApi.GetRomInfo().CoprocessorType == CoprocessorType.Gameboy; + DebugApi.SetViewerUpdateTiming(_viewerId, Math.Min(_scanline, isGameboy ? 153 : 312), _cycle); } private void SetUpdateScanlineCycle(int scanline, int cycle) @@ -64,7 +65,8 @@ namespace Mesen.GUI.Debugger.Controls private void btnReset_Click(object sender, EventArgs e) { - this.nudScanline.Value = 241; + bool isGameboy = EmuApi.GetRomInfo().CoprocessorType == CoprocessorType.Gameboy; + this.nudScanline.Value = isGameboy ? 144 : 241; this.nudCycle.Value = 0; } } diff --git a/UI/Debugger/PpuViewer/frmRegisterViewer.cs b/UI/Debugger/PpuViewer/frmRegisterViewer.cs index abc8b15..0d3e79e 100644 --- a/UI/Debugger/PpuViewer/frmRegisterViewer.cs +++ b/UI/Debugger/PpuViewer/frmRegisterViewer.cs @@ -90,19 +90,28 @@ namespace Mesen.GUI.Debugger private void UpdateTabs() { - if(tpgCoprocessor != null) { - tabMain.TabPages.Remove(tpgCoprocessor); + tabMain.SelectedIndexChanged -= tabMain_SelectedIndexChanged; + _coprocessorType = EmuApi.GetRomInfo().CoprocessorType; + + tabMain.TabPages.Clear(); + if(_coprocessorType != CoprocessorType.Gameboy) { + tabMain.TabPages.AddRange(new TabPage[] { tpgCpu, tpgDma, tpgPpu, tpgSpc, tpgDsp }); + tabMain.SelectedTab = tpgCpu; } - _coprocessorType = EmuApi.GetRomInfo().CoprocessorType; - if(_coprocessorType == CoprocessorType.SA1) { + if(_coprocessorType == CoprocessorType.SA1 || _coprocessorType == CoprocessorType.Gameboy) { tpgCoprocessor = new TabPage(); - tpgCoprocessor.Text = "SA-1"; + tpgCoprocessor.Text = _coprocessorType == CoprocessorType.SA1 ? "SA-1" : "Gameboy"; ctrlCoprocessor = new ctrlPropertyList(); ctrlCoprocessor.Dock = DockStyle.Fill; tpgCoprocessor.Controls.Add(ctrlCoprocessor); tabMain.TabPages.Add(tpgCoprocessor); + + if(_coprocessorType == CoprocessorType.Gameboy) { + tabMain.SelectedTab = tpgCoprocessor; + } } + tabMain.SelectedIndexChanged += tabMain_SelectedIndexChanged; } private void OnNotificationReceived(NotificationEventArgs e) @@ -132,482 +141,643 @@ namespace Mesen.GUI.Debugger public void RefreshViewer() { if(tabMain.SelectedTab == tpgCpu) { - InternalRegisterState regs = _state.InternalRegs; - AluState alu = _state.Alu; - ctrlPropertyCpu.UpdateState(new List() { - new RegEntry("$4200 - $4201", "IRQ/NMI/Autopoll Enabled", null), - new RegEntry("$4200.7", "NMI Enabled", regs.EnableNmi), - new RegEntry("$4200.5", "V IRQ Enabled", regs.EnableVerticalIrq), - new RegEntry("$4200.4", "H IRQ Enabled", regs.EnableHorizontalIrq), - new RegEntry("$4200.1", "Auto Joypad Poll", regs.EnableAutoJoypadRead), - - new RegEntry("$4201", "IO Port", regs.IoPortOutput, Format.X8), - - new RegEntry("$4202 - $4206", "Mult/Div Registers (Input)", null), - new RegEntry("$4202", "Multiplicand", alu.MultOperand1, Format.X8), - new RegEntry("$4203", "Multiplier", alu.MultOperand2, Format.X8), - new RegEntry("$4204/5", "Dividend", alu.Dividend, Format.X16), - new RegEntry("$4206", "Divisor", alu.Divisor, Format.X8), - - new RegEntry("$4207 - $420A", "H/V IRQ Timers", null), - new RegEntry("$4207/8", "H Timer", regs.HorizontalTimer, Format.X16), - new RegEntry("$4209/A", "V Timer", regs.VerticalTimer, Format.X16), - - new RegEntry("$4207 - $420A", "Misc. Flags", null), - - new RegEntry("$420D", "FastROM Enabled", regs.EnableFastRom), - new RegEntry("$4210", "NMI Flag", (_reg4210 & 0x80) != 0), - new RegEntry("$4211", "IRQ Flag", (_reg4211 & 0x80) != 0), - - new RegEntry("$4212.x", "V-Blank Flag", (_reg4212 & 0x80) != 0), - new RegEntry("$4212.x", "H-Blank Flag", (_reg4212 & 0x40) != 0), - new RegEntry("$4212.x", "Auto Joypad Read", (_reg4212 & 0x01) != 0), - - new RegEntry("$4214 - $4217", "Mult/Div Registers (Result)", null), - new RegEntry("$4214/5", "Quotient", alu.DivResult, Format.X16), - new RegEntry("$4216/7", "Product / Remainder", alu.MultOrRemainderResult, Format.X16), - - new RegEntry("$4218 - $421F", "Input Data", null), - new RegEntry("$4218/9", "P1 Data", regs.ControllerData[0], Format.X16), - new RegEntry("$421A/B", "P2 Data", regs.ControllerData[1], Format.X16), - new RegEntry("$421C/D", "P3 Data", regs.ControllerData[2], Format.X16), - new RegEntry("$421E/F", "P4 Data", regs.ControllerData[3], Format.X16), - - }); + UpdateCpuTab(); } else if(tabMain.SelectedTab == tpgDma) { - List entries = new List(); - - //TODO - /*for(int i = 0; i < 8; i++) { - entries.Add(new RegEntry("$420C." + i.ToString(), "HDMA Channel " + i.ToString() + " Enabled", _state.DmaChannels[i].DmaActive)); - }*/ - - for(int i = 0; i < 8; i++) { - DmaChannelConfig ch = _state.DmaChannels[i]; - entries.Add(new RegEntry("DMA Channel " + i.ToString(), "", null)); - entries.Add(new RegEntry("$420B." + i.ToString(), "Channel Enabled", _state.DmaChannels[i].DmaActive)); - entries.Add(new RegEntry("$43" + i.ToString() + "0.0-2", "Transfer Mode", ch.TransferMode, Format.D)); - entries.Add(new RegEntry("$43" + i.ToString() + "0.3", "Fixed", ch.FixedTransfer)); - entries.Add(new RegEntry("$43" + i.ToString() + "0.4", "Decrement", ch.Decrement)); - entries.Add(new RegEntry("$43" + i.ToString() + "0.6", "Indirect HDMA", ch.HdmaIndirectAddressing)); - entries.Add(new RegEntry("$43" + i.ToString() + "0.7", "Direction", ch.InvertDirection ? "B -> A" : "A -> B")); - - entries.Add(new RegEntry("$43" + i.ToString() + "1", "B Bus Address", ch.DestAddress, Format.X8)); - entries.Add(new RegEntry("$43" + i.ToString() + "2/3/4", "A Bus Address", ((ch.SrcBank << 16) | ch.SrcAddress), Format.X24)); - entries.Add(new RegEntry("$43" + i.ToString() + "5/6", "Size", ch.TransferSize, Format.X16)); - entries.Add(new RegEntry("$43" + i.ToString() + "7", "HDMA Bank", ch.HdmaBank, Format.X8)); - entries.Add(new RegEntry("$43" + i.ToString() + "8/9", "HDMA Address", ch.HdmaTableAddress, Format.X16)); - entries.Add(new RegEntry("$43" + i.ToString() + "A", "HDMA Line Counter", ch.HdmaLineCounterAndRepeat, Format.X8)); - } - ctrlPropertyDma.UpdateState(entries); + UpdateDmaTab(); } else if(tabMain.SelectedTab == tpgSpc) { - SpcState spc = _state.Spc; - ctrlPropertySpc.UpdateState(new List() { - new RegEntry("$F0", "Test", null), - new RegEntry("$F0.0", "Timers Disabled", spc.TimersDisabled), - new RegEntry("$F0.1", "RAM Write Enabled", spc.WriteEnabled), - new RegEntry("$F0.3", "Timers Enabled", spc.TimersEnabled), - new RegEntry("$F0.4-5", "External Speed", spc.ExternalSpeed, Format.D), - new RegEntry("$F0.6-7", "Internal Speed", spc.InternalSpeed, Format.D), - - new RegEntry("$F1", "Control", null), - new RegEntry("$F1.0", "Timer 0 Enabled", spc.Timer0.Enabled), - new RegEntry("$F1.1", "Timer 1 Enabled", spc.Timer1.Enabled), - new RegEntry("$F1.2", "Timer 2 Enabled", spc.Timer2.Enabled), - new RegEntry("$F1.7", "IPL ROM Enabled", spc.RomEnabled), - - new RegEntry("$F2", "DSP", null), - new RegEntry("$F2", "DSP Register", spc.DspReg, Format.X8), - - new RegEntry("$F4 - $F7", "CPU<->SPC Ports", null), - new RegEntry("$F4", "Port 0 (CPU read)", spc.OutputReg[0], Format.X8), - new RegEntry("$F4", "Port 0 (SPC read)", spc.CpuRegs[0], Format.X8), - new RegEntry("$F5", "Port 1 (CPU read)", spc.OutputReg[1], Format.X8), - new RegEntry("$F5", "Port 1 (SPC read)", spc.CpuRegs[1], Format.X8), - new RegEntry("$F6", "Port 2 (CPU read)", spc.OutputReg[2], Format.X8), - new RegEntry("$F6", "Port 2 (SPC read)", spc.CpuRegs[2], Format.X8), - new RegEntry("$F7", "Port 3 (CPU read)", spc.OutputReg[3], Format.X8), - new RegEntry("$F7", "Port 3 (SPC read)", spc.CpuRegs[3], Format.X8), - - new RegEntry("$F8 - $F9", "RAM Registers", null), - new RegEntry("$F8", "RAM Reg 0", spc.RamReg[0], Format.X8), - new RegEntry("$F9", "RAM Reg 1", spc.RamReg[1], Format.X8), - - new RegEntry("$FA - $FF", "Timers", null), - new RegEntry("$FA", "Timer 0 Divider", spc.Timer0.Target, Format.X8), - new RegEntry("$FA", "Timer 0 Frequency", GetTimerFrequency(8000, spc.Timer0.Target)), - new RegEntry("$FB", "Timer 1 Divider", spc.Timer1.Target, Format.X8), - new RegEntry("$FB", "Timer 1 Frequency", GetTimerFrequency(8000, spc.Timer1.Target)), - new RegEntry("$FC", "Timer 2 Divider", spc.Timer2.Target, Format.X8), - new RegEntry("$FC", "Timer 2 Frequency", GetTimerFrequency(64000, spc.Timer2.Target)), - - new RegEntry("$FD", "Timer 0 Output", spc.Timer0.Output, Format.X8), - new RegEntry("$FE", "Timer 1 Output", spc.Timer1.Output, Format.X8), - new RegEntry("$FF", "Timer 2 Output", spc.Timer2.Output, Format.X8), - }); + UpdateSpcTab(); } else if(tabMain.SelectedTab == tpgDsp) { - DspState dsp = _state.Dsp; - List entries = new List(); - - Action addReg = (int i, string name) => { - entries.Add(new RegEntry("$" + i.ToString("X2"), name, dsp.Regs[i], Format.X8)); - }; - - addReg(0x0C, "Main Volume (MVOL) - Left"); - addReg(0x1C, "Main Volume (MVOL) - Right"); - addReg(0x2C, "Echo Volume (EVOL) - Left"); - addReg(0x3C, "Echo Volume (EVOL) - Right"); - - addReg(0x4C, "Key On (KON)"); - addReg(0x5C, "Key Off (KOF)"); - - addReg(0x7C, "Source End Block (ENDX)"); - addReg(0x0D, "Echo Feedback (EFB)"); - addReg(0x2D, "Pitch Modulation (PMON)"); - addReg(0x3D, "Noise Enable (NON)"); - addReg(0x4D, "Echo Enable (EON)"); - addReg(0x5D, "Source Directory (Offset) (DIR)"); - addReg(0x6D, "Echo Buffer (Offset) (ESA)"); - addReg(0x6D, "Echo Delay (EDL)"); - - entries.Add(new RegEntry("$6C", "Flags (FLG)", null)); - entries.Add(new RegEntry("$6C.0-4", "Noise Clock", dsp.Regs[0x6C] & 0x1F, Format.X8)); - entries.Add(new RegEntry("$6C.5", "Echo Disabled", (dsp.Regs[0x6C] & 0x20) != 0)); - entries.Add(new RegEntry("$6C.6", "Mute", (dsp.Regs[0x6C] & 0x40) != 0)); - entries.Add(new RegEntry("$6C.7", "Reset", (dsp.Regs[0x6C] & 0x80) != 0)); - - entries.Add(new RegEntry("$xF", "Coefficients", null)); - for(int i = 0; i < 8; i++) { - addReg((i << 4) | 0x0F, "Coefficient " + i); - } - - for(int i = 0; i < 8; i++) { - entries.Add(new RegEntry("Voice #" + i.ToString(), "", null)); - - int voice = i << 4; - addReg(voice | 0x00, "Left Volume (VOL)"); - addReg(voice | 0x01, "Right Volume (VOL)"); - entries.Add(new RegEntry("$" + i + "2 + $" + i + "3", "Pitch (P)", dsp.Regs[voice | 0x02] | (dsp.Regs[voice | 0x03] << 8), Format.X16)); - addReg(voice | 0x04, "Source (SRCN)"); - addReg(voice | 0x05, "ADSR1"); - addReg(voice | 0x06, "ADSR2"); - addReg(voice | 0x07, "GAIN"); - addReg(voice | 0x08, "ENVX"); - addReg(voice | 0x09, "OUTX"); - } - ctrlPropertyDsp.UpdateState(entries); + UpdateDspTab(); } else if(tabMain.SelectedTab == tpgPpu) { - PpuState ppu = _state.Ppu; - - ctrlPropertyPpu.UpdateState(new List() { - new RegEntry("$2100", "Brightness", null), - new RegEntry("$2100.0", "Forced Blank", ppu.ForcedVblank), - new RegEntry("$2100.4-7", "Brightness", ppu.ScreenBrightness), - new RegEntry("$2101", "OAM Settings", null), - new RegEntry("$2100.0-2", "OAM Table Address", ppu.OamBaseAddress, Format.X16), - new RegEntry("$2100.3-4", "OAM Second Table Address", (ppu.OamBaseAddress + ppu.OamAddressOffset) & 0x7FFF, Format.X16), - new RegEntry("$2101.5-7", "OAM Size Mode", ppu.OamMode), - new RegEntry("$2102-2103", "OAM Base Address", ppu.OamRamAddress), - new RegEntry("$2103.7", "OAM Priority", ppu.EnableOamPriority), - - new RegEntry("$2105", "BG Mode/Size", null), - new RegEntry("$2105.0-2", "BG Mode", ppu.BgMode), - new RegEntry("$2105.3", "Mode 1 BG3 Priority", ppu.Mode1Bg3Priority), - new RegEntry("$2105.4", "BG1 16x16 Tiles", ppu.Layers[0].LargeTiles), - new RegEntry("$2105.5", "BG2 16x16 Tiles", ppu.Layers[1].LargeTiles), - new RegEntry("$2105.6", "BG3 16x16 Tiles", ppu.Layers[2].LargeTiles), - new RegEntry("$2105.7", "BG4 16x16 Tiles", ppu.Layers[3].LargeTiles), - - new RegEntry("$2106", "Mosaic", null), - new RegEntry("$2106.0", "BG1 Mosaic Enabled", (ppu.MosaicEnabled & 0x01) != 0), - new RegEntry("$2106.1", "BG2 Mosaic Enabled", (ppu.MosaicEnabled & 0x02) != 0), - new RegEntry("$2106.2", "BG3 Mosaic Enabled", (ppu.MosaicEnabled & 0x04) != 0), - new RegEntry("$2106.3", "BG4 Mosaic Enabled", (ppu.MosaicEnabled & 0x08) != 0), - new RegEntry("$2106.4-7", "Mosaic Size", (ppu.MosaicSize - 1).ToString() + " (" + ppu.MosaicSize.ToString() + "x" + ppu.MosaicSize.ToString() + ")"), - - new RegEntry("$2107 - $210A", "Tilemap Addresses/Sizes", null), - new RegEntry("$2107.0-1", "BG1 Size", GetLayerSize(ppu.Layers[0])), - new RegEntry("$2107.2-6", "BG1 Address", ppu.Layers[0].TilemapAddress, Format.X16), - new RegEntry("$2108.0-1", "BG2 Size", GetLayerSize(ppu.Layers[1])), - new RegEntry("$2108.2-6", "BG2 Address", ppu.Layers[1].TilemapAddress, Format.X16), - new RegEntry("$2109.0-1", "BG3 Size", GetLayerSize(ppu.Layers[2])), - new RegEntry("$2109.2-6", "BG3 Address", ppu.Layers[2].TilemapAddress, Format.X16), - new RegEntry("$210A.0-1", "BG4 Size", GetLayerSize(ppu.Layers[3])), - new RegEntry("$210A.2-6", "BG4 Address", ppu.Layers[3].TilemapAddress, Format.X16), - - new RegEntry("$210B - $210C", "Tile Addresses", null), - new RegEntry("$210B.0-2", "BG1 Tile Address", ppu.Layers[0].ChrAddress, Format.X16), - new RegEntry("$210B.4-6", "BG2 Tile Address", ppu.Layers[1].ChrAddress, Format.X16), - new RegEntry("$210C.0-2", "BG3 Tile Address", ppu.Layers[2].ChrAddress, Format.X16), - new RegEntry("$210C.4-6", "BG4 Tile Address", ppu.Layers[3].ChrAddress, Format.X16), - - new RegEntry("$210D - $2114", "H/V Scroll Offsets", null), - new RegEntry("$210D", "BG1 H Offset", ppu.Layers[0].HScroll, Format.X16), - new RegEntry("$210D", "Mode7 H Offset", ppu.Mode7.HScroll, Format.X16), - new RegEntry("$210E", "BG1 V Offset", ppu.Layers[0].VScroll, Format.X16), - new RegEntry("$210E", "Mode7 V Offset", ppu.Mode7.VScroll, Format.X16), - - new RegEntry("$210F", "BG2 H Offset", ppu.Layers[1].HScroll, Format.X16), - new RegEntry("$2110", "BG2 V Offset", ppu.Layers[1].VScroll, Format.X16), - new RegEntry("$2111", "BG3 H Offset", ppu.Layers[2].HScroll, Format.X16), - new RegEntry("$2112", "BG3 V Offset", ppu.Layers[2].VScroll, Format.X16), - new RegEntry("$2113", "BG4 H Offset", ppu.Layers[3].HScroll, Format.X16), - new RegEntry("$2114", "BG4 V Offset", ppu.Layers[3].VScroll, Format.X16), - - new RegEntry("$2115 - $2117", "VRAM", null), - new RegEntry("$2115.0-1", "Increment Value", ppu.VramIncrementValue), - new RegEntry("$2115.2-3", "Address Mapping", ppu.VramAddressRemapping), - new RegEntry("$2115.7", "Increment on $2119", ppu.VramAddrIncrementOnSecondReg), - new RegEntry("$2116/7", "VRAM Address", ppu.VramAddress, Format.X16), - - new RegEntry("$211A - $2120", "Mode 7", null), - new RegEntry("$211A.0", "Mode 7 - Hor. Mirroring", ppu.Mode7.HorizontalMirroring), - new RegEntry("$211A.1", "Mode 7 - Vert. Mirroring", ppu.Mode7.VerticalMirroring), - new RegEntry("$211A.6", "Mode 7 - Fill w/ Tile 0", ppu.Mode7.FillWithTile0), - new RegEntry("$211A.7", "Mode 7 - Large Tilemap", ppu.Mode7.LargeMap), - - new RegEntry("$211B", "Mode 7 - Matrix A", ppu.Mode7.Matrix[0], Format.X16), - new RegEntry("$211C", "Mode 7 - Matrix B", ppu.Mode7.Matrix[1], Format.X16), - new RegEntry("$211D", "Mode 7 - Matrix C", ppu.Mode7.Matrix[2], Format.X16), - new RegEntry("$211E", "Mode 7 - Matrix D", ppu.Mode7.Matrix[3], Format.X16), - - new RegEntry("$211F", "Mode 7 - Center X", ppu.Mode7.CenterX, Format.X16), - new RegEntry("$2120", "Mode 7 - Center Y", ppu.Mode7.CenterY, Format.X16), - - new RegEntry("$2123 - $212B", "Windows", null), - new RegEntry("", "BG1 Windows", null), - new RegEntry("$2123.0", "BG1 Window 1 Inverted", ppu.Window[0].InvertedLayers[0] != 0), - new RegEntry("$2123.1", "BG1 Window 1 Active", ppu.Window[0].ActiveLayers[0] != 0), - new RegEntry("$2123.2", "BG1 Window 2 Inverted", ppu.Window[1].InvertedLayers[0] != 0), - new RegEntry("$2123.3", "BG1 Window 2 Active", ppu.Window[1].ActiveLayers[0] != 0), - - new RegEntry("", "BG2 Windows", null), - new RegEntry("$2123.4", "BG2 Window 1 Inverted", ppu.Window[0].InvertedLayers[1] != 0), - new RegEntry("$2123.5", "BG2 Window 1 Active", ppu.Window[0].ActiveLayers[1] != 0), - new RegEntry("$2123.6", "BG2 Window 2 Inverted", ppu.Window[1].InvertedLayers[1] != 0), - new RegEntry("$2123.7", "BG2 Window 2 Active", ppu.Window[1].ActiveLayers[1] != 0), - - new RegEntry("", "BG3 Windows", null), - new RegEntry("$2124.0", "BG3 Window 1 Inverted", ppu.Window[0].InvertedLayers[2] != 0), - new RegEntry("$2124.1", "BG3 Window 1 Active", ppu.Window[0].ActiveLayers[2] != 0), - new RegEntry("$2124.2", "BG3 Window 2 Inverted", ppu.Window[1].InvertedLayers[2] != 0), - new RegEntry("$2124.3", "BG3 Window 2 Active", ppu.Window[1].ActiveLayers[2] != 0), - - new RegEntry("", "BG4 Windows", null), - new RegEntry("$2124.4", "BG4 Window 1 Inverted", ppu.Window[0].InvertedLayers[3] != 0), - new RegEntry("$2124.5", "BG4 Window 1 Active", ppu.Window[0].ActiveLayers[3] != 0), - new RegEntry("$2124.6", "BG4 Window 2 Inverted", ppu.Window[1].InvertedLayers[3] != 0), - new RegEntry("$2124.7", "BG4 Window 2 Active", ppu.Window[1].ActiveLayers[3] != 0), - - new RegEntry("", "OAM Windows", null), - new RegEntry("$2125.0", "OAM Window 1 Inverted", ppu.Window[0].InvertedLayers[4] != 0), - new RegEntry("$2125.1", "OAM Window 1 Active", ppu.Window[0].ActiveLayers[4] != 0), - new RegEntry("$2125.2", "OAM Window 2 Inverted", ppu.Window[1].InvertedLayers[4] != 0), - new RegEntry("$2125.3", "OAM Window 2 Active", ppu.Window[1].ActiveLayers[4] != 0), - - new RegEntry("", "Color Windows", null), - new RegEntry("$2125.4", "Color Window 1 Inverted", ppu.Window[0].InvertedLayers[5] != 0), - new RegEntry("$2125.5", "Color Window 1 Active", ppu.Window[0].ActiveLayers[5] != 0), - new RegEntry("$2125.6", "Color Window 2 Inverted", ppu.Window[1].InvertedLayers[5] != 0), - new RegEntry("$2125.7", "Color Window 2 Active", ppu.Window[1].ActiveLayers[5] != 0), - - new RegEntry("", "Window Position", null), - new RegEntry("$2126", "Window 1 Left", ppu.Window[0].Left), - new RegEntry("$2127", "Window 1 Right", ppu.Window[0].Right), - new RegEntry("$2128", "Window 2 Left", ppu.Window[1].Left), - new RegEntry("$2129", "Window 2 Right", ppu.Window[1].Right), - - new RegEntry("", "Window Masks", null), - new RegEntry("$212A.0-1", "BG1 Window Mask", ppu.MaskLogic[0].ToString().ToUpper()), - new RegEntry("$212A.2-3", "BG2 Window Mask", ppu.MaskLogic[1].ToString().ToUpper()), - new RegEntry("$212A.4-5", "BG3 Window Mask", ppu.MaskLogic[2].ToString().ToUpper()), - new RegEntry("$212A.6-7", "BG4 Window Mask", ppu.MaskLogic[3].ToString().ToUpper()), - new RegEntry("$212B.6-7", "OAM Window Mask", ppu.MaskLogic[4].ToString().ToUpper()), - new RegEntry("$212B.6-7", "Color Window Mask", ppu.MaskLogic[5].ToString().ToUpper()), - - new RegEntry("$212C", "Main Screen Layers", null), - new RegEntry("$212C.0", "BG1 Enabled", (ppu.MainScreenLayers & 0x01) != 0), - new RegEntry("$212C.1", "BG2 Enabled", (ppu.MainScreenLayers & 0x02) != 0), - new RegEntry("$212C.2", "BG3 Enabled", (ppu.MainScreenLayers & 0x04) != 0), - new RegEntry("$212C.3", "BG4 Enabled", (ppu.MainScreenLayers & 0x08) != 0), - new RegEntry("$212C.4", "OAM Enabled", (ppu.MainScreenLayers & 0x10) != 0), - - new RegEntry("$212D", "Sub Screen Layers", null), - new RegEntry("$212D.0", "BG1 Enabled", (ppu.SubScreenLayers & 0x01) != 0), - new RegEntry("$212D.1", "BG2 Enabled", (ppu.SubScreenLayers & 0x02) != 0), - new RegEntry("$212D.2", "BG3 Enabled", (ppu.SubScreenLayers & 0x04) != 0), - new RegEntry("$212D.3", "BG4 Enabled", (ppu.SubScreenLayers & 0x08) != 0), - new RegEntry("$212D.4", "OAM Enabled", (ppu.SubScreenLayers & 0x10) != 0), - - new RegEntry("$212E", "Main Screen Windows", null), - new RegEntry("$212E.0", "BG1 Mainscreen Window Enabled", ppu.WindowMaskMain[0] != 0), - new RegEntry("$212E.1", "BG2 Mainscreen Window Enabled", ppu.WindowMaskMain[1] != 0), - new RegEntry("$212E.2", "BG3 Mainscreen Window Enabled", ppu.WindowMaskMain[2] != 0), - new RegEntry("$212E.3", "BG4 Mainscreen Window Enabled", ppu.WindowMaskMain[3] != 0), - new RegEntry("$212E.4", "OAM Mainscreen Window Enabled", ppu.WindowMaskMain[4] != 0), - - new RegEntry("$212F", "Sub Screen Windows", null), - new RegEntry("$212F.0", "BG1 Subscreen Window Enabled", ppu.WindowMaskSub[0] != 0), - new RegEntry("$212F.1", "BG2 Subscreen Window Enabled", ppu.WindowMaskSub[1] != 0), - new RegEntry("$212F.2", "BG3 Subscreen Window Enabled", ppu.WindowMaskSub[2] != 0), - new RegEntry("$212F.3", "BG4 Subscreen Window Enabled", ppu.WindowMaskSub[3] != 0), - new RegEntry("$212F.4", "OAM Subscreen Window Enabled", ppu.WindowMaskSub[4] != 0), - - new RegEntry("$2130 - $2131", "Color Math", null), - new RegEntry("$2130.0", "Direct Color Mode", ppu.DirectColorMode), - new RegEntry("$2130.1", "CM - Add Subscreen", ppu.ColorMathAddSubscreen), - new RegEntry("$2130.4-5", "CM - Prevent Mode", ppu.ColorMathPreventMode.ToString()), - new RegEntry("$2130.6-7", "CM - Clip Mode", ppu.ColorMathClipMode.ToString()), - - new RegEntry("$2131.0", "CM - BG1 Enabled", (ppu.ColorMathEnabled & 0x01) != 0), - new RegEntry("$2131.1", "CM - BG2 Enabled", (ppu.ColorMathEnabled & 0x02) != 0), - new RegEntry("$2131.2", "CM - BG3 Enabled", (ppu.ColorMathEnabled & 0x04) != 0), - new RegEntry("$2131.3", "CM - BG4 Enabled", (ppu.ColorMathEnabled & 0x08) != 0), - new RegEntry("$2131.4", "CM - OAM Enabled", (ppu.ColorMathEnabled & 0x10) != 0), - new RegEntry("$2131.5", "CM - Background Enabled", (ppu.ColorMathEnabled & 0x20) != 0), - new RegEntry("$2131.6", "CM - Half Mode", ppu.ColorMathHalveResult), - new RegEntry("$2131.7", "CM - Substract Mode", ppu.ColorMathSubstractMode), - - new RegEntry("$2132 - $2133", "Misc.", null), - new RegEntry("$2132", "Fixed Color - BGR", ppu.FixedColor, Format.X16), - - new RegEntry("$2133.0", "Screen Interlace", ppu.ScreenInterlace), - new RegEntry("$2133.1", "OAM Interlace", ppu.ObjInterlace), - new RegEntry("$2133.2", "Overscan Mode", ppu.OverscanMode), - new RegEntry("$2133.3", "High Resolution Mode", ppu.HiResMode), - new RegEntry("$2133.4", "Ext. BG Enabled", ppu.ExtBgEnabled), - }); + UpdatePpuTab(); } else if(tabMain.SelectedTab == tpgCoprocessor) { if(_coprocessorType == CoprocessorType.SA1) { - Sa1State sa1 = _state.Sa1.Sa1; - - List entries = new List() { - new RegEntry("$2200", "SA-1 CPU Control", null), - new RegEntry("$2200.0-3", "Message", sa1.Sa1MessageReceived, Format.X8), - new RegEntry("$2200.4", "NMI Requested", sa1.Sa1NmiRequested), - new RegEntry("$2200.5", "Reset", sa1.Sa1Reset), - new RegEntry("$2200.6", "Wait", sa1.Sa1Wait), - new RegEntry("$2200.7", "IRQ Requested", sa1.Sa1IrqRequested), - - new RegEntry("$2201", "S-CPU Interrupt Enable", null), - new RegEntry("$2201.5", "Character Conversion IRQ Enable", sa1.CharConvIrqEnabled), - new RegEntry("$2201.7", "IRQ Enabled", sa1.CpuIrqEnabled), - - new RegEntry("$2202", "S-CPU Interrupt Clear", null), - new RegEntry("$2202.5", "Character IRQ Flag", sa1.CharConvIrqFlag), - new RegEntry("$2202.7", "IRQ Flag", sa1.CpuIrqRequested), - - new RegEntry("$2203/4", "SA-1 Reset Vector", sa1.Sa1ResetVector, Format.X16), - new RegEntry("$2205/6", "SA-1 NMI Vector", sa1.Sa1ResetVector, Format.X16), - new RegEntry("$2207/8", "SA-1 IRQ Vector", sa1.Sa1ResetVector, Format.X16), - - new RegEntry("$2209", "S-CPU Control", null), - new RegEntry("$2209.0-3", "Message", sa1.CpuMessageReceived, Format.X8), - new RegEntry("$2209.4", "Use NMI Vector", sa1.UseCpuNmiVector), - new RegEntry("$2209.6", "Use IRQ Vector", sa1.UseCpuIrqVector), - new RegEntry("$2209.7", "IRQ Requested", sa1.CpuIrqRequested), - - new RegEntry("$220A", "SA-1 CPU Interrupt Enable", null), - new RegEntry("$220A.4", "SA-1 NMI Enabled", sa1.Sa1NmiEnabled), - new RegEntry("$220A.5", "DMA IRQ Enabled", sa1.DmaIrqEnabled), - new RegEntry("$220A.6", "Timer IRQ Enabled", sa1.TimerIrqEnabled), - new RegEntry("$220A.7", "SA-1 IRQ Enabled", sa1.Sa1IrqEnabled), - - new RegEntry("$220B", "S-CPU Interrupt Clear", null), - new RegEntry("$220B.4", "SA-1 NMI Requested", sa1.Sa1NmiRequested), - new RegEntry("$220B.5", "DMA IRQ Flag", sa1.DmaIrqFlag), - new RegEntry("$220B.7", "SA-1 IRQ Requested", sa1.Sa1IrqRequested), - - new RegEntry("$220C/D", "S-CPU NMI Vector", sa1.CpuNmiVector, Format.X16), - new RegEntry("$220E/F", "S-CPU IRQ Vector", sa1.CpuIrqVector, Format.X16), - - new RegEntry("$2210", "H/V Timer Control", null), - new RegEntry("$2210.0", "Horizontal Timer Enabled", sa1.HorizontalTimerEnabled), - new RegEntry("$2210.1", "Vertical Timer Enabled", sa1.VerticalTimerEnabled), - new RegEntry("$2210.7", "Linear Timer", sa1.UseLinearTimer), - - new RegEntry("$2212/3", "H-Timer", sa1.HTimer, Format.X16), - new RegEntry("$2214/5", "V-Timer", sa1.VTimer, Format.X16), - - new RegEntry("", "ROM/BWRAM/IRAM Mappings", null), - new RegEntry("$2220", "MMC Bank C", sa1.Banks[0], Format.X8), - new RegEntry("$2221", "MMC Bank D", sa1.Banks[1], Format.X8), - new RegEntry("$2222", "MMC Bank E", sa1.Banks[2], Format.X8), - new RegEntry("$2223", "MMC Bank F", sa1.Banks[3], Format.X8), - - new RegEntry("$2224", "S-CPU BW-RAM Bank", sa1.CpuBwBank, Format.X8), - new RegEntry("$2225.0-6", "SA-1 CPU BW-RAM Bank", sa1.Sa1BwBank, Format.X8), - new RegEntry("$2225.7", "SA-1 CPU BW-RAM Mode", sa1.Sa1BwMode, Format.X8), - new RegEntry("$2226.7", "S-CPU BW-RAM Write Enabled", sa1.CpuBwWriteEnabled), - new RegEntry("$2227.7", "SA-1 BW-RAM Write Enabled", sa1.Sa1BwWriteEnabled), - new RegEntry("$2228.0-3", "S-CPU BW-RAM Write Protected Area", sa1.BwWriteProtectedArea, Format.X8), - new RegEntry("$2229", "S-CPU I-RAM Write Protection", sa1.CpuIRamWriteProtect, Format.X8), - new RegEntry("$222A", "SA-1 CPU I-RAM Write Protection", sa1.Sa1IRamWriteProtect, Format.X8), - - new RegEntry("$2230", "DMA Control", null), - new RegEntry("$2230.0-1", "DMA Source Device", sa1.DmaSrcDevice.ToString()), - new RegEntry("$2230.2-3", "DMA Destination Device", sa1.DmaDestDevice.ToString()), - new RegEntry("$2230.4", "Automatic DMA Character Conversion", sa1.DmaCharConvAuto), - new RegEntry("$2230.5", "DMA Character Conversion", sa1.DmaCharConv), - new RegEntry("$2230.6", "DMA Priority", sa1.DmaPriority), - new RegEntry("$2230.7", "DMA Enabled", sa1.DmaEnabled), - - new RegEntry("$2231.0-1", "Character Format (BPP)", sa1.CharConvBpp, Format.D), - new RegEntry("$2231.2-5", "Character Conversion Width", sa1.CharConvWidth, Format.X8), - new RegEntry("$2231.7", "Character DMA Active", sa1.CharConvDmaActive), - - new RegEntry("$2232/3/4", "DMA Source Address", sa1.DmaSrcAddr, Format.X24), - new RegEntry("$2235/6/7", "DMA Destination Address", sa1.DmaDestAddr, Format.X24), - - new RegEntry("$2238/9", "DMA Size", sa1.DmaSize, Format.X16), - new RegEntry("$223F.7", "BW-RAM 2 bpp mode", sa1.BwRam2BppMode) - }; - - entries.Add(new RegEntry("", "Bitmap Register File", null)); - for(int i = 0; i < 8; i++) { - entries.Add(new RegEntry("$224" + i, "BRF #" + i, sa1.BitmapRegister1[i])); - } - for(int i = 0; i < 8; i++) { - entries.Add(new RegEntry("$224" + (8 + i).ToString("X"), "BRF #" + (i+8), sa1.BitmapRegister2[i])); - } - - entries.AddRange(new List() { - new RegEntry("", "Math Registers", null), - new RegEntry("$2250.0-1", "Math Operation", sa1.MathOp.ToString()), - new RegEntry("$2251/2", "Multiplicand/Dividend", sa1.MultiplicandDividend, Format.X16), - new RegEntry("$2253/4", "Multiplier/Divisor", sa1.MultiplierDivisor, Format.X16), - - new RegEntry("", "Variable Length Registers", null), - new RegEntry("$2258", "Variable Length Bit Processing", null), - new RegEntry("$2258.0-3", "Variable Length Bit Count", sa1.VarLenBitCount, Format.X8), - new RegEntry("$2258.7", "Variable Length Auto-Increment", sa1.VarLenAutoInc), - new RegEntry("$2259/A/B", "Variable Length Address", sa1.VarLenAddress, Format.X24), - - new RegEntry("$2300", "S-CPU Status Flags", null), - new RegEntry("$2300.0-3", "Message Received", sa1.CpuMessageReceived, Format.X8), - new RegEntry("$2300.4", "Use NMI Vector", sa1.UseCpuNmiVector), - new RegEntry("$2300.5", "Character Conversion IRQ Flag", sa1.CharConvIrqFlag), - new RegEntry("$2300.6", "Use IRQ Vector", sa1.UseCpuIrqVector), - new RegEntry("$2300.7", "IRQ Requested", sa1.CpuIrqRequested), - - new RegEntry("$2301", "SA-1 Status Flags", null), - new RegEntry("$2301.0-3", "Message Received", sa1.Sa1MessageReceived, Format.X8), - new RegEntry("$2301.4", "NMI Requested", sa1.Sa1NmiRequested), - new RegEntry("$2301.5", "DMA IRQ Flag", sa1.DmaIrqFlag), - new RegEntry("$2301.7", "IRQ Requested", sa1.Sa1IrqRequested), - - new RegEntry("$2302/3", "SA-1 H-Counter", 0, Format.X16), - new RegEntry("$2304/5", "SA-1 V-Counter", 0, Format.X16), - - new RegEntry("$2306/7/8/9/A", "Math Result", sa1.MathOpResult), - new RegEntry("$230B.7", "Math Overflow", sa1.MathOverflow) - }); - - ctrlCoprocessor.UpdateState(entries); + UpdateSa1Tab(); + } else if(_coprocessorType == CoprocessorType.Gameboy) { + UpdateGameboyTab(); } } } + private void UpdateGameboyTab() + { + GbState gb = _state.Gameboy; + List entries = new List(); + + GbPpuState ppu = gb.Ppu; + entries.AddRange(new List() { + new RegEntry("$FF40", "LCD Control (LCDC)", null), + new RegEntry("$FF40.0", "Background Enabled", ppu.BgEnabled), + new RegEntry("$FF40.1", "Sprites Enabled", ppu.BgEnabled), + new RegEntry("$FF40.2", "Sprite size", ppu.LargeSprites ? "8x16" : "8x8"), + new RegEntry("$FF40.3", "BG Tilemap Select", ppu.BgTilemapSelect ? 0x9C00 : 0x9800, Format.X16), + new RegEntry("$FF40.4", "BG Tile Select", ppu.BgTileSelect ? "$8000-$8FFF" : "$8800-$97FF"), + new RegEntry("$FF40.5", "Window Enabled", ppu.WindowEnabled), + new RegEntry("$FF40.6", "Window Tilemap Select", ppu.WindowTilemapSelect ? 0x9C00 : 0x9800, Format.X16), + new RegEntry("$FF40.7", "LCD Enabled", ppu.LcdEnabled), + + new RegEntry("$FF41", "LCD Status (STAT)", null), + new RegEntry("$FF41.0-1", "Mode", (ppu.Status & 0x03)), + new RegEntry("$FF41.2", "Coincidence Flag", (ppu.Status & 0x04) != 0), + new RegEntry("$FF41.3", "Mode 0 H-Blank IRQ", (ppu.Status & 0x08) != 0), + new RegEntry("$FF41.4", "Mode 1 V-Blank IRQ", (ppu.Status & 0x10) != 0), + new RegEntry("$FF41.5", "Mode 2 OAM IRQ", (ppu.Status & 0x20) != 0), + new RegEntry("$FF41.6", "LYC=LY Coincidence IRQ", (ppu.Status & 0x40) != 0), + + new RegEntry("", "LCD Registers", null), + new RegEntry("$FF42", "Scroll Y (SCY)", ppu.ScrollY, Format.X8), + new RegEntry("$FF43", "Scroll X (SCX)", ppu.ScrollX, Format.X8), + new RegEntry("$FF44", "Y-Coordinate (LY)", ppu.Scanline, Format.X8), + new RegEntry("$FF45", "LY Compare (LYC)", ppu.LyCompare, Format.X8), + new RegEntry("$FF47", "BG Palette (BGP)", ppu.BgPalette, Format.X8), + new RegEntry("$FF48", "OBJ Palette 0 (OBP0)", ppu.ObjPalette0, Format.X8), + new RegEntry("$FF49", "OBJ Palette 1 (OBP1)", ppu.ObjPalette1, Format.X8), + new RegEntry("$FF4A", "Window Y (WY)", ppu.WindowY, Format.X8), + new RegEntry("$FF4B", "Window X (WX)", ppu.WindowX, Format.X8), + }); + + GbSquareState sq1 = gb.Apu.Square1; + entries.AddRange(new List() { + new RegEntry("$FF10-$FF14", "Square 1", null), + new RegEntry("$FF10.0-2", "Sweep Shift", sq1.SweepShift), + new RegEntry("$FF10.3", "Sweep Negate", sq1.SweepNegate), + new RegEntry("$FF10.4-7", "Sweep Period", sq1.SweepPeriod), + + new RegEntry("$FF11.0-5", "Length", sq1.Length), + new RegEntry("$FF11.6-7", "Duty", sq1.Duty), + + new RegEntry("$FF12.0-2", "Envelope Period", sq1.EnvPeriod), + new RegEntry("$FF12.3", "Envelope Increase Volume", sq1.EnvRaiseVolume), + new RegEntry("$FF12.4-7", "Envelope Volume", sq1.EnvVolume), + + new RegEntry("$FF13+$FF14.0-2", "Frequency", sq1.Frequency), + new RegEntry("$FF14.6", "Length Counter Enabled", sq1.LengthEnabled), + new RegEntry("$FF14.7", "Channel Enabled", sq1.Enabled), + + new RegEntry("--", "Timer", sq1.Timer), + new RegEntry("--", "Duty Position", sq1.DutyPos), + new RegEntry("--", "Sweep Enabled", sq1.SweepEnabled), + new RegEntry("--", "Sweep Frequency", sq1.SweepFreq), + new RegEntry("--", "Sweep Timer", sq1.SweepTimer), + new RegEntry("--", "Envelope Timer", sq1.EnvTimer), + new RegEntry("--", "Output", sq1.Output) + }); + + GbSquareState sq2 = gb.Apu.Square2; + entries.AddRange(new List() { + new RegEntry("$FF16-$FF19", "Square 2", null), + new RegEntry("$FF16.0-5", "Length", sq2.Length), + new RegEntry("$FF16.6-7", "Duty", sq2.Duty), + + new RegEntry("$FF17.0-2", "Envelope Period", sq2.EnvPeriod), + new RegEntry("$FF17.3", "Envelope Increase Volume", sq2.EnvRaiseVolume), + new RegEntry("$FF17.4-7", "Envelope Volume", sq2.EnvVolume), + + new RegEntry("$FF18+$FF19.0-2", "Frequency", sq2.Frequency), + new RegEntry("$FF19.6", "Length Counter Enabled", sq2.LengthEnabled), + new RegEntry("$FF19.7", "Channel Enabled", sq2.Enabled), + + new RegEntry("--", "Timer", sq2.Timer), + new RegEntry("--", "Duty Position", sq2.DutyPos), + new RegEntry("--", "Envelope Timer", sq2.EnvTimer), + new RegEntry("--", "Output", sq2.Output) + }); + + GbNoiseState noise = gb.Apu.Noise; + entries.AddRange(new List() { + new RegEntry("$FF20-$FF23", "Noise", null), + new RegEntry("$FF20.0-5", "Length", noise.Length), + + new RegEntry("$FF21.0-2", "Envelope Period", noise.EnvPeriod), + new RegEntry("$FF21.3", "Envelope Increase Volume", noise.EnvRaiseVolume), + new RegEntry("$FF21.4-7", "Envelope Volume", noise.EnvVolume), + + new RegEntry("$FF23.0-2", "Divisor", noise.Divisor), + new RegEntry("$FF23.3", "Short Mode", noise.ShortWidthMode), + new RegEntry("$FF23.4-7", "Period Shift", noise.PeriodShift), + + new RegEntry("$FF24.6", "Length Counter Enabled", noise.LengthEnabled), + new RegEntry("$FF24.7", "Channel Enabled", noise.Enabled), + + new RegEntry("--", "Timer", noise.Timer), + new RegEntry("--", "Envelope Timer", noise.EnvTimer), + new RegEntry("--", "Shift Register", noise.ShiftRegister, Format.X16), + new RegEntry("--", "Output", noise.Output) + }); + + + GbWaveState wave = gb.Apu.Wave; + entries.AddRange(new List() { + new RegEntry("$FF1A-$FF1E", "Wave", null), + new RegEntry("$FF1A.7", "Sound Enabled", wave.DacEnabled), + + new RegEntry("$FF1B", "Length", wave.Length), + + new RegEntry("$FF1C.5-6", "Volume", wave.Volume), + + new RegEntry("$FF1D+$FF1E.0-2", "Frequency", wave.Frequency), + + new RegEntry("$FF1E.6", "Length Counter Enabled", wave.LengthEnabled), + new RegEntry("$FF1E.7", "Channel Enabled", wave.Enabled), + + new RegEntry("--", "Timer", wave.Timer), + new RegEntry("--", "Position", wave.SampleBuffer), + new RegEntry("--", "Position", wave.Position), + new RegEntry("--", "Output", wave.Output), + }); + + ctrlCoprocessor.UpdateState(entries); + } + + private void UpdateSa1Tab() + { + Sa1State sa1 = _state.Sa1.Sa1; + + List entries = new List() { + new RegEntry("$2200", "SA-1 CPU Control", null), + new RegEntry("$2200.0-3", "Message", sa1.Sa1MessageReceived, Format.X8), + new RegEntry("$2200.4", "NMI Requested", sa1.Sa1NmiRequested), + new RegEntry("$2200.5", "Reset", sa1.Sa1Reset), + new RegEntry("$2200.6", "Wait", sa1.Sa1Wait), + new RegEntry("$2200.7", "IRQ Requested", sa1.Sa1IrqRequested), + + new RegEntry("$2201", "S-CPU Interrupt Enable", null), + new RegEntry("$2201.5", "Character Conversion IRQ Enable", sa1.CharConvIrqEnabled), + new RegEntry("$2201.7", "IRQ Enabled", sa1.CpuIrqEnabled), + + new RegEntry("$2202", "S-CPU Interrupt Clear", null), + new RegEntry("$2202.5", "Character IRQ Flag", sa1.CharConvIrqFlag), + new RegEntry("$2202.7", "IRQ Flag", sa1.CpuIrqRequested), + + new RegEntry("$2203/4", "SA-1 Reset Vector", sa1.Sa1ResetVector, Format.X16), + new RegEntry("$2205/6", "SA-1 NMI Vector", sa1.Sa1ResetVector, Format.X16), + new RegEntry("$2207/8", "SA-1 IRQ Vector", sa1.Sa1ResetVector, Format.X16), + + new RegEntry("$2209", "S-CPU Control", null), + new RegEntry("$2209.0-3", "Message", sa1.CpuMessageReceived, Format.X8), + new RegEntry("$2209.4", "Use NMI Vector", sa1.UseCpuNmiVector), + new RegEntry("$2209.6", "Use IRQ Vector", sa1.UseCpuIrqVector), + new RegEntry("$2209.7", "IRQ Requested", sa1.CpuIrqRequested), + + new RegEntry("$220A", "SA-1 CPU Interrupt Enable", null), + new RegEntry("$220A.4", "SA-1 NMI Enabled", sa1.Sa1NmiEnabled), + new RegEntry("$220A.5", "DMA IRQ Enabled", sa1.DmaIrqEnabled), + new RegEntry("$220A.6", "Timer IRQ Enabled", sa1.TimerIrqEnabled), + new RegEntry("$220A.7", "SA-1 IRQ Enabled", sa1.Sa1IrqEnabled), + + new RegEntry("$220B", "S-CPU Interrupt Clear", null), + new RegEntry("$220B.4", "SA-1 NMI Requested", sa1.Sa1NmiRequested), + new RegEntry("$220B.5", "DMA IRQ Flag", sa1.DmaIrqFlag), + new RegEntry("$220B.7", "SA-1 IRQ Requested", sa1.Sa1IrqRequested), + + new RegEntry("$220C/D", "S-CPU NMI Vector", sa1.CpuNmiVector, Format.X16), + new RegEntry("$220E/F", "S-CPU IRQ Vector", sa1.CpuIrqVector, Format.X16), + + new RegEntry("$2210", "H/V Timer Control", null), + new RegEntry("$2210.0", "Horizontal Timer Enabled", sa1.HorizontalTimerEnabled), + new RegEntry("$2210.1", "Vertical Timer Enabled", sa1.VerticalTimerEnabled), + new RegEntry("$2210.7", "Linear Timer", sa1.UseLinearTimer), + + new RegEntry("$2212/3", "H-Timer", sa1.HTimer, Format.X16), + new RegEntry("$2214/5", "V-Timer", sa1.VTimer, Format.X16), + + new RegEntry("", "ROM/BWRAM/IRAM Mappings", null), + new RegEntry("$2220", "MMC Bank C", sa1.Banks[0], Format.X8), + new RegEntry("$2221", "MMC Bank D", sa1.Banks[1], Format.X8), + new RegEntry("$2222", "MMC Bank E", sa1.Banks[2], Format.X8), + new RegEntry("$2223", "MMC Bank F", sa1.Banks[3], Format.X8), + + new RegEntry("$2224", "S-CPU BW-RAM Bank", sa1.CpuBwBank, Format.X8), + new RegEntry("$2225.0-6", "SA-1 CPU BW-RAM Bank", sa1.Sa1BwBank, Format.X8), + new RegEntry("$2225.7", "SA-1 CPU BW-RAM Mode", sa1.Sa1BwMode, Format.X8), + new RegEntry("$2226.7", "S-CPU BW-RAM Write Enabled", sa1.CpuBwWriteEnabled), + new RegEntry("$2227.7", "SA-1 BW-RAM Write Enabled", sa1.Sa1BwWriteEnabled), + new RegEntry("$2228.0-3", "S-CPU BW-RAM Write Protected Area", sa1.BwWriteProtectedArea, Format.X8), + new RegEntry("$2229", "S-CPU I-RAM Write Protection", sa1.CpuIRamWriteProtect, Format.X8), + new RegEntry("$222A", "SA-1 CPU I-RAM Write Protection", sa1.Sa1IRamWriteProtect, Format.X8), + + new RegEntry("$2230", "DMA Control", null), + new RegEntry("$2230.0-1", "DMA Source Device", sa1.DmaSrcDevice.ToString()), + new RegEntry("$2230.2-3", "DMA Destination Device", sa1.DmaDestDevice.ToString()), + new RegEntry("$2230.4", "Automatic DMA Character Conversion", sa1.DmaCharConvAuto), + new RegEntry("$2230.5", "DMA Character Conversion", sa1.DmaCharConv), + new RegEntry("$2230.6", "DMA Priority", sa1.DmaPriority), + new RegEntry("$2230.7", "DMA Enabled", sa1.DmaEnabled), + + new RegEntry("$2231.0-1", "Character Format (BPP)", sa1.CharConvBpp, Format.D), + new RegEntry("$2231.2-5", "Character Conversion Width", sa1.CharConvWidth, Format.X8), + new RegEntry("$2231.7", "Character DMA Active", sa1.CharConvDmaActive), + + new RegEntry("$2232/3/4", "DMA Source Address", sa1.DmaSrcAddr, Format.X24), + new RegEntry("$2235/6/7", "DMA Destination Address", sa1.DmaDestAddr, Format.X24), + + new RegEntry("$2238/9", "DMA Size", sa1.DmaSize, Format.X16), + new RegEntry("$223F.7", "BW-RAM 2 bpp mode", sa1.BwRam2BppMode) + }; + + entries.Add(new RegEntry("", "Bitmap Register File", null)); + for(int i = 0; i < 8; i++) { + entries.Add(new RegEntry("$224" + i, "BRF #" + i, sa1.BitmapRegister1[i])); + } + for(int i = 0; i < 8; i++) { + entries.Add(new RegEntry("$224" + (8 + i).ToString("X"), "BRF #" + (i + 8), sa1.BitmapRegister2[i])); + } + + entries.AddRange(new List() { + new RegEntry("", "Math Registers", null), + new RegEntry("$2250.0-1", "Math Operation", sa1.MathOp.ToString()), + new RegEntry("$2251/2", "Multiplicand/Dividend", sa1.MultiplicandDividend, Format.X16), + new RegEntry("$2253/4", "Multiplier/Divisor", sa1.MultiplierDivisor, Format.X16), + + new RegEntry("", "Variable Length Registers", null), + new RegEntry("$2258", "Variable Length Bit Processing", null), + new RegEntry("$2258.0-3", "Variable Length Bit Count", sa1.VarLenBitCount, Format.X8), + new RegEntry("$2258.7", "Variable Length Auto-Increment", sa1.VarLenAutoInc), + new RegEntry("$2259/A/B", "Variable Length Address", sa1.VarLenAddress, Format.X24), + + new RegEntry("$2300", "S-CPU Status Flags", null), + new RegEntry("$2300.0-3", "Message Received", sa1.CpuMessageReceived, Format.X8), + new RegEntry("$2300.4", "Use NMI Vector", sa1.UseCpuNmiVector), + new RegEntry("$2300.5", "Character Conversion IRQ Flag", sa1.CharConvIrqFlag), + new RegEntry("$2300.6", "Use IRQ Vector", sa1.UseCpuIrqVector), + new RegEntry("$2300.7", "IRQ Requested", sa1.CpuIrqRequested), + + new RegEntry("$2301", "SA-1 Status Flags", null), + new RegEntry("$2301.0-3", "Message Received", sa1.Sa1MessageReceived, Format.X8), + new RegEntry("$2301.4", "NMI Requested", sa1.Sa1NmiRequested), + new RegEntry("$2301.5", "DMA IRQ Flag", sa1.DmaIrqFlag), + new RegEntry("$2301.7", "IRQ Requested", sa1.Sa1IrqRequested), + + new RegEntry("$2302/3", "SA-1 H-Counter", 0, Format.X16), + new RegEntry("$2304/5", "SA-1 V-Counter", 0, Format.X16), + + new RegEntry("$2306/7/8/9/A", "Math Result", sa1.MathOpResult), + new RegEntry("$230B.7", "Math Overflow", sa1.MathOverflow) + }); + + ctrlCoprocessor.UpdateState(entries); + } + + private void UpdatePpuTab() + { + PpuState ppu = _state.Ppu; + + ctrlPropertyPpu.UpdateState(new List() { + new RegEntry("$2100", "Brightness", null), + new RegEntry("$2100.0", "Forced Blank", ppu.ForcedVblank), + new RegEntry("$2100.4-7", "Brightness", ppu.ScreenBrightness), + new RegEntry("$2101", "OAM Settings", null), + new RegEntry("$2100.0-2", "OAM Table Address", ppu.OamBaseAddress, Format.X16), + new RegEntry("$2100.3-4", "OAM Second Table Address", (ppu.OamBaseAddress + ppu.OamAddressOffset) & 0x7FFF, Format.X16), + new RegEntry("$2101.5-7", "OAM Size Mode", ppu.OamMode), + new RegEntry("$2102-2103", "OAM Base Address", ppu.OamRamAddress), + new RegEntry("$2103.7", "OAM Priority", ppu.EnableOamPriority), + + new RegEntry("$2105", "BG Mode/Size", null), + new RegEntry("$2105.0-2", "BG Mode", ppu.BgMode), + new RegEntry("$2105.3", "Mode 1 BG3 Priority", ppu.Mode1Bg3Priority), + new RegEntry("$2105.4", "BG1 16x16 Tiles", ppu.Layers[0].LargeTiles), + new RegEntry("$2105.5", "BG2 16x16 Tiles", ppu.Layers[1].LargeTiles), + new RegEntry("$2105.6", "BG3 16x16 Tiles", ppu.Layers[2].LargeTiles), + new RegEntry("$2105.7", "BG4 16x16 Tiles", ppu.Layers[3].LargeTiles), + + new RegEntry("$2106", "Mosaic", null), + new RegEntry("$2106.0", "BG1 Mosaic Enabled", (ppu.MosaicEnabled & 0x01) != 0), + new RegEntry("$2106.1", "BG2 Mosaic Enabled", (ppu.MosaicEnabled & 0x02) != 0), + new RegEntry("$2106.2", "BG3 Mosaic Enabled", (ppu.MosaicEnabled & 0x04) != 0), + new RegEntry("$2106.3", "BG4 Mosaic Enabled", (ppu.MosaicEnabled & 0x08) != 0), + new RegEntry("$2106.4-7", "Mosaic Size", (ppu.MosaicSize - 1).ToString() + " (" + ppu.MosaicSize.ToString() + "x" + ppu.MosaicSize.ToString() + ")"), + + new RegEntry("$2107 - $210A", "Tilemap Addresses/Sizes", null), + new RegEntry("$2107.0-1", "BG1 Size", GetLayerSize(ppu.Layers[0])), + new RegEntry("$2107.2-6", "BG1 Address", ppu.Layers[0].TilemapAddress, Format.X16), + new RegEntry("$2108.0-1", "BG2 Size", GetLayerSize(ppu.Layers[1])), + new RegEntry("$2108.2-6", "BG2 Address", ppu.Layers[1].TilemapAddress, Format.X16), + new RegEntry("$2109.0-1", "BG3 Size", GetLayerSize(ppu.Layers[2])), + new RegEntry("$2109.2-6", "BG3 Address", ppu.Layers[2].TilemapAddress, Format.X16), + new RegEntry("$210A.0-1", "BG4 Size", GetLayerSize(ppu.Layers[3])), + new RegEntry("$210A.2-6", "BG4 Address", ppu.Layers[3].TilemapAddress, Format.X16), + + new RegEntry("$210B - $210C", "Tile Addresses", null), + new RegEntry("$210B.0-2", "BG1 Tile Address", ppu.Layers[0].ChrAddress, Format.X16), + new RegEntry("$210B.4-6", "BG2 Tile Address", ppu.Layers[1].ChrAddress, Format.X16), + new RegEntry("$210C.0-2", "BG3 Tile Address", ppu.Layers[2].ChrAddress, Format.X16), + new RegEntry("$210C.4-6", "BG4 Tile Address", ppu.Layers[3].ChrAddress, Format.X16), + + new RegEntry("$210D - $2114", "H/V Scroll Offsets", null), + new RegEntry("$210D", "BG1 H Offset", ppu.Layers[0].HScroll, Format.X16), + new RegEntry("$210D", "Mode7 H Offset", ppu.Mode7.HScroll, Format.X16), + new RegEntry("$210E", "BG1 V Offset", ppu.Layers[0].VScroll, Format.X16), + new RegEntry("$210E", "Mode7 V Offset", ppu.Mode7.VScroll, Format.X16), + + new RegEntry("$210F", "BG2 H Offset", ppu.Layers[1].HScroll, Format.X16), + new RegEntry("$2110", "BG2 V Offset", ppu.Layers[1].VScroll, Format.X16), + new RegEntry("$2111", "BG3 H Offset", ppu.Layers[2].HScroll, Format.X16), + new RegEntry("$2112", "BG3 V Offset", ppu.Layers[2].VScroll, Format.X16), + new RegEntry("$2113", "BG4 H Offset", ppu.Layers[3].HScroll, Format.X16), + new RegEntry("$2114", "BG4 V Offset", ppu.Layers[3].VScroll, Format.X16), + + new RegEntry("$2115 - $2117", "VRAM", null), + new RegEntry("$2115.0-1", "Increment Value", ppu.VramIncrementValue), + new RegEntry("$2115.2-3", "Address Mapping", ppu.VramAddressRemapping), + new RegEntry("$2115.7", "Increment on $2119", ppu.VramAddrIncrementOnSecondReg), + new RegEntry("$2116/7", "VRAM Address", ppu.VramAddress, Format.X16), + + new RegEntry("$211A - $2120", "Mode 7", null), + new RegEntry("$211A.0", "Mode 7 - Hor. Mirroring", ppu.Mode7.HorizontalMirroring), + new RegEntry("$211A.1", "Mode 7 - Vert. Mirroring", ppu.Mode7.VerticalMirroring), + new RegEntry("$211A.6", "Mode 7 - Fill w/ Tile 0", ppu.Mode7.FillWithTile0), + new RegEntry("$211A.7", "Mode 7 - Large Tilemap", ppu.Mode7.LargeMap), + + new RegEntry("$211B", "Mode 7 - Matrix A", ppu.Mode7.Matrix[0], Format.X16), + new RegEntry("$211C", "Mode 7 - Matrix B", ppu.Mode7.Matrix[1], Format.X16), + new RegEntry("$211D", "Mode 7 - Matrix C", ppu.Mode7.Matrix[2], Format.X16), + new RegEntry("$211E", "Mode 7 - Matrix D", ppu.Mode7.Matrix[3], Format.X16), + + new RegEntry("$211F", "Mode 7 - Center X", ppu.Mode7.CenterX, Format.X16), + new RegEntry("$2120", "Mode 7 - Center Y", ppu.Mode7.CenterY, Format.X16), + + new RegEntry("$2123 - $212B", "Windows", null), + new RegEntry("", "BG1 Windows", null), + new RegEntry("$2123.0", "BG1 Window 1 Inverted", ppu.Window[0].InvertedLayers[0] != 0), + new RegEntry("$2123.1", "BG1 Window 1 Active", ppu.Window[0].ActiveLayers[0] != 0), + new RegEntry("$2123.2", "BG1 Window 2 Inverted", ppu.Window[1].InvertedLayers[0] != 0), + new RegEntry("$2123.3", "BG1 Window 2 Active", ppu.Window[1].ActiveLayers[0] != 0), + + new RegEntry("", "BG2 Windows", null), + new RegEntry("$2123.4", "BG2 Window 1 Inverted", ppu.Window[0].InvertedLayers[1] != 0), + new RegEntry("$2123.5", "BG2 Window 1 Active", ppu.Window[0].ActiveLayers[1] != 0), + new RegEntry("$2123.6", "BG2 Window 2 Inverted", ppu.Window[1].InvertedLayers[1] != 0), + new RegEntry("$2123.7", "BG2 Window 2 Active", ppu.Window[1].ActiveLayers[1] != 0), + + new RegEntry("", "BG3 Windows", null), + new RegEntry("$2124.0", "BG3 Window 1 Inverted", ppu.Window[0].InvertedLayers[2] != 0), + new RegEntry("$2124.1", "BG3 Window 1 Active", ppu.Window[0].ActiveLayers[2] != 0), + new RegEntry("$2124.2", "BG3 Window 2 Inverted", ppu.Window[1].InvertedLayers[2] != 0), + new RegEntry("$2124.3", "BG3 Window 2 Active", ppu.Window[1].ActiveLayers[2] != 0), + + new RegEntry("", "BG4 Windows", null), + new RegEntry("$2124.4", "BG4 Window 1 Inverted", ppu.Window[0].InvertedLayers[3] != 0), + new RegEntry("$2124.5", "BG4 Window 1 Active", ppu.Window[0].ActiveLayers[3] != 0), + new RegEntry("$2124.6", "BG4 Window 2 Inverted", ppu.Window[1].InvertedLayers[3] != 0), + new RegEntry("$2124.7", "BG4 Window 2 Active", ppu.Window[1].ActiveLayers[3] != 0), + + new RegEntry("", "OAM Windows", null), + new RegEntry("$2125.0", "OAM Window 1 Inverted", ppu.Window[0].InvertedLayers[4] != 0), + new RegEntry("$2125.1", "OAM Window 1 Active", ppu.Window[0].ActiveLayers[4] != 0), + new RegEntry("$2125.2", "OAM Window 2 Inverted", ppu.Window[1].InvertedLayers[4] != 0), + new RegEntry("$2125.3", "OAM Window 2 Active", ppu.Window[1].ActiveLayers[4] != 0), + + new RegEntry("", "Color Windows", null), + new RegEntry("$2125.4", "Color Window 1 Inverted", ppu.Window[0].InvertedLayers[5] != 0), + new RegEntry("$2125.5", "Color Window 1 Active", ppu.Window[0].ActiveLayers[5] != 0), + new RegEntry("$2125.6", "Color Window 2 Inverted", ppu.Window[1].InvertedLayers[5] != 0), + new RegEntry("$2125.7", "Color Window 2 Active", ppu.Window[1].ActiveLayers[5] != 0), + + new RegEntry("", "Window Position", null), + new RegEntry("$2126", "Window 1 Left", ppu.Window[0].Left), + new RegEntry("$2127", "Window 1 Right", ppu.Window[0].Right), + new RegEntry("$2128", "Window 2 Left", ppu.Window[1].Left), + new RegEntry("$2129", "Window 2 Right", ppu.Window[1].Right), + + new RegEntry("", "Window Masks", null), + new RegEntry("$212A.0-1", "BG1 Window Mask", ppu.MaskLogic[0].ToString().ToUpper()), + new RegEntry("$212A.2-3", "BG2 Window Mask", ppu.MaskLogic[1].ToString().ToUpper()), + new RegEntry("$212A.4-5", "BG3 Window Mask", ppu.MaskLogic[2].ToString().ToUpper()), + new RegEntry("$212A.6-7", "BG4 Window Mask", ppu.MaskLogic[3].ToString().ToUpper()), + new RegEntry("$212B.6-7", "OAM Window Mask", ppu.MaskLogic[4].ToString().ToUpper()), + new RegEntry("$212B.6-7", "Color Window Mask", ppu.MaskLogic[5].ToString().ToUpper()), + + new RegEntry("$212C", "Main Screen Layers", null), + new RegEntry("$212C.0", "BG1 Enabled", (ppu.MainScreenLayers & 0x01) != 0), + new RegEntry("$212C.1", "BG2 Enabled", (ppu.MainScreenLayers & 0x02) != 0), + new RegEntry("$212C.2", "BG3 Enabled", (ppu.MainScreenLayers & 0x04) != 0), + new RegEntry("$212C.3", "BG4 Enabled", (ppu.MainScreenLayers & 0x08) != 0), + new RegEntry("$212C.4", "OAM Enabled", (ppu.MainScreenLayers & 0x10) != 0), + + new RegEntry("$212D", "Sub Screen Layers", null), + new RegEntry("$212D.0", "BG1 Enabled", (ppu.SubScreenLayers & 0x01) != 0), + new RegEntry("$212D.1", "BG2 Enabled", (ppu.SubScreenLayers & 0x02) != 0), + new RegEntry("$212D.2", "BG3 Enabled", (ppu.SubScreenLayers & 0x04) != 0), + new RegEntry("$212D.3", "BG4 Enabled", (ppu.SubScreenLayers & 0x08) != 0), + new RegEntry("$212D.4", "OAM Enabled", (ppu.SubScreenLayers & 0x10) != 0), + + new RegEntry("$212E", "Main Screen Windows", null), + new RegEntry("$212E.0", "BG1 Mainscreen Window Enabled", ppu.WindowMaskMain[0] != 0), + new RegEntry("$212E.1", "BG2 Mainscreen Window Enabled", ppu.WindowMaskMain[1] != 0), + new RegEntry("$212E.2", "BG3 Mainscreen Window Enabled", ppu.WindowMaskMain[2] != 0), + new RegEntry("$212E.3", "BG4 Mainscreen Window Enabled", ppu.WindowMaskMain[3] != 0), + new RegEntry("$212E.4", "OAM Mainscreen Window Enabled", ppu.WindowMaskMain[4] != 0), + + new RegEntry("$212F", "Sub Screen Windows", null), + new RegEntry("$212F.0", "BG1 Subscreen Window Enabled", ppu.WindowMaskSub[0] != 0), + new RegEntry("$212F.1", "BG2 Subscreen Window Enabled", ppu.WindowMaskSub[1] != 0), + new RegEntry("$212F.2", "BG3 Subscreen Window Enabled", ppu.WindowMaskSub[2] != 0), + new RegEntry("$212F.3", "BG4 Subscreen Window Enabled", ppu.WindowMaskSub[3] != 0), + new RegEntry("$212F.4", "OAM Subscreen Window Enabled", ppu.WindowMaskSub[4] != 0), + + new RegEntry("$2130 - $2131", "Color Math", null), + new RegEntry("$2130.0", "Direct Color Mode", ppu.DirectColorMode), + new RegEntry("$2130.1", "CM - Add Subscreen", ppu.ColorMathAddSubscreen), + new RegEntry("$2130.4-5", "CM - Prevent Mode", ppu.ColorMathPreventMode.ToString()), + new RegEntry("$2130.6-7", "CM - Clip Mode", ppu.ColorMathClipMode.ToString()), + + new RegEntry("$2131.0", "CM - BG1 Enabled", (ppu.ColorMathEnabled & 0x01) != 0), + new RegEntry("$2131.1", "CM - BG2 Enabled", (ppu.ColorMathEnabled & 0x02) != 0), + new RegEntry("$2131.2", "CM - BG3 Enabled", (ppu.ColorMathEnabled & 0x04) != 0), + new RegEntry("$2131.3", "CM - BG4 Enabled", (ppu.ColorMathEnabled & 0x08) != 0), + new RegEntry("$2131.4", "CM - OAM Enabled", (ppu.ColorMathEnabled & 0x10) != 0), + new RegEntry("$2131.5", "CM - Background Enabled", (ppu.ColorMathEnabled & 0x20) != 0), + new RegEntry("$2131.6", "CM - Half Mode", ppu.ColorMathHalveResult), + new RegEntry("$2131.7", "CM - Substract Mode", ppu.ColorMathSubstractMode), + + new RegEntry("$2132 - $2133", "Misc.", null), + new RegEntry("$2132", "Fixed Color - BGR", ppu.FixedColor, Format.X16), + + new RegEntry("$2133.0", "Screen Interlace", ppu.ScreenInterlace), + new RegEntry("$2133.1", "OAM Interlace", ppu.ObjInterlace), + new RegEntry("$2133.2", "Overscan Mode", ppu.OverscanMode), + new RegEntry("$2133.3", "High Resolution Mode", ppu.HiResMode), + new RegEntry("$2133.4", "Ext. BG Enabled", ppu.ExtBgEnabled), + }); + } + + private void UpdateDspTab() + { + DspState dsp = _state.Dsp; + List entries = new List(); + + Action addReg = (int i, string name) => { + entries.Add(new RegEntry("$" + i.ToString("X2"), name, dsp.Regs[i], Format.X8)); + }; + + addReg(0x0C, "Main Volume (MVOL) - Left"); + addReg(0x1C, "Main Volume (MVOL) - Right"); + addReg(0x2C, "Echo Volume (EVOL) - Left"); + addReg(0x3C, "Echo Volume (EVOL) - Right"); + + addReg(0x4C, "Key On (KON)"); + addReg(0x5C, "Key Off (KOF)"); + + addReg(0x7C, "Source End Block (ENDX)"); + addReg(0x0D, "Echo Feedback (EFB)"); + addReg(0x2D, "Pitch Modulation (PMON)"); + addReg(0x3D, "Noise Enable (NON)"); + addReg(0x4D, "Echo Enable (EON)"); + addReg(0x5D, "Source Directory (Offset) (DIR)"); + addReg(0x6D, "Echo Buffer (Offset) (ESA)"); + addReg(0x6D, "Echo Delay (EDL)"); + + entries.Add(new RegEntry("$6C", "Flags (FLG)", null)); + entries.Add(new RegEntry("$6C.0-4", "Noise Clock", dsp.Regs[0x6C] & 0x1F, Format.X8)); + entries.Add(new RegEntry("$6C.5", "Echo Disabled", (dsp.Regs[0x6C] & 0x20) != 0)); + entries.Add(new RegEntry("$6C.6", "Mute", (dsp.Regs[0x6C] & 0x40) != 0)); + entries.Add(new RegEntry("$6C.7", "Reset", (dsp.Regs[0x6C] & 0x80) != 0)); + + entries.Add(new RegEntry("$xF", "Coefficients", null)); + for(int i = 0; i < 8; i++) { + addReg((i << 4) | 0x0F, "Coefficient " + i); + } + + for(int i = 0; i < 8; i++) { + entries.Add(new RegEntry("Voice #" + i.ToString(), "", null)); + + int voice = i << 4; + addReg(voice | 0x00, "Left Volume (VOL)"); + addReg(voice | 0x01, "Right Volume (VOL)"); + entries.Add(new RegEntry("$" + i + "2 + $" + i + "3", "Pitch (P)", dsp.Regs[voice | 0x02] | (dsp.Regs[voice | 0x03] << 8), Format.X16)); + addReg(voice | 0x04, "Source (SRCN)"); + addReg(voice | 0x05, "ADSR1"); + addReg(voice | 0x06, "ADSR2"); + addReg(voice | 0x07, "GAIN"); + addReg(voice | 0x08, "ENVX"); + addReg(voice | 0x09, "OUTX"); + } + ctrlPropertyDsp.UpdateState(entries); + } + + private void UpdateSpcTab() + { + SpcState spc = _state.Spc; + ctrlPropertySpc.UpdateState(new List() { + new RegEntry("$F0", "Test", null), + new RegEntry("$F0.0", "Timers Disabled", spc.TimersDisabled), + new RegEntry("$F0.1", "RAM Write Enabled", spc.WriteEnabled), + new RegEntry("$F0.3", "Timers Enabled", spc.TimersEnabled), + new RegEntry("$F0.4-5", "External Speed", spc.ExternalSpeed, Format.D), + new RegEntry("$F0.6-7", "Internal Speed", spc.InternalSpeed, Format.D), + + new RegEntry("$F1", "Control", null), + new RegEntry("$F1.0", "Timer 0 Enabled", spc.Timer0.Enabled), + new RegEntry("$F1.1", "Timer 1 Enabled", spc.Timer1.Enabled), + new RegEntry("$F1.2", "Timer 2 Enabled", spc.Timer2.Enabled), + new RegEntry("$F1.7", "IPL ROM Enabled", spc.RomEnabled), + + new RegEntry("$F2", "DSP", null), + new RegEntry("$F2", "DSP Register", spc.DspReg, Format.X8), + + new RegEntry("$F4 - $F7", "CPU<->SPC Ports", null), + new RegEntry("$F4", "Port 0 (CPU read)", spc.OutputReg[0], Format.X8), + new RegEntry("$F4", "Port 0 (SPC read)", spc.CpuRegs[0], Format.X8), + new RegEntry("$F5", "Port 1 (CPU read)", spc.OutputReg[1], Format.X8), + new RegEntry("$F5", "Port 1 (SPC read)", spc.CpuRegs[1], Format.X8), + new RegEntry("$F6", "Port 2 (CPU read)", spc.OutputReg[2], Format.X8), + new RegEntry("$F6", "Port 2 (SPC read)", spc.CpuRegs[2], Format.X8), + new RegEntry("$F7", "Port 3 (CPU read)", spc.OutputReg[3], Format.X8), + new RegEntry("$F7", "Port 3 (SPC read)", spc.CpuRegs[3], Format.X8), + + new RegEntry("$F8 - $F9", "RAM Registers", null), + new RegEntry("$F8", "RAM Reg 0", spc.RamReg[0], Format.X8), + new RegEntry("$F9", "RAM Reg 1", spc.RamReg[1], Format.X8), + + new RegEntry("$FA - $FF", "Timers", null), + new RegEntry("$FA", "Timer 0 Divider", spc.Timer0.Target, Format.X8), + new RegEntry("$FA", "Timer 0 Frequency", GetTimerFrequency(8000, spc.Timer0.Target)), + new RegEntry("$FB", "Timer 1 Divider", spc.Timer1.Target, Format.X8), + new RegEntry("$FB", "Timer 1 Frequency", GetTimerFrequency(8000, spc.Timer1.Target)), + new RegEntry("$FC", "Timer 2 Divider", spc.Timer2.Target, Format.X8), + new RegEntry("$FC", "Timer 2 Frequency", GetTimerFrequency(64000, spc.Timer2.Target)), + + new RegEntry("$FD", "Timer 0 Output", spc.Timer0.Output, Format.X8), + new RegEntry("$FE", "Timer 1 Output", spc.Timer1.Output, Format.X8), + new RegEntry("$FF", "Timer 2 Output", spc.Timer2.Output, Format.X8), + }); + } + + private void UpdateDmaTab() + { + List entries = new List(); + + //TODO + /*for(int i = 0; i < 8; i++) { + entries.Add(new RegEntry("$420C." + i.ToString(), "HDMA Channel " + i.ToString() + " Enabled", _state.DmaChannels[i].DmaActive)); + }*/ + + for(int i = 0; i < 8; i++) { + DmaChannelConfig ch = _state.DmaChannels[i]; + entries.Add(new RegEntry("DMA Channel " + i.ToString(), "", null)); + entries.Add(new RegEntry("$420B." + i.ToString(), "Channel Enabled", _state.DmaChannels[i].DmaActive)); + entries.Add(new RegEntry("$43" + i.ToString() + "0.0-2", "Transfer Mode", ch.TransferMode, Format.D)); + entries.Add(new RegEntry("$43" + i.ToString() + "0.3", "Fixed", ch.FixedTransfer)); + entries.Add(new RegEntry("$43" + i.ToString() + "0.4", "Decrement", ch.Decrement)); + entries.Add(new RegEntry("$43" + i.ToString() + "0.6", "Indirect HDMA", ch.HdmaIndirectAddressing)); + entries.Add(new RegEntry("$43" + i.ToString() + "0.7", "Direction", ch.InvertDirection ? "B -> A" : "A -> B")); + + entries.Add(new RegEntry("$43" + i.ToString() + "1", "B Bus Address", ch.DestAddress, Format.X8)); + entries.Add(new RegEntry("$43" + i.ToString() + "2/3/4", "A Bus Address", ((ch.SrcBank << 16) | ch.SrcAddress), Format.X24)); + entries.Add(new RegEntry("$43" + i.ToString() + "5/6", "Size", ch.TransferSize, Format.X16)); + entries.Add(new RegEntry("$43" + i.ToString() + "7", "HDMA Bank", ch.HdmaBank, Format.X8)); + entries.Add(new RegEntry("$43" + i.ToString() + "8/9", "HDMA Address", ch.HdmaTableAddress, Format.X16)); + entries.Add(new RegEntry("$43" + i.ToString() + "A", "HDMA Line Counter", ch.HdmaLineCounterAndRepeat, Format.X8)); + } + ctrlPropertyDma.UpdateState(entries); + } + + private void UpdateCpuTab() + { + InternalRegisterState regs = _state.InternalRegs; + AluState alu = _state.Alu; + ctrlPropertyCpu.UpdateState(new List() { + new RegEntry("$4200 - $4201", "IRQ/NMI/Autopoll Enabled", null), + new RegEntry("$4200.7", "NMI Enabled", regs.EnableNmi), + new RegEntry("$4200.5", "V IRQ Enabled", regs.EnableVerticalIrq), + new RegEntry("$4200.4", "H IRQ Enabled", regs.EnableHorizontalIrq), + new RegEntry("$4200.1", "Auto Joypad Poll", regs.EnableAutoJoypadRead), + + new RegEntry("$4201", "IO Port", regs.IoPortOutput, Format.X8), + + new RegEntry("$4202 - $4206", "Mult/Div Registers (Input)", null), + new RegEntry("$4202", "Multiplicand", alu.MultOperand1, Format.X8), + new RegEntry("$4203", "Multiplier", alu.MultOperand2, Format.X8), + new RegEntry("$4204/5", "Dividend", alu.Dividend, Format.X16), + new RegEntry("$4206", "Divisor", alu.Divisor, Format.X8), + + new RegEntry("$4207 - $420A", "H/V IRQ Timers", null), + new RegEntry("$4207/8", "H Timer", regs.HorizontalTimer, Format.X16), + new RegEntry("$4209/A", "V Timer", regs.VerticalTimer, Format.X16), + + new RegEntry("$4207 - $420A", "Misc. Flags", null), + + new RegEntry("$420D", "FastROM Enabled", regs.EnableFastRom), + new RegEntry("$4210", "NMI Flag", (_reg4210 & 0x80) != 0), + new RegEntry("$4211", "IRQ Flag", (_reg4211 & 0x80) != 0), + + new RegEntry("$4212.x", "V-Blank Flag", (_reg4212 & 0x80) != 0), + new RegEntry("$4212.x", "H-Blank Flag", (_reg4212 & 0x40) != 0), + new RegEntry("$4212.x", "Auto Joypad Read", (_reg4212 & 0x01) != 0), + + new RegEntry("$4214 - $4217", "Mult/Div Registers (Result)", null), + new RegEntry("$4214/5", "Quotient", alu.DivResult, Format.X16), + new RegEntry("$4216/7", "Product / Remainder", alu.MultOrRemainderResult, Format.X16), + + new RegEntry("$4218 - $421F", "Input Data", null), + new RegEntry("$4218/9", "P1 Data", regs.ControllerData[0], Format.X16), + new RegEntry("$421A/B", "P2 Data", regs.ControllerData[1], Format.X16), + new RegEntry("$421C/D", "P3 Data", regs.ControllerData[2], Format.X16), + new RegEntry("$421E/F", "P4 Data", regs.ControllerData[3], Format.X16), + }); + } + private string GetTimerFrequency(double baseFreq, int divider) { return (divider == 0 ? (baseFreq / 256) : (baseFreq / divider)).ToString(".00") + " Hz"; diff --git a/UI/Debugger/PpuViewer/frmTileViewer.Designer.cs b/UI/Debugger/PpuViewer/frmTileViewer.Designer.cs index 48ba9e8..a033425 100644 --- a/UI/Debugger/PpuViewer/frmTileViewer.Designer.cs +++ b/UI/Debugger/PpuViewer/frmTileViewer.Designer.cs @@ -37,12 +37,12 @@ this.cboFormat = new System.Windows.Forms.ComboBox(); this.ctrlPaletteViewer = new Mesen.GUI.Debugger.ctrlPaletteViewer(); this.lblPresets = new System.Windows.Forms.Label(); - this.tableLayoutPanel3 = new System.Windows.Forms.TableLayoutPanel(); + this.tlpPresets1 = new System.Windows.Forms.TableLayoutPanel(); this.btnPresetBg1 = new System.Windows.Forms.Button(); this.btnPresetBg2 = new System.Windows.Forms.Button(); this.btnPresetBg3 = new System.Windows.Forms.Button(); this.btnPresetBg4 = new System.Windows.Forms.Button(); - this.tableLayoutPanel4 = new System.Windows.Forms.TableLayoutPanel(); + this.tlpPresets2 = new System.Windows.Forms.TableLayoutPanel(); this.btnPresetOam1 = new System.Windows.Forms.Button(); this.btnPresetOam2 = new System.Windows.Forms.Button(); this.lblTileAddress = new System.Windows.Forms.Label(); @@ -76,8 +76,8 @@ this.tableLayoutPanel1.SuspendLayout(); this.tableLayoutPanel2.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.ctrlPaletteViewer)).BeginInit(); - this.tableLayoutPanel3.SuspendLayout(); - this.tableLayoutPanel4.SuspendLayout(); + this.tlpPresets1.SuspendLayout(); + this.tlpPresets2.SuspendLayout(); this.ctrlMesenMenuStrip1.SuspendLayout(); this.SuspendLayout(); // @@ -117,8 +117,8 @@ this.tableLayoutPanel2.Controls.Add(this.cboFormat, 1, 1); this.tableLayoutPanel2.Controls.Add(this.ctrlPaletteViewer, 0, 9); this.tableLayoutPanel2.Controls.Add(this.lblPresets, 0, 7); - this.tableLayoutPanel2.Controls.Add(this.tableLayoutPanel3, 1, 7); - this.tableLayoutPanel2.Controls.Add(this.tableLayoutPanel4, 1, 8); + this.tableLayoutPanel2.Controls.Add(this.tlpPresets1, 1, 7); + this.tableLayoutPanel2.Controls.Add(this.tlpPresets2, 1, 8); this.tableLayoutPanel2.Controls.Add(this.lblTileAddress, 0, 10); this.tableLayoutPanel2.Controls.Add(this.txtTileAddress, 1, 10); this.tableLayoutPanel2.Controls.Add(this.lblTileLayout, 0, 2); @@ -229,25 +229,25 @@ this.lblPresets.TabIndex = 13; this.lblPresets.Text = "Presets:"; // - // tableLayoutPanel3 + // tlpPresets1 // - this.tableLayoutPanel3.ColumnCount = 4; - this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); - this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); - this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); - this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); - this.tableLayoutPanel3.Controls.Add(this.btnPresetBg1, 0, 0); - this.tableLayoutPanel3.Controls.Add(this.btnPresetBg2, 1, 0); - this.tableLayoutPanel3.Controls.Add(this.btnPresetBg3, 2, 0); - this.tableLayoutPanel3.Controls.Add(this.btnPresetBg4, 3, 0); - this.tableLayoutPanel3.Dock = System.Windows.Forms.DockStyle.Fill; - this.tableLayoutPanel3.Location = new System.Drawing.Point(74, 185); - this.tableLayoutPanel3.Margin = new System.Windows.Forms.Padding(0); - this.tableLayoutPanel3.Name = "tableLayoutPanel3"; - this.tableLayoutPanel3.RowCount = 1; - this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle()); - this.tableLayoutPanel3.Size = new System.Drawing.Size(126, 28); - this.tableLayoutPanel3.TabIndex = 14; + this.tlpPresets1.ColumnCount = 4; + this.tlpPresets1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tlpPresets1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tlpPresets1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tlpPresets1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tlpPresets1.Controls.Add(this.btnPresetBg1, 0, 0); + this.tlpPresets1.Controls.Add(this.btnPresetBg2, 1, 0); + this.tlpPresets1.Controls.Add(this.btnPresetBg3, 2, 0); + this.tlpPresets1.Controls.Add(this.btnPresetBg4, 3, 0); + this.tlpPresets1.Dock = System.Windows.Forms.DockStyle.Fill; + this.tlpPresets1.Location = new System.Drawing.Point(74, 185); + this.tlpPresets1.Margin = new System.Windows.Forms.Padding(0); + this.tlpPresets1.Name = "tlpPresets1"; + this.tlpPresets1.RowCount = 1; + this.tlpPresets1.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tlpPresets1.Size = new System.Drawing.Size(126, 28); + this.tlpPresets1.TabIndex = 14; // // btnPresetBg1 // @@ -285,21 +285,21 @@ this.btnPresetBg4.Text = "4"; this.btnPresetBg4.UseVisualStyleBackColor = true; // - // tableLayoutPanel4 + // tlpPresets2 // - this.tableLayoutPanel4.ColumnCount = 2; - this.tableLayoutPanel4.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); - this.tableLayoutPanel4.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); - this.tableLayoutPanel4.Controls.Add(this.btnPresetOam1, 0, 0); - this.tableLayoutPanel4.Controls.Add(this.btnPresetOam2, 1, 0); - this.tableLayoutPanel4.Dock = System.Windows.Forms.DockStyle.Fill; - this.tableLayoutPanel4.Location = new System.Drawing.Point(74, 213); - this.tableLayoutPanel4.Margin = new System.Windows.Forms.Padding(0); - this.tableLayoutPanel4.Name = "tableLayoutPanel4"; - this.tableLayoutPanel4.RowCount = 1; - this.tableLayoutPanel4.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.tableLayoutPanel4.Size = new System.Drawing.Size(126, 28); - this.tableLayoutPanel4.TabIndex = 15; + this.tlpPresets2.ColumnCount = 2; + this.tlpPresets2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tlpPresets2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tlpPresets2.Controls.Add(this.btnPresetOam1, 0, 0); + this.tlpPresets2.Controls.Add(this.btnPresetOam2, 1, 0); + this.tlpPresets2.Dock = System.Windows.Forms.DockStyle.Fill; + this.tlpPresets2.Location = new System.Drawing.Point(74, 213); + this.tlpPresets2.Margin = new System.Windows.Forms.Padding(0); + this.tlpPresets2.Name = "tlpPresets2"; + this.tlpPresets2.RowCount = 1; + this.tlpPresets2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tlpPresets2.Size = new System.Drawing.Size(126, 28); + this.tlpPresets2.TabIndex = 15; // // btnPresetOam1 // @@ -654,8 +654,8 @@ this.tableLayoutPanel2.ResumeLayout(false); this.tableLayoutPanel2.PerformLayout(); ((System.ComponentModel.ISupportInitialize)(this.ctrlPaletteViewer)).EndInit(); - this.tableLayoutPanel3.ResumeLayout(false); - this.tableLayoutPanel4.ResumeLayout(false); + this.tlpPresets1.ResumeLayout(false); + this.tlpPresets2.ResumeLayout(false); this.ctrlMesenMenuStrip1.ResumeLayout(false); this.ctrlMesenMenuStrip1.PerformLayout(); this.ResumeLayout(false); @@ -691,12 +691,12 @@ private System.Windows.Forms.ToolStripMenuItem mnuZoomIn; private System.Windows.Forms.ToolStripMenuItem mnuZoomOut; private System.Windows.Forms.Label lblPresets; - private System.Windows.Forms.TableLayoutPanel tableLayoutPanel3; + private System.Windows.Forms.TableLayoutPanel tlpPresets1; private System.Windows.Forms.Button btnPresetBg1; private System.Windows.Forms.Button btnPresetBg2; private System.Windows.Forms.Button btnPresetBg3; private System.Windows.Forms.Button btnPresetBg4; - private System.Windows.Forms.TableLayoutPanel tableLayoutPanel4; + private System.Windows.Forms.TableLayoutPanel tlpPresets2; private System.Windows.Forms.Button btnPresetOam1; private System.Windows.Forms.Button btnPresetOam2; private System.Windows.Forms.Label lblTileAddress; diff --git a/UI/Debugger/PpuViewer/frmTileViewer.cs b/UI/Debugger/PpuViewer/frmTileViewer.cs index 75f9860..1952942 100644 --- a/UI/Debugger/PpuViewer/frmTileViewer.cs +++ b/UI/Debugger/PpuViewer/frmTileViewer.cs @@ -196,7 +196,12 @@ namespace Mesen.GUI.Debugger public void RefreshData() { _state = DebugApi.GetState(); - _cgram = DebugApi.GetMemoryState(SnesMemoryType.CGRam); + + if(EmuApi.GetRomInfo().CoprocessorType == CoprocessorType.Gameboy) { + _cgram = ctrlPaletteViewer.GetGameboyPalette(); + } else { + _cgram = DebugApi.GetMemoryState(SnesMemoryType.CGRam); + } byte[] source = DebugApi.GetMemoryState(_memoryType); @@ -246,6 +251,11 @@ namespace Mesen.GUI.Debugger btnPresetBg3.Enabled = _layerBpp[_state.Ppu.BgMode, 2] > 0; btnPresetBg4.Enabled = _layerBpp[_state.Ppu.BgMode, 3] > 0; + bool isGameboy = EmuApi.GetRomInfo().CoprocessorType == CoprocessorType.Gameboy; + lblPresets.Visible = !isGameboy; + tlpPresets1.Visible = !isGameboy; + tlpPresets2.Visible = !isGameboy; + UpdateMapSize(); ctrlImagePanel.Refresh(); @@ -266,37 +276,56 @@ namespace Mesen.GUI.Debugger cboMemoryType.BeginUpdate(); cboMemoryType.Items.Clear(); - cboMemoryType.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.VideoRam)); - cboMemoryType.Items.Add("-"); - cboMemoryType.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.CpuMemory)); - cboMemoryType.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.PrgRom)); - cboMemoryType.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.WorkRam)); - if(DebugApi.GetMemorySize(SnesMemoryType.SaveRam) > 0) { - cboMemoryType.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.SaveRam)); - } + if(EmuApi.GetRomInfo().CoprocessorType == CoprocessorType.Gameboy) { + AddGameboyTypes(); + } else { + cboMemoryType.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.VideoRam)); + cboMemoryType.Items.Add("-"); + cboMemoryType.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.CpuMemory)); + cboMemoryType.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.PrgRom)); + cboMemoryType.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.WorkRam)); + if(DebugApi.GetMemorySize(SnesMemoryType.SaveRam) > 0) { + cboMemoryType.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.SaveRam)); + } - if(DebugApi.GetMemorySize(SnesMemoryType.GsuWorkRam) > 0) { - cboMemoryType.Items.Add("-"); - cboMemoryType.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.GsuWorkRam)); - } - if(DebugApi.GetMemorySize(SnesMemoryType.Sa1InternalRam) > 0) { - cboMemoryType.Items.Add("-"); - cboMemoryType.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.Sa1InternalRam)); - cboMemoryType.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.Sa1Memory)); - } - if(DebugApi.GetMemorySize(SnesMemoryType.BsxPsRam) > 0) { - cboMemoryType.Items.Add("-"); - cboMemoryType.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.BsxPsRam)); - } - if(DebugApi.GetMemorySize(SnesMemoryType.BsxMemoryPack) > 0) { - cboMemoryType.Items.Add("-"); - cboMemoryType.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.BsxMemoryPack)); + if(DebugApi.GetMemorySize(SnesMemoryType.GsuWorkRam) > 0) { + cboMemoryType.Items.Add("-"); + cboMemoryType.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.GsuWorkRam)); + } + if(DebugApi.GetMemorySize(SnesMemoryType.Sa1InternalRam) > 0) { + cboMemoryType.Items.Add("-"); + cboMemoryType.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.Sa1InternalRam)); + cboMemoryType.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.Sa1Memory)); + } + if(DebugApi.GetMemorySize(SnesMemoryType.BsxPsRam) > 0) { + cboMemoryType.Items.Add("-"); + cboMemoryType.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.BsxPsRam)); + } + if(DebugApi.GetMemorySize(SnesMemoryType.BsxMemoryPack) > 0) { + cboMemoryType.Items.Add("-"); + cboMemoryType.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.BsxMemoryPack)); + } + AddGameboyTypes(); } cboMemoryType.SelectedIndex = 0; cboMemoryType.EndUpdate(); } + private void AddGameboyTypes() + { + if(DebugApi.GetMemorySize(SnesMemoryType.GbPrgRom) > 0) { + if(cboMemoryType.Items.Count > 0) { + cboMemoryType.Items.Add("-"); + } + cboMemoryType.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.GbVideoRam)); + cboMemoryType.Items.Add("-"); + cboMemoryType.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.GbPrgRom)); + cboMemoryType.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.GbWorkRam)); + cboMemoryType.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.GbCartRam)); + } + } + private void UpdatePaletteControl() { if(_options.Format == TileFormat.Bpp2) { diff --git a/UI/Debugger/PpuViewer/frmTilemapViewer.cs b/UI/Debugger/PpuViewer/frmTilemapViewer.cs index e543c5b..f79f11d 100644 --- a/UI/Debugger/PpuViewer/frmTilemapViewer.cs +++ b/UI/Debugger/PpuViewer/frmTilemapViewer.cs @@ -29,6 +29,7 @@ namespace Mesen.GUI.Debugger private int _selectedColumn = 0; private DateTime _lastUpdate = DateTime.MinValue; private WindowRefreshManager _refreshManager; + private bool _isGameboyMode = false; public frmTilemapViewer() { @@ -103,8 +104,9 @@ namespace Mesen.GUI.Debugger public void RefreshData() { + _isGameboyMode = EmuApi.GetRomInfo().CoprocessorType == CoprocessorType.Gameboy; _state = DebugApi.GetState().Ppu; - _vram = DebugApi.GetMemoryState(SnesMemoryType.VideoRam); + _vram = DebugApi.GetMemoryState(_isGameboyMode ? SnesMemoryType.GbVideoRam : SnesMemoryType.VideoRam); _cgram = DebugApi.GetMemoryState(SnesMemoryType.CGRam); } @@ -135,7 +137,9 @@ namespace Mesen.GUI.Debugger private int GetWidth() { - if(_state.BgMode == 7) { + if(_isGameboyMode) { + return 256; + } else if(_state.BgMode == 7) { return 1024; } @@ -154,7 +158,9 @@ namespace Mesen.GUI.Debugger private int GetHeight() { - if(_state.BgMode == 7) { + if(_isGameboyMode) { + return 256; + } else if(_state.BgMode == 7) { return 1024; } @@ -176,7 +182,11 @@ namespace Mesen.GUI.Debugger _options.Layer = 0; } - DebugApi.GetTilemap(_options, _state, _vram, _cgram, _tilemapData); + if(_isGameboyMode) { + DebugApi.GetGameboyTilemap(_vram, (ushort)(_options.Layer == 0 ? 0x1800 : 0x1C00), _tilemapData); + } else { + DebugApi.GetTilemap(_options, _state, _vram, _cgram, _tilemapData); + } int mapWidth = GetWidth(); int mapHeight = GetHeight(); @@ -201,13 +211,20 @@ namespace Mesen.GUI.Debugger btnLayer3.Enabled = _layerBpp[_state.BgMode, 2] > 0; btnLayer4.Enabled = _layerBpp[_state.BgMode, 3] > 0; + grpLayerInfo.Visible = !_isGameboyMode; + grpTileInfo.Visible = !_isGameboyMode; + btnLayer3.Visible = !_isGameboyMode; + btnLayer4.Visible = !_isGameboyMode; + + chkShowScrollOverlay.Visible = !_isGameboyMode; + ctrlImagePanel.ImageSize = new Size(GetWidth(), GetHeight()); ctrlImagePanel.Selection = new Rectangle(_selectedColumn * 8, _selectedRow * 8, IsLargeTileWidth ? 16 : 8, IsLargeTileHeight ? 16 : 8); ctrlImagePanel.GridSizeX = chkShowTileGrid.Checked ? (IsLargeTileWidth ? 16 : 8): 0; ctrlImagePanel.GridSizeY = chkShowTileGrid.Checked ? (IsLargeTileHeight ? 16 : 8): 0; - if(chkShowScrollOverlay.Checked) { + if(!_isGameboyMode && chkShowScrollOverlay.Checked) { LayerConfig layer = _state.Layers[_options.Layer]; int hScroll = _state.BgMode == 7 ? (int)_state.Mode7.HScroll : layer.HScroll; int vScroll = _state.BgMode == 7 ? (int)_state.Mode7.VScroll : layer.VScroll; @@ -217,7 +234,10 @@ namespace Mesen.GUI.Debugger ctrlImagePanel.Overlay = Rectangle.Empty; } ctrlImagePanel.Refresh(); - UpdateFields(); + + if(!_isGameboyMode) { + UpdateFields(); + } } private void UpdateFields() diff --git a/UI/Debugger/Profiler/ctrlProfiler.cs b/UI/Debugger/Profiler/ctrlProfiler.cs index ffc543b..d559dae 100644 --- a/UI/Debugger/Profiler/ctrlProfiler.cs +++ b/UI/Debugger/Profiler/ctrlProfiler.cs @@ -120,6 +120,10 @@ namespace Mesen.GUI.Debugger.Controls case SnesMemoryType.SpcRam: functionName = "SPC: $"; break; case SnesMemoryType.SpcRom: functionName = "SPC ROM: $"; break; case SnesMemoryType.Sa1InternalRam: functionName = "IRAM: $"; break; + case SnesMemoryType.GbPrgRom: functionName = "PRG: $"; break; + case SnesMemoryType.GbWorkRam: functionName = "WRAM: $"; break; + case SnesMemoryType.GbCartRam: functionName = "SRAM: $"; break; + case SnesMemoryType.GbHighRam: functionName = "HRAM: $"; break; default: throw new Exception("Unsupported type"); } diff --git a/UI/Debugger/Profiler/frmProfiler.cs b/UI/Debugger/Profiler/frmProfiler.cs index 141f13f..9ad08d3 100644 --- a/UI/Debugger/Profiler/frmProfiler.cs +++ b/UI/Debugger/Profiler/frmProfiler.cs @@ -70,7 +70,7 @@ namespace Mesen.GUI.Debugger ctrlProfiler.RefreshData(); } else if(_selectedTab == tpgSpc) { ctrlProfilerSpc.RefreshData(); - } else { + } else if(_selectedTab != null) { ctrlProfilerCoprocessor.RefreshData(); } } @@ -81,27 +81,34 @@ namespace Mesen.GUI.Debugger ctrlProfiler.RefreshList(); } else if(_selectedTab == tpgSpc) { ctrlProfilerSpc.RefreshList(); - } else { + } else if(_selectedTab != null) { ctrlProfilerCoprocessor.RefreshList(); } } private void UpdateAvailableTabs() { - if(tabMain.Controls.Count > 2) { - tabMain.Controls.RemoveAt(2); - } - tabMain.SelectedTab = tpgCpu; - _selectedTab = tpgCpu; + tabMain.SelectedIndexChanged -= tabMain_SelectedIndexChanged; + tabMain.TabPages.Clear(); - if(EmuApi.GetRomInfo().CoprocessorType == CoprocessorType.SA1) { - TabPage coprocessorTab = new TabPage(ResourceHelper.GetEnumText(CoprocessorType.SA1)); + RomInfo romInfo = EmuApi.GetRomInfo(); + if(romInfo.CoprocessorType != CoprocessorType.Gameboy) { + tabMain.TabPages.Add(tpgCpu); + tabMain.TabPages.Add(tpgSpc); + tabMain.SelectedTab = tpgCpu; + } + + if(romInfo.CoprocessorType == CoprocessorType.SA1 || romInfo.CoprocessorType == CoprocessorType.Gameboy) { + TabPage coprocessorTab = new TabPage(ResourceHelper.GetEnumText(romInfo.CoprocessorType)); tabMain.TabPages.Add(coprocessorTab); coprocessorTab.Controls.Add(ctrlProfilerCoprocessor); ctrlProfilerCoprocessor.Dock = DockStyle.Fill; - ctrlProfilerCoprocessor.CpuType = CpuType.Sa1; + ctrlProfilerCoprocessor.CpuType = romInfo.CoprocessorType == CoprocessorType.SA1 ? CpuType.Sa1 : CpuType.Gameboy; } + + _selectedTab = tabMain.TabPages[0]; + tabMain.SelectedIndexChanged += tabMain_SelectedIndexChanged; } protected override void OnFormClosing(FormClosingEventArgs e) diff --git a/UI/Debugger/Scripts/frmScript.cs b/UI/Debugger/Scripts/frmScript.cs index a583e08..f439a23 100644 --- a/UI/Debugger/Scripts/frmScript.cs +++ b/UI/Debugger/Scripts/frmScript.cs @@ -506,8 +506,8 @@ namespace Mesen.GUI.Debugger new List {"enum", "emu", "", "", "", "", "" }, new List {"func","emu.addEventCallback","emu.addEventCallback(function, type)","function - A Lua function.\ntype - *Enum* See eventType.","Returns an integer value that can be used to remove the callback by calling removeEventCallback.","Registers a callback function to be called whenever the specified event occurs.",}, new List {"func","emu.removeEventCallback","emu.removeEventCallback(reference, type)","reference - The value returned by the call to addEventCallback.\ntype - *Enum* See eventType.","","Removes a previously registered callback function.",}, - new List {"func","emu.addMemoryCallback","emu.addMemoryCallback(function, type, startAddress, endAddress)","function - A Lua function.\ntype - *Enum* See memCallbackType\nstartAddress - *Integer* Start of the CPU memory address range to register the callback on.\nendAddress - (optional) *Integer* End of the CPU memory address range to register the callback on.","Returns an integer value that can be used to remove the callback by callingremoveMemoryCallback.","Registers a callback function to be called whenever the specified event occurs."}, - new List {"func","emu.removeMemoryCallback","emu.removeMemoryCallback(reference, type, startAddress, endAddress)","reference - The value returned by the call to addMemoryCallback.\ntype - *Enum* See memCallbackType.\nstartAddress - *Integer* Start of the CPU memory address range to unregister the callback from.\nendAddress - (optional) *Integer* End of the CPU memory address range to unregister the callback from.","","Removes a previously registered callback function."}, + new List {"func","emu.addMemoryCallback","emu.addMemoryCallback(function, type, startAddress, endAddress, cpuType)", "function - A Lua function.\ntype - *Enum* See memCallbackType\nstartAddress - *Integer* Start of the CPU memory address range to register the callback on.\nendAddress - (optional) *Integer* End of the CPU memory address range to register the callback on.\ncpuType - (optional) *Enum* See emu.cpuType. (Defaults to emu.cpuType.cpu)", "Returns an integer value that can be used to remove the callback by callingremoveMemoryCallback.","Registers a callback function to be called whenever the specified event occurs."}, + new List {"func","emu.removeMemoryCallback","emu.removeMemoryCallback(reference, type, startAddress, endAddress, cpuType)", "reference - The value returned by the call to addMemoryCallback.\ntype - *Enum* See memCallbackType.\nstartAddress - *Integer* Start of the CPU memory address range to unregister the callback from.\nendAddress - (optional) *Integer* End of the CPU memory address range to unregister the callback from.\ncpuType - (optional) *Enum* See emu.cpuType. (Defaults to emu.cpuType.cpu)", "","Removes a previously registered callback function."}, new List {"func","emu.read","emu.read(address, type, signed)","address - *Integer* The address/offset to read from.\ntype - *Enum* The type of memory to read from. See memType.\nsigned - (optional) *Boolean* If true, the value returned will be interpreted as a signed value.","An 8-bit (read) or 16-bit (readWord) value.","Reads a value from the specified memory type.\n\nWhen calling read / readWord with the memType.cpu or memType.ppu memory types, emulation side-effects may occur.\nTo avoid triggering side-effects, use the memType.cpuDebug or memType.ppuDebug types, which will not cause side-effects."}, new List {"func","emu.readWord","emu.readWord(address, type, signed)","address - *Integer* The address/offset to read from.\ntype - *Enum* The type of memory to read from. See memType.\nsigned - (optional) *Boolean* If true, the value returned will be interpreted as a signed value.","An 8-bit (read) or 16-bit (readWord) value.","Reads a value from the specified memory type.\n\nWhen calling read / readWord with the memType.cpu or memType.ppu memory types, emulation side-effects may occur.\nTo avoid triggering side-effects, use the memType.cpuDebug or memType.ppuDebug types, which will not cause side-effects."}, new List {"func","emu.write","emu.write(address, value, type)","address - *Integer* The address/offset to write to.\nvalue - *Integer* The value to write.\ntype - *Enum* The type of memory to write to. See memType.","","Writes an 8-bit or 16-bit value to the specified memory type.\n\nNormally read-only types such as PRG-ROM or CHR-ROM can be written to when using memType.prgRom or memType.chrRom.\nChanges will remain in effect until a power cycle occurs.\nTo revert changes done to ROM, see revertPrgChrChanges.\n\nWhen calling write / writeWord with the memType.cpu or memType.ppu memory types, emulation side-effects may occur.\nTo avoid triggering side-effects, use the memType.cpuDebug or memType.ppuDebug types, which will not cause side-effects."}, @@ -578,22 +578,62 @@ namespace Mesen.GUI.Debugger new List {"enum","emu.memType","emu.memType.[value]", "","",""}, new List {"enum","emu.memType.cpu","CPU memory - Max: $FFFFFF","","","Warning: Reading or writing to this memory type may cause side-effects!"}, new List {"enum","emu.memType.spc","SPC memory - Max: $FFFF","","","Warning: Reading or writing to this memory type may cause side-effects!"}, + new List {"enum","emu.memType.sa1","SA-1 memory - Max: $FFFFFF","","","Warning: Reading or writing to this memory type may cause side-effects!"}, + new List {"enum","emu.memType.gsu","GSU memory - Max: $FFFFFF","","","Warning: Reading or writing to this memory type may cause side-effects!"}, + new List {"enum","emu.memType.cx4","CX4 memory - Max: $FFFFFF","","","Warning: Reading or writing to this memory type may cause side-effects!"}, + new List {"enum","emu.memType.gameboy","Game Boy memory - Max: $FFFF","","","Warning: Reading or writing to this memory type may cause side-effects!"}, new List {"enum","emu.memType.cgram","CG RAM - Max: $1FF","","",""}, new List {"enum","emu.memType.oam","OAM memory - Max: $21F","","",""}, new List {"enum","emu.memType.vram","Video RAM - Max: $FFFF","","",""}, new List {"enum","emu.memType.prgRom","PRG ROM - Range varies by game","","",""}, new List {"enum","emu.memType.workRam","Work RAM - Max: $1FFFF","","",""}, new List {"enum","emu.memType.saveRam","Save RAM - Range varies by game","","",""}, + new List {"enum","emu.memType.gbPrgRom","Game Boy PRG ROM - Range varies by game","","",""}, + new List {"enum","emu.memType.gbWorkRam", "Game Boy Work RAM - Range varies by game", "","",""}, + new List {"enum","emu.memType.gbCartRam", "Game Boy Cart RAM - Range varies by game", "","",""}, + new List {"enum","emu.memType.gbVideoRam", "Game Boy Video RAM - Range varies by game", "","",""}, new List {"enum","emu.memType.cpuDebug","CPU memory - Max: $FFFFFF","","","Same as \"memType.cpu\" but does NOT cause any side-effects."}, new List {"enum","emu.memType.spcDebug", "SPC memory - Max: $FFFF", "","","Same as \"memType.spc\" but does NOT cause any side-effects."}, + new List {"enum","emu.memType.sa1Debug", "SA-1 memory - Max: $FFFFFF", "","","Same as \"memType.sa1\" but does NOT cause any side-effects."}, + new List {"enum","emu.memType.gsuDebug", "GSU memory - Max: $FFFFFF", "","","Same as \"memType.gsu\" but does NOT cause any side-effects."}, + new List {"enum","emu.memType.cx4Debug", "CX4 memory - Max: $FFFFFF", "","","Same as \"memType.cx4\" but does NOT cause any side-effects."}, + new List {"enum","emu.memType.gameboyDebug", "Game Boy memory - Max: $FFFF", "","","Same as \"memType.gameboy\" but does NOT cause any side-effects."}, new List {"enum","emu.counterMemType","emu.counterMemType.[value]","","","Values:\nprgRom,\nworkRam,\nsaveRam\n\nUsed by getAccessCounters calls."}, new List {"enum","emu.counterMemType.prgRom","Returns access counter data for PRG ROM","","",""}, new List {"enum","emu.counterMemType.workRam", "Returns access counter data for Work RAM", "","",""}, new List {"enum","emu.counterMemType.saveRam", "Returns access counter data for Save RAM", "","",""}, + new List {"enum","emu.counterMemType.videoRam", "Returns access counter data for VAM", "","",""}, + new List {"enum","emu.counterMemType.spriteRam", "Returns access counter data for OAM", "","",""}, + new List {"enum","emu.counterMemType.cgRam", "Returns access counter data for CG RAM", "","",""}, + new List {"enum","emu.counterMemType.spcRam", "Returns access counter data for SPC RAM", "","",""}, + new List {"enum","emu.counterMemType.spcRom", "Returns access counter data for SPC ROM", "","",""}, + new List {"enum","emu.counterMemType.dspProgramRom", "Returns access counter data for the DSP's Program ROM", "","",""}, + new List {"enum","emu.counterMemType.dspDataRom", "Returns access counter data for the DSP's Data ROM", "","",""}, + new List {"enum","emu.counterMemType.dspDataRam", "Returns access counter data for the DSP's Data RAM", "","",""}, + new List {"enum","emu.counterMemType.sa1InternalRam", "Returns access counter data for the SA1's internal RAM", "","",""}, + new List {"enum","emu.counterMemType.gsuWorkRam", "Returns access counter data for the GSU's Work RAM", "","",""}, + new List {"enum","emu.counterMemType.cx4DataRam", "Returns access counter data for the CX4's Data RAM", "","",""}, + new List {"enum","emu.counterMemType.bsxPsRam", "Returns access counter data for the BS-X's PS RAM", "","",""}, + new List {"enum","emu.counterMemType.bsxMemoryPack", "Returns access counter data for the BS-X's Memory Pack", "","",""}, + new List {"enum","emu.counterMemType.gbPrgRom", "Returns access counter data for the Game Boy's PRG ROM", "","",""}, + new List {"enum","emu.counterMemType.gbWorkRam", "Returns access counter data for the Game Boy's Work RAM", "","",""}, + new List {"enum","emu.counterMemType.gbCartRam", "Returns access counter data for the Game Boy's Cart RAM", "","",""}, + new List {"enum","emu.counterMemType.gbVideoRam", "Returns access counter data for the Game Boy's Video RAM", "","",""}, + new List {"enum","emu.counterMemType.gbHighRam", "Returns access counter data for the Game Boy's High RAM", "","",""}, new List {"enum","emu.counterOpType","emu.counterOpType.[value]","","","Values:\nread,\nwrite,\nexec\n\nUsed by getAccessCounters calls."}, new List {"enum","emu.counterOpType.read","Returns access counter data for reads","","",""}, new List {"enum","emu.counterOpType.write","Returns access counter data for writes","","",""}, new List {"enum","emu.counterOpType.exec", "Returns access counter data for executed bytes", "","",""}, + + new List {"enum","emu.cpuType","emu.cpuType.[value]","","","Used by addMemoryCallback calls."}, + new List {"enum","emu.cpuType.cpu","SNES CPU","","",""}, + new List {"enum","emu.cpuType.spc","SNES SPC","","",""}, + new List {"enum","emu.cpuType.dsp","DSP Enhancement Chip", "","",""}, + new List {"enum","emu.cpuType.sa1","SA-1 Enhancement Chip", "","",""}, + new List {"enum","emu.cpuType.gsu","GSU Enhancement Chip", "","",""}, + new List {"enum","emu.cpuType.cx4","CX4 Enhancement Chip", "","",""}, + new List {"enum","emu.cpuType.gameboy","Game Boy / Super Game Boy", "","",""}, + }; } diff --git a/UI/Debugger/frmDbgPreferences.cs b/UI/Debugger/frmDbgPreferences.cs index 2b0e095..4ecb2b0 100644 --- a/UI/Debugger/frmDbgPreferences.cs +++ b/UI/Debugger/frmDbgPreferences.cs @@ -60,6 +60,7 @@ namespace Mesen.GUI.Debugger GetMember(nameof(DebuggerShortcutsConfig.OpenSpcDebugger)), GetMember(nameof(DebuggerShortcutsConfig.OpenSa1Debugger)), + GetMember(nameof(DebuggerShortcutsConfig.OpenGameboyDebugger)), GetMember(nameof(DebuggerShortcutsConfig.OpenGsuDebugger)), GetMember(nameof(DebuggerShortcutsConfig.OpenNecDspDebugger)), GetMember(nameof(DebuggerShortcutsConfig.OpenCx4Debugger)), diff --git a/UI/Debugger/frmDebugger.Designer.cs b/UI/Debugger/frmDebugger.Designer.cs index a8123d6..bcd0b2c 100644 --- a/UI/Debugger/frmDebugger.Designer.cs +++ b/UI/Debugger/frmDebugger.Designer.cs @@ -130,6 +130,8 @@ this.toolStripMenuItem4 = new System.Windows.Forms.ToolStripSeparator(); this.mnuConfigureColors = new System.Windows.Forms.ToolStripMenuItem(); this.mnuPreferences = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripMenuItem12 = new System.Windows.Forms.ToolStripSeparator(); + this.mnuShowMemoryMappings = new System.Windows.Forms.ToolStripMenuItem(); this.ctrlSplitContainer = new Mesen.GUI.Controls.ctrlSplitContainer(); this.pnlStatus = new System.Windows.Forms.Panel(); this.ctrlLabelList = new Mesen.GUI.Debugger.Controls.ctrlLabelList(); @@ -636,7 +638,9 @@ this.mnuFontOptions, this.toolStripMenuItem4, this.mnuConfigureColors, - this.mnuPreferences}); + this.mnuPreferences, + this.toolStripMenuItem12, + this.mnuShowMemoryMappings}); this.optionsToolStripMenuItem.Name = "optionsToolStripMenuItem"; this.optionsToolStripMenuItem.Size = new System.Drawing.Size(61, 20); this.optionsToolStripMenuItem.Text = "Options"; @@ -902,6 +906,18 @@ this.mnuPreferences.Text = "Configure shortcut keys..."; this.mnuPreferences.Click += new System.EventHandler(this.mnuPreferences_Click); // + // toolStripMenuItem12 + // + this.toolStripMenuItem12.Name = "toolStripMenuItem12"; + this.toolStripMenuItem12.Size = new System.Drawing.Size(206, 6); + // + // mnuShowMemoryMappings + // + this.mnuShowMemoryMappings.CheckOnClick = true; + this.mnuShowMemoryMappings.Name = "mnuShowMemoryMappings"; + this.mnuShowMemoryMappings.Size = new System.Drawing.Size(209, 22); + this.mnuShowMemoryMappings.Text = "Show memory mappings"; + // // ctrlSplitContainer // this.ctrlSplitContainer.Dock = System.Windows.Forms.DockStyle.Fill; @@ -976,9 +992,10 @@ this.grpWatch.Controls.Add(this.picWatchHelp); this.grpWatch.Controls.Add(this.ctrlWatch); this.grpWatch.Dock = System.Windows.Forms.DockStyle.Fill; - this.grpWatch.Location = new System.Drawing.Point(3, 3); + this.grpWatch.Location = new System.Drawing.Point(2, 0); + this.grpWatch.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); this.grpWatch.Name = "grpWatch"; - this.grpWatch.Size = new System.Drawing.Size(271, 152); + this.grpWatch.Size = new System.Drawing.Size(273, 158); this.grpWatch.TabIndex = 1; this.grpWatch.TabStop = false; this.grpWatch.Text = "Watch"; @@ -986,7 +1003,7 @@ // picWatchHelp // this.picWatchHelp.Image = global::Mesen.GUI.Properties.Resources.Help; - this.picWatchHelp.Location = new System.Drawing.Point(44, -1); + this.picWatchHelp.Location = new System.Drawing.Point(44, 0); this.picWatchHelp.Name = "picWatchHelp"; this.picWatchHelp.Size = new System.Drawing.Size(16, 16); this.picWatchHelp.SizeMode = System.Windows.Forms.PictureBoxSizeMode.CenterImage; @@ -998,17 +1015,19 @@ this.ctrlWatch.CpuType = Mesen.GUI.CpuType.Cpu; this.ctrlWatch.Dock = System.Windows.Forms.DockStyle.Fill; this.ctrlWatch.Location = new System.Drawing.Point(3, 16); + this.ctrlWatch.Margin = new System.Windows.Forms.Padding(0); this.ctrlWatch.Name = "ctrlWatch"; - this.ctrlWatch.Size = new System.Drawing.Size(265, 133); + this.ctrlWatch.Size = new System.Drawing.Size(267, 139); this.ctrlWatch.TabIndex = 0; // // grpBreakpoints // this.grpBreakpoints.Controls.Add(this.ctrlBreakpoints); this.grpBreakpoints.Dock = System.Windows.Forms.DockStyle.Fill; - this.grpBreakpoints.Location = new System.Drawing.Point(280, 3); + this.grpBreakpoints.Location = new System.Drawing.Point(279, 0); + this.grpBreakpoints.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); this.grpBreakpoints.Name = "grpBreakpoints"; - this.grpBreakpoints.Size = new System.Drawing.Size(271, 152); + this.grpBreakpoints.Size = new System.Drawing.Size(273, 158); this.grpBreakpoints.TabIndex = 2; this.grpBreakpoints.TabStop = false; this.grpBreakpoints.Text = "Breakpoints"; @@ -1017,8 +1036,9 @@ // this.ctrlBreakpoints.Dock = System.Windows.Forms.DockStyle.Fill; this.ctrlBreakpoints.Location = new System.Drawing.Point(3, 16); + this.ctrlBreakpoints.Margin = new System.Windows.Forms.Padding(0); this.ctrlBreakpoints.Name = "ctrlBreakpoints"; - this.ctrlBreakpoints.Size = new System.Drawing.Size(265, 133); + this.ctrlBreakpoints.Size = new System.Drawing.Size(267, 139); this.ctrlBreakpoints.TabIndex = 0; this.ctrlBreakpoints.BreakpointNavigation += new Mesen.GUI.Debugger.Controls.ctrlBreakpoints.BreakpointNavigationHandler(this.ctrlBreakpoints_BreakpointNavigation); // @@ -1026,9 +1046,10 @@ // this.grpCallstack.Controls.Add(this.ctrlCallstack); this.grpCallstack.Dock = System.Windows.Forms.DockStyle.Fill; - this.grpCallstack.Location = new System.Drawing.Point(557, 3); + this.grpCallstack.Location = new System.Drawing.Point(556, 0); + this.grpCallstack.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); this.grpCallstack.Name = "grpCallstack"; - this.grpCallstack.Size = new System.Drawing.Size(272, 152); + this.grpCallstack.Size = new System.Drawing.Size(274, 158); this.grpCallstack.TabIndex = 3; this.grpCallstack.TabStop = false; this.grpCallstack.Text = "Call Stack"; @@ -1037,8 +1058,9 @@ // this.ctrlCallstack.Dock = System.Windows.Forms.DockStyle.Fill; this.ctrlCallstack.Location = new System.Drawing.Point(3, 16); + this.ctrlCallstack.Margin = new System.Windows.Forms.Padding(0); this.ctrlCallstack.Name = "ctrlCallstack"; - this.ctrlCallstack.Size = new System.Drawing.Size(266, 133); + this.ctrlCallstack.Size = new System.Drawing.Size(268, 139); this.ctrlCallstack.TabIndex = 0; this.ctrlCallstack.FunctionSelected += new Mesen.GUI.Debugger.Controls.ctrlCallstack.NavigateToAddressHandler(this.ctrlCallstack_FunctionSelected); // @@ -1196,5 +1218,7 @@ private System.Windows.Forms.ToolStripMenuItem mnuReloadRom; private System.Windows.Forms.ToolStripSeparator toolStripMenuItem16; private System.Windows.Forms.ToolStripMenuItem mnuAutoResetCdl; + private System.Windows.Forms.ToolStripSeparator toolStripMenuItem12; + private System.Windows.Forms.ToolStripMenuItem mnuShowMemoryMappings; } } \ No newline at end of file diff --git a/UI/Debugger/frmDebugger.cs b/UI/Debugger/frmDebugger.cs index ddf32f7..046eddb 100644 --- a/UI/Debugger/frmDebugger.cs +++ b/UI/Debugger/frmDebugger.cs @@ -27,6 +27,9 @@ namespace Mesen.GUI.Debugger private ctrlGsuStatus ctrlGsuStatus; private ctrlNecDspStatus ctrlNecDspStatus; private ctrlCx4Status ctrlCx4Status; + private ctrlGameboyStatus ctrlGameboyStatus; + + private ctrlMemoryMapping ctrlMemoryMapping; public CpuType CpuType { get { return _cpuType; } } @@ -98,8 +101,7 @@ namespace Mesen.GUI.Debugger ConfigApi.SetDebuggerFlag(DebuggerFlags.GsuDebuggerEnabled, true); this.Text = "GSU Debugger"; HideDebuggerElements(); - - + this.ctrlGsuStatus = new ctrlGsuStatus(); this.ctrlGsuStatus.Padding = new Padding(3, 0, 3, 0); this.ctrlGsuStatus.Dock = DockStyle.Top; @@ -111,8 +113,7 @@ namespace Mesen.GUI.Debugger ConfigApi.SetDebuggerFlag(DebuggerFlags.NecDspDebuggerEnabled, true); this.Text = "DSP Debugger"; HideDebuggerElements(); - - + this.ctrlNecDspStatus = new ctrlNecDspStatus(); this.ctrlNecDspStatus.Padding = new Padding(3, 0, 3, 0); this.ctrlNecDspStatus.Dock = DockStyle.Top; @@ -132,6 +133,34 @@ namespace Mesen.GUI.Debugger this.ctrlCx4Status.Dock = DockStyle.Top; pnlStatus.Controls.Add(this.ctrlCx4Status); break; + + case CpuType.Gameboy: + ctrlDisassemblyView.Initialize(new GbDisassemblyManager(), new GbLineStyleProvider()); + ConfigApi.SetDebuggerFlag(DebuggerFlags.GbDebuggerEnabled, true); + this.Text = "Game Boy Debugger"; + + ctrlMemoryMapping = new ctrlMemoryMapping(); + ctrlMemoryMapping.Size = new Size(this.ClientSize.Width, 33); + ctrlMemoryMapping.Margin = new Padding(3, 0, 3, 3); + ctrlMemoryMapping.Dock = DockStyle.Bottom; + ctrlMemoryMapping.Visible = ConfigManager.Config.Debug.Debugger.ShowMemoryMappings; + this.Controls.Add(ctrlMemoryMapping); + ctrlMemoryMapping.SendToBack(); + + sepBrkCopStpWdm.Visible = false; + mnuBreakOnBrk.Visible = false; + mnuBreakOnWdm.Visible = false; + mnuBreakOnCop.Visible = false; + mnuBreakOnStp.Visible = false; + sepBreakOnUnitRead.Visible = false; + mnuBreakOnUnitRead.Visible = false; + ctrlPpuStatus.Visible = false; + + this.ctrlGameboyStatus = new ctrlGameboyStatus(); + this.ctrlGameboyStatus.Padding = new Padding(3, 0, 3, 0); + this.ctrlGameboyStatus.Dock = DockStyle.Top; + pnlStatus.Controls.Add(this.ctrlGameboyStatus); + break; } ctrlBreakpoints.CpuType = _cpuType; @@ -388,10 +417,16 @@ namespace Mesen.GUI.Debugger DebuggerInfo cfg = ConfigManager.Config.Debug.Debugger; _entityBinder.Entity = cfg; _entityBinder.AddBinding(nameof(cfg.ShowByteCode), mnuShowByteCode); + _entityBinder.AddBinding(nameof(cfg.ShowMemoryMappings), mnuShowMemoryMappings); _entityBinder.AddBinding(nameof(cfg.UseLowerCaseDisassembly), mnuUseLowerCaseDisassembly); _entityBinder.AddBinding(nameof(cfg.UseAltSpcOpNames), mnuUseAltSpcOpNames); mnuShowByteCode.CheckedChanged += (s, e) => { ctrlDisassemblyView.CodeViewer.ShowContentNotes = mnuShowByteCode.Checked; }; + mnuShowMemoryMappings.CheckedChanged += (s, e) => { + if(_cpuType == CpuType.Gameboy) { + ctrlMemoryMapping.Visible = mnuShowMemoryMappings.Checked; + } + }; mnuUseLowerCaseDisassembly.CheckedChanged += this.UpdateFlags; mnuUseAltSpcOpNames.CheckedChanged += this.UpdateFlags; @@ -461,6 +496,10 @@ namespace Mesen.GUI.Debugger case CpuType.Sa1: ctrlCpuStatus.UpdateStatus(state.Sa1.Cpu); break; case CpuType.Gsu: ctrlGsuStatus.UpdateStatus(state.Gsu); break; case CpuType.Cx4: ctrlCx4Status.UpdateStatus(state.Cx4); break; + case CpuType.Gameboy: + ctrlGameboyStatus.UpdateStatus(state.Gameboy); + ctrlMemoryMapping.UpdateCpuRegions(state.Gameboy); + break; default: throw new Exception("Unsupported CPU type"); } @@ -568,6 +607,7 @@ namespace Mesen.GUI.Debugger case CpuType.Sa1: activeAddress = (int)((state.Sa1.Cpu.K << 16) | state.Sa1.Cpu.PC); break; case CpuType.Gsu: activeAddress = (int)((state.Gsu.ProgramBank << 16) | state.Gsu.R[15]); break; case CpuType.Cx4: activeAddress = (int)((state.Cx4.Cache.Address[state.Cx4.Cache.Page] + (state.Cx4.PC * 2)) & 0xFFFFFF); break; + case CpuType.Gameboy: activeAddress = (int)(state.Gameboy.Cpu.PC & 0xFFFF); break; default: throw new Exception("Unsupported cpu type"); } diff --git a/UI/Debugger/frmTraceLogger.Designer.cs b/UI/Debugger/frmTraceLogger.Designer.cs index dc90b99..9c668c0 100644 --- a/UI/Debugger/frmTraceLogger.Designer.cs +++ b/UI/Debugger/frmTraceLogger.Designer.cs @@ -68,6 +68,8 @@ namespace Mesen.GUI.Debugger this.chkLogSpc = new System.Windows.Forms.CheckBox(); this.chkLogNecDsp = new System.Windows.Forms.CheckBox(); this.chkLogGsu = new System.Windows.Forms.CheckBox(); + this.chkLogCx4 = new System.Windows.Forms.CheckBox(); + this.chkLogGameboy = new System.Windows.Forms.CheckBox(); this.btnClearLog = new System.Windows.Forms.Button(); this.tableLayoutPanel3 = new System.Windows.Forms.TableLayoutPanel(); this.grpExecutionLog = new System.Windows.Forms.GroupBox(); @@ -94,7 +96,6 @@ namespace Mesen.GUI.Debugger this.mnuAutoRefresh = new System.Windows.Forms.ToolStripMenuItem(); this.toolStripMenuItem1 = new System.Windows.Forms.ToolStripSeparator(); this.mnuRefresh = new System.Windows.Forms.ToolStripMenuItem(); - this.chkLogCx4 = new System.Windows.Forms.CheckBox(); this.tableLayoutPanel1.SuspendLayout(); this.grpLogOptions.SuspendLayout(); this.tableLayoutPanel2.SuspendLayout(); @@ -570,6 +571,7 @@ namespace Mesen.GUI.Debugger this.tableLayoutPanel6.Controls.Add(this.chkLogNecDsp, 2, 0); this.tableLayoutPanel6.Controls.Add(this.chkLogGsu, 4, 0); this.tableLayoutPanel6.Controls.Add(this.chkLogCx4, 5, 0); + this.tableLayoutPanel6.Controls.Add(this.chkLogGameboy, 6, 0); this.tableLayoutPanel6.Dock = System.Windows.Forms.DockStyle.Fill; this.tableLayoutPanel6.Location = new System.Drawing.Point(81, 0); this.tableLayoutPanel6.Margin = new System.Windows.Forms.Padding(0); @@ -641,6 +643,30 @@ namespace Mesen.GUI.Debugger this.chkLogGsu.Text = "GSU"; this.chkLogGsu.UseVisualStyleBackColor = true; // + // chkLogCx4 + // + this.chkLogCx4.AutoSize = true; + this.chkLogCx4.Checked = true; + this.chkLogCx4.CheckState = System.Windows.Forms.CheckState.Checked; + this.chkLogCx4.Location = new System.Drawing.Point(322, 3); + this.chkLogCx4.Name = "chkLogCx4"; + this.chkLogCx4.Size = new System.Drawing.Size(46, 16); + this.chkLogCx4.TabIndex = 27; + this.chkLogCx4.Text = "CX4"; + this.chkLogCx4.UseVisualStyleBackColor = true; + // + // chkLogGameboy + // + this.chkLogGameboy.AutoSize = true; + this.chkLogGameboy.Checked = true; + this.chkLogGameboy.CheckState = System.Windows.Forms.CheckState.Checked; + this.chkLogGameboy.Location = new System.Drawing.Point(374, 3); + this.chkLogGameboy.Name = "chkLogGameboy"; + this.chkLogGameboy.Size = new System.Drawing.Size(71, 16); + this.chkLogGameboy.TabIndex = 28; + this.chkLogGameboy.Text = "Gameboy"; + this.chkLogGameboy.UseVisualStyleBackColor = true; + // // btnClearLog // this.btnClearLog.Location = new System.Drawing.Point(205, 3); @@ -876,18 +902,6 @@ namespace Mesen.GUI.Debugger this.mnuRefresh.Text = "Refresh"; this.mnuRefresh.Click += new System.EventHandler(this.mnuRefresh_Click); // - // chkLogCx4 - // - this.chkLogCx4.AutoSize = true; - this.chkLogCx4.Checked = true; - this.chkLogCx4.CheckState = System.Windows.Forms.CheckState.Checked; - this.chkLogCx4.Location = new System.Drawing.Point(322, 3); - this.chkLogCx4.Name = "chkLogCx4"; - this.chkLogCx4.Size = new System.Drawing.Size(46, 16); - this.chkLogCx4.TabIndex = 27; - this.chkLogCx4.Text = "CX4"; - this.chkLogCx4.UseVisualStyleBackColor = true; - // // frmTraceLogger // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); @@ -991,5 +1005,6 @@ namespace Mesen.GUI.Debugger private System.Windows.Forms.CheckBox chkLogSa1; private System.Windows.Forms.CheckBox chkLogGsu; private System.Windows.Forms.CheckBox chkLogCx4; - } + private System.Windows.Forms.CheckBox chkLogGameboy; + } } \ No newline at end of file diff --git a/UI/Debugger/frmTraceLogger.cs b/UI/Debugger/frmTraceLogger.cs index 29c82c2..e7f0c8a 100644 --- a/UI/Debugger/frmTraceLogger.cs +++ b/UI/Debugger/frmTraceLogger.cs @@ -50,6 +50,7 @@ namespace Mesen.GUI.Debugger _entityBinder.AddBinding(nameof(TraceLoggerOptions.LogSa1), chkLogSa1); _entityBinder.AddBinding(nameof(TraceLoggerOptions.LogGsu), chkLogGsu); _entityBinder.AddBinding(nameof(TraceLoggerOptions.LogCx4), chkLogCx4); + _entityBinder.AddBinding(nameof(TraceLoggerOptions.LogGameboy), chkLogGameboy); _entityBinder.AddBinding(nameof(TraceLoggerOptions.ShowByteCode), chkShowByteCode); //_entityBinder.AddBinding(nameof(TraceLoggerOptions.ShowCpuCycles), chkShowCpuCycles); @@ -94,6 +95,8 @@ namespace Mesen.GUI.Debugger "[Align,50]: Align is a special tag that is useful when trying to align some content. [Align,50] will make the next tag start on column 50." ); + UpdateAvailableOptions(); + _notifListener = new NotificationListener(); _notifListener.OnNotification += OnNotificationReceived; @@ -165,10 +168,23 @@ namespace Mesen.GUI.Debugger case ConsoleNotificationType.GameLoaded: //Configuration is lost when debugger is restarted (when switching game or power cycling) SetCoreOptions(); + this.BeginInvoke((Action)(() => this.UpdateAvailableOptions())); break; } } + private void UpdateAvailableOptions() + { + CoprocessorType coproc = EmuApi.GetRomInfo().CoprocessorType; + chkLogCpu.Visible = coproc != CoprocessorType.Gameboy; + chkLogSpc.Visible = coproc != CoprocessorType.Gameboy; + chkLogNecDsp.Visible = (coproc >= CoprocessorType.DSP1 && coproc <= CoprocessorType.DSP4) || (coproc >= CoprocessorType.ST010 && coproc <= CoprocessorType.ST011); + chkLogSa1.Visible = coproc == CoprocessorType.SA1; + chkLogGsu.Visible = coproc == CoprocessorType.GSU; + chkLogCx4.Visible = coproc == CoprocessorType.CX4; + chkLogGameboy.Visible = coproc == CoprocessorType.Gameboy; + } + protected void UpdateFormatOptions() { if(!chkOverrideFormat.Checked) { @@ -232,13 +248,16 @@ namespace Mesen.GUI.Debugger _entityBinder.UpdateObject(); TraceLoggerOptions options = (TraceLoggerOptions)_entityBinder.Entity; + CoprocessorType coproc = EmuApi.GetRomInfo().CoprocessorType; + _interopOptions = new InteropTraceLoggerOptions(); - _interopOptions.LogCpu = !disableLogging && options.LogCpu; - _interopOptions.LogSpc = !disableLogging && options.LogSpc; + _interopOptions.LogCpu = !disableLogging && options.LogCpu && coproc != CoprocessorType.Gameboy; + _interopOptions.LogSpc = !disableLogging && options.LogSpc && coproc != CoprocessorType.Gameboy; _interopOptions.LogNecDsp = !disableLogging && options.LogNecDsp; _interopOptions.LogSa1 = !disableLogging && options.LogSa1; _interopOptions.LogGsu = !disableLogging && options.LogGsu; _interopOptions.LogCx4 = !disableLogging && options.LogCx4; + _interopOptions.LogGameboy = !disableLogging && options.LogGameboy; _interopOptions.IndentCode = options.IndentCode; _interopOptions.ShowExtraInfo = options.ShowExtraInfo; _interopOptions.UseLabels = options.UseLabels; diff --git a/UI/Dependencies/resources.en.xml b/UI/Dependencies/resources.en.xml index e7d37cf..892c1a8 100644 --- a/UI/Dependencies/resources.en.xml +++ b/UI/Dependencies/resources.en.xml @@ -638,7 +638,7 @@ Avi files (*.avi)|*.avi|All Files (*.*)|*.* GIF files (*.gif)|*.gif|All Files (*.*)|*.* Palette Files (*.pal)|*.pal|All Files (*.*)|*.* - All supported formats (*.sfc, *.bs, *.spc, *.zip, *.7z)|*.SFC;*.SMC;*.SWC;*.FIG;*.ZIP;*.7Z;*.SPC;*.BS|SNES Roms (*.sfc)|*.SFC;*.SMC;*.SWC;*.FIG;*.BS|SPC Sound Files (*.spc)|*.SPC|ZIP Archives (*.zip)|*.ZIP|7-Zip Archives (*.7z)|*.7z|All (*.*)|*.* + All supported formats (*.sfc, *.gb, *.bs, *.spc, *.zip, *.7z)|*.SFC;*.SMC;*.SWC;*.FIG;*.ZIP;*.7Z;*.SPC;*.BS;*.GB|SNES Roms (*.sfc)|*.SFC;*.SMC;*.SWC;*.FIG;*.BS|Gameboy Roms (*.gb)|*.GB|SPC Sound Files (*.spc)|*.SPC|ZIP Archives (*.zip)|*.ZIP|7-Zip Archives (*.7z)|*.7z|All (*.*)|*.* Test files (*.mtp)|*.mtp|All (*.*)|*.* All supported formats (*.cht, *.xml)|*.cht;*.xml Mesen-S Savestates (*.mss)|*.mss|All files (*.*)|*.* @@ -956,6 +956,12 @@ CX4 Data RAM BS-X - PSRAM BS-X - Memory Pack + GB - CPU Memory + GB - PRG ROM + GB - Work RAM + GB - Video RAM + GB - Cart RAM + GB - High RAM Register diff --git a/UI/Forms/frmMain.Designer.cs b/UI/Forms/frmMain.Designer.cs index 1863183..fbab926 100644 --- a/UI/Forms/frmMain.Designer.cs +++ b/UI/Forms/frmMain.Designer.cs @@ -149,6 +149,8 @@ this.mnuRandomGame = new System.Windows.Forms.ToolStripMenuItem(); this.mnuTakeScreenshot = new System.Windows.Forms.ToolStripMenuItem(); this.mnuDebug = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuGbDebugger = new System.Windows.Forms.ToolStripMenuItem(); + this.sepGameboyDebugger = new System.Windows.Forms.ToolStripSeparator(); this.mnuDebugger = new System.Windows.Forms.ToolStripMenuItem(); this.mnuEventViewer = new System.Windows.Forms.ToolStripMenuItem(); this.mnuMemoryTools = new System.Windows.Forms.ToolStripMenuItem(); @@ -163,11 +165,12 @@ this.mnuTileViewer = new System.Windows.Forms.ToolStripMenuItem(); this.mnuSpriteViewer = new System.Windows.Forms.ToolStripMenuItem(); this.mnuPaletteViewer = new System.Windows.Forms.ToolStripMenuItem(); - this.toolStripMenuItem22 = new System.Windows.Forms.ToolStripSeparator(); + this.sepCoprocessors = new System.Windows.Forms.ToolStripSeparator(); this.mnuSpcDebugger = new System.Windows.Forms.ToolStripMenuItem(); this.mnuSa1Debugger = new System.Windows.Forms.ToolStripMenuItem(); this.mnuGsuDebugger = new System.Windows.Forms.ToolStripMenuItem(); this.mnuNecDspDebugger = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuCx4Debugger = new System.Windows.Forms.ToolStripMenuItem(); this.mnuHelp = new System.Windows.Forms.ToolStripMenuItem(); this.mnuCheckForUpdates = new System.Windows.Forms.ToolStripMenuItem(); this.toolStripMenuItem20 = new System.Windows.Forms.ToolStripSeparator(); @@ -176,7 +179,6 @@ this.mnuAbout = new System.Windows.Forms.ToolStripMenuItem(); this.pnlRenderer = new System.Windows.Forms.Panel(); this.ctrlRecentGames = new Mesen.GUI.Controls.ctrlRecentGames(); - this.mnuCx4Debugger = new System.Windows.Forms.ToolStripMenuItem(); this.mnuMain.SuspendLayout(); this.pnlRenderer.SuspendLayout(); this.SuspendLayout(); @@ -1092,6 +1094,8 @@ // mnuDebug // this.mnuDebug.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.mnuGbDebugger, + this.sepGameboyDebugger, this.mnuDebugger, this.mnuEventViewer, this.mnuMemoryTools, @@ -1106,7 +1110,7 @@ this.mnuTileViewer, this.mnuSpriteViewer, this.mnuPaletteViewer, - this.toolStripMenuItem22, + this.sepCoprocessors, this.mnuSpcDebugger, this.mnuSa1Debugger, this.mnuGsuDebugger, @@ -1118,6 +1122,18 @@ this.mnuDebug.DropDownClosed += new System.EventHandler(this.mnu_DropDownClosed); this.mnuDebug.DropDownOpened += new System.EventHandler(this.mnu_DropDownOpened); // + // mnuGbDebugger + // + this.mnuGbDebugger.Image = global::Mesen.GUI.Properties.Resources.GbDebugger; + this.mnuGbDebugger.Name = "mnuGbDebugger"; + this.mnuGbDebugger.Size = new System.Drawing.Size(183, 22); + this.mnuGbDebugger.Text = "Game Boy Debugger"; + // + // sepGameboyDebugger + // + this.sepGameboyDebugger.Name = "sepGameboyDebugger"; + this.sepGameboyDebugger.Size = new System.Drawing.Size(180, 6); + // // mnuDebugger // this.mnuDebugger.Image = global::Mesen.GUI.Properties.Resources.Debugger; @@ -1212,10 +1228,10 @@ this.mnuPaletteViewer.Size = new System.Drawing.Size(183, 22); this.mnuPaletteViewer.Text = "Palette Viewer"; // - // toolStripMenuItem22 + // sepCoprocessors // - this.toolStripMenuItem22.Name = "toolStripMenuItem22"; - this.toolStripMenuItem22.Size = new System.Drawing.Size(180, 6); + this.sepCoprocessors.Name = "sepCoprocessors"; + this.sepCoprocessors.Size = new System.Drawing.Size(180, 6); // // mnuSpcDebugger // @@ -1245,6 +1261,13 @@ this.mnuNecDspDebugger.Size = new System.Drawing.Size(183, 22); this.mnuNecDspDebugger.Text = "DSP Debugger"; // + // mnuCx4Debugger + // + this.mnuCx4Debugger.Image = global::Mesen.GUI.Properties.Resources.Cx4Debugger; + this.mnuCx4Debugger.Name = "mnuCx4Debugger"; + this.mnuCx4Debugger.Size = new System.Drawing.Size(183, 22); + this.mnuCx4Debugger.Text = "CX4 Debugger"; + // // mnuHelp // this.mnuHelp.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { @@ -1313,13 +1336,6 @@ this.ctrlRecentGames.TabIndex = 1; this.ctrlRecentGames.Visible = false; // - // mnuCx4Debugger - // - this.mnuCx4Debugger.Image = global::Mesen.GUI.Properties.Resources.Cx4Debugger; - this.mnuCx4Debugger.Name = "mnuCx4Debugger"; - this.mnuCx4Debugger.Size = new System.Drawing.Size(183, 22); - this.mnuCx4Debugger.Text = "CX4 Debugger"; - // // frmMain // this.AllowDrop = true; @@ -1455,7 +1471,7 @@ private System.Windows.Forms.ToolStripMenuItem mnuWaveStop; private System.Windows.Forms.ToolStripSeparator toolStripMenuItem12; private System.Windows.Forms.ToolStripMenuItem mnuPaletteViewer; - private System.Windows.Forms.ToolStripSeparator toolStripMenuItem22; + private System.Windows.Forms.ToolStripSeparator sepCoprocessors; private System.Windows.Forms.ToolStripMenuItem mnuTileViewer; private System.Windows.Forms.ToolStripMenuItem mnuSpcDebugger; private System.Windows.Forms.ToolStripMenuItem mnuSpriteViewer; @@ -1495,5 +1511,7 @@ private System.Windows.Forms.ToolStripMenuItem mnuReloadRom; private System.Windows.Forms.ToolStripMenuItem mnuNecDspDebugger; private System.Windows.Forms.ToolStripMenuItem mnuCx4Debugger; + private System.Windows.Forms.ToolStripMenuItem mnuGbDebugger; + private System.Windows.Forms.ToolStripSeparator sepGameboyDebugger; } } \ No newline at end of file diff --git a/UI/Forms/frmMain.cs b/UI/Forms/frmMain.cs index 9bb11e6..e5abb46 100644 --- a/UI/Forms/frmMain.cs +++ b/UI/Forms/frmMain.cs @@ -282,6 +282,7 @@ namespace Mesen.GUI.Forms mnuGsuDebugger.InitShortcut(this, nameof(DebuggerShortcutsConfig.OpenGsuDebugger)); mnuNecDspDebugger.InitShortcut(this, nameof(DebuggerShortcutsConfig.OpenNecDspDebugger)); mnuCx4Debugger.InitShortcut(this, nameof(DebuggerShortcutsConfig.OpenCx4Debugger)); + mnuGbDebugger.InitShortcut(this, nameof(DebuggerShortcutsConfig.OpenGameboyDebugger)); mnuMemoryTools.InitShortcut(this, nameof(DebuggerShortcutsConfig.OpenMemoryTools)); mnuEventViewer.InitShortcut(this, nameof(DebuggerShortcutsConfig.OpenEventViewer)); mnuTilemapViewer.InitShortcut(this, nameof(DebuggerShortcutsConfig.OpenTilemapViewer)); @@ -346,6 +347,7 @@ namespace Mesen.GUI.Forms mnuGsuDebugger.Click += (s, e) => { DebugWindowManager.OpenDebugWindow(DebugWindow.GsuDebugger); }; mnuNecDspDebugger.Click += (s, e) => { DebugWindowManager.OpenDebugWindow(DebugWindow.NecDspDebugger); }; mnuCx4Debugger.Click += (s, e) => { DebugWindowManager.OpenDebugWindow(DebugWindow.Cx4Debugger); }; + mnuGbDebugger.Click += (s, e) => { DebugWindowManager.OpenDebugWindow(DebugWindow.GbDebugger); }; mnuTraceLogger.Click += (s, e) => { DebugWindowManager.OpenDebugWindow(DebugWindow.TraceLogger); }; mnuMemoryTools.Click += (s, e) => { DebugWindowManager.OpenDebugWindow(DebugWindow.MemoryTools); }; mnuTilemapViewer.Click += (s, e) => { DebugWindowManager.OpenDebugWindow(DebugWindow.TilemapViewer); }; @@ -467,6 +469,28 @@ namespace Mesen.GUI.Forms mnuRegisterViewer.Enabled = running; mnuProfiler.Enabled = running; mnuAssembler.Enabled = running; + + bool isGameboyMode = coprocessor == CoprocessorType.Gameboy; + mnuGbDebugger.Enabled = isGameboyMode; + mnuGbDebugger.Visible = isGameboyMode; + sepGameboyDebugger.Visible = isGameboyMode; + + if(isGameboyMode) { + //Remove/disable all tools that aren't useful when running a plain GB game + mnuGbDebugger.Text = "Debugger"; + + mnuDebugger.Enabled = false; + mnuDebugger.Visible = false; + mnuSpcDebugger.Enabled = false; + mnuSpcDebugger.Visible = false; + mnuSpriteViewer.Enabled = false; + mnuSpriteViewer.Visible = false; + mnuEventViewer.Enabled = false; + mnuEventViewer.Visible = false; + mnuAssembler.Enabled = false; + mnuAssembler.Visible = false; + sepCoprocessors.Visible = false; + } } private void ResizeRecentGames() @@ -666,8 +690,9 @@ namespace Mesen.GUI.Forms { bool isClient = NetplayApi.IsConnected(); bool runAheadDisabled = ConfigManager.Config.Emulation.RunAheadFrames == 0; + bool isGameboyMode = EmuApi.GetRomInfo().CoprocessorType == CoprocessorType.Gameboy; - mnuNetPlay.Enabled = runAheadDisabled; + mnuNetPlay.Enabled = runAheadDisabled && !isGameboyMode; mnuMovies.Enabled = runAheadDisabled && EmuRunner.IsRunning(); mnuPlayMovie.Enabled = runAheadDisabled && EmuRunner.IsRunning() && !RecordApi.MoviePlaying() && !RecordApi.MovieRecording() && !isClient; @@ -682,7 +707,7 @@ namespace Mesen.GUI.Forms mnuAviRecord.Enabled = EmuRunner.IsRunning() && !RecordApi.AviIsRecording(); mnuAviStop.Enabled = EmuRunner.IsRunning() && RecordApi.AviIsRecording(); - mnuCheats.Enabled = EmuRunner.IsRunning() && !isClient; + mnuCheats.Enabled = EmuRunner.IsRunning() && !isClient && !isGameboyMode; } private void mnuAviRecord_Click(object sender, EventArgs e) diff --git a/UI/Interop/ConfigApi.cs b/UI/Interop/ConfigApi.cs index 4fc7eb6..028f9c2 100644 --- a/UI/Interop/ConfigApi.cs +++ b/UI/Interop/ConfigApi.cs @@ -64,6 +64,7 @@ namespace Mesen.GUI AutoResetCdl = 0x4000, + GbDebuggerEnabled = 0x02000000, Cx4DebuggerEnabled = 0x04000000, NecDspDebuggerEnabled = 0x08000000, GsuDebuggerEnabled = 0x10000000, diff --git a/UI/Interop/DebugApi.cs b/UI/Interop/DebugApi.cs index 1d68c2d..fcc8bb7 100644 --- a/UI/Interop/DebugApi.cs +++ b/UI/Interop/DebugApi.cs @@ -88,6 +88,7 @@ namespace Mesen.GUI } [DllImport(DllPath)] public static extern void GetTilemap(GetTilemapOptions options, PpuState state, byte[] vram, byte[] cgram, [In, Out] byte[] buffer); + [DllImport(DllPath)] public static extern void GetGameboyTilemap(byte[] vram, UInt16 offset, [In, Out] byte[] buffer); [DllImport(DllPath)] public static extern void GetTileView(GetTileViewOptions options, byte[] source, int srcSize, byte[] cgram, [In, Out] byte[] buffer); [DllImport(DllPath)] public static extern void GetSpritePreview(GetSpritePreviewOptions options, PpuState state, byte[] vram, byte[] oamRam, byte[] cgram, [In, Out] byte[] buffer); @@ -191,6 +192,7 @@ namespace Mesen.GUI NecDspMemory, GsuMemory, Cx4Memory, + GameboyMemory, PrgRom, WorkRam, SaveRam, @@ -207,6 +209,11 @@ namespace Mesen.GUI Cx4DataRam, BsxPsRam, BsxMemoryPack, + GbPrgRom, + GbWorkRam, + GbCartRam, + GbVideoRam, + GbHighRam, Register, } @@ -233,6 +240,12 @@ namespace Mesen.GUI case SnesMemoryType.DspProgramRom: return CpuType.NecDsp; + case SnesMemoryType.GbPrgRom: + case SnesMemoryType.GbWorkRam: + case SnesMemoryType.GbCartRam: + case SnesMemoryType.GbHighRam: + return CpuType.Gameboy; + default: return CpuType.Cpu; } @@ -245,6 +258,9 @@ namespace Mesen.GUI case SnesMemoryType.SpcMemory: case SnesMemoryType.Sa1Memory: case SnesMemoryType.GsuMemory: + case SnesMemoryType.NecDspMemory: + case SnesMemoryType.Cx4Memory: + case SnesMemoryType.GameboyMemory: return true; } return false; @@ -260,6 +276,10 @@ namespace Mesen.GUI case SnesMemoryType.SpcRam: case SnesMemoryType.SpcRom: case SnesMemoryType.Sa1InternalRam: + case SnesMemoryType.GbPrgRom: + case SnesMemoryType.GbWorkRam: + case SnesMemoryType.GbCartRam: + case SnesMemoryType.GbHighRam: return true; } @@ -273,6 +293,9 @@ namespace Mesen.GUI case SnesMemoryType.SpcMemory: case SnesMemoryType.Sa1Memory: case SnesMemoryType.GsuMemory: + case SnesMemoryType.NecDspMemory: + case SnesMemoryType.Cx4Memory: + case SnesMemoryType.GameboyMemory: return true; } @@ -461,6 +484,7 @@ namespace Mesen.GUI [MarshalAs(UnmanagedType.I1)] public bool LogSa1; [MarshalAs(UnmanagedType.I1)] public bool LogGsu; [MarshalAs(UnmanagedType.I1)] public bool LogCx4; + [MarshalAs(UnmanagedType.I1)] public bool LogGameboy; [MarshalAs(UnmanagedType.I1)] public bool ShowExtraInfo; [MarshalAs(UnmanagedType.I1)] public bool IndentCode; @@ -507,7 +531,8 @@ namespace Mesen.GUI NecDsp, Sa1, Gsu, - Cx4 + Cx4, + Gameboy } public static class CpuTypeExtensions @@ -521,6 +546,7 @@ namespace Mesen.GUI case CpuType.Sa1: return SnesMemoryType.Sa1Memory; case CpuType.Gsu: return SnesMemoryType.GsuMemory; case CpuType.Cx4: return SnesMemoryType.Cx4Memory; + case CpuType.Gameboy: return SnesMemoryType.GameboyMemory; default: throw new Exception("Invalid CPU type"); @@ -536,6 +562,7 @@ namespace Mesen.GUI case CpuType.Sa1: return 6; case CpuType.Gsu: return 6; case CpuType.Cx4: return 6; + case CpuType.Gameboy: return 4; default: throw new Exception("Invalid CPU type"); diff --git a/UI/Interop/DebugState.cs b/UI/Interop/DebugState.cs index d6a544d..e584ecb 100644 --- a/UI/Interop/DebugState.cs +++ b/UI/Interop/DebugState.cs @@ -607,6 +607,225 @@ namespace Mesen.GUI public UInt16[] ControllerData; } + public enum GbMemoryType + { + None = 0, + PrgRom = (int)SnesMemoryType.GbPrgRom, + WorkRam = (int)SnesMemoryType.GbWorkRam, + CartRam = (int)SnesMemoryType.GbCartRam + } + + public enum RegisterAccess + { + None = 0, + Read = 1, + Write = 2, + ReadWrite = 3 + } + + public struct GbMemoryManagerState + { + [MarshalAs(UnmanagedType.I1)] public bool DisableBootRom; + public byte IrqRequests; + public byte IrqEnabled; + public byte InputSelect; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x100)] + public byte[] IsReadRegister; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x100)] + public byte[] IsWriteRegister; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x100)] + public GbMemoryType[] MemoryType; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x100)] + public UInt32[] MemoryOffset; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x100)] + public RegisterAccess[] MemoryAccessType; + } + + public struct GbState + { + public GbCpuState Cpu; + public GbPpuState Ppu; + public GbApuDebugState Apu; + public GbMemoryManagerState MemoryManager; + [MarshalAs(UnmanagedType.I1)] public bool HasBattery; + } + + [Flags] + public enum GameboyFlags : byte + { + Carry = 0x10, + HalfCarry = 0x20, + AddSub = 0x40, + Zero = 0x80 + } + + public struct GbCpuState + { + public UInt64 CycleCount; + public UInt16 PC; + public UInt16 SP; + + public byte A; + public byte Flags; + + public byte B; + public byte C; + public byte D; + public byte E; + + public byte H; + public byte L; + + [MarshalAs(UnmanagedType.I1)] public bool IME; + [MarshalAs(UnmanagedType.I1)] public bool Halted; + } + + public enum PpuMode + { + HBlank, + VBlank, + OamEvaluation, + Drawing + } + + public struct GbPpuState + { + public byte Scanline; + public UInt16 Cycle; + public PpuMode Mode; + + public byte LyCompare; + public byte BgPalette; + public byte ObjPalette0; + public byte ObjPalette1; + public byte ScrollX; + public byte ScrollY; + public byte WindowX; + public byte WindowY; + + public byte Control; + [MarshalAs(UnmanagedType.I1)] public bool LcdEnabled; + [MarshalAs(UnmanagedType.I1)] public bool WindowTilemapSelect; + [MarshalAs(UnmanagedType.I1)] public bool WindowEnabled; + [MarshalAs(UnmanagedType.I1)] public bool BgTileSelect; + [MarshalAs(UnmanagedType.I1)] public bool BgTilemapSelect; + [MarshalAs(UnmanagedType.I1)] public bool LargeSprites; + [MarshalAs(UnmanagedType.I1)] public bool SpritesEnabled; + [MarshalAs(UnmanagedType.I1)] public bool BgEnabled; + + public byte Status; + public UInt32 FrameCount; + } + + public struct GbSquareState + { + public UInt16 SweepPeriod; + [MarshalAs(UnmanagedType.I1)] public bool SweepNegate; + public byte SweepShift; + + public UInt16 SweepTimer; + [MarshalAs(UnmanagedType.I1)] public bool SweepEnabled; + public UInt16 SweepFreq; + + public byte Volume; + public byte EnvVolume; + [MarshalAs(UnmanagedType.I1)] public bool EnvRaiseVolume; + public byte EnvPeriod; + public byte EnvTimer; + + public byte Duty; + public UInt16 Frequency; + + public byte Length; + [MarshalAs(UnmanagedType.I1)] public bool LengthEnabled; + + [MarshalAs(UnmanagedType.I1)] public bool Enabled; + public UInt16 Timer; + public byte DutyPos; + public byte Output; + } + + public struct GbNoiseState + { + public byte Volume; + public byte EnvVolume; + [MarshalAs(UnmanagedType.I1)] public bool EnvRaiseVolume; + public byte EnvPeriod; + public byte EnvTimer; + + public byte Length; + [MarshalAs(UnmanagedType.I1)] public bool LengthEnabled; + + public UInt16 ShiftRegister; + + public byte PeriodShift; + public byte Divisor; + [MarshalAs(UnmanagedType.I1)] public bool ShortWidthMode; + + [MarshalAs(UnmanagedType.I1)] public bool Enabled; + public UInt32 Timer; + public byte Output; + } + + public struct GbWaveState + { + [MarshalAs(UnmanagedType.I1)] public bool DacEnabled; + + public byte SampleBuffer; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] Ram; + + public byte Position; + + public byte Volume; + public UInt16 Frequency; + + public UInt16 Length; + [MarshalAs(UnmanagedType.I1)] public bool LengthEnabled; + + [MarshalAs(UnmanagedType.I1)] public bool Enabled; + public UInt16 Timer; + public byte Output; + } + + public struct GbApuState + { + [MarshalAs(UnmanagedType.I1)] public bool ApuEnabled; + + public byte EnableLeftSq1; + public byte EnableLeftSq2; + public byte EnableLeftWave; + public byte EnableLeftNoise; + + public byte EnableRightSq1; + public byte EnableRightSq2; + public byte EnableRightWave; + public byte EnableRightNoise; + + public byte LeftVolume; + public byte RightVolume; + + [MarshalAs(UnmanagedType.I1)] public bool ExtAudioLeftEnabled; + [MarshalAs(UnmanagedType.I1)] public bool ExtAudioRightEnabled; + + public byte FrameSequenceStep; + } + + public struct GbApuDebugState + { + public GbApuState Common; + public GbSquareState Square1; + public GbSquareState Square2; + public GbWaveState Wave; + public GbNoiseState Noise; + } + public struct DebugState { public UInt64 MasterClock; @@ -618,6 +837,8 @@ namespace Mesen.GUI public DebugSa1State Sa1; public GsuState Gsu; public Cx4State Cx4; + + public GbState Gameboy; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public DmaChannelConfig[] DmaChannels; diff --git a/UI/Interop/EmuApi.cs b/UI/Interop/EmuApi.cs index abd5dfd..5751b19 100644 --- a/UI/Interop/EmuApi.cs +++ b/UI/Interop/EmuApi.cs @@ -204,7 +204,8 @@ namespace Mesen.GUI ST010, ST011, ST018, - CX4 + CX4, + Gameboy } public struct MissingFirmwareMessage diff --git a/UI/Properties/Resources.Designer.cs b/UI/Properties/Resources.Designer.cs index 7f5d0d3..727adbe 100644 --- a/UI/Properties/Resources.Designer.cs +++ b/UI/Properties/Resources.Designer.cs @@ -480,6 +480,16 @@ namespace Mesen.GUI.Properties { } } + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap GbDebugger { + get { + object obj = ResourceManager.GetObject("GbDebugger", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// diff --git a/UI/Properties/Resources.resx b/UI/Properties/Resources.resx index 018a6c5..861fe47 100644 --- a/UI/Properties/Resources.resx +++ b/UI/Properties/Resources.resx @@ -469,4 +469,7 @@ ..\Resources\Cx4Debugger.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Resources\GbDebugger.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + \ No newline at end of file diff --git a/UI/Resources/GbDebugger.png b/UI/Resources/GbDebugger.png new file mode 100644 index 0000000000000000000000000000000000000000..0720a6196b10d990ff12b8ed18bb7081ec335d34 GIT binary patch literal 643 zcmV-}0(||6P)N2bZe?^J zG%heMF*(%MvSa`N0su)wK~y+Tm6AJ%8c`I62LcP*E(EmjQ5nl^EP^O@T1Ywxh$(a{ z3(?BXA|QfDKrFQKiD*zklwBKHG2)wm_{2&qQb_uw2nm1AxycM>W3vZ7=APsI|M}-$ zrE0bM4|=^mNnD@xFNpWV6~Vm%07(&Y!{~IntXi$UQ>j!pG=JrEI$g9G#1;SkpFz1? z7H~tNq(-BG!C-*NWWvM418TJzg2CVq+H-SeAkqkfLZKjFvC(LR#bSZgYDFj%!fLg` z>2$($Iz>L8|CGsO-jVPF@rAe+2DC_0?5b9)h1qNtVw=r|a5#+Ra*6eNjs1R)-EN0$ zHVeDm{);qkh(8HZsT9B6&9Pa`_ + + @@ -289,6 +291,9 @@ ctrlCx4Status.cs + + Component + UserControl @@ -298,6 +303,12 @@ Component + + UserControl + + + ctrlGameboyStatus.cs + UserControl @@ -989,6 +1000,7 @@ + @@ -1066,6 +1078,9 @@ ctrlNecDspStatus.cs + + ctrlGameboyStatus.cs + ctrlMemoryAccessCounters.cs diff --git a/UI/Utilities/FolderHelper.cs b/UI/Utilities/FolderHelper.cs index ce49779..4cb0ca9 100644 --- a/UI/Utilities/FolderHelper.cs +++ b/UI/Utilities/FolderHelper.cs @@ -15,7 +15,7 @@ namespace Mesen.GUI.Utilities public static bool IsRomFile(string path) { string ext = Path.GetExtension(path).ToLower(); - return ext == ".sfc" || ext == ".smc" || ext == ".fig" || ext == ".swc" || ext == ".bs"; + return ext == ".sfc" || ext == ".smc" || ext == ".fig" || ext == ".swc" || ext == ".bs" || ext == ".gb"; } public static bool IsArchiveFile(string path) diff --git a/Utilities/Utilities.vcxproj b/Utilities/Utilities.vcxproj index 3dec204..fd79d56 100644 --- a/Utilities/Utilities.vcxproj +++ b/Utilities/Utilities.vcxproj @@ -423,6 +423,7 @@ + @@ -477,6 +478,7 @@ + diff --git a/Utilities/Utilities.vcxproj.filters b/Utilities/Utilities.vcxproj.filters index 39c9256..8fd62fd 100644 --- a/Utilities/Utilities.vcxproj.filters +++ b/Utilities/Utilities.vcxproj.filters @@ -196,6 +196,9 @@ Audio + + Audio + @@ -330,5 +333,8 @@ Audio + + Audio + \ No newline at end of file diff --git a/Utilities/VirtualFile.cpp b/Utilities/VirtualFile.cpp index ae25b46..d146976 100644 --- a/Utilities/VirtualFile.cpp +++ b/Utilities/VirtualFile.cpp @@ -10,7 +10,7 @@ #include "../Utilities/IpsPatcher.h" #include "../Utilities/UpsPatcher.h" -const std::initializer_list VirtualFile::RomExtensions = { ".sfc", ".smc", ".swc", ".fig", ".bs" }; +const std::initializer_list VirtualFile::RomExtensions = { ".sfc", ".smc", ".swc", ".fig", ".bs", ".gb" }; VirtualFile::VirtualFile() { diff --git a/Utilities/blip_buf.cpp b/Utilities/blip_buf.cpp new file mode 100644 index 0000000..ad42438 --- /dev/null +++ b/Utilities/blip_buf.cpp @@ -0,0 +1,345 @@ +/* blip_buf 1.1.0. http://www.slack.net/~ant/ */ + +#include "stdafx.h" +#include "blip_buf.h" + +#include +#include +#include +#include + +/* Library Copyright (C) 2003-2009 Shay Green. This library is free software; +you can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +library is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#if defined (BLARGG_TEST) && BLARGG_TEST + #include "blargg_test.h" +#endif + +/* Equivalent to ULONG_MAX >= 0xFFFFFFFF00000000. +Avoids constants that don't fit in 32 bits. */ +#if ULONG_MAX/0xFFFFFFFF > 0xFFFFFFFF + typedef unsigned long fixed_t; + enum { pre_shift = 32 }; + +#elif defined(ULLONG_MAX) + typedef unsigned long long fixed_t; + enum { pre_shift = 32 }; + +#else + typedef unsigned fixed_t; + enum { pre_shift = 0 }; + +#endif + +enum { time_bits = pre_shift + 20 }; + +static fixed_t const time_unit = (fixed_t) 1 << time_bits; + +enum { bass_shift = 9 }; /* affects high-pass filter breakpoint frequency */ +enum { end_frame_extra = 2 }; /* allows deltas slightly after frame length */ + +enum { half_width = 8 }; +enum { buf_extra = half_width*2 + end_frame_extra }; +enum { phase_bits = 5 }; +enum { phase_count = 1 << phase_bits }; +enum { delta_bits = 15 }; +enum { delta_unit = 1 << delta_bits }; +enum { frac_bits = time_bits - pre_shift }; + +/* We could eliminate avail and encode whole samples in offset, but that would +limit the total buffered samples to blip_max_frame. That could only be +increased by decreasing time_bits, which would reduce resample ratio accuracy. +*/ + +/** Sample buffer that resamples to output rate and accumulates samples +until they're read out */ +struct blip_t +{ + fixed_t factor; + fixed_t offset; + int avail; + int size; + int integrator; +}; + +typedef int buf_t; + +/* probably not totally portable */ +#define SAMPLES( buf ) ((buf_t*) ((buf) + 1)) + +/* Arithmetic (sign-preserving) right shift */ +#define ARITH_SHIFT( n, shift ) \ + ((n) >> (shift)) + +enum { max_sample = +32767 }; +enum { min_sample = -32768 }; + +#define CLAMP( n ) \ + {\ + if ( (short) n != n )\ + n = ARITH_SHIFT( n, 16 ) ^ max_sample;\ + } + +static void check_assumptions( void ) +{ + int n; + + #if INT_MAX < 0x7FFFFFFF || UINT_MAX < 0xFFFFFFFF + #error "int must be at least 32 bits" + #endif + + assert( (-3 >> 1) == -2 ); /* right shift must preserve sign */ + + n = max_sample * 2; + CLAMP( n ); + assert( n == max_sample ); + + n = min_sample * 2; + CLAMP( n ); + assert( n == min_sample ); + + assert( blip_max_ratio <= time_unit ); + assert( blip_max_frame <= (fixed_t) -1 >> time_bits ); +} + +blip_t* blip_new( int size ) +{ + blip_t* m; + assert( size >= 0 ); + + m = (blip_t*) malloc( sizeof *m + (size + buf_extra) * sizeof (buf_t) ); + if ( m ) + { + m->factor = time_unit / blip_max_ratio; + m->size = size; + blip_clear( m ); + check_assumptions(); + } + return m; +} + +void blip_delete( blip_t* m ) +{ + if ( m != NULL ) + { + /* Clear fields in case user tries to use after freeing */ + memset( m, 0, sizeof *m ); + free( m ); + } +} + +void blip_set_rates( blip_t* m, double clock_rate, double sample_rate ) +{ + double factor = time_unit * sample_rate / clock_rate; + m->factor = (fixed_t) factor; + + /* Fails if clock_rate exceeds maximum, relative to sample_rate */ + assert( 0 <= factor - m->factor && factor - m->factor < 1 ); + + /* Avoid requiring math.h. Equivalent to + m->factor = (int) ceil( factor ) */ + if ( m->factor < factor ) + m->factor++; + + /* At this point, factor is most likely rounded up, but could still + have been rounded down in the floating-point calculation. */ +} + +void blip_clear( blip_t* m ) +{ + /* We could set offset to 0, factor/2, or factor-1. 0 is suitable if + factor is rounded up. factor-1 is suitable if factor is rounded down. + Since we don't know rounding direction, factor/2 accommodates either, + with the slight loss of showing an error in half the time. Since for + a 64-bit factor this is years, the halving isn't a problem. */ + + m->offset = m->factor / 2; + m->avail = 0; + m->integrator = 0; + memset( SAMPLES( m ), 0, (m->size + buf_extra) * sizeof (buf_t) ); +} + +int blip_clocks_needed( const blip_t* m, int samples ) +{ + fixed_t needed; + + /* Fails if buffer can't hold that many more samples */ + assert( samples >= 0 && m->avail + samples <= m->size ); + + needed = (fixed_t) samples * time_unit; + if ( needed < m->offset ) + return 0; + + return (int)((needed - m->offset + m->factor - 1) / m->factor); +} + +void blip_end_frame( blip_t* m, unsigned t ) +{ + fixed_t off = t * m->factor + m->offset; + m->avail += off >> time_bits; + m->offset = off & (time_unit - 1); + + /* Fails if buffer size was exceeded */ + assert( m->avail <= m->size ); +} + +int blip_samples_avail( const blip_t* m ) +{ + return m->avail; +} + +static void remove_samples( blip_t* m, int count ) +{ + buf_t* buf = SAMPLES( m ); + int remain = m->avail + buf_extra - count; + m->avail -= count; + + memmove( &buf [0], &buf [count], remain * sizeof buf [0] ); + memset( &buf [remain], 0, count * sizeof buf [0] ); +} + +int blip_read_samples( blip_t* m, short out [], int count, int stereo ) +{ + assert( count >= 0 ); + + if ( count > m->avail ) + count = m->avail; + + if ( count ) + { + int const step = stereo ? 2 : 1; + buf_t const* in = SAMPLES( m ); + buf_t const* end = in + count; + int sum = m->integrator; + do + { + /* Eliminate fraction */ + int s = ARITH_SHIFT( sum, delta_bits ); + + sum += *in++; + + CLAMP( s ); + + *out = s; + out += step; + + /* High-pass filter */ + sum -= s << (delta_bits - bass_shift); + } + while ( in != end ); + m->integrator = sum; + + remove_samples( m, count ); + } + + return count; +} + +/* Things that didn't help performance on x86: + __attribute__((aligned(128))) + #define short int + restrict +*/ + +/* Sinc_Generator( 0.9, 0.55, 4.5 ) */ +static short const bl_step [phase_count + 1] [half_width] = +{ +{ 43, -115, 350, -488, 1136, -914, 5861,21022}, +{ 44, -118, 348, -473, 1076, -799, 5274,21001}, +{ 45, -121, 344, -454, 1011, -677, 4706,20936}, +{ 46, -122, 336, -431, 942, -549, 4156,20829}, +{ 47, -123, 327, -404, 868, -418, 3629,20679}, +{ 47, -122, 316, -375, 792, -285, 3124,20488}, +{ 47, -120, 303, -344, 714, -151, 2644,20256}, +{ 46, -117, 289, -310, 634, -17, 2188,19985}, +{ 46, -114, 273, -275, 553, 117, 1758,19675}, +{ 44, -108, 255, -237, 471, 247, 1356,19327}, +{ 43, -103, 237, -199, 390, 373, 981,18944}, +{ 42, -98, 218, -160, 310, 495, 633,18527}, +{ 40, -91, 198, -121, 231, 611, 314,18078}, +{ 38, -84, 178, -81, 153, 722, 22,17599}, +{ 36, -76, 157, -43, 80, 824, -241,17092}, +{ 34, -68, 135, -3, 8, 919, -476,16558}, +{ 32, -61, 115, 34, -60, 1006, -683,16001}, +{ 29, -52, 94, 70, -123, 1083, -862,15422}, +{ 27, -44, 73, 106, -184, 1152,-1015,14824}, +{ 25, -36, 53, 139, -239, 1211,-1142,14210}, +{ 22, -27, 34, 170, -290, 1261,-1244,13582}, +{ 20, -20, 16, 199, -335, 1301,-1322,12942}, +{ 18, -12, -3, 226, -375, 1331,-1376,12293}, +{ 15, -4, -19, 250, -410, 1351,-1408,11638}, +{ 13, 3, -35, 272, -439, 1361,-1419,10979}, +{ 11, 9, -49, 292, -464, 1362,-1410,10319}, +{ 9, 16, -63, 309, -483, 1354,-1383, 9660}, +{ 7, 22, -75, 322, -496, 1337,-1339, 9005}, +{ 6, 26, -85, 333, -504, 1312,-1280, 8355}, +{ 4, 31, -94, 341, -507, 1278,-1205, 7713}, +{ 3, 35, -102, 347, -506, 1238,-1119, 7082}, +{ 1, 40, -110, 350, -499, 1190,-1021, 6464}, +{ 0, 43, -115, 350, -488, 1136, -914, 5861} +}; + +/* Shifting by pre_shift allows calculation using unsigned int rather than +possibly-wider fixed_t. On 32-bit platforms, this is likely more efficient. +And by having pre_shift 32, a 32-bit platform can easily do the shift by +simply ignoring the low half. */ + +void blip_add_delta( blip_t* m, unsigned time, int delta ) +{ + unsigned fixed = (unsigned) ((time * m->factor + m->offset) >> pre_shift); + buf_t* out = SAMPLES( m ) + m->avail + (fixed >> frac_bits); + + int const phase_shift = frac_bits - phase_bits; + int phase = fixed >> phase_shift & (phase_count - 1); + short const* in = bl_step [phase]; + short const* rev = bl_step [phase_count - phase]; + + int interp = fixed >> (phase_shift - delta_bits) & (delta_unit - 1); + int delta2 = (delta * interp) >> delta_bits; + delta -= delta2; + + /* Fails if buffer size was exceeded */ + assert( out <= &SAMPLES( m ) [m->size + end_frame_extra] ); + + out [0] += in[0]*delta + in[half_width+0]*delta2; + out [1] += in[1]*delta + in[half_width+1]*delta2; + out [2] += in[2]*delta + in[half_width+2]*delta2; + out [3] += in[3]*delta + in[half_width+3]*delta2; + out [4] += in[4]*delta + in[half_width+4]*delta2; + out [5] += in[5]*delta + in[half_width+5]*delta2; + out [6] += in[6]*delta + in[half_width+6]*delta2; + out [7] += in[7]*delta + in[half_width+7]*delta2; + + in = rev; + out [ 8] += in[7]*delta + in[7-half_width]*delta2; + out [ 9] += in[6]*delta + in[6-half_width]*delta2; + out [10] += in[5]*delta + in[5-half_width]*delta2; + out [11] += in[4]*delta + in[4-half_width]*delta2; + out [12] += in[3]*delta + in[3-half_width]*delta2; + out [13] += in[2]*delta + in[2-half_width]*delta2; + out [14] += in[1]*delta + in[1-half_width]*delta2; + out [15] += in[0]*delta + in[0-half_width]*delta2; +} + +void blip_add_delta_fast( blip_t* m, unsigned time, int delta ) +{ + unsigned fixed = (unsigned) ((time * m->factor + m->offset) >> pre_shift); + buf_t* out = SAMPLES( m ) + m->avail + (fixed >> frac_bits); + + int interp = fixed >> (frac_bits - delta_bits) & (delta_unit - 1); + int delta2 = delta * interp; + + /* Fails if buffer size was exceeded */ + assert( out <= &SAMPLES( m ) [m->size + end_frame_extra] ); + + out [7] += delta * delta_unit - delta2; + out [8] += delta2; +} diff --git a/Utilities/blip_buf.h b/Utilities/blip_buf.h new file mode 100644 index 0000000..a50ff96 --- /dev/null +++ b/Utilities/blip_buf.h @@ -0,0 +1,81 @@ +#pragma once +#include "stdafx.h" + +/** \file +Sample buffer that resamples from input clock rate to output sample rate */ + +/* blip_buf 1.1.0 */ +#ifndef BLIP_BUF_H +#define BLIP_BUF_H + +#if defined(_MSC_VER) + #define EXPORT __declspec(dllexport) +#else + #define EXPORT +#endif + +#ifdef __cplusplus + extern "C" { +#endif + +/** First parameter of most functions is blip_t*, or const blip_t* if nothing +is changed. */ +typedef struct blip_t blip_t; + +/** Creates new buffer that can hold at most sample_count samples. Sets rates +so that there are blip_max_ratio clocks per sample. Returns pointer to new +buffer, or NULL if insufficient memory. */ +EXPORT blip_t* blip_new( int sample_count ); + +/** Sets approximate input clock rate and output sample rate. For every +clock_rate input clocks, approximately sample_rate samples are generated. */ +EXPORT void blip_set_rates( blip_t*, double clock_rate, double sample_rate ); + +enum { /** Maximum clock_rate/sample_rate ratio. For a given sample_rate, +clock_rate must not be greater than sample_rate*blip_max_ratio. */ +blip_max_ratio = 1 << 20 }; + +/** Clears entire buffer. Afterwards, blip_samples_avail() == 0. */ +EXPORT void blip_clear( blip_t* ); + +/** Adds positive/negative delta into buffer at specified clock time. */ +EXPORT void blip_add_delta( blip_t*, unsigned int clock_time, int delta ); + +/** Same as blip_add_delta(), but uses faster, lower-quality synthesis. */ +void blip_add_delta_fast( blip_t*, unsigned int clock_time, int delta ); + +/** Length of time frame, in clocks, needed to make sample_count additional +samples available. */ +int blip_clocks_needed( const blip_t*, int sample_count ); + +enum { /** Maximum number of samples that can be generated from one time frame. */ +blip_max_frame = 4000 }; + +/** Makes input clocks before clock_duration available for reading as output +samples. Also begins new time frame at clock_duration, so that clock time 0 in +the new time frame specifies the same clock as clock_duration in the old time +frame specified. Deltas can have been added slightly past clock_duration (up to +however many clocks there are in two output samples). */ +EXPORT void blip_end_frame( blip_t*, unsigned int clock_duration ); + +/** Number of buffered samples available for reading. */ +int blip_samples_avail( const blip_t* ); + +/** Reads and removes at most 'count' samples and writes them to 'out'. If +'stereo' is true, writes output to every other element of 'out', allowing easy +interleaving of two buffers into a stereo sample stream. Outputs 16-bit signed +samples. Returns number of samples actually read. */ +EXPORT int blip_read_samples( blip_t*, short out [], int count, int stereo ); + +/** Frees buffer. No effect if NULL is passed. */ +EXPORT void blip_delete( blip_t* ); + + +/* Deprecated */ +typedef blip_t blip_buffer_t; + +#ifdef __cplusplus + } +#endif + +#endif