diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 53098042..422b990f 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -22,6 +22,7 @@ + @@ -685,6 +686,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index 2a1cdf16..2526a4ce 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -2,11 +2,6 @@ - - - - - @@ -2597,6 +2592,24 @@ SNES\DSP + + Debugger + + + Shared + + + Shared + + + Shared + + + Shared + + + Shared + @@ -2719,6 +2732,9 @@ SNES\DSP + + Debugger + diff --git a/Core/Debugger/DebugTypes.h b/Core/Debugger/DebugTypes.h index 73ab9b58..581657c0 100644 --- a/Core/Debugger/DebugTypes.h +++ b/Core/Debugger/DebugTypes.h @@ -343,6 +343,7 @@ enum class StepType SpecificScanline, RunToNmi, RunToIrq, + StepBack }; struct StepRequest diff --git a/Core/Debugger/Debugger.cpp b/Core/Debugger/Debugger.cpp index 36c246e3..349e9526 100644 --- a/Core/Debugger/Debugger.cpp +++ b/Core/Debugger/Debugger.cpp @@ -166,8 +166,21 @@ uint64_t Debugger::GetCpuCycleCount() template void Debugger::ProcessInstruction() { - _debuggers[(int)type].Debugger->IgnoreBreakpoints = false; - _debuggers[(int)type].Debugger->AllowChangeProgramCounter = true; + IDebugger* debugger = _debuggers[(int)type].Debugger.get(); + if(debugger->CheckStepBack()) { + //Step back target reached, break at the current instruction + debugger->GetStepRequest()->Break(BreakSource::CpuStep); + + //Reset prev op code flag to prevent debugger code from incorrectly flagging + //an instruction as the start of a function, etc. after loading the state + debugger->ResetPrevOpCode(); + } else if(debugger->IsStepBack()) { + //While step back is running, don't process instructions + return; + } + + debugger->IgnoreBreakpoints = false; + debugger->AllowChangeProgramCounter = true; switch(type) { case CpuType::Snes: GetDebugger()->ProcessInstruction(); break; @@ -181,10 +194,10 @@ void Debugger::ProcessInstruction() case CpuType::Pce: GetDebugger()->ProcessInstruction(); break; } - _debuggers[(int)type].Debugger->AllowChangeProgramCounter = false; + debugger->AllowChangeProgramCounter = false; if(_scriptManager->HasCpuMemoryCallbacks()) { - MemoryOperationInfo memOp = _debuggers[(int)type].Debugger->InstructionProgress.LastMemOperation; + MemoryOperationInfo memOp = debugger->InstructionProgress.LastMemOperation; AddressInfo relAddr = { (int32_t)memOp.Address, memOp.MemType }; uint8_t value = (uint8_t)memOp.Value; _scriptManager->ProcessMemoryOperation(relAddr, value, MemoryOperationType::ExecOpCode, type, true); @@ -194,6 +207,10 @@ void Debugger::ProcessInstruction() template void Debugger::ProcessMemoryRead(uint32_t addr, T& value, MemoryOperationType opType) { + if(_debuggers[(int)type].Debugger->IsStepBack()) { + return; + } + switch(type) { case CpuType::Snes: GetDebugger()->ProcessRead(addr, value, opType); break; case CpuType::Spc: GetDebugger()->ProcessRead(addr, value, opType); break; @@ -214,6 +231,10 @@ void Debugger::ProcessMemoryRead(uint32_t addr, T& value, MemoryOperationType op template void Debugger::ProcessMemoryWrite(uint32_t addr, T& value, MemoryOperationType opType) { + if(_debuggers[(int)type].Debugger->IsStepBack()) { + return; + } + switch(type) { case CpuType::Snes: GetDebugger()->ProcessWrite(addr, value, opType); break; case CpuType::Spc: GetDebugger()->ProcessWrite(addr, value, opType); break; @@ -234,6 +255,10 @@ void Debugger::ProcessMemoryWrite(uint32_t addr, T& value, MemoryOperationType o template void Debugger::ProcessIdleCycle() { + if(_debuggers[(int)type].Debugger->IsStepBack()) { + return; + } + _debuggers[(int)type].Debugger->InstructionProgress.LastMemOperation.Type = MemoryOperationType::Idle; switch(type) { @@ -245,6 +270,10 @@ void Debugger::ProcessIdleCycle() template void Debugger::ProcessPpuRead(uint16_t addr, T& value, MemoryType memoryType, MemoryOperationType opType) { + if(_debuggers[(int)type].Debugger->IsStepBack()) { + return; + } + switch(type) { case CpuType::Snes: GetDebugger()->ProcessPpuRead(addr, value, memoryType); break; case CpuType::Gameboy: GetDebugger()->ProcessPpuRead(addr, value, memoryType); break; @@ -261,6 +290,10 @@ void Debugger::ProcessPpuRead(uint16_t addr, T& value, MemoryType memoryType, Me template void Debugger::ProcessPpuWrite(uint16_t addr, T& value, MemoryType memoryType) { + if(_debuggers[(int)type].Debugger->IsStepBack()) { + return; + } + switch(type) { case CpuType::Snes: GetDebugger()->ProcessPpuWrite(addr, value, memoryType); break; case CpuType::Gameboy: GetDebugger()->ProcessPpuWrite(addr, value, memoryType); break; @@ -277,6 +310,10 @@ void Debugger::ProcessPpuWrite(uint16_t addr, T& value, MemoryType memoryType) template void Debugger::ProcessPpuCycle() { + if(_debuggers[(int)type].Debugger->IsStepBack()) { + return; + } + switch(type) { case CpuType::Snes: GetDebugger()->ProcessPpuCycle(); break; case CpuType::Gameboy: GetDebugger()->ProcessPpuCycle(); break; @@ -359,6 +396,10 @@ void Debugger::ProcessPredictiveBreakpoint(CpuType sourceCpu, BreakpointManager* template void Debugger::ProcessInterrupt(uint32_t originalPc, uint32_t currentPc, bool forNmi) { + if(_debuggers[(int)type].Debugger->IsStepBack()) { + return; + } + _debuggers[(int)type].Debugger->ProcessInterrupt(originalPc, currentPc, forNmi); ProcessEvent(forNmi ? EventType::Nmi : EventType::Irq); } @@ -476,6 +517,7 @@ void Debugger::Run() { for(int i = 0; i <= (int)DebugUtilities::GetLastCpuType(); i++) { if(_debuggers[i].Debugger) { + _debuggers[i].Debugger->ResetStepBackCache(); _debuggers[i].Debugger->Run(); } } @@ -499,12 +541,19 @@ void Debugger::Step(CpuType cpuType, int32_t stepCount, StepType type, BreakSour IDebugger* debugger = _debuggers[(int)cpuType].Debugger.get(); if(debugger) { + if(type != StepType::StepBack) { + debugger->ResetStepBackCache(); + } else { + debugger->StepBack(); + } + debugger->Step(stepCount, type); debugger->GetStepRequest()->SetBreakSource(source); } for(int i = 0; i <= (int)DebugUtilities::GetLastCpuType(); i++) { if(_debuggers[i].Debugger && _debuggers[i].Debugger.get() != debugger) { + _debuggers[i].Debugger->ResetStepBackCache(); _debuggers[i].Debugger->Run(); } } diff --git a/Core/Debugger/IDebugger.h b/Core/Debugger/IDebugger.h index a014d99d..d12f0841 100644 --- a/Core/Debugger/IDebugger.h +++ b/Core/Debugger/IDebugger.h @@ -2,6 +2,7 @@ #include "pch.h" #include "Debugger/DebuggerFeatures.h" #include "Debugger/DebugTypes.h" +#include "Debugger/StepBackManager.h" enum class StepType; class BreakpointManager; @@ -15,10 +16,12 @@ struct BaseState; enum class EventType; enum class MemoryOperationType; +//TODO rename/refactor to BaseDebugger class IDebugger { protected: unique_ptr _step; + unique_ptr _stepBackManager = unique_ptr(new StepBackManager(nullptr, nullptr)); public: bool IgnoreBreakpoints = false; @@ -28,6 +31,12 @@ public: virtual ~IDebugger() = default; StepRequest* GetStepRequest() { return _step.get(); } + bool CheckStepBack() { return _stepBackManager->CheckStepBack(); } + bool IsStepBack() { return _stepBackManager->IsRewinding(); } + void ResetStepBackCache() { return _stepBackManager->ResetCache(); } + void StepBack() { return _stepBackManager->StepBack(); } + + virtual void ResetPrevOpCode() {} virtual void Step(int32_t stepCount, StepType type) = 0; virtual void Reset() = 0; diff --git a/Core/Debugger/LuaApi.cpp b/Core/Debugger/LuaApi.cpp index 3ad94841..8c3c7a01 100644 --- a/Core/Debugger/LuaApi.cpp +++ b/Core/Debugger/LuaApi.cpp @@ -174,21 +174,23 @@ int LuaApi::GetLibrary(lua_State *lua) GenerateEnumDefinition(lua, "counterType"); GenerateEnumDefinition(lua, "cpuType"); GenerateEnumDefinition(lua, "drawSurface"); - GenerateEnumDefinition(lua, "eventType"); - GenerateEnumDefinition(lua, "stepType"); + GenerateEnumDefinition(lua, "eventType", { EventType::LastValue }); + GenerateEnumDefinition(lua, "stepType", { StepType::StepBack }); return 1; } template -void LuaApi::GenerateEnumDefinition(lua_State* lua, string enumName) +void LuaApi::GenerateEnumDefinition(lua_State* lua, string enumName, unordered_set excludedValues) { lua_pushstring(lua, enumName.c_str()); lua_newtable(lua); for(auto& entry : magic_enum::enum_entries()) { - string name = string(entry.second); - name[0] = ::tolower(name[0]); - LuaPushIntValue(lua, name, (int)entry.first); + if(excludedValues.find(entry.first) == excludedValues.end()) { + string name = string(entry.second); + name[0] = ::tolower(name[0]); + LuaPushIntValue(lua, name, (int)entry.first); + } } lua_settable(lua, -3); } diff --git a/Core/Debugger/LuaApi.h b/Core/Debugger/LuaApi.h index 17f28237..9820d6d4 100644 --- a/Core/Debugger/LuaApi.h +++ b/Core/Debugger/LuaApi.h @@ -97,5 +97,5 @@ private: static ScriptingContext* _context; static std::pair, FrameInfo> GetRenderedFrame(); - template static void GenerateEnumDefinition(lua_State* lua, string enumName); + template static void GenerateEnumDefinition(lua_State* lua, string enumName, unordered_set excludedValues = {}); }; diff --git a/Core/Debugger/StepBackManager.cpp b/Core/Debugger/StepBackManager.cpp new file mode 100644 index 00000000..03f856e2 --- /dev/null +++ b/Core/Debugger/StepBackManager.cpp @@ -0,0 +1,95 @@ +#include "pch.h" +#include "Debugger/StepBackManager.h" +#include "Debugger/IDebugger.h" +#include "Shared/Emulator.h" +#include "Shared/SaveStateManager.h" +#include "Shared/NotificationManager.h" +#include "Shared/RewindManager.h" + +StepBackManager::StepBackManager(Emulator* emu, IDebugger* debugger) +{ + _emu = emu; + _debugger = debugger; +} + +void StepBackManager::StepBack() +{ + if(!_active) { + _targetClock = _debugger->GetCpuCycleCount(); + _active = true; + _allowRetry = true; + _stateClockLimit = StepBackManager::DefaultClockLimit; + } +} + +bool StepBackManager::CheckStepBack() +{ + if(!_active) { + return false; + } + + uint64_t clock = _debugger->GetCpuCycleCount(); + + if(!_emu->GetRewindManager()->IsStepBack()) { + if(_cache.size() > 1) { + //Check to see if previous instruction is already in cache + if(_cache.back().Clock == _targetClock) { + //End of cache is the current instruction, remove it first + _cache.pop_back(); + if(_cache.size()) { + //If cache isn't empty, load the last state + _emu->GetRewindManager()->SetIgnoreLoadState(true); + _emu->Deserialize(_cache.back().SaveState, SaveStateManager::FileFormatVersion, true); + _emu->GetRewindManager()->SetIgnoreLoadState(false); + + _emu->GetRewindManager()->StopRewinding(true); + _active = false; + _prevClock = clock; + return true; + } + } else { + //On mismatch, clear cache and rewind normally instead + _cache.clear(); + } + } + + //Start rewinding on next instruction after StepBack() is called + _cache.clear(); + _emu->GetRewindManager()->StartRewinding(true); + clock = _debugger->GetCpuCycleCount(); + } + + if(clock < _targetClock && _targetClock - clock < _stateClockLimit) { + //Create a save state every instruction for the last X clocks + _cache.push_back({}); + _cache.back().Clock = clock; + _emu->Serialize(_cache.back().SaveState, true, 0); + } + + if(clock >= _targetClock) { + //If the CPU is back to where it was before step back, check if the cache contains data + if(_cache.size() > 0) { + //If it does, load the last state + _emu->GetRewindManager()->SetIgnoreLoadState(true); + _emu->Deserialize(_cache.back().SaveState, SaveStateManager::FileFormatVersion, true); + _emu->GetRewindManager()->SetIgnoreLoadState(false); + } else if(_allowRetry && clock > _prevClock && (clock - _prevClock) > StepBackManager::DefaultClockLimit) { + //Cache is empty, this can happen when a single instruction takes more than X clocks (e.g block transfers, dma) + //In this case, re-run the step back process again but start recordings state earlier + _emu->GetRewindManager()->StopRewinding(true); + _emu->GetRewindManager()->StartRewinding(true); + _stateClockLimit = (clock - _prevClock) + StepBackManager::DefaultClockLimit; + _allowRetry = false; + return false; + } + + //Stop rewinding, even if the target was not found + _emu->GetRewindManager()->StopRewinding(true); + _active = false; + _prevClock = clock; + return true; + } + + _prevClock = clock; + return false; +} diff --git a/Core/Debugger/StepBackManager.h b/Core/Debugger/StepBackManager.h new file mode 100644 index 00000000..ec24f763 --- /dev/null +++ b/Core/Debugger/StepBackManager.h @@ -0,0 +1,36 @@ +#pragma once +#include "pch.h" + +class Emulator; +class IDebugger; + +struct StepBackCacheEntry +{ + stringstream SaveState; + uint64_t Clock; +}; + +class StepBackManager +{ +private: + static constexpr uint64_t DefaultClockLimit = 600; //Default to 600 clocks to avoid retry when NES sprite DMA occurs (~512 cycles) + + Emulator* _emu = nullptr; + IDebugger* _debugger = nullptr; + + vector _cache; + uint64_t _targetClock = 0; + uint64_t _prevClock = 0; + bool _active = false; + bool _allowRetry = false; + uint64_t _stateClockLimit = StepBackManager::DefaultClockLimit; + +public: + StepBackManager(Emulator* emu, IDebugger* debugger); + + void StepBack(); + bool CheckStepBack(); + + void ResetCache() { _cache.clear(); } + bool IsRewinding() { return _active; } +}; \ No newline at end of file diff --git a/Core/Gameboy/Debugger/GbDebugger.cpp b/Core/Gameboy/Debugger/GbDebugger.cpp index d2c59f9a..572a9a61 100644 --- a/Core/Gameboy/Debugger/GbDebugger.cpp +++ b/Core/Gameboy/Debugger/GbDebugger.cpp @@ -18,6 +18,7 @@ #include "Debugger/MemoryDumper.h" #include "Debugger/CodeDataLogger.h" #include "Debugger/BaseEventManager.h" +#include "Debugger/StepBackManager.h" #include "Utilities/Patches/IpsPatcher.h" #include "Utilities/HexUtilities.h" #include "Gameboy/Debugger/GbAssembler.h" @@ -56,6 +57,7 @@ GbDebugger::GbDebugger(Debugger* debugger) _traceLogger.reset(new GbTraceLogger(debugger, this, _ppu)); _ppuTools.reset(new GbPpuTools(debugger, debugger->GetEmulator())); + _stepBackManager.reset(new StepBackManager(_emu, this)); _eventManager.reset(new GbEventManager(debugger, _gameboy->GetCpu(), _ppu)); _callstackManager.reset(new CallstackManager(debugger, _gameboy)); _breakpointManager.reset(new BreakpointManager(debugger, this, CpuType::Gameboy, _eventManager.get())); @@ -79,7 +81,7 @@ GbDebugger::~GbDebugger() void GbDebugger::Reset() { _callstackManager->Clear(); - _prevOpCode = 0; + ResetPrevOpCode(); } void GbDebugger::ProcessInstruction() @@ -277,7 +279,7 @@ void GbDebugger::ProcessInterrupt(uint32_t originalPc, uint32_t currentPc, bool //If a call/return occurred just before IRQ, it needs to be processed now ProcessCallStackUpdates(ret, originalPc); - _prevOpCode = 0; + ResetPrevOpCode(); _debugger->InternalProcessInterrupt( CpuType::Gameboy, *this, *_step.get(), @@ -326,6 +328,7 @@ DebuggerFeatures GbDebugger::GetSupportedFeatures() features.RunToNmi = false; features.StepOver = true; features.StepOut = true; + features.StepBack = true; features.CallStack = true; features.ChangeProgramCounter = AllowChangeProgramCounter; @@ -351,6 +354,16 @@ uint32_t GbDebugger::GetProgramCounter(bool getInstPc) return getInstPc ? _prevProgramCounter : _cpu->GetState().PC; } +uint64_t GbDebugger::GetCpuCycleCount() +{ + return _gameboy->GetCycleCount(); +} + +void GbDebugger::ResetPrevOpCode() +{ + _prevOpCode = 0; +} + BaseEventManager* GbDebugger::GetEventManager() { return _eventManager.get(); diff --git a/Core/Gameboy/Debugger/GbDebugger.h b/Core/Gameboy/Debugger/GbDebugger.h index 705e3c3e..6acd944e 100644 --- a/Core/Gameboy/Debugger/GbDebugger.h +++ b/Core/Gameboy/Debugger/GbDebugger.h @@ -76,6 +76,8 @@ public: void SetProgramCounter(uint32_t addr) override; uint32_t GetProgramCounter(bool getInstPc) override; + uint64_t GetCpuCycleCount() override; + void ResetPrevOpCode(); DebuggerFeatures GetSupportedFeatures() override; diff --git a/Core/NES/Debugger/NesDebugger.cpp b/Core/NES/Debugger/NesDebugger.cpp index 3733f959..615c1c94 100644 --- a/Core/NES/Debugger/NesDebugger.cpp +++ b/Core/NES/Debugger/NesDebugger.cpp @@ -6,6 +6,7 @@ #include "Debugger/CodeDataLogger.h" #include "Debugger/ScriptManager.h" #include "Debugger/Debugger.h" +#include "Debugger/StepBackManager.h" #include "Debugger/MemoryDumper.h" #include "Debugger/MemoryAccessCounter.h" #include "Debugger/ExpressionEvaluator.h" @@ -73,6 +74,7 @@ NesDebugger::NesDebugger(Debugger* debugger) _ppuTools.reset(new NesPpuTools(debugger, debugger->GetEmulator(), console)); + _stepBackManager.reset(new StepBackManager(_emu, this)); _eventManager.reset(new NesEventManager(debugger, console)); _callstackManager.reset(new CallstackManager(debugger, console)); _breakpointManager.reset(new BreakpointManager(debugger, this, CpuType::Nes, _eventManager.get())); @@ -94,7 +96,7 @@ void NesDebugger::Reset() { _enableBreakOnUninitRead = true; _callstackManager->Clear(); - _prevOpCode = 0xFF; + ResetPrevOpCode(); } uint64_t NesDebugger::GetCpuCycleCount() @@ -102,6 +104,11 @@ uint64_t NesDebugger::GetCpuCycleCount() return _cpu->GetState().CycleCount; } +void NesDebugger::ResetPrevOpCode() +{ + _prevOpCode = 0xFF; +} + void NesDebugger::ProcessInstruction() { NesCpuState& state = _cpu->GetState(); @@ -306,7 +313,7 @@ void NesDebugger::ProcessInterrupt(uint32_t originalPc, uint32_t currentPc, bool //If a call/return occurred just before IRQ, it needs to be processed now ProcessCallStackUpdates(ret, originalPc); - _prevOpCode = 0xFF; + ResetPrevOpCode(); _debugger->InternalProcessInterrupt( CpuType::Nes, *this, *_step.get(), @@ -378,6 +385,7 @@ DebuggerFeatures NesDebugger::GetSupportedFeatures() features.RunToNmi = true; features.StepOver = true; features.StepOut = true; + features.StepBack = true; features.CallStack = true; features.CpuCycleStep = true; features.ChangeProgramCounter = AllowChangeProgramCounter; diff --git a/Core/NES/Debugger/NesDebugger.h b/Core/NES/Debugger/NesDebugger.h index 55efc9f3..1260fb55 100644 --- a/Core/NES/Debugger/NesDebugger.h +++ b/Core/NES/Debugger/NesDebugger.h @@ -67,6 +67,7 @@ public: void Reset() override; uint64_t GetCpuCycleCount() override; + void ResetPrevOpCode() override; void ProcessInstruction(); void ProcessRead(uint32_t addr, uint8_t value, MemoryOperationType type); diff --git a/Core/PCE/Debugger/PceDebugger.cpp b/Core/PCE/Debugger/PceDebugger.cpp index dbdc6c20..2d85d12e 100644 --- a/Core/PCE/Debugger/PceDebugger.cpp +++ b/Core/PCE/Debugger/PceDebugger.cpp @@ -10,6 +10,7 @@ #include "Debugger/MemoryAccessCounter.h" #include "Debugger/ExpressionEvaluator.h" #include "Debugger/CodeDataLogger.h" +#include "Debugger/StepBackManager.h" #include "PCE/PceConsole.h" #include "PCE/PceCpu.h" #include "PCE/PceVdc.h" @@ -59,6 +60,7 @@ PceDebugger::PceDebugger(Debugger* debugger) _cdlFile = _codeDataLogger->GetCdlFilePath(_console->GetRomFormat() == RomFormat::PceCdRom ? "PceCdromBios.cdl" : _emu->GetRomInfo().RomFile.GetFileName()); _codeDataLogger->LoadCdlFile(_cdlFile, _settings->GetDebugConfig().AutoResetCdl); + _stepBackManager.reset(new StepBackManager(_emu, this)); _eventManager.reset(new PceEventManager(debugger, console)); _callstackManager.reset(new CallstackManager(debugger, console)); _breakpointManager.reset(new BreakpointManager(debugger, this, CpuType::Pce, _eventManager.get())); @@ -80,7 +82,7 @@ void PceDebugger::Reset() { _enableBreakOnUninitRead = true; _callstackManager->Clear(); - _prevOpCode = 0x01; + ResetPrevOpCode(); } uint64_t PceDebugger::GetCpuCycleCount() @@ -88,6 +90,11 @@ uint64_t PceDebugger::GetCpuCycleCount() return _cpu->GetState().CycleCount; } +void PceDebugger::ResetPrevOpCode() +{ + _prevOpCode = 0x01; +} + void PceDebugger::ProcessInstruction() { PceCpuState& state = _cpu->GetState(); @@ -285,7 +292,7 @@ void PceDebugger::ProcessInterrupt(uint32_t originalPc, uint32_t currentPc, bool //If a call/return occurred just before IRQ, it needs to be processed now ProcessCallStackUpdates(ret, originalPc); - _prevOpCode = 0x01; + ResetPrevOpCode(); _debugger->InternalProcessInterrupt( CpuType::Pce, *this, *_step.get(), @@ -341,6 +348,7 @@ DebuggerFeatures PceDebugger::GetSupportedFeatures() features.RunToNmi = false; features.StepOver = true; features.StepOut = true; + features.StepBack = true; features.CallStack = true; features.ChangeProgramCounter = AllowChangeProgramCounter; features.CpuCycleStep = true; diff --git a/Core/PCE/Debugger/PceDebugger.h b/Core/PCE/Debugger/PceDebugger.h index 9cbc4614..d0569cba 100644 --- a/Core/PCE/Debugger/PceDebugger.h +++ b/Core/PCE/Debugger/PceDebugger.h @@ -68,6 +68,7 @@ public: void Reset() override; uint64_t GetCpuCycleCount() override; + void ResetPrevOpCode() override; void ProcessInstruction(); void ProcessRead(uint32_t addr, uint8_t value, MemoryOperationType type); diff --git a/Core/SNES/Debugger/SnesDebugger.cpp b/Core/SNES/Debugger/SnesDebugger.cpp index 39aead63..562dc6d8 100644 --- a/Core/SNES/Debugger/SnesDebugger.cpp +++ b/Core/SNES/Debugger/SnesDebugger.cpp @@ -28,6 +28,7 @@ #include "Debugger/ScriptManager.h" #include "Debugger/Debugger.h" #include "Debugger/CodeDataLogger.h" +#include "Debugger/StepBackManager.h" #include "Shared/SettingTypes.h" #include "Shared/BaseControlManager.h" #include "Shared/EmuSettings.h" @@ -75,7 +76,8 @@ SnesDebugger::SnesDebugger(Debugger* debugger, CpuType cpuType) } else { _cdl = (SnesCodeDataLogger*)_debugger->GetCdlManager()->GetCodeDataLogger(MemoryType::SnesPrgRom); } - + + _stepBackManager.reset(new StepBackManager(_emu, this)); _eventManager.reset(new SnesEventManager(debugger, _cpu, console->GetPpu(), _memoryManager, console->GetDmaController())); _callstackManager.reset(new CallstackManager(debugger, console)); _breakpointManager.reset(new BreakpointManager(debugger, this, cpuType, _eventManager.get())); @@ -107,7 +109,7 @@ void SnesDebugger::Reset() { _enableBreakOnUninitRead = true; _callstackManager->Clear(); - _prevOpCode = 0xFF; + ResetPrevOpCode(); } void SnesDebugger::ProcessConfigChange() @@ -129,6 +131,11 @@ uint64_t SnesDebugger::GetCpuCycleCount() return GetCpuState().CycleCount; } +void SnesDebugger::ResetPrevOpCode() +{ + _prevOpCode = 0xFF; +} + void SnesDebugger::ProcessInstruction() { SnesCpuState& state = GetCpuState(); @@ -354,7 +361,7 @@ void SnesDebugger::ProcessInterrupt(uint32_t originalPc, uint32_t currentPc, boo //If a call/return occurred just before IRQ, it needs to be processed now ProcessCallStackUpdates(ret, originalPc, GetCpuState().PS); - _prevOpCode = 0xFF; + ResetPrevOpCode(); _debugger->InternalProcessInterrupt( _cpuType, *this, *_step.get(), @@ -427,6 +434,7 @@ DebuggerFeatures SnesDebugger::GetSupportedFeatures() features.RunToNmi = true; features.StepOver = true; features.StepOut = true; + features.StepBack = true; features.CallStack = true; features.ChangeProgramCounter = AllowChangeProgramCounter; features.CpuCycleStep = true; diff --git a/Core/SNES/Debugger/SnesDebugger.h b/Core/SNES/Debugger/SnesDebugger.h index b76c1ce8..fd9c7402 100644 --- a/Core/SNES/Debugger/SnesDebugger.h +++ b/Core/SNES/Debugger/SnesDebugger.h @@ -87,6 +87,7 @@ public: void ProcessConfigChange() override; uint64_t GetCpuCycleCount() override; + void ResetPrevOpCode() override; void ProcessInstruction(); void ProcessRead(uint32_t addr, uint8_t value, MemoryOperationType type); diff --git a/Core/Shared/RewindManager.cpp b/Core/Shared/RewindManager.cpp index 6f1c9815..09f9db78 100644 --- a/Core/Shared/RewindManager.cpp +++ b/Core/Shared/RewindManager.cpp @@ -85,7 +85,7 @@ void RewindManager::ProcessNotification(ConsoleNotificationType type, void * par ClearBuffer(); } } else if(type == ConsoleNotificationType::StateLoaded) { - if(_rewindState == RewindState::Stopped) { + if(_rewindState == RewindState::Stopped && !_ignoreLoadState) { //A save state was loaded by the user, mark as the end of the current "segment" (for history viewer) _currentHistory.EndOfSegment = true; AddHistoryBlock(); @@ -111,10 +111,10 @@ void RewindManager::AddHistoryBlock() void RewindManager::PopHistory() { - if(_history.empty() && _currentHistory.FrameCount <= 0) { + if(_history.empty() && _currentHistory.FrameCount <= 0 && !IsStepBack()) { StopRewinding(); } else { - if(_currentHistory.FrameCount <= 0) { + if(_currentHistory.FrameCount <= 0 && !IsStepBack()) { _currentHistory = _history.back(); _history.pop_back(); } @@ -131,22 +131,30 @@ void RewindManager::PopHistory() void RewindManager::Start(bool forDebugger) { if(_rewindState == RewindState::Stopped && _settings->GetRewindBufferSize() > 0) { - auto lock = _emu->AcquireLock(); - - _rewindState = forDebugger ? RewindState::Debugging : RewindState::Starting; - _videoHistoryBuilder.clear(); - _videoHistory.clear(); - _audioHistoryBuilder.clear(); - _audioHistory.clear(); - _historyBackup.clear(); - - PopHistory(); - _emu->GetSoundMixer()->StopAudio(true); - _settings->SetFlag(EmulationFlags::MaximumSpeed); - _settings->SetFlag(EmulationFlags::Rewind); + if(forDebugger) { + InternalStart(forDebugger); + } else { + auto lock = _emu->AcquireLock(); + InternalStart(forDebugger); + } } } +void RewindManager::InternalStart(bool forDebugger) +{ + _rewindState = forDebugger ? RewindState::Debugging : RewindState::Starting; + _videoHistoryBuilder.clear(); + _videoHistory.clear(); + _audioHistoryBuilder.clear(); + _audioHistory.clear(); + _historyBackup.clear(); + + PopHistory(); + _emu->GetSoundMixer()->StopAudio(true); + _settings->SetFlag(EmulationFlags::MaximumSpeed); + _settings->SetFlag(EmulationFlags::Rewind); +} + void RewindManager::ForceStop() { if(_rewindState != RewindState::Stopped) { @@ -374,3 +382,8 @@ bool RewindManager::SendAudio(int16_t* soundBuffer, uint32_t sampleCount) { return ProcessAudio(soundBuffer, sampleCount); } + +void RewindManager::SetIgnoreLoadState(bool ignore) +{ + _ignoreLoadState = ignore; +} diff --git a/Core/Shared/RewindManager.h b/Core/Shared/RewindManager.h index b87f2e8c..da9daf6d 100644 --- a/Core/Shared/RewindManager.h +++ b/Core/Shared/RewindManager.h @@ -39,6 +39,7 @@ private: EmuSettings* _settings = nullptr; bool _hasHistory = false; + bool _ignoreLoadState = false; deque _history; deque _historyBackup; @@ -56,6 +57,7 @@ private: void PopHistory(); void Start(bool forDebugger); + void InternalStart(bool forDebugger); void Stop(); void ForceStop(); @@ -85,4 +87,5 @@ public: void SendFrame(RenderedFrame& frame, bool forRewind); bool SendAudio(int16_t *soundBuffer, uint32_t sampleCount); + void SetIgnoreLoadState(bool ignore); }; \ No newline at end of file diff --git a/NewUI/Assets/StepBack.png b/NewUI/Assets/StepBack.png index 01a9b388..32ecfe47 100644 Binary files a/NewUI/Assets/StepBack.png and b/NewUI/Assets/StepBack.png differ diff --git a/NewUI/Debugger/StatusViews/BaseConsoleStatusViewModel.cs b/NewUI/Debugger/StatusViews/BaseConsoleStatusViewModel.cs index 1e1cebcf..ecddb712 100644 --- a/NewUI/Debugger/StatusViews/BaseConsoleStatusViewModel.cs +++ b/NewUI/Debugger/StatusViews/BaseConsoleStatusViewModel.cs @@ -1,6 +1,7 @@ using Mesen.ViewModels; using ReactiveUI.Fody.Helpers; using System; +using System.ComponentModel; namespace Mesen.Debugger.StatusViews { @@ -10,6 +11,18 @@ namespace Mesen.Debugger.StatusViews [Reactive] public UInt64 ElapsedCycles { get; set; } [Reactive] public UInt64 CycleCount { get; set; } + private bool _needUpdate = false; + + public BaseConsoleStatusViewModel() + { + PropertyChanged += BaseConsoleStatusViewModel_PropertyChanged; + } + + private void BaseConsoleStatusViewModel_PropertyChanged(object? sender, PropertyChangedEventArgs e) + { + _needUpdate = true; + } + public void UpdateCycleCount(UInt64 newCycleCount) { if(newCycleCount > CycleCount) { @@ -20,7 +33,22 @@ namespace Mesen.Debugger.StatusViews CycleCount = newCycleCount; } - public abstract void UpdateUiState(); - public abstract void UpdateConsoleState(); + public void UpdateUiState() + { + InternalUpdateUiState(); + _needUpdate = false; + } + + public void UpdateConsoleState() + { + if(_needUpdate) { + //Only update emulator state if user manually changed the state in the UI + //Otherwise this causes issues when a state is loaded (e.g step back) + InternalUpdateConsoleState(); + } + } + + protected abstract void InternalUpdateUiState(); + protected abstract void InternalUpdateConsoleState(); } } diff --git a/NewUI/Debugger/StatusViews/Cx4StatusViewModel.cs b/NewUI/Debugger/StatusViews/Cx4StatusViewModel.cs index d06883e4..ec852f15 100644 --- a/NewUI/Debugger/StatusViews/Cx4StatusViewModel.cs +++ b/NewUI/Debugger/StatusViews/Cx4StatusViewModel.cs @@ -51,7 +51,7 @@ namespace Mesen.Debugger.StatusViews { } - public override void UpdateUiState() + protected override void InternalUpdateUiState() { Cx4State cpu = DebugApi.GetCpuState(CpuType.Cx4); @@ -95,7 +95,7 @@ namespace Mesen.Debugger.StatusViews FlagIrq = cpu.IrqFlag; } - public override void UpdateConsoleState() + protected override void InternalUpdateConsoleState() { Cx4State cpu = DebugApi.GetCpuState(CpuType.Cx4); diff --git a/NewUI/Debugger/StatusViews/GbStatusViewModel.cs b/NewUI/Debugger/StatusViews/GbStatusViewModel.cs index f6bcd2d7..97ab7b29 100644 --- a/NewUI/Debugger/StatusViews/GbStatusViewModel.cs +++ b/NewUI/Debugger/StatusViews/GbStatusViewModel.cs @@ -58,7 +58,7 @@ namespace Mesen.Debugger.StatusViews ); } - public override void UpdateUiState() + protected override void InternalUpdateUiState() { GbState state = DebugApi.GetConsoleState(ConsoleType.Gameboy); @@ -94,7 +94,7 @@ namespace Mesen.Debugger.StatusViews StackPreview = sb.ToString(); } - public override void UpdateConsoleState() + protected override void InternalUpdateConsoleState() { GbCpuState cpu = DebugApi.GetCpuState(CpuType.Gameboy); diff --git a/NewUI/Debugger/StatusViews/GsuStatusViewModel.cs b/NewUI/Debugger/StatusViews/GsuStatusViewModel.cs index 10989a71..f88d2938 100644 --- a/NewUI/Debugger/StatusViews/GsuStatusViewModel.cs +++ b/NewUI/Debugger/StatusViews/GsuStatusViewModel.cs @@ -75,7 +75,7 @@ namespace Mesen.Debugger.StatusViews ); } - public override void UpdateUiState() + protected override void InternalUpdateUiState() { GsuState cpu = DebugApi.GetCpuState(CpuType.Gsu); @@ -118,7 +118,7 @@ namespace Mesen.Debugger.StatusViews FlagIrq = cpu.SFR.Irq; } - public override void UpdateConsoleState() + protected override void InternalUpdateConsoleState() { GsuState cpu = DebugApi.GetCpuState(CpuType.Gsu); diff --git a/NewUI/Debugger/StatusViews/NecDspStatusViewModel.cs b/NewUI/Debugger/StatusViews/NecDspStatusViewModel.cs index 0090b5ca..8299f2d0 100644 --- a/NewUI/Debugger/StatusViews/NecDspStatusViewModel.cs +++ b/NewUI/Debugger/StatusViews/NecDspStatusViewModel.cs @@ -42,7 +42,7 @@ namespace Mesen.Debugger.StatusViews { } - public override void UpdateUiState() + protected override void InternalUpdateUiState() { NecDspState cpu = DebugApi.GetCpuState(CpuType.NecDsp); @@ -80,7 +80,7 @@ namespace Mesen.Debugger.StatusViews RegB_S1 = cpu.FlagsB.Sign1; } - public override void UpdateConsoleState() + protected override void InternalUpdateConsoleState() { NecDspState cpu = DebugApi.GetCpuState(CpuType.NecDsp); diff --git a/NewUI/Debugger/StatusViews/NesStatusViewModel.cs b/NewUI/Debugger/StatusViews/NesStatusViewModel.cs index 3b954e7e..606cc614 100644 --- a/NewUI/Debugger/StatusViews/NesStatusViewModel.cs +++ b/NewUI/Debugger/StatusViews/NesStatusViewModel.cs @@ -84,7 +84,7 @@ namespace Mesen.Debugger.StatusViews }); } - public override void UpdateUiState() + protected override void InternalUpdateUiState() { NesCpuState cpu = DebugApi.GetCpuState(CpuType.Nes); NesPpuState ppu = DebugApi.GetPpuState(CpuType.Nes); @@ -139,7 +139,7 @@ namespace Mesen.Debugger.StatusViews IntensifyBlue = ppu.Mask.IntensifyBlue; } - public override void UpdateConsoleState() + protected override void InternalUpdateConsoleState() { NesCpuState cpu = DebugApi.GetCpuState(CpuType.Nes); NesPpuState ppu = DebugApi.GetPpuState(CpuType.Nes); diff --git a/NewUI/Debugger/StatusViews/PceStatusViewModel.cs b/NewUI/Debugger/StatusViews/PceStatusViewModel.cs index 8c81b1b7..0bc89efc 100644 --- a/NewUI/Debugger/StatusViews/PceStatusViewModel.cs +++ b/NewUI/Debugger/StatusViews/PceStatusViewModel.cs @@ -55,7 +55,7 @@ namespace Mesen.Debugger.StatusViews }); } - public override void UpdateUiState() + protected override void InternalUpdateUiState() { PceCpuState cpu = DebugApi.GetCpuState(CpuType.Pce); PceVideoState video = DebugApi.GetPpuState(CpuType.Pce); @@ -80,7 +80,7 @@ namespace Mesen.Debugger.StatusViews FrameCount = video.Vdc.FrameCount; } - public override void UpdateConsoleState() + protected override void InternalUpdateConsoleState() { PceCpuState cpu = DebugApi.GetCpuState(CpuType.Pce); diff --git a/NewUI/Debugger/StatusViews/SnesStatusViewModel.cs b/NewUI/Debugger/StatusViews/SnesStatusViewModel.cs index 46259292..083625d8 100644 --- a/NewUI/Debugger/StatusViews/SnesStatusViewModel.cs +++ b/NewUI/Debugger/StatusViews/SnesStatusViewModel.cs @@ -80,7 +80,7 @@ namespace Mesen.Debugger.StatusViews ); } - public override void UpdateUiState() + protected override void InternalUpdateUiState() { SnesCpuState cpu = DebugApi.GetCpuState(_cpuType); SnesPpuState ppu = DebugApi.GetPpuState(CpuType.Snes); @@ -115,7 +115,7 @@ namespace Mesen.Debugger.StatusViews Scanline = ppu.Scanline; } - public override void UpdateConsoleState() + protected override void InternalUpdateConsoleState() { SnesCpuState cpu = DebugApi.GetCpuState(_cpuType); diff --git a/NewUI/Debugger/StatusViews/SpcStatusViewModel.cs b/NewUI/Debugger/StatusViews/SpcStatusViewModel.cs index 2c82943f..89932206 100644 --- a/NewUI/Debugger/StatusViews/SpcStatusViewModel.cs +++ b/NewUI/Debugger/StatusViews/SpcStatusViewModel.cs @@ -58,7 +58,7 @@ namespace Mesen.Debugger.StatusViews ); } - public override void UpdateUiState() + protected override void InternalUpdateUiState() { SpcState cpu = DebugApi.GetCpuState(CpuType.Spc); @@ -78,7 +78,7 @@ namespace Mesen.Debugger.StatusViews StackPreview = sb.ToString(); } - public override void UpdateConsoleState() + protected override void InternalUpdateConsoleState() { SpcState cpu = DebugApi.GetCpuState(CpuType.Spc); diff --git a/NewUI/Debugger/Utilities/DebugSharedActions.cs b/NewUI/Debugger/Utilities/DebugSharedActions.cs index 2675c3c6..7f41e477 100644 --- a/NewUI/Debugger/Utilities/DebugSharedActions.cs +++ b/NewUI/Debugger/Utilities/DebugSharedActions.cs @@ -66,8 +66,7 @@ namespace Mesen.Debugger.Utilities ActionType = ActionType.StepBack, Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.StepBack), IsVisible = () => DebugApi.GetDebuggerFeatures(getCpuType()).StepBack, - IsEnabled = () => false, - OnClick = () => { } //TODO + OnClick = () => Step(getCpuType(), StepType.StepBack, 1) }, new ContextMenuSeparator() { IsVisible = () => DebugApi.GetDebuggerFeatures(getCpuType()).CpuCycleStep }, diff --git a/NewUI/Debugger/ViewModels/DebuggerConfigWindowViewModel.cs b/NewUI/Debugger/ViewModels/DebuggerConfigWindowViewModel.cs index 29134575..e1c0df61 100644 --- a/NewUI/Debugger/ViewModels/DebuggerConfigWindowViewModel.cs +++ b/NewUI/Debugger/ViewModels/DebuggerConfigWindowViewModel.cs @@ -217,7 +217,7 @@ namespace Mesen.Debugger.ViewModels DebuggerShortcut.StepInto, DebuggerShortcut.StepOver, DebuggerShortcut.StepOut, - //DebuggerShortcut.StepBack, + DebuggerShortcut.StepBack, DebuggerShortcut.RunCpuCycle, DebuggerShortcut.RunPpuCycle, DebuggerShortcut.RunPpuScanline, diff --git a/NewUI/Interop/DebugApi.cs b/NewUI/Interop/DebugApi.cs index 43accd24..215cfecb 100644 --- a/NewUI/Interop/DebugApi.cs +++ b/NewUI/Interop/DebugApi.cs @@ -1236,6 +1236,7 @@ namespace Mesen.Interop SpecificScanline, RunToNmi, RunToIrq, + StepBack } public enum BreakSource diff --git a/NewUI/Windows/MainWindow.axaml.cs b/NewUI/Windows/MainWindow.axaml.cs index 0a0762ba..c610d636 100644 --- a/NewUI/Windows/MainWindow.axaml.cs +++ b/NewUI/Windows/MainWindow.axaml.cs @@ -19,6 +19,8 @@ using Mesen.Debugger.Utilities; using System.ComponentModel; using System.Threading; using Mesen.Debugger.Windows; +using Avalonia.Input.Platform; +using System.Collections.Generic; namespace Mesen.Windows { @@ -46,6 +48,14 @@ namespace Mesen.Windows { DataContext = new MainWindowViewModel(); + List gestures = AvaloniaLocator.Current.GetRequiredService().OpenContextMenu; + for(int i = gestures.Count - 1; i >= 0; i--) { + if(gestures[i].Key == Key.F10 && gestures[i].KeyModifiers == KeyModifiers.Shift) { + //Disable Shift-F10 shortcut to open context menu - interferes with default shortcut for step back + gestures.RemoveAt(i); + } + } + EmuApi.InitDll(); Directory.CreateDirectory(ConfigManager.HomeFolder);