Added Game Boy support

CPU/APU are decent - PPU is still just a scanline renderer
No Super Game Boy support yet
This commit is contained in:
Sour 2020-05-18 16:10:53 -04:00
parent 9dbca4f26d
commit 5f055110fa
138 changed files with 8659 additions and 904 deletions

View file

@ -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> 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

View file

@ -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<BsxMemoryPack> _bsxMemPack;
unique_ptr<Gameboy> _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();

View file

@ -3,7 +3,6 @@
#include "BaseRenderer.h"
#include "Console.h"
#include "EmuSettings.h"
#include "Ppu.h"
#include "MessageManager.h"
BaseRenderer::BaseRenderer(shared_ptr<Console> 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> 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> 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> 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);
}

View file

@ -44,7 +44,8 @@ enum class CoprocessorType
ST010,
ST011,
ST018,
CX4
CX4,
Gameboy
};
struct RomInfo

View file

@ -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 = _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 = _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<BaseCartridge> cart = _cart;
if(_settings->CheckFlag(EmulationFlags::GameboyMode) && cart->GetGameboy()) {
return cart->GetGameboy()->GetState().Ppu.FrameCount;
} else {
shared_ptr<Ppu> ppu = _ppu;
return ppu ? ppu->GetFrameCount() : 0;
}
}
template<CpuType type>
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<CpuType::Spc>(uint32_t addr, uint8_t va
template void Console::ProcessMemoryRead<CpuType::Gsu>(uint32_t addr, uint8_t value, MemoryOperationType opType);
template void Console::ProcessMemoryRead<CpuType::NecDsp>(uint32_t addr, uint8_t value, MemoryOperationType opType);
template void Console::ProcessMemoryRead<CpuType::Cx4>(uint32_t addr, uint8_t value, MemoryOperationType opType);
template void Console::ProcessMemoryRead<CpuType::Gameboy>(uint32_t addr, uint8_t value, MemoryOperationType opType);
template void Console::ProcessMemoryWrite<CpuType::Cpu>(uint32_t addr, uint8_t value, MemoryOperationType opType);
template void Console::ProcessMemoryWrite<CpuType::Sa1>(uint32_t addr, uint8_t value, MemoryOperationType opType);
@ -903,6 +967,7 @@ template void Console::ProcessMemoryWrite<CpuType::Spc>(uint32_t addr, uint8_t v
template void Console::ProcessMemoryWrite<CpuType::Gsu>(uint32_t addr, uint8_t value, MemoryOperationType opType);
template void Console::ProcessMemoryWrite<CpuType::NecDsp>(uint32_t addr, uint8_t value, MemoryOperationType opType);
template void Console::ProcessMemoryWrite<CpuType::Cx4>(uint32_t addr, uint8_t value, MemoryOperationType opType);
template void Console::ProcessMemoryWrite<CpuType::Gameboy>(uint32_t addr, uint8_t value, MemoryOperationType opType);
template void Console::ProcessInterrupt<CpuType::Cpu>(uint32_t originalPc, uint32_t currentPc, bool forNmi);
template void Console::ProcessInterrupt<CpuType::Sa1>(uint32_t originalPc, uint32_t currentPc, bool forNmi);

View file

@ -84,6 +84,7 @@ private:
uint32_t _masterClockRate;
atomic<bool> _isRunAheadFrame;
bool _frameRunning = false;
unique_ptr<DebugStats> _stats;
unique_ptr<FrameLimiter> _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<CpuType type> 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<CpuType type> void ProcessInterrupt(uint32_t originalPc, uint32_t currentPc, bool forNmi);
void ProcessEvent(EventType type);
};

View file

@ -64,6 +64,25 @@
<ClInclude Include="Cx4Types.h" />
<ClInclude Include="DebugUtilities.h" />
<ClInclude Include="DmaControllerTypes.h" />
<ClInclude Include="Gameboy.h" />
<ClInclude Include="GameboyDisUtils.h" />
<ClInclude Include="GameboyHeader.h" />
<ClInclude Include="GbApu.h" />
<ClInclude Include="GbCart.h" />
<ClInclude Include="GbCartFactory.h" />
<ClInclude Include="GbCpu.h" />
<ClInclude Include="GbDebugger.h" />
<ClInclude Include="GbMbc1.h" />
<ClInclude Include="GbMbc2.h" />
<ClInclude Include="GbMbc3.h" />
<ClInclude Include="GbMbc5.h" />
<ClInclude Include="GbMemoryManager.h" />
<ClInclude Include="GbNoiseChannel.h" />
<ClInclude Include="GbPpu.h" />
<ClInclude Include="GbSquareChannel.h" />
<ClInclude Include="GbTimer.h" />
<ClInclude Include="GbTypes.h" />
<ClInclude Include="GbWaveChannel.h" />
<ClInclude Include="NecDspDebugger.h" />
<ClInclude Include="ForceDisconnectMessage.h" />
<ClInclude Include="GameClient.h" />
@ -196,6 +215,7 @@
<ClInclude Include="SettingTypes.h" />
<ClInclude Include="ShortcutKeyHandler.h" />
<ClInclude Include="SnesController.h" />
<ClInclude Include="SnesMemoryType.h" />
<ClInclude Include="SnesMouse.h" />
<ClInclude Include="SoundMixer.h" />
<ClInclude Include="SoundResampler.h" />
@ -253,6 +273,14 @@
<ClCompile Include="Disassembler.cpp" />
<ClCompile Include="DisassemblyInfo.cpp" />
<ClCompile Include="DmaController.cpp" />
<ClCompile Include="Gameboy.cpp" />
<ClCompile Include="GameboyDisUtils.cpp" />
<ClCompile Include="GbApu.cpp" />
<ClCompile Include="GbCpu.cpp" />
<ClCompile Include="GbDebugger.cpp" />
<ClCompile Include="GbMemoryManager.cpp" />
<ClCompile Include="GbPpu.cpp" />
<ClCompile Include="GbTimer.cpp" />
<ClCompile Include="NecDspDebugger.cpp" />
<ClCompile Include="EmuSettings.cpp" />
<ClCompile Include="EventManager.cpp" />

View file

@ -521,6 +521,66 @@
<ClInclude Include="Cx4Debugger.h">
<Filter>Debugger\Debuggers</Filter>
</ClInclude>
<ClInclude Include="GbCpu.h">
<Filter>GB</Filter>
</ClInclude>
<ClInclude Include="GbTypes.h">
<Filter>GB</Filter>
</ClInclude>
<ClInclude Include="Gameboy.h">
<Filter>GB</Filter>
</ClInclude>
<ClInclude Include="GbMemoryManager.h">
<Filter>GB</Filter>
</ClInclude>
<ClInclude Include="GbPpu.h">
<Filter>GB</Filter>
</ClInclude>
<ClInclude Include="GbTimer.h">
<Filter>GB</Filter>
</ClInclude>
<ClInclude Include="GameboyDisUtils.h">
<Filter>Debugger\Disassembler</Filter>
</ClInclude>
<ClInclude Include="GbDebugger.h">
<Filter>Debugger\Debuggers</Filter>
</ClInclude>
<ClInclude Include="GbMbc1.h">
<Filter>GB\Carts</Filter>
</ClInclude>
<ClInclude Include="GbCart.h">
<Filter>GB\Carts</Filter>
</ClInclude>
<ClInclude Include="GbCartFactory.h">
<Filter>GB\Carts</Filter>
</ClInclude>
<ClInclude Include="SnesMemoryType.h">
<Filter>Debugger</Filter>
</ClInclude>
<ClInclude Include="GameboyHeader.h">
<Filter>GB</Filter>
</ClInclude>
<ClInclude Include="GbApu.h">
<Filter>GB\APU</Filter>
</ClInclude>
<ClInclude Include="GbSquareChannel.h">
<Filter>GB\APU</Filter>
</ClInclude>
<ClInclude Include="GbWaveChannel.h">
<Filter>GB\APU</Filter>
</ClInclude>
<ClInclude Include="GbNoiseChannel.h">
<Filter>GB\APU</Filter>
</ClInclude>
<ClInclude Include="GbMbc5.h">
<Filter>GB\Carts</Filter>
</ClInclude>
<ClInclude Include="GbMbc2.h">
<Filter>GB\Carts</Filter>
</ClInclude>
<ClInclude Include="GbMbc3.h">
<Filter>GB\Carts</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="stdafx.cpp" />
@ -839,6 +899,30 @@
<ClCompile Include="Cx4Debugger.cpp">
<Filter>Debugger\Debuggers</Filter>
</ClCompile>
<ClCompile Include="GbMemoryManager.cpp">
<Filter>GB</Filter>
</ClCompile>
<ClCompile Include="GbTimer.cpp">
<Filter>GB</Filter>
</ClCompile>
<ClCompile Include="GbPpu.cpp">
<Filter>GB</Filter>
</ClCompile>
<ClCompile Include="Gameboy.cpp">
<Filter>GB</Filter>
</ClCompile>
<ClCompile Include="GameboyDisUtils.cpp">
<Filter>Debugger\Disassembler</Filter>
</ClCompile>
<ClCompile Include="GbDebugger.cpp">
<Filter>Debugger\Debuggers</Filter>
</ClCompile>
<ClCompile Include="GbApu.cpp">
<Filter>GB\APU</Filter>
</ClCompile>
<ClCompile Include="GbCpu.cpp">
<Filter>GB</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Filter Include="SNES">
@ -916,5 +1000,14 @@
<Filter Include="SNES\Coprocessors\BSX">
<UniqueIdentifier>{c519e61b-e1c6-4182-8a23-ed23b60fbf96}</UniqueIdentifier>
</Filter>
<Filter Include="GB">
<UniqueIdentifier>{3b255a1a-392b-4005-b21e-4c3f5b6f08d2}</UniqueIdentifier>
</Filter>
<Filter Include="GB\Carts">
<UniqueIdentifier>{92514dec-b6e7-4085-843b-bcff985f9e41}</UniqueIdentifier>
</Filter>
<Filter Include="GB\APU">
<UniqueIdentifier>{c020f128-b5e1-4f6c-9849-ff098fe93d39}</UniqueIdentifier>
</Filter>
</ItemGroup>
</Project>

View file

@ -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()

View file

@ -27,7 +27,6 @@ class CpuDebugger final : public IDebugger
MemoryManager* _memoryManager;
EmuSettings* _settings;
CodeDataLogger* _codeDataLogger;
ScriptManager* _scriptManager;
EventManager* _eventManager;
Cpu* _cpu;
Sa1* _sa1;

View file

@ -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);
}
}

