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