Skip to content
27 changes: 21 additions & 6 deletions Core/Debugger/Debugger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,9 @@ bool Debugger::ProcessStepBack(IDebugger* debugger)
template<CpuType type>
void Debugger::ProcessInstruction()
{
if(_emu->IsDebuggerDisabled()) {
return;
}
IDebugger* debugger = _debuggers[(int)type].Debugger.get();
if(debugger->IsStepBack() && ProcessStepBack(debugger)) {
debugger->AllowChangeProgramCounter = true; //set to true temporarily to allow debugger to pause on break requests when rewinding/step back is active
Expand Down Expand Up @@ -256,6 +259,9 @@ void Debugger::ProcessInstruction()
template<CpuType type, uint8_t accessWidth, MemoryAccessFlags flags, typename T>
void Debugger::ProcessMemoryRead(uint32_t addr, T& value, MemoryOperationType opType)
{
if(_emu->IsDebuggerDisabled()) {
return;
}
if(_debuggers[(int)type].Debugger->IsStepBack()) {
SleepOnBreakRequest<type>();
return;
Expand Down Expand Up @@ -289,6 +295,9 @@ void Debugger::ProcessMemoryRead(uint32_t addr, T& value, MemoryOperationType op
template<CpuType type, uint8_t accessWidth, MemoryAccessFlags flags, typename T>
bool Debugger::ProcessMemoryWrite(uint32_t addr, T& value, MemoryOperationType opType)
{
if(_emu->IsDebuggerDisabled()) {
return true;
}
if(_debuggers[(int)type].Debugger->IsStepBack()) {
SleepOnBreakRequest<type>();
return !_debuggers[(int)type].Debugger->GetFrozenAddressManager().IsFrozenAddress(addr);
Expand Down Expand Up @@ -328,7 +337,7 @@ void Debugger::ProcessMemoryAccess(uint32_t addr, T& value)

constexpr int accessWidth = std::is_same<T, uint16_t>::value ? 2 : 1;

if(debugger->IsStepBack()) {
if(debugger->IsStepBack() || _emu->IsDebuggerDisabled()) {
return;
}

Expand Down Expand Up @@ -357,6 +366,9 @@ void Debugger::ProcessMemoryAccess(uint32_t addr, T& value)
template<CpuType type>
void Debugger::ProcessIdleCycle()
{
if(_emu->IsDebuggerDisabled()) {
return;
}
if(_debuggers[(int)type].Debugger->IsStepBack()) {
SleepOnBreakRequest<type>();
return;
Expand All @@ -375,6 +387,9 @@ template<CpuType type>
void Debugger::ProcessHaltedCpu()
{
IDebugger* dbg = _debuggers[(int)type].Debugger.get();
if(_emu->IsDebuggerDisabled()) {
return;
}
if(dbg->IsStepBack() && ProcessStepBack(dbg)) {
dbg->AllowChangeProgramCounter = true; //set to true temporarily to allow debugger to pause on break requests when rewinding/step back is active
SleepOnBreakRequest<type>();
Expand Down Expand Up @@ -410,7 +425,7 @@ void Debugger::SleepOnBreakRequest()
template<CpuType type, typename T>
void Debugger::ProcessPpuRead(uint16_t addr, T& value, MemoryType memoryType, MemoryOperationType opType)
{
if(_debuggers[(int)type].Debugger->IsStepBack()) {
if(_debuggers[(int)type].Debugger->IsStepBack() || _emu->IsDebuggerDisabled()) {
return;
}

Expand All @@ -431,7 +446,7 @@ void Debugger::ProcessPpuRead(uint16_t addr, T& value, MemoryType memoryType, Me
template<CpuType type, typename T>
void Debugger::ProcessPpuWrite(uint16_t addr, T& value, MemoryType memoryType)
{
if(_debuggers[(int)type].Debugger->IsStepBack()) {
if(_debuggers[(int)type].Debugger->IsStepBack() || _emu->IsDebuggerDisabled()) {
return;
}

Expand All @@ -452,7 +467,7 @@ void Debugger::ProcessPpuWrite(uint16_t addr, T& value, MemoryType memoryType)
template<CpuType type>
void Debugger::ProcessPpuCycle()
{
if(_debuggers[(int)type].Debugger->IsStepBack()) {
if(_debuggers[(int)type].Debugger->IsStepBack() || _emu->IsDebuggerDisabled()) {
return;
}

Expand Down Expand Up @@ -571,7 +586,7 @@ void Debugger::ProcessPredictiveBreakpoint(CpuType sourceCpu, BreakpointManager*
template<CpuType type>
void Debugger::ProcessInterrupt(uint32_t originalPc, uint32_t currentPc, bool forNmi)
{
if(_debuggers[(int)type].Debugger->IsStepBack()) {
if(_debuggers[(int)type].Debugger->IsStepBack() || _emu->IsDebuggerDisabled()) {
return;
}

Expand Down Expand Up @@ -826,7 +841,7 @@ bool Debugger::IsBreakOptionEnabled(BreakSource src)

void Debugger::BreakImmediately(CpuType sourceCpu, BreakSource source)
{
if(_debuggers[(int)sourceCpu].Debugger->IsStepBack()) {
if(_debuggers[(int)sourceCpu].Debugger->IsStepBack() || _emu->IsDebuggerDisabled()) {
return;
}

Expand Down
64 changes: 58 additions & 6 deletions Core/Gameboy/APU/GbApu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@

GbApu::GbApu()
{
_soundBuffer = new int16_t[GbApu::MaxSamples * 2];
memset(_soundBuffer, 0, GbApu::MaxSamples * 2 * sizeof(int16_t));
_soundBuffer = new int16_t[GbApu::MaxSamples * 2 * 2]; // *2 for stereo, then *2 as a precaution against buffer overflows
memset(_soundBuffer, 0, GbApu::MaxSamples * 2 * 2 * sizeof(int16_t));

_leftChannel = blip_new(GbApu::MaxSamples);
_rightChannel = blip_new(GbApu::MaxSamples);
Expand All @@ -27,6 +27,7 @@ void GbApu::Init(Emulator* emu, Gameboy* gameboy)
_prevRightOutput = 0;
_clockCounter = 0;
_prevClockCount = 0;
_sampleCount = 0;

_emu = emu;
_settings = emu->GetSettings();
Expand Down Expand Up @@ -109,7 +110,7 @@ void GbApu::Run()
}
}

if(!_gameboy->IsSgb() && _clockCounter >= 20000) {
if(!_gameboy->IsSgb() && _clockCounter >= 20000 && !_gameboy->IsPrimaryConsole()) {
PlayQueuedAudio();
}
}
Expand Down Expand Up @@ -148,10 +149,60 @@ void GbApu::PlayQueuedAudio()
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);
_soundMixer->PlayAudioBuffer(_soundBuffer, sampleCount, GbApu::SampleRate);
int16_t* out = _soundBuffer + (_sampleCount * 2);
size_t sampleCount = blip_read_samples(_leftChannel, out, GbApu::MaxSamples, 1);
blip_read_samples(_rightChannel, out + 1, GbApu::MaxSamples, 1);
_sampleCount += sampleCount;
if(_sampleCount > GbApu::MaxSamples) { // Hacky safeguard against buffer overflows
_sampleCount = GbApu::MaxSamples;
}
_clockCounter = 0;

if(!_gameboy->IsPrimaryConsole()) {
// For the secondary console, leave the data in the buffer for the primary console to mix into its own audio
return;
}

GameboyConfig& cfg = _emu->GetSettings()->GetGameboyConfig();
if(_gameboy->IsPrimaryConsole() && _gameboy->GetLinkedConsole()) {
ProcessLinkCableAudio();
}

_soundMixer->PlayAudioBuffer(_soundBuffer, (uint32_t)_sampleCount, GbApu::SampleRate);

_sampleCount = 0;
}

void GbApu::ProcessLinkCableAudio()
{
GameboyConfig& cfg = _emu->GetSettings()->GetGameboyConfig();

if(cfg.LocalLinkCableAudioOutput == GbLocalLinkOutputOption::SubSystemOnly) {
//Mute the main system's sound
memset(_soundBuffer, 0, _sampleCount * sizeof(int16_t) * 2);
}

GbApu* subApu = _gameboy->GetLinkedConsole()->GetApu();
subApu->Run();
subApu->PlayQueuedAudio();

if(cfg.LocalLinkCableAudioOutput != GbLocalLinkOutputOption::MainSystemOnly) {
size_t i;
for(i = 0; i < _sampleCount && i < subApu->_sampleCount; i++) {
_soundBuffer[i * 2] += subApu->_soundBuffer[i * 2];
_soundBuffer[i * 2 + 1] += subApu->_soundBuffer[i * 2 + 1];
}

if(i < subApu->_sampleCount) {
size_t samplesToCopy = subApu->_sampleCount - i;
memmove(subApu->_soundBuffer, subApu->_soundBuffer + i * 2, samplesToCopy * 2 * sizeof(int16_t));
subApu->_sampleCount = samplesToCopy;
} else {
subApu->_sampleCount = 0;
}
} else {
subApu->_sampleCount = 0;
}
}

void GbApu::GetSoundSamples(int16_t*& samples, uint32_t& sampleCount)
Expand Down Expand Up @@ -464,6 +515,7 @@ void GbApu::Serialize(Serializer& s)
Run();
} else {
_clockCounter = 0;
_sampleCount = 0;
blip_clear(_leftChannel);
blip_clear(_rightChannel);
}
Expand Down
3 changes: 3 additions & 0 deletions Core/Gameboy/APU/GbApu.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class GbApu : public ISerializable
int16_t* _soundBuffer = nullptr;
blip_t* _leftChannel = nullptr;
blip_t* _rightChannel = nullptr;
size_t _sampleCount = 0;

int16_t _prevLeftOutput = 0;
int16_t _prevRightOutput = 0;
Expand Down Expand Up @@ -64,6 +65,8 @@ class GbApu : public ISerializable

void PlayQueuedAudio();

void ProcessLinkCableAudio();

void GetSoundSamples(int16_t*& samples, uint32_t& sampleCount);

void ClockFrameSequencer();
Expand Down
68 changes: 67 additions & 1 deletion Core/Gameboy/Gameboy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -156,20 +156,28 @@ void Gameboy::Run(uint64_t runUntilClock)
{
while(_cpu->GetCycleCount() < runUntilClock) {
_cpu->Exec();
if(_secondaryConsole) {
RunLinkedConsole();
}
}
}

void Gameboy::LoadBattery()
{
if(_hasBattery) {
_emu->GetBatteryManager()->LoadBattery(".srm", _cartRam, _cartRamSize);
_emu->GetBatteryManager()->LoadBattery(IsPrimaryConsole() ? ".srm" : ".p2.srm", _cartRam, _cartRamSize);
}
}

void Gameboy::SaveBattery()
{
if(_hasBattery) {
_emu->GetBatteryManager()->SaveBattery(".srm", _cartRam, _cartRamSize);

// SaveBattery only gets called on the primary console, so write the secondary console's save too
if(_secondaryConsole) {
_emu->GetBatteryManager()->SaveBattery(".p2.srm", _secondaryConsole->_cartRam, _secondaryConsole->_cartRamSize);
}
}
_cart->SaveBattery();
}
Expand Down Expand Up @@ -232,6 +240,11 @@ Emulator* Gameboy::GetEmulator()
return _emu;
}

GbApu* Gameboy::GetApu()
{
return _apu.get();
}

GbPpu* Gameboy::GetPpu()
{
return _ppu.get();
Expand Down Expand Up @@ -333,6 +346,14 @@ SuperGameboy* Gameboy::GetSgb()
return _superGameboy;
}

Gameboy* Gameboy::GetLinkedConsole()
{
if(_secondaryConsole) {
return _secondaryConsole.get();
}
return _mainConsole;
}

uint64_t Gameboy::GetCycleCount()
{
return _cpu->GetCycleCount();
Expand All @@ -343,6 +364,11 @@ uint64_t Gameboy::GetApuCycleCount()
return _memoryManager->GetApuCycleCount();
}

bool Gameboy::IsPrimaryConsole()
{
return _mainConsole == nullptr;
}

void Gameboy::Serialize(Serializer& s)
{
SV(_cpu);
Expand All @@ -361,6 +387,10 @@ void Gameboy::Serialize(Serializer& s)
SVArray(_highRam, Gameboy::HighRamSize);

SV(_controlManager);

if(_secondaryConsole) {
SV(_secondaryConsole);
}
}

SaveStateCompatInfo Gameboy::ValidateSaveStateCompatibility(ConsoleType stateConsoleType)
Expand Down Expand Up @@ -461,6 +491,22 @@ LoadRomResult Gameboy::LoadRom(VirtualFile& romFile)
} else {
Init(cart, romData, header.GetCartRamSize(), header.HasBattery());
}

EmuSettings* settings = _emu->GetSettings();
GameboyConfig cfg = settings->GetGameboyConfig();
if(!_mainConsole && cfg.UseLocalLinkCable && !_allowSgb) { // Don't allow link cable with SGB for now
_emu->SetDebuggerDisabled(true);

// Create second console and link it with this one
_secondaryConsole.reset(new Gameboy(_emu));
_secondaryConsole->_mainConsole = this;
LoadRomResult result = _secondaryConsole->LoadRom(romFile);

_emu->SetDebuggerDisabled(false);
if(result != LoadRomResult::Success) {
return result;
}
}
return LoadRomResult::Success;
} else {
MessageManager::DisplayMessage("Error", "Unsupported cart type: " + (gbxFooter.IsValid() ? gbxFooter.GetMapperId() : std::to_string(header.CartType)));
Expand Down Expand Up @@ -562,14 +608,34 @@ GameboyModel Gameboy::GetEffectiveModel(GameboyHeader& header)
void Gameboy::RunFrame()
{
uint32_t frameCount = _ppu->GetFrameCount();

while(frameCount == _ppu->GetFrameCount()) {
_cpu->Exec();
if(_secondaryConsole) {
RunLinkedConsole();
}
}

_apu->Run();
_apu->PlayQueuedAudio();
}

void Gameboy::RunLinkedConsole()
{
_emu->SetDebuggerDisabled(true);
int64_t cycleGap;
while(true) {
//Run the sub console until it catches up to the main CPU
cycleGap = (int64_t)(_cpu->GetCycleCount() - _secondaryConsole->_cpu->GetCycleCount());
if(cycleGap > 5 || _ppu->GetFrameCount() > _secondaryConsole->_ppu->GetFrameCount()) {
_secondaryConsole->_cpu->Exec();
} else {
break;
}
}
_emu->SetDebuggerDisabled(false);
}

void Gameboy::ProcessEndOfFrame()
{
_controlManager->UpdateControlDevices();
Expand Down
8 changes: 8 additions & 0 deletions Core/Gameboy/Gameboy.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ class Gameboy final : public IConsole
SuperGameboy* _superGameboy = nullptr;
bool _allowSgb = false;

unique_ptr<Gameboy> _secondaryConsole;
Gameboy* _mainConsole = nullptr;

unique_ptr<GbMemoryManager> _memoryManager;
unique_ptr<GbCpu> _cpu;
unique_ptr<GbPpu> _ppu;
Expand Down Expand Up @@ -83,6 +86,7 @@ class Gameboy final : public IConsole

Emulator* GetEmulator();

GbApu* GetApu();
GbPpu* GetPpu();
GbCpu* GetCpu();
GbTimer* GetTimer();
Expand All @@ -101,6 +105,7 @@ class Gameboy final : public IConsole
bool IsCgb();
bool IsSgb();
SuperGameboy* GetSgb();
Gameboy* GetLinkedConsole();

uint64_t GetCycleCount();
uint64_t GetApuCycleCount();
Expand All @@ -109,6 +114,9 @@ class Gameboy final : public IConsole

void RunApu();

void RunLinkedConsole();
bool IsPrimaryConsole();

void Serialize(Serializer& s) override;
SaveStateCompatInfo ValidateSaveStateCompatibility(ConsoleType stateConsoleType) override;

Expand Down
Loading
Loading