View file

@ -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 {

View file

@ -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
};

View file

@ -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;
}
};

View file

@ -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> 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> 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<CpuType type>
@ -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<CallstackManager> 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<CpuType::Spc>(uint32_t addr, uint8_t v
template void Debugger::ProcessMemoryRead<CpuType::Gsu>(uint32_t addr, uint8_t value, MemoryOperationType opType);
template void Debugger::ProcessMemoryRead<CpuType::NecDsp>(uint32_t addr, uint8_t value, MemoryOperationType opType);
template void Debugger::ProcessMemoryRead<CpuType::Cx4>(uint32_t addr, uint8_t value, MemoryOperationType opType);
template void Debugger::ProcessMemoryRead<CpuType::Gameboy>(uint32_t addr, uint8_t value, MemoryOperationType opType);
template void Debugger::ProcessMemoryWrite<CpuType::Cpu>(uint32_t addr, uint8_t value, MemoryOperationType opType);
template void Debugger::ProcessMemoryWrite<CpuType::Sa1>(uint32_t addr, uint8_t value, MemoryOperationType opType);
@ -693,6 +725,7 @@ template void Debugger::ProcessMemoryWrite<CpuType::Spc>(uint32_t addr, uint8_t
template void Debugger::ProcessMemoryWrite<CpuType::Gsu>(uint32_t addr, uint8_t value, MemoryOperationType opType);
template void Debugger::ProcessMemoryWrite<CpuType::NecDsp>(uint32_t addr, uint8_t value, MemoryOperationType opType);
template void Debugger::ProcessMemoryWrite<CpuType::Cx4>(uint32_t addr, uint8_t value, MemoryOperationType opType);
template void Debugger::ProcessMemoryWrite<CpuType::Gameboy>(uint32_t addr, uint8_t value, MemoryOperationType opType);
template void Debugger::ProcessInterrupt<CpuType::Cpu>(uint32_t originalPc, uint32_t currentPc, bool forNmi);
template void Debugger::ProcessInterrupt<CpuType::Sa1>(uint32_t originalPc, uint32_t currentPc, bool forNmi);

View file

@ -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> _gsuDebugger;
unique_ptr<NecDspDebugger> _necDspDebugger;
unique_ptr<Cx4Debugger> _cx4Debugger;
unique_ptr<GbDebugger> _gbDebugger;
shared_ptr<ScriptManager> _scriptManager;
shared_ptr<TraceLogger> _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<CpuType type>
void ProcessInterrupt(uint32_t originalPc, uint32_t currentPc, bool forNmi);

View file

@ -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> console, shared_ptr<CodeDataLogge
_spc = console->GetSpc().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> console, shared_ptr<CodeDataLogge
_necDspProgramRom = cart->GetDsp() ? 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<DisassemblyInfo>(_prgRomSize);
_sramCache = vector<DisassemblyInfo>(_sramSize);
_wramCache = vector<DisassemblyInfo>(_wramSize);
@ -77,13 +88,18 @@ Disassembler::Disassembler(shared_ptr<Console> console, shared_ptr<CodeDataLogge
_gsuWorkRamCache = vector<DisassemblyInfo>(_gsuWorkRamSize);
_bsxPsRamCache = vector<DisassemblyInfo>(_bsxPsRamSize);
_bsxMemPackCache = vector<DisassemblyInfo>(_bsxMemPackSize);
_gbPrgCache = vector<DisassemblyInfo>(_gbPrgRomSize);
_gbWorkRamCache = vector<DisassemblyInfo>(_gbWorkRamSize);
_gbCartRamCache = vector<DisassemblyInfo>(_gbCartRamSize);
_gbHighRamCache = vector<DisassemblyInfo>(_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> console, shared_ptr<CodeDataLogge
_sources[(int)SnesMemoryType::GsuWorkRam] = { _gsuWorkRam, &_gsuWorkRamCache, _gsuWorkRamSize };
_sources[(int)SnesMemoryType::BsxPsRam] = { _bsxPsRam, &_bsxPsRamCache, _bsxPsRamSize };
_sources[(int)SnesMemoryType::BsxMemoryPack] = { _bsxMemPack, &_bsxMemPackCache, _bsxMemPackSize };
_sources[(int)SnesMemoryType::GbPrgRom] = { _gbPrgRom, &_gbPrgCache, _gbPrgRomSize };
_sources[(int)SnesMemoryType::GbWorkRam] = { _gbWorkRam, &_gbWorkRamCache, _gbWorkRamSize };
_sources[(int)SnesMemoryType::GbCartRam] = { _gbCartRam, &_gbCartRamCache, _gbCartRamSize };
_sources[(int)SnesMemoryType::GbHighRam] = { _gbHighRam, &_gbHighRamCache, _gbHighRamSize };
if(_necDspProgramRomSize > 0) {
//Build cache for the entire DSP chip (since it only contains instructions)
@ -118,6 +139,7 @@ vector<DisassemblyResult>& 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;

View file

@ -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<CodeDataLogger> _cdl;
@ -52,6 +54,11 @@ private:
vector<DisassemblyInfo> _bsxPsRamCache;
vector<DisassemblyInfo> _bsxMemPackCache;
vector<DisassemblyInfo> _gbPrgCache;
vector<DisassemblyInfo> _gbWorkRamCache;
vector<DisassemblyInfo> _gbCartRamCache;
vector<DisassemblyInfo> _gbHighRamCache;
SimpleLock _disassemblyLock;
vector<DisassemblyResult> _disassembly;
vector<DisassemblyResult> _spcDisassembly;
@ -59,6 +66,7 @@ private:
vector<DisassemblyResult> _gsuDisassembly;
vector<DisassemblyResult> _necDspDisassembly;
vector<DisassemblyResult> _cx4Disassembly;
vector<DisassemblyResult> _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<DisassemblyResult>& 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);

View file

@ -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 {

View file

@ -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);

View file

@ -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<KeyCombination> 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;
}

View file

@ -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;

View file

@ -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<EvalOperators> &opStack, std::stack<int> &precedenceStack, vector<int64_t> &outputQueue);
bool ToRpn(string expression, ExpressionData &data);

239
Core/Gameboy.cpp Normal file
View file

@ -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<uint8_t> 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);
}

68
Core/Gameboy.h Normal file
View file

@ -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<GbMemoryManager> _memoryManager;
unique_ptr<GbCpu> _cpu;
unique_ptr<GbPpu> _ppu;
unique_ptr<GbApu> _apu;
unique_ptr<GbCart> _cart;
unique_ptr<GbTimer> _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;
};

134
Core/GameboyDisUtils.cpp Normal file
View file

@ -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
);
}

18
Core/GameboyDisUtils.h Normal file
View file

@ -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);
};

77
Core/GameboyHeader.h Normal file
View file

@ -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;
}
}
};

272
Core/GbApu.cpp Normal file
View file

@ -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<uint32_t>({ 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);
}

53
Core/GbApu.h Normal file
View file

@ -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;
};

57
Core/GbCart.h Normal file
View file

@ -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
{
}
};

36
Core/GbCartFactory.h Normal file
View file

@ -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;
}
};

1352
Core/GbCpu.cpp Normal file

File diff suppressed because it is too large Load diff

146
Core/GbCpu.h Normal file
View file

@ -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<uint8_t bit>
void BIT(uint8_t src);
template<uint8_t bit>
void RES(uint8_t& dst);
template<uint8_t bit>
void RES_Indirect(uint16_t addr);
template<uint8_t bit>
void SET(uint8_t& dst);
template<uint8_t bit>
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;
};

146
Core/GbDebugger.cpp Normal file
View file

@ -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<CallstackManager> GbDebugger::GetCallstackManager()
{
return _callstackManager;
}
BreakpointManager* GbDebugger::GetBreakpointManager()
{
return _breakpointManager.get();
}

44
Core/GbDebugger.h Normal file
View file

@ -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> _callstackManager;
unique_ptr<BreakpointManager> _breakpointManager;
unique_ptr<StepRequest> _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<CallstackManager> GetCallstackManager();
BreakpointManager* GetBreakpointManager();
};

62
Core/GbMbc1.h Normal file
View file

@ -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);
}
};

68
Core/GbMbc2.h Normal file
View file

@ -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);
}
};

95
Core/GbMbc3.h Normal file
View file

@ -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<uint8_t>(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);
}
};

71
Core/GbMbc5.h Normal file
View file

@ -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);
}
};

323
Core/GbMemoryManager.cpp Normal file
View file

@ -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<CpuType::Gameboy>(addr, value, opType);
return value;
}
void GbMemoryManager::Write(uint16_t addr, uint8_t value)
{
_console->ProcessMemoryWrite<CpuType::Gameboy>(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);
}
}
}

73
Core/GbMemoryManager.h Normal file
View file

@ -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;
};

194
Core/GbNoiseChannel.h Normal file
View file

@ -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
);
}
};

378
Core/GbPpu.cpp Normal file
View file

@ -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
);
}

50
Core/GbPpu.h Normal file
View file

@ -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;
};

241
Core/GbSquareChannel.h Normal file
View file

@ -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
);
}
};

81
Core/GbTimer.cpp Normal file
View file

@ -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);
}

34
Core/GbTimer.h Normal file
View file

@ -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;
};

272
Core/GbTypes.h Normal file
View file

@ -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;
};

154
Core/GbWaveChannel.h Normal file
View file

@ -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);
}
};

View file

@ -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);
}
}

View file

@ -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");

View file

@ -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);

View file

@ -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;
}

View file

@ -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;
};

View file

@ -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;

View file

@ -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);

View file

@ -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;
}
}

View file

@ -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();
}

View file

@ -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;

View file

@ -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<KeyMapping> _mappings[4];
uint8_t _turboSpeed[4] = {};
uint16_t _stateBuffer[4] = {};

View file

@ -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);
}
}

View file

@ -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);
}
}
}
}
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]);
}
}
}
}
}
}

View file

@ -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);
};

View file

@ -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();

View file

@ -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<int32_t, ProfiledFunction> _functions;

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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();

View file

@ -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<ScriptHost> &script : _scripts) {
script->ProcessMemoryOperation(address, value, type);
script->ProcessMemoryOperation(address, value, type, cpuType);
}
}
}

View file

@ -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);
};

View file

@ -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;
}

View file

@ -18,6 +18,7 @@ struct MemoryCallback
{
uint32_t StartAddress;
uint32_t EndAddress;
CpuType Type;
int Reference;
};
@ -47,7 +48,7 @@ protected:
vector<MemoryCallback> _callbacks[3];
vector<int> _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);
};

View file

@ -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,

View file

@ -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);

View file

@ -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;

34
Core/SnesMemoryType.h Normal file
View file

@ -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
};

View file

@ -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> msu1 = _console->GetMsu1();
if(msu1) {

View file

@ -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);

View file

@ -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);
}

View file

@ -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);
};

View file

@ -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);
}

View file

@ -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);
}
}

View file

@ -22,6 +22,7 @@ TraceLogger::TraceLogger(Debugger* debugger, shared_ptr<Console> 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<RowPart> &rowParts, string format)
@ -131,6 +134,20 @@ void TraceLogger::ParseFormatString(vector<RowPart> &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;

View file

@ -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<RowPart> _dspRowParts;
vector<RowPart> _gsuRowParts;
vector<RowPart> _cx4RowParts;
vector<RowPart> _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<typename T> void WriteValue(string &output, T value, RowPart& rowPart);

View file

@ -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); }

View file

@ -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 \

View file

@ -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<BaseCartridge> 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<BaseCartridge> 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;
}

View file

@ -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 "<any>";
@ -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;
}

View file

@ -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.");

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}
}
}

View file

@ -30,6 +30,8 @@ namespace Mesen.GUI.Config
public bool AutoResetCdl = true;
public bool ShowMemoryMappings = true;
public bool BringToFrontOnBreak = true;
public bool BringToFrontOnPause = false;

View file

@ -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")]

View file

@ -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;

View file

@ -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");
}

View file

@ -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);

View file

@ -0,0 +1,541 @@
namespace Mesen.GUI.Debugger.Controls
{
partial class ctrlGameboyStatus
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if(disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
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;
}
}

View file

@ -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;
}
}
}

View file

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

Some files were not shown because too many files have changed in this diff Show more