diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj
index 9b24f27..3e82d0e 100644
--- a/Core/Core.vcxproj
+++ b/Core/Core.vcxproj
@@ -57,6 +57,7 @@
+
@@ -123,6 +124,7 @@
+
diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters
index 8ae66a4..d6b4380 100644
--- a/Core/Core.vcxproj.filters
+++ b/Core/Core.vcxproj.filters
@@ -185,6 +185,9 @@
SNES
+
+ Debugger
+
@@ -287,6 +290,9 @@
SNES
+
+ Debugger
+
diff --git a/Core/DebugTypes.h b/Core/DebugTypes.h
index b0fdaa3..566c381 100644
--- a/Core/DebugTypes.h
+++ b/Core/DebugTypes.h
@@ -1,5 +1,13 @@
#pragma once
#include "stdafx.h"
+#include "CpuTypes.h"
+#include "PpuTypes.h"
+
+struct DebugState
+{
+ CpuState Cpu;
+ PpuState Ppu;
+};
enum class SnesMemoryType
{
@@ -18,6 +26,13 @@ struct AddressInfo
SnesMemoryType Type;
};
+struct MemoryOperationInfo
+{
+ uint32_t Address;
+ int32_t Value;
+ MemoryOperationType OperationType;
+};
+
namespace CdlFlags
{
enum CdlFlags : uint8_t
diff --git a/Core/Debugger.cpp b/Core/Debugger.cpp
index 6c380fc..ef36f97 100644
--- a/Core/Debugger.cpp
+++ b/Core/Debugger.cpp
@@ -1,5 +1,6 @@
#include "stdafx.h"
#include "Debugger.h"
+#include "DebugTypes.h"
#include "Console.h"
#include "Cpu.h"
#include "Ppu.h"
@@ -12,6 +13,7 @@
#include "MemoryDumper.h"
#include "CodeDataLogger.h"
#include "Disassembler.h"
+#include "ExpressionEvaluator.h"
#include "../Utilities/HexUtilities.h"
#include "../Utilities/FolderUtilities.h"
@@ -22,6 +24,7 @@ Debugger::Debugger(shared_ptr console)
_ppu = console->GetPpu();
_memoryManager = console->GetMemoryManager();
+ _watchExpEval.reset(new ExpressionEvaluator(this));
_codeDataLogger.reset(new CodeDataLogger(console->GetCartridge()->DebugGetPrgRomSize()));
_disassembler.reset(new Disassembler(console, _codeDataLogger));
_traceLogger.reset(new TraceLogger(this, _memoryManager));
@@ -109,6 +112,19 @@ void Debugger::ProcessCpuWrite(uint32_t addr, uint8_t value, MemoryOperationType
}
}
+int32_t Debugger::EvaluateExpression(string expression, EvalResultType &resultType, bool useCache)
+{
+ DebugState state;
+ MemoryOperationInfo operationInfo { 0, 0, MemoryOperationType::DummyRead };
+ GetState(&state);
+ if(useCache) {
+ return _watchExpEval->Evaluate(expression, state, resultType, operationInfo);
+ } else {
+ ExpressionEvaluator expEval(this);
+ return expEval.Evaluate(expression, state, resultType, operationInfo);
+ }
+}
+
void Debugger::Run()
{
_cpuStepCount = -1;
diff --git a/Core/Debugger.h b/Core/Debugger.h
index f79e110..ec61bf9 100644
--- a/Core/Debugger.h
+++ b/Core/Debugger.h
@@ -11,16 +11,12 @@ class MemoryManager;
class CodeDataLogger;
enum class MemoryOperationType;
+enum class EvalResultType : int32_t;
class TraceLogger;
+class ExpressionEvaluator;
class MemoryDumper;
class Disassembler;
-
-struct DebugState
-{
- CpuState Cpu;
- PpuState Ppu;
- //ApuState apuState;
-};
+struct DebugState;
class Debugger
{
@@ -36,6 +32,8 @@ private:
shared_ptr _codeDataLogger;
shared_ptr _disassembler;
+ unique_ptr _watchExpEval;
+
atomic _cpuStepCount;
uint8_t _prevOpCode = 0;
@@ -46,6 +44,8 @@ public:
void ProcessCpuRead(uint32_t addr, uint8_t value, MemoryOperationType type);
void ProcessCpuWrite(uint32_t addr, uint8_t value, MemoryOperationType type);
+ int32_t EvaluateExpression(string expression, EvalResultType &resultType, bool useCache);
+
void Run();
void Step(int32_t stepCount);
bool IsExecutionStopped();
diff --git a/Core/ExpressionEvaluator.cpp b/Core/ExpressionEvaluator.cpp
new file mode 100644
index 0000000..8b12fe7
--- /dev/null
+++ b/Core/ExpressionEvaluator.cpp
@@ -0,0 +1,604 @@
+#include "stdafx.h"
+#include
+#include
+#include "DebugTypes.h"
+#include "ExpressionEvaluator.h"
+#include "Console.h"
+#include "Debugger.h"
+#include "MemoryDumper.h"
+#include "Disassembler.h"
+#include "../Utilities/HexUtilities.h"
+
+const vector ExpressionEvaluator::_binaryOperators = { { "*", "/", "%", "+", "-", "<<", ">>", "<", "<=", ">", ">=", "==", "!=", "&", "^", "|", "&&", "||" } };
+const vector ExpressionEvaluator::_binaryPrecedence = { { 10, 10, 10, 9, 9, 8, 8, 7, 7, 7, 7, 6, 6, 5, 4, 3, 2, 1 } };
+const vector ExpressionEvaluator::_unaryOperators = { { "+", "-", "~", "!" } };
+const vector ExpressionEvaluator::_unaryPrecedence = { { 11, 11, 11, 11 } };
+const std::unordered_set ExpressionEvaluator::_operators = { { "*", "/", "%", "+", "-", "<<", ">>", "<", "<=", ">", ">=", "==", "!=", "&", "^", "|", "&&", "||", "~", "!", "(", ")", "{", "}", "[", "]" } };
+
+bool ExpressionEvaluator::IsOperator(string token, int &precedence, bool unaryOperator)
+{
+ if(unaryOperator) {
+ for(size_t i = 0, len = _unaryOperators.size(); i < len; i++) {
+ if(token.compare(_unaryOperators[i]) == 0) {
+ precedence = _unaryPrecedence[i];
+ return true;
+ }
+ }
+ } else {
+ for(size_t i = 0, len = _binaryOperators.size(); i < len; i++) {
+ if(token.compare(_binaryOperators[i]) == 0) {
+ precedence = _binaryPrecedence[i];
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+EvalOperators ExpressionEvaluator::GetOperator(string token, bool unaryOperator)
+{
+ if(unaryOperator) {
+ for(size_t i = 0, len = _unaryOperators.size(); i < len; i++) {
+ if(token.compare(_unaryOperators[i]) == 0) {
+ return (EvalOperators)(EvalOperators::Plus + i);
+ }
+ }
+ } else {
+ for(size_t i = 0, len = _binaryOperators.size(); i < len; i++) {
+ if(token.compare(_binaryOperators[i]) == 0) {
+ return (EvalOperators)(EvalOperators::Multiplication + i);
+ }
+ }
+ }
+ return EvalOperators::Addition;
+}
+
+bool ExpressionEvaluator::CheckSpecialTokens(string expression, size_t &pos, string &output, ExpressionData &data)
+{
+ string token;
+ size_t initialPos = pos;
+ size_t len = expression.size();
+ do {
+ char c = std::tolower(expression[pos]);
+ if((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_' || c == '@') {
+ //Only letters, numbers and underscore are allowed in code labels
+ token += c;
+ pos++;
+ } else {
+ break;
+ }
+ } while(pos < len);
+
+ if(token == "a") {
+ output += std::to_string((int64_t)EvalValues::RegA);
+ } else if(token == "x") {
+ output += std::to_string((int64_t)EvalValues::RegX);
+ } else if(token == "y") {
+ output += std::to_string((int64_t)EvalValues::RegY);
+ } else if(token == "ps") {
+ output += std::to_string((int64_t)EvalValues::RegPS);
+ } else if(token == "sp") {
+ output += std::to_string((int64_t)EvalValues::RegSP);
+ } else if(token == "pc") {
+ output += std::to_string((int64_t)EvalValues::RegPC);
+ } else if(token == "oppc") {
+ output += std::to_string((int64_t)EvalValues::RegOpPC);
+ } else if(token == "previousoppc") {
+ output += std::to_string((int64_t)EvalValues::PreviousOpPC);
+ } else if(token == "frame") {
+ output += std::to_string((int64_t)EvalValues::PpuFrameCount);
+ } else if(token == "cycle") {
+ output += std::to_string((int64_t)EvalValues::PpuCycle);
+ } else if(token == "scanline") {
+ output += std::to_string((int64_t)EvalValues::PpuScanline);
+ } else if(token == "irq") {
+ output += std::to_string((int64_t)EvalValues::Irq);
+ } else if(token == "nmi") {
+ output += std::to_string((int64_t)EvalValues::Nmi);
+ } else if(token == "verticalblank") {
+ output += std::to_string((int64_t)EvalValues::VerticalBlank);
+ } else if(token == "sprite0hit") {
+ output += std::to_string((int64_t)EvalValues::Sprite0Hit);
+ } else if(token == "spriteoverflow") {
+ output += std::to_string((int64_t)EvalValues::SpriteOverflow);
+ } else if(token == "value") {
+ output += std::to_string((int64_t)EvalValues::Value);
+ } else if(token == "address") {
+ output += std::to_string((int64_t)EvalValues::Address);
+ } else if(token == "romaddress") {
+ output += std::to_string((int64_t)EvalValues::AbsoluteAddress);
+ } else if(token == "iswrite") {
+ output += std::to_string((int64_t)EvalValues::IsWrite);
+ } else if(token == "isread") {
+ output += std::to_string((int64_t)EvalValues::IsRead);
+ } else if(token == "branched") {
+ output += std::to_string((int64_t)EvalValues::Branched);
+ } else {
+ return false;
+ //TODO LABELS
+ /*string originalExpression = expression.substr(initialPos, pos - initialPos);
+ bool validLabel = _debugger->GetLabelManager()->ContainsLabel(originalExpression);
+ if(!validLabel) {
+ //Check if a multi-byte label exists for this name
+ string label = originalExpression + "+0";
+ validLabel = _debugger->GetLabelManager()->ContainsLabel(label);
+ }
+
+ if(validLabel) {
+ data.Labels.push_back(originalExpression);
+ output += std::to_string(EvalValues::FirstLabelIndex + data.Labels.size() - 1);
+ } else {
+ return false;
+ }*/
+ }
+
+ return true;
+}
+
+string ExpressionEvaluator::GetNextToken(string expression, size_t &pos, ExpressionData &data, bool &success)
+{
+ string output;
+ success = true;
+
+ char c = std::tolower(expression[pos]);
+ if(c == '$') {
+ //Hex numbers
+ pos++;
+ for(size_t len = expression.size(); pos < len; pos++) {
+ c = std::tolower(expression[pos]);
+ if((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')) {
+ output += c;
+ } else {
+ break;
+ }
+ }
+ if(output.empty()) {
+ //No numbers followed the hex mark, this isn't a valid expression
+ success = false;
+ }
+ output = std::to_string((uint32_t)HexUtilities::FromHex(output));
+ } else if(c == '%') {
+ //Binary numbers
+ pos++;
+ for(size_t len = expression.size(); pos < len; pos++) {
+ c = std::tolower(expression[pos]);
+ if(c == '0' || c <= '1') {
+ output += c;
+ } else {
+ break;
+ }
+ }
+ if(output.empty()) {
+ //No numbers followed the binary mark, this isn't a valid expression
+ success = false;
+ }
+
+ uint32_t value = 0;
+ for(size_t i = 0; i < output.size(); i++) {
+ value <<= 1;
+ value |= output[i] == '1' ? 1 : 0;
+ }
+ output = std::to_string(value);
+ } else if(c >= '0' && c <= '9') {
+ //Regular numbers
+ for(size_t len = expression.size(); pos < len; pos++) {
+ c = std::tolower(expression[pos]);
+ if(c >= '0' && c <= '9') {
+ output += c;
+ } else {
+ break;
+ }
+ }
+ } else if((c < 'a' || c > 'z') && c != '_' && c != '@') {
+ //Operators
+ string operatorToken;
+ for(size_t len = expression.size(); pos < len; pos++) {
+ c = std::tolower(expression[pos]);
+ operatorToken += c;
+ if(output.empty() || _operators.find(operatorToken) != _operators.end()) {
+ //If appending the next char results in a valid operator, append it (or if this is the first character)
+ output += c;
+ } else {
+ //Reached the end of the operator, return
+ break;
+ }
+ }
+ } else {
+ //Special tokens and labels
+ success = CheckSpecialTokens(expression, pos, output, data);
+ }
+
+ return output;
+}
+
+bool ExpressionEvaluator::ProcessSpecialOperator(EvalOperators evalOp, std::stack &opStack, std::stack &precedenceStack, vector &outputQueue)
+{
+ if(opStack.empty()) {
+ return false;
+ }
+ while(opStack.top() != evalOp) {
+ outputQueue.push_back(opStack.top());
+ opStack.pop();
+ precedenceStack.pop();
+
+ if(opStack.empty()) {
+ return false;
+ }
+ }
+ if(evalOp != EvalOperators::Parenthesis) {
+ outputQueue.push_back(opStack.top());
+ }
+ opStack.pop();
+ precedenceStack.pop();
+
+ return true;
+}
+
+bool ExpressionEvaluator::ToRpn(string expression, ExpressionData &data)
+{
+ std::stack opStack = std::stack();
+ std::stack precedenceStack;
+
+ size_t position = 0;
+ int parenthesisCount = 0;
+ int bracketCount = 0;
+ int braceCount = 0;
+
+ bool previousTokenIsOp = true;
+ bool operatorExpected = false;
+ bool operatorOrEndTokenExpected = false;
+ while(true) {
+ bool success = true;
+ string token = GetNextToken(expression, position, data, success);
+ if(!success) {
+ return false;
+ }
+
+ if(token.empty()) {
+ break;
+ }
+
+ bool requireOperator = operatorExpected;
+ bool requireOperatorOrEndToken = operatorOrEndTokenExpected;
+ bool unaryOperator = previousTokenIsOp;
+
+ operatorExpected = false;
+ operatorOrEndTokenExpected = false;
+ previousTokenIsOp = false;
+
+ int precedence = 0;
+ if(IsOperator(token, precedence, unaryOperator)) {
+ EvalOperators op = GetOperator(token, unaryOperator);
+ bool rightAssociative = unaryOperator;
+ while(!opStack.empty() && ((rightAssociative && precedence < precedenceStack.top()) || (!rightAssociative && precedence <= precedenceStack.top()))) {
+ //Pop operators from the stack until we find something with higher priority (or empty the stack)
+ data.RpnQueue.push_back(opStack.top());
+ opStack.pop();
+ precedenceStack.pop();
+ }
+ opStack.push(op);
+ precedenceStack.push(precedence);
+
+ previousTokenIsOp = true;
+ } else if(requireOperator) {
+ //We needed an operator, and got something else, this isn't a valid expression (e.g "(3)4" or "[$00]22")
+ return false;
+ } else if(requireOperatorOrEndToken && token[0] != ')' && token[0] != ']' && token[0] != '}') {
+ //We needed an operator or close token - this isn't a valid expression (e.g "%1134")
+ return false;
+ } else if(token[0] == '(') {
+ parenthesisCount++;
+ opStack.push(EvalOperators::Parenthesis);
+ precedenceStack.push(0);
+ previousTokenIsOp = true;
+ } else if(token[0] == ')') {
+ parenthesisCount--;
+ if(!ProcessSpecialOperator(EvalOperators::Parenthesis, opStack, precedenceStack, data.RpnQueue)) {
+ return false;
+ }
+ operatorExpected = true;
+ } else if(token[0] == '[') {
+ bracketCount++;
+ opStack.push(EvalOperators::Bracket);
+ precedenceStack.push(0);
+ } else if(token[0] == ']') {
+ bracketCount--;
+ if(!ProcessSpecialOperator(EvalOperators::Bracket, opStack, precedenceStack, data.RpnQueue)) {
+ return false;
+ }
+ operatorExpected = true;
+ } else if(token[0] == '{') {
+ braceCount++;
+ opStack.push(EvalOperators::Braces);
+ precedenceStack.push(0);
+ } else if(token[0] == '}') {
+ braceCount--;
+ if(!ProcessSpecialOperator(EvalOperators::Braces, opStack, precedenceStack, data.RpnQueue)){
+ return false;
+ }
+ operatorExpected = true;
+ } else {
+ if(token[0] < '0' || token[0] > '9') {
+ return false;
+ } else {
+ data.RpnQueue.push_back(std::stoll(token));
+ operatorOrEndTokenExpected = true;
+ }
+ }
+ }
+
+ if(braceCount || bracketCount || parenthesisCount) {
+ //Mismatching number of brackets/braces/parenthesis
+ return false;
+ }
+
+ while(!opStack.empty()) {
+ data.RpnQueue.push_back(opStack.top());
+ opStack.pop();
+ }
+
+ return true;
+}
+
+int32_t ExpressionEvaluator::Evaluate(ExpressionData &data, DebugState &state, EvalResultType &resultType, MemoryOperationInfo &operationInfo)
+{
+ if(data.RpnQueue.empty()) {
+ resultType = EvalResultType::Invalid;
+ return 0;
+ }
+
+ int pos = 0;
+ int64_t right = 0;
+ int64_t left = 0;
+ resultType = EvalResultType::Numeric;
+
+ for(size_t i = 0, len = data.RpnQueue.size(); i < len; i++) {
+ int64_t token = data.RpnQueue[i];
+
+ if(token >= EvalValues::RegA) {
+ //Replace value with a special value
+ if(token >= EvalValues::FirstLabelIndex) {
+ resultType = EvalResultType::Invalid;
+ return 0;
+
+ //TODO
+ /*int64_t labelIndex = token - EvalValues::FirstLabelIndex;
+ if((size_t)labelIndex < data.Labels.size()) {
+ token = _debugger->GetLabelManager()->GetLabelRelativeAddress(data.Labels[(uint32_t)labelIndex]);
+ if(token < -1) {
+ //Label doesn't exist, try to find a matching multi-byte label
+ string label = data.Labels[(uint32_t)labelIndex] + "+0";
+ token = _debugger->GetLabelManager()->GetLabelRelativeAddress(label);
+ }
+ } else {
+ token = -2;
+ }
+ if(token < 0) {
+ //Label is no longer valid
+ resultType = token == -1 ? EvalResultType::OutOfScope : EvalResultType::Invalid;
+ return 0;
+ }*/
+ } else {
+ switch(token) {
+ case EvalValues::RegA: token = state.Cpu.A; break;
+ case EvalValues::RegX: token = state.Cpu.X; break;
+ case EvalValues::RegY: token = state.Cpu.Y; break;
+ case EvalValues::RegSP: token = state.Cpu.SP; break;
+ case EvalValues::RegPS: token = state.Cpu.PS; break;
+ case EvalValues::RegPC: token = state.Cpu.PC; break;
+
+ //TODO
+ /*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;
+ //TODO
+ /*case EvalValues::Nmi: token = state.CPU.NMIFlag; resultType = EvalResultType::Boolean; break;
+ case EvalValues::Irq: token = state.CPU.IRQFlag; resultType = EvalResultType::Boolean; break;
+ case EvalValues::Value: token = operationInfo.Value; break;
+ case EvalValues::Address: token = operationInfo.Address; break;
+ case EvalValues::AbsoluteAddress: token = _debugger->GetAbsoluteAddress(operationInfo.Address); break;
+ case EvalValues::IsWrite: token = operationInfo.OperationType == MemoryOperationType::Write || operationInfo.OperationType == MemoryOperationType::DummyWrite; break;
+ case EvalValues::IsRead: token = operationInfo.OperationType == MemoryOperationType::Read || operationInfo.OperationType == MemoryOperationType::DummyRead; break;
+ case EvalValues::PreviousOpPC: token = state.CPU.PreviousDebugPC; break;
+ case EvalValues::Sprite0Hit: token = state.PPU.StatusFlags.Sprite0Hit; resultType = EvalResultType::Boolean; break;
+ case EvalValues::SpriteOverflow: token = state.PPU.StatusFlags.SpriteOverflow; resultType = EvalResultType::Boolean; break;
+ case EvalValues::VerticalBlank: token = state.PPU.StatusFlags.VerticalBlank; resultType = EvalResultType::Boolean; break;
+ case EvalValues::Branched: token = Disassembler::IsJump(_debugger->GetMemoryDumper()->GetMemoryValue(DebugMemoryType::CpuMemory, state.CPU.PreviousDebugPC, true)); resultType = EvalResultType::Boolean; break;*/
+ }
+ }
+ } else if(token >= EvalOperators::Multiplication) {
+ right = operandStack[--pos];
+ if(pos > 0 && token <= EvalOperators::LogicalOr) {
+ //Only do this for binary operators
+ left = operandStack[--pos];
+ }
+
+ resultType = EvalResultType::Numeric;
+ switch(token) {
+ case EvalOperators::Multiplication: token = left * right; break;
+ case EvalOperators::Division:
+ if(right == 0) {
+ resultType = EvalResultType::DivideBy0;
+ return 0;
+ }
+ token = left / right; break;
+ case EvalOperators::Modulo:
+ if(right == 0) {
+ resultType = EvalResultType::DivideBy0;
+ return 0;
+ }
+ token = left % right;
+ break;
+ case EvalOperators::Addition: token = left + right; break;
+ case EvalOperators::Substration: token = left - right; break;
+ case EvalOperators::ShiftLeft: token = left << right; break;
+ case EvalOperators::ShiftRight: token = left >> right; break;
+ case EvalOperators::SmallerThan: token = left < right; resultType = EvalResultType::Boolean; break;
+ case EvalOperators::SmallerOrEqual: token = left <= right; resultType = EvalResultType::Boolean; break;
+ case EvalOperators::GreaterThan: token = left > right; resultType = EvalResultType::Boolean; break;
+ case EvalOperators::GreaterOrEqual: token = left >= right; resultType = EvalResultType::Boolean; break;
+ case EvalOperators::Equal: token = left == right; resultType = EvalResultType::Boolean; break;
+ case EvalOperators::NotEqual: token = left != right; resultType = EvalResultType::Boolean; break;
+ case EvalOperators::BinaryAnd: token = left & right; break;
+ case EvalOperators::BinaryXor: token = left ^ right; break;
+ case EvalOperators::BinaryOr: token = left | right; break;
+ case EvalOperators::LogicalAnd: token = left && right; resultType = EvalResultType::Boolean; break;
+ case EvalOperators::LogicalOr: token = left || right; resultType = EvalResultType::Boolean; break;
+
+ //Unary operators
+ case EvalOperators::Plus: token = right; break;
+ case EvalOperators::Minus: token = -right; break;
+ case EvalOperators::BinaryNot: token = ~right; break;
+ case EvalOperators::LogicalNot: token = !right; break;
+ case EvalOperators::Bracket: token = _debugger->GetMemoryDumper()->GetMemoryValue(SnesMemoryType::CpuMemory, (uint32_t)right); break;
+ case EvalOperators::Braces: token = _debugger->GetMemoryDumper()->GetMemoryValueWord(SnesMemoryType::CpuMemory, (uint32_t)right); break;
+ default: throw std::runtime_error("Invalid operator");
+ }
+ }
+ operandStack[pos++] = token;
+ }
+ return (int32_t)operandStack[0];
+}
+
+ExpressionEvaluator::ExpressionEvaluator(Debugger* debugger)
+{
+ _debugger = debugger;
+}
+
+ExpressionData ExpressionEvaluator::GetRpnList(string expression, bool &success)
+{
+ ExpressionData* cachedData = PrivateGetRpnList(expression, success);
+ if(cachedData) {
+ return *cachedData;
+ } else {
+ return ExpressionData();
+ }
+}
+
+ExpressionData* ExpressionEvaluator::PrivateGetRpnList(string expression, bool& success)
+{
+ ExpressionData *cachedData = nullptr;
+ {
+ LockHandler lock = _cacheLock.AcquireSafe();
+
+ auto result = _cache.find(expression);
+ if(result != _cache.end()) {
+ cachedData = &(result->second);
+ }
+ }
+
+ if(cachedData == nullptr) {
+ string fixedExp = expression;
+ fixedExp.erase(std::remove(fixedExp.begin(), fixedExp.end(), ' '), fixedExp.end());
+ ExpressionData data;
+ success = ToRpn(fixedExp, data);
+ if(success) {
+ LockHandler lock = _cacheLock.AcquireSafe();
+ _cache[expression] = data;
+ cachedData = &_cache[expression];
+ }
+ } else {
+ success = true;
+ }
+
+ return cachedData;
+}
+
+int32_t ExpressionEvaluator::PrivateEvaluate(string expression, DebugState &state, EvalResultType &resultType, MemoryOperationInfo &operationInfo, bool& success)
+{
+ success = true;
+ ExpressionData *cachedData = PrivateGetRpnList(expression, success);
+
+ if(!success) {
+ resultType = EvalResultType::Invalid;
+ return 0;
+ }
+
+ return Evaluate(*cachedData, state, resultType, operationInfo);
+}
+
+int32_t ExpressionEvaluator::Evaluate(string expression, DebugState &state, EvalResultType &resultType, MemoryOperationInfo &operationInfo)
+{
+ try {
+ bool success;
+ int32_t result = PrivateEvaluate(expression, state, resultType, operationInfo, success);
+ if(success) {
+ return result;
+ }
+ } catch(std::exception e) {
+ }
+ resultType = EvalResultType::Invalid;
+ return 0;
+}
+
+bool ExpressionEvaluator::Validate(string expression)
+{
+ try {
+ DebugState state;
+ EvalResultType type;
+ MemoryOperationInfo operationInfo;
+ bool success;
+ PrivateEvaluate(expression, state, type, operationInfo, success);
+ return success;
+ } catch(std::exception e) {
+ return false;
+ }
+}
+
+#if _DEBUG
+#include
+void ExpressionEvaluator::RunTests()
+{
+ //Some basic unit tests to run in debug mode
+ auto test = [=](string expr, EvalResultType expectedType, int expectedResult) {
+ DebugState state = { 0 };
+ OperationInfo opInfo = { 0 };
+ EvalResultType type;
+ int32_t result = Evaluate(expr, state, type, opInfo);
+
+ assert(type == expectedType);
+ assert(result == expectedResult);
+ };
+
+ test("1 - -1", EvalResultType::Numeric, 2);
+ test("1 - (-1)", EvalResultType::Numeric, 2);
+ test("1 - -(-1)", EvalResultType::Numeric, 0);
+ test("(0 - 1) == -1 && 5 < 10", EvalResultType::Boolean, true);
+ test("(0 - 1) == 0 || 5 < 10", EvalResultType::Boolean, true);
+ test("(0 - 1) == 0 || 5 < -10", EvalResultType::Boolean, false);
+ test("(0 - 1) == 0 || 15 < 10", EvalResultType::Boolean, false);
+
+ test("10 != $10", EvalResultType::Boolean, true);
+ test("10 == $A", EvalResultType::Boolean, true);
+ test("10 == $0A", EvalResultType::Boolean, true);
+
+ test("(0 - 1 == 0 || 15 < 10", EvalResultType::Invalid, 0);
+ test("10 / 0", EvalResultType::DivideBy0, 0);
+
+ test("x + 5", EvalResultType::Numeric, 5);
+ test("x == 0", EvalResultType::Boolean, true);
+ test("x == y", EvalResultType::Boolean, true);
+ test("x == y == scanline", EvalResultType::Boolean, false); //because (x == y) is true, and true != scanline
+ test("x == y && !(a == x)", EvalResultType::Boolean, false);
+
+ test("(~0 & ~1) & $FFF == $FFE", EvalResultType::Numeric, 0); //because of operator priority (& is done after ==)
+ test("((~0 & ~1) & $FFF) == $FFE", EvalResultType::Boolean, true);
+
+ test("1+3*3+10/(3+4)", EvalResultType::Numeric, 11);
+ test("(1+3*3+10)/(3+4)", EvalResultType::Numeric, 2);
+ test("(1+3*3+10)/3+4", EvalResultType::Numeric, 10);
+
+ test("{$4500}", EvalResultType::Numeric, 0x4545);
+ test("[$4500]", EvalResultType::Numeric, 0x45);
+
+ test("[$45]3", EvalResultType::Invalid, 0);
+ test("($45)3", EvalResultType::Invalid, 0);
+ test("($45]", EvalResultType::Invalid, 0);
+
+ test("%11", EvalResultType::Numeric, 3);
+ test("%011", EvalResultType::Numeric, 3);
+ test("%1011", EvalResultType::Numeric, 11);
+ test("%12", EvalResultType::Invalid, 0);
+}
+#endif
\ No newline at end of file
diff --git a/Core/ExpressionEvaluator.h b/Core/ExpressionEvaluator.h
new file mode 100644
index 0000000..94d78fc
--- /dev/null
+++ b/Core/ExpressionEvaluator.h
@@ -0,0 +1,137 @@
+#pragma once
+#include "stdafx.h"
+#include
+#include
+#include
+#include
+#include "DebugTypes.h"
+#include "../Utilities/SimpleLock.h"
+
+class Debugger;
+
+enum EvalOperators : int64_t
+{
+ //Binary operators
+ Multiplication = 20000000000,
+ Division = 20000000001,
+ Modulo = 20000000002,
+ Addition = 20000000003,
+ Substration = 20000000004,
+ ShiftLeft = 20000000005,
+ ShiftRight = 20000000006,
+ SmallerThan = 20000000007,
+ SmallerOrEqual = 20000000008,
+ GreaterThan = 20000000009,
+ GreaterOrEqual = 20000000010,
+ Equal = 20000000011,
+ NotEqual = 20000000012,
+ BinaryAnd = 20000000013,
+ BinaryXor = 20000000014,
+ BinaryOr = 20000000015,
+ LogicalAnd = 20000000016,
+ LogicalOr = 20000000017,
+
+ //Unary operators
+ Plus = 20000000050,
+ Minus = 20000000051,
+ BinaryNot = 20000000052,
+ LogicalNot = 20000000053,
+
+ //Used to read ram address
+ Bracket = 20000000054, //Read byte
+ Braces = 20000000055, //Read word
+
+ //Special value, not used as an operator
+ Parenthesis = 20000000100,
+};
+
+enum EvalValues : int64_t
+{
+ RegA = 20000000100,
+ RegX = 20000000101,
+ RegY = 20000000102,
+ RegSP = 20000000103,
+ RegPS = 20000000104,
+ RegPC = 20000000105,
+ RegOpPC = 20000000106,
+ PpuFrameCount = 20000000107,
+ PpuCycle = 20000000108,
+ PpuScanline = 20000000109,
+ Nmi = 20000000110,
+ Irq = 20000000111,
+ Value = 20000000112,
+ Address = 20000000113,
+ AbsoluteAddress = 20000000114,
+ IsWrite = 20000000115,
+ IsRead = 20000000116,
+ PreviousOpPC = 20000000117,
+ Sprite0Hit = 20000000118,
+ SpriteOverflow = 20000000119,
+ VerticalBlank = 20000000120,
+ Branched = 20000000121,
+
+ FirstLabelIndex = 20000002000,
+};
+
+enum class EvalResultType : int32_t
+{
+ Numeric = 0,
+ Boolean = 1,
+ Invalid = 2,
+ DivideBy0 = 3,
+ OutOfScope = 4
+};
+
+class StringHasher
+{
+public:
+ size_t operator()(const std::string& t) const
+ {
+ //Quick hash for expressions - most are likely to have different lengths, and not expecting dozens of breakpoints, either, so this should be fine.
+ return t.size();
+ }
+};
+
+struct ExpressionData
+{
+ std::vector RpnQueue;
+ std::vector Labels;
+};
+
+class ExpressionEvaluator
+{
+private:
+ static const vector _binaryOperators;
+ static const vector _binaryPrecedence;
+ static const vector _unaryOperators;
+ static const vector _unaryPrecedence;
+ static const std::unordered_set _operators;
+
+ std::unordered_map _cache;
+ SimpleLock _cacheLock;
+
+ int64_t operandStack[1000];
+ Debugger* _debugger;
+
+ bool IsOperator(string token, int &precedence, bool unaryOperator);
+ EvalOperators GetOperator(string token, bool unaryOperator);
+ bool CheckSpecialTokens(string expression, size_t &pos, string &output, ExpressionData &data);
+ string GetNextToken(string expression, size_t &pos, ExpressionData &data, bool &success);
+ bool ProcessSpecialOperator(EvalOperators evalOp, std::stack &opStack, std::stack &precedenceStack, vector &outputQueue);
+ bool ToRpn(string expression, ExpressionData &data);
+ int32_t PrivateEvaluate(string expression, DebugState &state, EvalResultType &resultType, MemoryOperationInfo &operationInfo, bool &success);
+ ExpressionData* PrivateGetRpnList(string expression, bool& success);
+
+public:
+ ExpressionEvaluator(Debugger* debugger);
+
+ int32_t Evaluate(ExpressionData &data, DebugState &state, EvalResultType &resultType, MemoryOperationInfo &operationInfo);
+ int32_t Evaluate(string expression, DebugState &state, EvalResultType &resultType, MemoryOperationInfo &operationInfo);
+ ExpressionData GetRpnList(string expression, bool &success);
+
+ bool Validate(string expression);
+
+#if _DEBUG
+ void RunTests();
+#endif
+};
\ No newline at end of file
diff --git a/Core/MemoryDumper.cpp b/Core/MemoryDumper.cpp
index 5018b93..78acf84 100644
--- a/Core/MemoryDumper.cpp
+++ b/Core/MemoryDumper.cpp
@@ -111,3 +111,11 @@ uint8_t MemoryDumper::GetMemoryValue(SnesMemoryType memoryType, uint32_t address
return 0;
}
+
+uint8_t MemoryDumper::GetMemoryValueWord(SnesMemoryType memoryType, uint32_t address)
+{
+ uint32_t memorySize = GetMemorySize(memoryType);
+ uint8_t lsb = GetMemoryValue(memoryType, address);
+ uint8_t msb = GetMemoryValue(memoryType, (address + 1) & (memorySize - 1));
+ return (msb << 8) | lsb;
+}
\ No newline at end of file
diff --git a/Core/MemoryDumper.h b/Core/MemoryDumper.h
index 144dd54..d47f736 100644
--- a/Core/MemoryDumper.h
+++ b/Core/MemoryDumper.h
@@ -22,6 +22,7 @@ public:
void GetMemoryState(SnesMemoryType type, uint8_t *buffer);
uint8_t GetMemoryValue(SnesMemoryType memoryType, uint32_t address, bool disableSideEffects = true);
+ uint8_t GetMemoryValueWord(SnesMemoryType memoryType, uint32_t address);
void SetMemoryValue(SnesMemoryType memoryType, uint32_t address, uint8_t value, bool disableSideEffects = true);
void SetMemoryValues(SnesMemoryType memoryType, uint32_t address, uint8_t* data, uint32_t length);
void SetMemoryState(SnesMemoryType type, uint8_t *buffer, uint32_t length);
diff --git a/InteropDLL/DebugApiWrapper.cpp b/InteropDLL/DebugApiWrapper.cpp
index 7a411b4..c41836d 100644
--- a/InteropDLL/DebugApiWrapper.cpp
+++ b/InteropDLL/DebugApiWrapper.cpp
@@ -46,6 +46,8 @@ extern "C"
DllExport void __stdcall StopTraceLogger() { GetDebugger()->GetTraceLogger()->StopLogging(); }
DllExport const char* GetExecutionTrace(uint32_t lineCount) { return GetDebugger()->GetTraceLogger()->GetExecutionTrace(lineCount); }
+ DllExport int32_t __stdcall EvaluateExpression(char* expression, EvalResultType *resultType, bool useCache) { return GetDebugger()->EvaluateExpression(expression, *resultType, useCache); }
+
DllExport void __stdcall GetState(DebugState *state) { GetDebugger()->GetState(state); }
DllExport void __stdcall SetMemoryState(SnesMemoryType type, uint8_t *buffer, int32_t length) { GetDebugger()->GetMemoryDumper()->SetMemoryState(type, buffer, length); }
diff --git a/UI/Debugger/Config/DebugInfo.cs b/UI/Debugger/Config/DebugInfo.cs
index c05eda6..130cdbf 100644
--- a/UI/Debugger/Config/DebugInfo.cs
+++ b/UI/Debugger/Config/DebugInfo.cs
@@ -39,6 +39,8 @@ namespace Mesen.GUI.Config
public XmlColor CodeReadBreakpointColor = Color.FromArgb(40, 40, 200);
public XmlColor CodeActiveStatementColor = Color.Yellow;
+ public WatchFormatStyle WatchFormat = WatchFormatStyle.Hex;
+
public DebugInfo()
{
}
diff --git a/UI/Debugger/Controls/ctrlWatch.cs b/UI/Debugger/Controls/ctrlWatch.cs
new file mode 100644
index 0000000..7041584
--- /dev/null
+++ b/UI/Debugger/Controls/ctrlWatch.cs
@@ -0,0 +1,515 @@
+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.Config;
+using Mesen.GUI.Controls;
+using System.Text.RegularExpressions;
+using System.Globalization;
+using Mesen.GUI.Forms;
+
+namespace Mesen.GUI.Debugger
+{
+ public partial class ctrlWatch : BaseControl
+ {
+ private Color _updatedColor = Color.Red;
+ private Color _normalColor = SystemColors.ControlText;
+
+ private static Regex _watchAddressOrLabel = new Regex(@"^(\[|{)(\s*((\$[0-9A-Fa-f]+)|(\d+)|([@_a-zA-Z0-9]+)))\s*[,]{0,1}\d*\s*(\]|})$", RegexOptions.Compiled);
+
+ private int _previousMaxLength = -1;
+ private int _selectedAddress = -1;
+ //private CodeLabel _selectedLabel = null;
+ private List _previousValues = new List();
+
+ private bool _isEditing = false;
+ ListViewItem _keyDownItem = null;
+
+ public ctrlWatch()
+ {
+ InitializeComponent();
+
+ this.DoubleBuffered = true;
+ }
+
+ protected override void OnLoad(EventArgs e)
+ {
+ base.OnLoad(e);
+ if(!IsDesignMode) {
+ WatchManager.WatchChanged += WatchManager_WatchChanged;
+ mnuRemoveWatch.InitShortcut(this, nameof(DebuggerShortcutsConfig.WatchList_Delete));
+ mnuEditInMemoryViewer.InitShortcut(this, nameof(DebuggerShortcutsConfig.CodeWindow_EditInMemoryViewer));
+ mnuViewInDisassembly.InitShortcut(this, nameof(DebuggerShortcutsConfig.MemoryViewer_ViewInDisassembly));
+ mnuMoveUp.InitShortcut(this, nameof(DebuggerShortcutsConfig.WatchList_MoveUp));
+ mnuMoveDown.InitShortcut(this, nameof(DebuggerShortcutsConfig.WatchList_MoveDown));
+ }
+ }
+
+ public string GetTooltipText()
+ {
+ return "";
+ /*return (
+ frmBreakpoint.GetConditionTooltip(true) + Environment.NewLine + Environment.NewLine +
+ "Additionally, the watch window supports a syntax to display X bytes starting from a specific address. e.g:" + Environment.NewLine +
+ "[$10, 16]: Display 16 bytes starting from address $10" + Environment.NewLine +
+ "[MyLabel, 4]: Display 4 bytes starting from the address the specified label (MyLabel) refers to"
+ );*/
+ }
+
+ protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
+ {
+ if(lstWatch.SelectedItems.Count > 0) {
+ //Used to prevent a Mono issue where pressing a key will change the selected item before we get a chance to edit it
+ _keyDownItem = lstWatch.SelectedItems[0];
+
+ if((_isEditing && keyData == Keys.Escape) || keyData == Keys.Enter) {
+ if(keyData == Keys.Enter) {
+ if(_isEditing) {
+ ApplyEdit();
+ } else {
+ StartEdit(lstWatch.SelectedItems[0].Text);
+ }
+ } else if(keyData == Keys.Escape) {
+ CancelEdit();
+ }
+ return true;
+ }
+ } else {
+ _keyDownItem = null;
+ }
+
+ UpdateActions();
+ return base.ProcessCmdKey(ref msg, keyData);
+ }
+
+ private void contextMenuWatch_Opening(object sender, CancelEventArgs e)
+ {
+ UpdateActions();
+ }
+
+ private void WatchManager_WatchChanged(object sender, EventArgs e)
+ {
+ if(this.InvokeRequired) {
+ this.BeginInvoke((Action)(() => this.UpdateWatch()));
+ } else {
+ this.UpdateWatch();
+ }
+ }
+
+ public void UpdateWatch(bool autoResizeColumns = true)
+ {
+ List watchContent = WatchManager.GetWatchContent(_previousValues);
+ _previousValues = watchContent;
+
+ bool updating = false;
+ if(watchContent.Count != lstWatch.Items.Count - 1) {
+ int currentFocus = lstWatch.FocusedItem?.Selected == true ? (lstWatch.FocusedItem?.Index ?? -1) : -1;
+ lstWatch.BeginUpdate();
+ lstWatch.Items.Clear();
+
+ List itemsToAdd = new List();
+ foreach(WatchValueInfo watch in watchContent) {
+ ListViewItem item = new ListViewItem(watch.Expression);
+ item.UseItemStyleForSubItems = false;
+ item.SubItems.Add(watch.Value).ForeColor = watch.HasChanged ? _updatedColor : _normalColor;
+ itemsToAdd.Add(item);
+ }
+ var lastItem = new ListViewItem("");
+ lastItem.SubItems.Add("");
+ itemsToAdd.Add(lastItem);
+ lstWatch.Items.AddRange(itemsToAdd.ToArray());
+ if(currentFocus >= 0 && currentFocus < lstWatch.Items.Count) {
+ SetSelectedItem(currentFocus);
+ }
+ updating = true;
+ } else {
+ for(int i = 0; i < watchContent.Count; i++) {
+ ListViewItem item = lstWatch.Items[i];
+ bool needUpdate = (
+ item.SubItems[0].Text != watchContent[i].Expression ||
+ item.SubItems[1].Text != watchContent[i].Value ||
+ item.SubItems[1].ForeColor != (watchContent[i].HasChanged ? _updatedColor : _normalColor)
+ );
+ if(needUpdate) {
+ updating = true;
+ item.SubItems[0].Text = watchContent[i].Expression;
+ item.SubItems[1].Text = watchContent[i].Value;
+ item.SubItems[1].ForeColor = watchContent[i].HasChanged ? _updatedColor : _normalColor;
+ }
+ }
+ }
+
+ if(updating) {
+ if(watchContent.Count > 0) {
+ int maxLength = watchContent.Select(info => info.Value.Length).Max();
+ if(_previousMaxLength != maxLength) {
+ if(autoResizeColumns) {
+ lstWatch.AutoResizeColumn(1, ColumnHeaderAutoResizeStyle.ColumnContent);
+ }
+ if(colValue.Width < 100) {
+ colValue.Width = 100;
+ }
+ _previousMaxLength = maxLength;
+ }
+ }
+ lstWatch.EndUpdate();
+ }
+ }
+
+ private void lstWatch_SelectedIndexChanged(object sender, EventArgs e)
+ {
+ mnuRemoveWatch.Enabled = lstWatch.SelectedItems.Count >= 1;
+ UpdateActions();
+ }
+
+ private void UpdateActions()
+ {
+ mnuHexDisplay.Checked = ConfigManager.Config.Debug.WatchFormat == WatchFormatStyle.Hex;
+ mnuDecimalDisplay.Checked = ConfigManager.Config.Debug.WatchFormat == WatchFormatStyle.Signed;
+ mnuBinaryDisplay.Checked = ConfigManager.Config.Debug.WatchFormat == WatchFormatStyle.Binary;
+ mnuRowDisplayFormat.Enabled = lstWatch.SelectedItems.Count > 0;
+
+ mnuEditInMemoryViewer.Enabled = false;
+ mnuViewInDisassembly.Enabled = false;
+ mnuMoveUp.Enabled = false;
+ mnuMoveDown.Enabled = false;
+
+ if(lstWatch.SelectedItems.Count == 1) {
+ Match match = _watchAddressOrLabel.Match(lstWatch.SelectedItems[0].Text);
+ if(match.Success) {
+ string address = match.Groups[3].Value;
+
+ if(address[0] >= '0' && address[0] <= '9' || address[0] == '$') {
+ //CPU Address
+ _selectedAddress = Int32.Parse(address[0] == '$' ? address.Substring(1) : address, address[0] == '$' ? NumberStyles.AllowHexSpecifier : NumberStyles.None);
+ //TODO
+ //_selectedLabel = null;
+ mnuEditInMemoryViewer.Enabled = true;
+ mnuViewInDisassembly.Enabled = true;
+ } else {
+ //Label
+ _selectedAddress = -1;
+ //TODO
+ /*_selectedLabel = LabelManager.GetLabel(address);
+ if(_selectedLabel != null) {
+ mnuEditInMemoryViewer.Enabled = true;
+ mnuViewInDisassembly.Enabled = true;
+ }*/
+ }
+ }
+
+ mnuMoveUp.Enabled = lstWatch.SelectedIndices[0] > 0 && lstWatch.SelectedIndices[0] < lstWatch.Items.Count - 1;
+ mnuMoveDown.Enabled = lstWatch.SelectedIndices[0] < lstWatch.Items.Count - 2;
+ }
+ }
+
+ private void mnuRemoveWatch_Click(object sender, EventArgs e)
+ {
+ if(lstWatch.SelectedItems.Count >= 1) {
+ var itemsToRemove = new List();
+ foreach(ListViewItem item in lstWatch.SelectedItems) {
+ itemsToRemove.Add(item.Index);
+ }
+ WatchManager.RemoveWatch(itemsToRemove.ToArray());
+ }
+ }
+
+ private void mnuViewInDisassembly_Click(object sender, EventArgs e)
+ {
+ //TODO
+ /*if(lstWatch.SelectedItems.Count != 1) {
+ return;
+ }
+
+ if(_selectedAddress >= 0) {
+ DebugWindowManager.GetDebugger().ScrollToAddress(_selectedAddress);
+ } else if(_selectedLabel != null) {
+ int relAddress = _selectedLabel.GetRelativeAddress();
+ if(relAddress >= 0) {
+ DebugWindowManager.GetDebugger().ScrollToAddress(relAddress);
+ }
+ }*/
+ }
+
+ private void mnuEditInMemoryViewer_Click(object sender, EventArgs e)
+ {
+ //TODO
+ /*if(lstWatch.SelectedItems.Count != 1) {
+ return;
+ }
+
+ if(_selectedAddress >= 0) {
+ DebugWindowManager.OpenMemoryViewer(_selectedAddress, DebugMemoryType.CpuMemory);
+ } else if(_selectedLabel != null) {
+ DebugWindowManager.OpenMemoryViewer((int)_selectedLabel.Address, _selectedLabel.AddressType.ToMemoryType());
+ }*/
+ }
+
+ private void StartEdit(string text, ListViewItem selectedItem = null)
+ {
+ if(selectedItem == null) {
+ selectedItem = lstWatch.SelectedItems[0];
+ }
+
+ SetSelectedItem(selectedItem.Index);
+
+ txtEdit.Location = selectedItem.Position;
+ txtEdit.Width = selectedItem.Bounds.Width;
+ txtEdit.Text = text;
+ txtEdit.SelectionLength = 0;
+ txtEdit.SelectionStart = text.Length;
+ txtEdit.Visible = true;
+ txtEdit.Focus();
+ _isEditing = true;
+ }
+
+ private void lstWatch_Click(object sender, EventArgs e)
+ {
+ if(lstWatch.SelectedItems.Count == 1 && string.IsNullOrWhiteSpace(lstWatch.SelectedItems[0].Text)) {
+ StartEdit("");
+ }
+ }
+
+ private void lstWatch_DoubleClick(object sender, EventArgs e)
+ {
+ if(lstWatch.SelectedItems.Count == 1) {
+ StartEdit(lstWatch.SelectedItems[0].Text);
+ }
+ }
+
+ private void ApplyEdit()
+ {
+ if(lstWatch.SelectedItems.Count > 0) {
+ lstWatch.SelectedItems[0].Text = txtEdit.Text;
+ WatchManager.UpdateWatch(lstWatch.SelectedIndices[0], txtEdit.Text);
+ }
+ lstWatch.Focus();
+ }
+
+ private void CancelEdit()
+ {
+ if(lstWatch.SelectedItems.Count > 0) {
+ txtEdit.Text = lstWatch.SelectedItems[0].Text;
+ }
+ lstWatch.Focus();
+ }
+
+ private void lstWatch_KeyPress(object sender, KeyPressEventArgs e)
+ {
+ if(lstWatch.SelectedItems.Count > 0) {
+ if(e.KeyChar >= ' ' && e.KeyChar < 128) {
+ e.Handled = true;
+ StartEdit(e.KeyChar.ToString(), _keyDownItem);
+ _keyDownItem = null;
+ }
+ }
+ }
+
+ private void txtEdit_Leave(object sender, EventArgs e)
+ {
+ _isEditing = false;
+ txtEdit.Visible = false;
+ lstWatch.Focus();
+ ApplyEdit();
+ }
+
+ private void mnuMoveUp_Click(object sender, EventArgs e)
+ {
+ MoveUp(false);
+ }
+
+ private void mnuMoveDown_Click(object sender, EventArgs e)
+ {
+ MoveDown();
+ }
+
+ private void SetSelectedItem(int index)
+ {
+ if(index < lstWatch.Items.Count) {
+ lstWatch.FocusedItem = lstWatch.Items[index];
+ foreach(ListViewItem item in lstWatch.Items) {
+ item.Selected = lstWatch.FocusedItem == item;
+ }
+ }
+ }
+
+ private void MoveUp(bool fromUpDownArrow)
+ {
+ if(lstWatch.SelectedIndices.Count == 0) {
+ return;
+ }
+
+ int index = lstWatch.SelectedIndices[0];
+ if(Program.IsMono && fromUpDownArrow) {
+ //Mono appears to move the selection up before processing this
+ index++;
+ }
+
+ if(index > 0 && index < lstWatch.Items.Count - 1) {
+ string currentEntry = lstWatch.Items[index].SubItems[0].Text;
+ string entryAbove = lstWatch.Items[index - 1].SubItems[0].Text;
+ SetSelectedItem(index - 1);
+ WatchManager.UpdateWatch(index - 1, currentEntry);
+ WatchManager.UpdateWatch(index, entryAbove);
+ } else {
+ SetSelectedItem(index);
+ }
+ }
+
+ private void MoveDown()
+ {
+ if(lstWatch.SelectedIndices.Count == 0) {
+ return;
+ }
+
+ int index = lstWatch.SelectedIndices[0];
+ if(index < lstWatch.Items.Count - 2) {
+ string currentEntry = lstWatch.Items[index].SubItems[0].Text;
+ string entryBelow = lstWatch.Items[index + 1].SubItems[0].Text;
+ SetSelectedItem(index + 1);
+ WatchManager.UpdateWatch(index + 1, currentEntry);
+ WatchManager.UpdateWatch(index, entryBelow);
+ } else {
+ SetSelectedItem(index);
+ }
+ }
+
+ private void lstWatch_OnMoveUpDown(Keys keyData, ref bool processed)
+ {
+ if(keyData == ConfigManager.Config.Debug.Shortcuts.WatchList_MoveUp) {
+ MoveUp(true);
+ processed = true;
+ } else if(keyData == ConfigManager.Config.Debug.Shortcuts.WatchList_MoveDown) {
+ MoveDown();
+ processed = true;
+ }
+ }
+
+ private void mnuImport_Click(object sender, EventArgs e)
+ {
+ using(OpenFileDialog ofd = new OpenFileDialog()) {
+ ofd.SetFilter("Watch files (*.mwf)|*.mwf");
+ if(ofd.ShowDialog() == DialogResult.OK) {
+ WatchManager.Import(ofd.FileName);
+ }
+ }
+ }
+
+ private void mnuExport_Click(object sender, EventArgs e)
+ {
+ using(SaveFileDialog sfd = new SaveFileDialog()) {
+ sfd.SetFilter("Watch files (*.mwf)|*.mwf");
+ if(sfd.ShowDialog() == DialogResult.OK) {
+ WatchManager.Export(sfd.FileName);
+ }
+ }
+ }
+
+ private void mnuHexDisplay_Click(object sender, EventArgs e)
+ {
+ ConfigManager.Config.Debug.WatchFormat = WatchFormatStyle.Hex;
+ ConfigManager.ApplyChanges();
+ UpdateWatch();
+ }
+
+ private void mnuDecimalDisplay_Click(object sender, EventArgs e)
+ {
+ ConfigManager.Config.Debug.WatchFormat = WatchFormatStyle.Signed;
+ ConfigManager.ApplyChanges();
+ UpdateWatch();
+ }
+
+ private void mnuBinaryDisplay_Click(object sender, EventArgs e)
+ {
+ ConfigManager.Config.Debug.WatchFormat = WatchFormatStyle.Binary;
+ ConfigManager.ApplyChanges();
+ UpdateWatch();
+ }
+
+ private string GetFormatString(WatchFormatStyle format, int byteLength)
+ {
+ string formatString = ", ";
+ switch(format) {
+ case WatchFormatStyle.Binary: formatString += "B"; break;
+ case WatchFormatStyle.Hex: formatString += "H"; break;
+ case WatchFormatStyle.Signed: formatString += "S"; break;
+ case WatchFormatStyle.Unsigned: formatString += "U"; break;
+ default: throw new Exception("Unsupported type");
+ }
+ if(byteLength > 1) {
+ formatString += byteLength.ToString();
+ }
+ return formatString;
+ }
+
+ private void SetSelectionFormat(WatchFormatStyle format, int byteLength)
+ {
+ SetSelectionFormat(GetFormatString(format, byteLength));
+ }
+
+ private void SetSelectionFormat(string formatString)
+ {
+ List entries = WatchManager.WatchEntries;
+ foreach(int i in lstWatch.SelectedIndices) {
+ if(i < entries.Count) {
+ Match match = WatchManager.FormatSuffixRegex.Match(entries[i]);
+ if(match.Success) {
+ WatchManager.UpdateWatch(i, match.Groups[1].Value + formatString);
+ } else {
+ WatchManager.UpdateWatch(i, entries[i] + formatString);
+ }
+ }
+ }
+ }
+
+ private void mnuRowBinary_Click(object sender, EventArgs e)
+ {
+ SetSelectionFormat(WatchFormatStyle.Binary, 1);
+ }
+
+ private void mnuRowHex1_Click(object sender, EventArgs e)
+ {
+ SetSelectionFormat(WatchFormatStyle.Hex, 1);
+ }
+
+ private void mnuRowHex2_Click(object sender, EventArgs e)
+ {
+ SetSelectionFormat(WatchFormatStyle.Hex, 2);
+ }
+
+ private void mnuRowHex3_Click(object sender, EventArgs e)
+ {
+ SetSelectionFormat(WatchFormatStyle.Hex, 3);
+ }
+
+ private void mnuRowSigned1_Click(object sender, EventArgs e)
+ {
+ SetSelectionFormat(WatchFormatStyle.Signed, 1);
+ }
+
+ private void mnuRowSigned2_Click(object sender, EventArgs e)
+ {
+ SetSelectionFormat(WatchFormatStyle.Signed, 2);
+ }
+
+ private void mnuRowSigned3_Click(object sender, EventArgs e)
+ {
+ SetSelectionFormat(WatchFormatStyle.Unsigned, 1);
+ }
+
+ private void mnuRowUnsigned_Click(object sender, EventArgs e)
+ {
+ SetSelectionFormat(WatchFormatStyle.Unsigned, 1);
+ }
+
+ private void mnuRowClearFormat_Click(object sender, EventArgs e)
+ {
+ SetSelectionFormat("");
+ }
+ }
+}
diff --git a/UI/Debugger/Controls/ctrlWatch.designer.cs b/UI/Debugger/Controls/ctrlWatch.designer.cs
new file mode 100644
index 0000000..14d5f51
--- /dev/null
+++ b/UI/Debugger/Controls/ctrlWatch.designer.cs
@@ -0,0 +1,396 @@
+namespace Mesen.GUI.Debugger
+{
+ partial class ctrlWatch
+ {
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if(disposing && (components != null)) {
+ components.Dispose();
+ }
+ WatchManager.WatchChanged -= WatchManager_WatchChanged;
+ base.Dispose(disposing);
+ }
+
+ #region Component Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ this.components = new System.ComponentModel.Container();
+ System.Windows.Forms.ListViewItem listViewItem1 = new System.Windows.Forms.ListViewItem("");
+ this.lstWatch = new Mesen.GUI.Controls.WatchListView();
+ this.colName = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
+ this.colValue = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
+ this.contextMenuWatch = new System.Windows.Forms.ContextMenuStrip(this.components);
+ this.mnuRemoveWatch = new System.Windows.Forms.ToolStripMenuItem();
+ this.toolStripMenuItem1 = new System.Windows.Forms.ToolStripSeparator();
+ this.mnuEditInMemoryViewer = new System.Windows.Forms.ToolStripMenuItem();
+ this.mnuViewInDisassembly = new System.Windows.Forms.ToolStripMenuItem();
+ this.toolStripMenuItem4 = new System.Windows.Forms.ToolStripSeparator();
+ this.mnuMoveUp = new System.Windows.Forms.ToolStripMenuItem();
+ this.mnuMoveDown = new System.Windows.Forms.ToolStripMenuItem();
+ this.toolStripMenuItem2 = new System.Windows.Forms.ToolStripSeparator();
+ this.mnuDecimalDisplay = new System.Windows.Forms.ToolStripMenuItem();
+ this.mnuHexDisplay = new System.Windows.Forms.ToolStripMenuItem();
+ this.mnuBinaryDisplay = new System.Windows.Forms.ToolStripMenuItem();
+ this.toolStripMenuItem5 = new System.Windows.Forms.ToolStripSeparator();
+ this.mnuRowDisplayFormat = new System.Windows.Forms.ToolStripMenuItem();
+ this.mnuRowBinary = new System.Windows.Forms.ToolStripMenuItem();
+ this.toolStripMenuItem8 = new System.Windows.Forms.ToolStripSeparator();
+ this.mnuRowHex1 = new System.Windows.Forms.ToolStripMenuItem();
+ this.mnuRowHex2 = new System.Windows.Forms.ToolStripMenuItem();
+ this.mnuRowHex3 = new System.Windows.Forms.ToolStripMenuItem();
+ this.toolStripMenuItem6 = new System.Windows.Forms.ToolStripSeparator();
+ this.mnuRowSigned1 = new System.Windows.Forms.ToolStripMenuItem();
+ this.mnuRowSigned2 = new System.Windows.Forms.ToolStripMenuItem();
+ this.mnuRowSigned3 = new System.Windows.Forms.ToolStripMenuItem();
+ this.toolStripMenuItem7 = new System.Windows.Forms.ToolStripSeparator();
+ this.mnuRowUnsigned = new System.Windows.Forms.ToolStripMenuItem();
+ this.toolStripMenuItem9 = new System.Windows.Forms.ToolStripSeparator();
+ this.mnuRowClearFormat = new System.Windows.Forms.ToolStripMenuItem();
+ this.toolStripMenuItem3 = new System.Windows.Forms.ToolStripSeparator();
+ this.mnuImport = new System.Windows.Forms.ToolStripMenuItem();
+ this.mnuExport = new System.Windows.Forms.ToolStripMenuItem();
+ this.txtEdit = new System.Windows.Forms.TextBox();
+ this.contextMenuWatch.SuspendLayout();
+ this.SuspendLayout();
+ //
+ // lstWatch
+ //
+ this.lstWatch.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
+ this.colName,
+ this.colValue});
+ this.lstWatch.ContextMenuStrip = this.contextMenuWatch;
+ this.lstWatch.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.lstWatch.FullRowSelect = true;
+ this.lstWatch.GridLines = true;
+ this.lstWatch.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.Nonclickable;
+ this.lstWatch.Items.AddRange(new System.Windows.Forms.ListViewItem[] {
+ listViewItem1});
+ this.lstWatch.Location = new System.Drawing.Point(0, 0);
+ this.lstWatch.Name = "lstWatch";
+ this.lstWatch.Size = new System.Drawing.Size(378, 112);
+ this.lstWatch.TabIndex = 6;
+ this.lstWatch.UseCompatibleStateImageBehavior = false;
+ this.lstWatch.View = System.Windows.Forms.View.Details;
+ this.lstWatch.OnMoveUpDown += new Mesen.GUI.Controls.WatchListView.MoveUpDownHandler(this.lstWatch_OnMoveUpDown);
+ this.lstWatch.SelectedIndexChanged += new System.EventHandler(this.lstWatch_SelectedIndexChanged);
+ this.lstWatch.Click += new System.EventHandler(this.lstWatch_Click);
+ this.lstWatch.DoubleClick += new System.EventHandler(this.lstWatch_DoubleClick);
+ this.lstWatch.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.lstWatch_KeyPress);
+ //
+ // colName
+ //
+ this.colName.Text = "Name";
+ this.colName.Width = 180;
+ //
+ // colValue
+ //
+ this.colValue.Text = "Value";
+ this.colValue.Width = 110;
+ //
+ // contextMenuWatch
+ //
+ this.contextMenuWatch.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
+ this.mnuRemoveWatch,
+ this.toolStripMenuItem1,
+ this.mnuEditInMemoryViewer,
+ this.mnuViewInDisassembly,
+ this.toolStripMenuItem4,
+ this.mnuMoveUp,
+ this.mnuMoveDown,
+ this.toolStripMenuItem2,
+ this.mnuRowDisplayFormat,
+ this.toolStripMenuItem3,
+ this.mnuDecimalDisplay,
+ this.mnuHexDisplay,
+ this.mnuBinaryDisplay,
+ this.toolStripMenuItem5,
+ this.mnuImport,
+ this.mnuExport});
+ this.contextMenuWatch.Name = "contextMenuWatch";
+ this.contextMenuWatch.Size = new System.Drawing.Size(194, 298);
+ this.contextMenuWatch.Opening += new System.ComponentModel.CancelEventHandler(this.contextMenuWatch_Opening);
+ //
+ // mnuRemoveWatch
+ //
+ this.mnuRemoveWatch.Image = global::Mesen.GUI.Properties.Resources.Close;
+ this.mnuRemoveWatch.Name = "mnuRemoveWatch";
+ this.mnuRemoveWatch.Size = new System.Drawing.Size(193, 22);
+ this.mnuRemoveWatch.Text = "Remove";
+ this.mnuRemoveWatch.Click += new System.EventHandler(this.mnuRemoveWatch_Click);
+ //
+ // toolStripMenuItem1
+ //
+ this.toolStripMenuItem1.Name = "toolStripMenuItem1";
+ this.toolStripMenuItem1.Size = new System.Drawing.Size(190, 6);
+ //
+ // mnuEditInMemoryViewer
+ //
+ this.mnuEditInMemoryViewer.Image = global::Mesen.GUI.Properties.Resources.CheatCode;
+ this.mnuEditInMemoryViewer.Name = "mnuEditInMemoryViewer";
+ this.mnuEditInMemoryViewer.Size = new System.Drawing.Size(193, 22);
+ this.mnuEditInMemoryViewer.Text = "Edit in Memory Viewer";
+ this.mnuEditInMemoryViewer.Click += new System.EventHandler(this.mnuEditInMemoryViewer_Click);
+ //
+ // mnuViewInDisassembly
+ //
+ this.mnuViewInDisassembly.Image = global::Mesen.GUI.Properties.Resources.Debugger;
+ this.mnuViewInDisassembly.Name = "mnuViewInDisassembly";
+ this.mnuViewInDisassembly.Size = new System.Drawing.Size(193, 22);
+ this.mnuViewInDisassembly.Text = "View in disassembly";
+ this.mnuViewInDisassembly.Click += new System.EventHandler(this.mnuViewInDisassembly_Click);
+ //
+ // toolStripMenuItem4
+ //
+ this.toolStripMenuItem4.Name = "toolStripMenuItem4";
+ this.toolStripMenuItem4.Size = new System.Drawing.Size(190, 6);
+ //
+ // mnuMoveUp
+ //
+ this.mnuMoveUp.Image = global::Mesen.GUI.Properties.Resources.MoveUp;
+ this.mnuMoveUp.Name = "mnuMoveUp";
+ this.mnuMoveUp.Size = new System.Drawing.Size(193, 22);
+ this.mnuMoveUp.Text = "Move up";
+ this.mnuMoveUp.Click += new System.EventHandler(this.mnuMoveUp_Click);
+ //
+ // mnuMoveDown
+ //
+ this.mnuMoveDown.Image = global::Mesen.GUI.Properties.Resources.MoveDown;
+ this.mnuMoveDown.Name = "mnuMoveDown";
+ this.mnuMoveDown.Size = new System.Drawing.Size(193, 22);
+ this.mnuMoveDown.Text = "Move down";
+ this.mnuMoveDown.Click += new System.EventHandler(this.mnuMoveDown_Click);
+ //
+ // toolStripMenuItem2
+ //
+ this.toolStripMenuItem2.Name = "toolStripMenuItem2";
+ this.toolStripMenuItem2.Size = new System.Drawing.Size(190, 6);
+ //
+ // mnuDecimalDisplay
+ //
+ this.mnuDecimalDisplay.Name = "mnuDecimalDisplay";
+ this.mnuDecimalDisplay.Size = new System.Drawing.Size(193, 22);
+ this.mnuDecimalDisplay.Text = "Decimal Display";
+ this.mnuDecimalDisplay.Click += new System.EventHandler(this.mnuDecimalDisplay_Click);
+ //
+ // mnuHexDisplay
+ //
+ this.mnuHexDisplay.Checked = true;
+ this.mnuHexDisplay.CheckState = System.Windows.Forms.CheckState.Checked;
+ this.mnuHexDisplay.Name = "mnuHexDisplay";
+ this.mnuHexDisplay.Size = new System.Drawing.Size(193, 22);
+ this.mnuHexDisplay.Text = "Hexadecimal Display";
+ this.mnuHexDisplay.Click += new System.EventHandler(this.mnuHexDisplay_Click);
+ //
+ // mnuBinaryDisplay
+ //
+ this.mnuBinaryDisplay.Name = "mnuBinaryDisplay";
+ this.mnuBinaryDisplay.Size = new System.Drawing.Size(193, 22);
+ this.mnuBinaryDisplay.Text = "Binary Display";
+ this.mnuBinaryDisplay.Click += new System.EventHandler(this.mnuBinaryDisplay_Click);
+ //
+ // toolStripMenuItem5
+ //
+ this.toolStripMenuItem5.Name = "toolStripMenuItem5";
+ this.toolStripMenuItem5.Size = new System.Drawing.Size(190, 6);
+ //
+ // mnuRowDisplayFormat
+ //
+ this.mnuRowDisplayFormat.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
+ this.mnuRowBinary,
+ this.toolStripMenuItem8,
+ this.mnuRowHex1,
+ this.mnuRowHex2,
+ this.mnuRowHex3,
+ this.toolStripMenuItem6,
+ this.mnuRowSigned1,
+ this.mnuRowSigned2,
+ this.mnuRowSigned3,
+ this.toolStripMenuItem7,
+ this.mnuRowUnsigned,
+ this.toolStripMenuItem9,
+ this.mnuRowClearFormat});
+ this.mnuRowDisplayFormat.Name = "mnuRowDisplayFormat";
+ this.mnuRowDisplayFormat.Size = new System.Drawing.Size(193, 22);
+ this.mnuRowDisplayFormat.Text = "Row Display Format";
+ //
+ // mnuRowBinary
+ //
+ this.mnuRowBinary.Name = "mnuRowBinary";
+ this.mnuRowBinary.Size = new System.Drawing.Size(197, 22);
+ this.mnuRowBinary.Text = "Binary";
+ this.mnuRowBinary.Click += new System.EventHandler(this.mnuRowBinary_Click);
+ //
+ // toolStripMenuItem8
+ //
+ this.toolStripMenuItem8.Name = "toolStripMenuItem8";
+ this.toolStripMenuItem8.Size = new System.Drawing.Size(194, 6);
+ //
+ // mnuRowHex1
+ //
+ this.mnuRowHex1.Name = "mnuRowHex1";
+ this.mnuRowHex1.Size = new System.Drawing.Size(197, 22);
+ this.mnuRowHex1.Text = "Hexadecimal (8-bit)";
+ this.mnuRowHex1.Click += new System.EventHandler(this.mnuRowHex1_Click);
+ //
+ // mnuRowHex2
+ //
+ this.mnuRowHex2.Name = "mnuRowHex2";
+ this.mnuRowHex2.Size = new System.Drawing.Size(197, 22);
+ this.mnuRowHex2.Text = "Hexadecimal (16-bit)";
+ this.mnuRowHex2.Click += new System.EventHandler(this.mnuRowHex2_Click);
+ //
+ // mnuRowHex3
+ //
+ this.mnuRowHex3.Name = "mnuRowHex3";
+ this.mnuRowHex3.Size = new System.Drawing.Size(197, 22);
+ this.mnuRowHex3.Text = "Hexadecimal (24-bit)";
+ this.mnuRowHex3.Click += new System.EventHandler(this.mnuRowHex3_Click);
+ //
+ // toolStripMenuItem6
+ //
+ this.toolStripMenuItem6.Name = "toolStripMenuItem6";
+ this.toolStripMenuItem6.Size = new System.Drawing.Size(194, 6);
+ //
+ // mnuRowSigned1
+ //
+ this.mnuRowSigned1.Name = "mnuRowSigned1";
+ this.mnuRowSigned1.Size = new System.Drawing.Size(197, 22);
+ this.mnuRowSigned1.Text = "Signed decimal (8-bit)";
+ this.mnuRowSigned1.Click += new System.EventHandler(this.mnuRowSigned1_Click);
+ //
+ // mnuRowSigned2
+ //
+ this.mnuRowSigned2.Name = "mnuRowSigned2";
+ this.mnuRowSigned2.Size = new System.Drawing.Size(197, 22);
+ this.mnuRowSigned2.Text = "Signed decimal (16-bit)";
+ this.mnuRowSigned2.Click += new System.EventHandler(this.mnuRowSigned2_Click);
+ //
+ // mnuRowSigned3
+ //
+ this.mnuRowSigned3.Name = "mnuRowSigned3";
+ this.mnuRowSigned3.Size = new System.Drawing.Size(197, 22);
+ this.mnuRowSigned3.Text = "Signed decimal (24-bit)";
+ this.mnuRowSigned3.Click += new System.EventHandler(this.mnuRowSigned3_Click);
+ //
+ // toolStripMenuItem7
+ //
+ this.toolStripMenuItem7.Name = "toolStripMenuItem7";
+ this.toolStripMenuItem7.Size = new System.Drawing.Size(194, 6);
+ //
+ // mnuRowUnsigned
+ //
+ this.mnuRowUnsigned.Name = "mnuRowUnsigned";
+ this.mnuRowUnsigned.Size = new System.Drawing.Size(197, 22);
+ this.mnuRowUnsigned.Text = "Unsigned decimal";
+ this.mnuRowUnsigned.Click += new System.EventHandler(this.mnuRowUnsigned_Click);
+ //
+ // toolStripMenuItem9
+ //
+ this.toolStripMenuItem9.Name = "toolStripMenuItem9";
+ this.toolStripMenuItem9.Size = new System.Drawing.Size(194, 6);
+ //
+ // mnuRowClearFormat
+ //
+ this.mnuRowClearFormat.Image = global::Mesen.GUI.Properties.Resources.Close;
+ this.mnuRowClearFormat.Name = "mnuRowClearFormat";
+ this.mnuRowClearFormat.Size = new System.Drawing.Size(197, 22);
+ this.mnuRowClearFormat.Text = "Clear";
+ this.mnuRowClearFormat.Click += new System.EventHandler(this.mnuRowClearFormat_Click);
+ //
+ // toolStripMenuItem3
+ //
+ this.toolStripMenuItem3.Name = "toolStripMenuItem3";
+ this.toolStripMenuItem3.Size = new System.Drawing.Size(190, 6);
+ //
+ // mnuImport
+ //
+ this.mnuImport.Image = global::Mesen.GUI.Properties.Resources.Import;
+ this.mnuImport.Name = "mnuImport";
+ this.mnuImport.Size = new System.Drawing.Size(193, 22);
+ this.mnuImport.Text = "Import...";
+ this.mnuImport.Click += new System.EventHandler(this.mnuImport_Click);
+ //
+ // mnuExport
+ //
+ this.mnuExport.Image = global::Mesen.GUI.Properties.Resources.Export;
+ this.mnuExport.Name = "mnuExport";
+ this.mnuExport.Size = new System.Drawing.Size(193, 22);
+ this.mnuExport.Text = "Export...";
+ this.mnuExport.Click += new System.EventHandler(this.mnuExport_Click);
+ //
+ // txtEdit
+ //
+ this.txtEdit.AcceptsReturn = true;
+ this.txtEdit.Location = new System.Drawing.Point(3, 24);
+ this.txtEdit.Name = "txtEdit";
+ this.txtEdit.Size = new System.Drawing.Size(177, 20);
+ this.txtEdit.TabIndex = 7;
+ this.txtEdit.Visible = false;
+ this.txtEdit.Leave += new System.EventHandler(this.txtEdit_Leave);
+ //
+ // ctrlWatch
+ //
+ this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.Controls.Add(this.txtEdit);
+ this.Controls.Add(this.lstWatch);
+ this.Name = "ctrlWatch";
+ this.Size = new System.Drawing.Size(378, 112);
+ this.contextMenuWatch.ResumeLayout(false);
+ this.ResumeLayout(false);
+ this.PerformLayout();
+
+ }
+
+ #endregion
+
+ private Mesen.GUI.Controls.WatchListView lstWatch;
+ private System.Windows.Forms.ColumnHeader colName;
+ private System.Windows.Forms.ColumnHeader colValue;
+ private System.Windows.Forms.ContextMenuStrip contextMenuWatch;
+ private System.Windows.Forms.ToolStripMenuItem mnuRemoveWatch;
+ private System.Windows.Forms.ToolStripMenuItem mnuHexDisplay;
+ private System.Windows.Forms.ToolStripSeparator toolStripMenuItem1;
+ private System.Windows.Forms.ToolStripMenuItem mnuEditInMemoryViewer;
+ private System.Windows.Forms.ToolStripMenuItem mnuViewInDisassembly;
+ private System.Windows.Forms.ToolStripSeparator toolStripMenuItem2;
+ private System.Windows.Forms.TextBox txtEdit;
+ private System.Windows.Forms.ToolStripSeparator toolStripMenuItem4;
+ private System.Windows.Forms.ToolStripMenuItem mnuMoveUp;
+ private System.Windows.Forms.ToolStripMenuItem mnuMoveDown;
+ private System.Windows.Forms.ToolStripSeparator toolStripMenuItem3;
+ private System.Windows.Forms.ToolStripMenuItem mnuImport;
+ private System.Windows.Forms.ToolStripMenuItem mnuExport;
+ private System.Windows.Forms.ToolStripMenuItem mnuDecimalDisplay;
+ private System.Windows.Forms.ToolStripMenuItem mnuBinaryDisplay;
+ private System.Windows.Forms.ToolStripSeparator toolStripMenuItem5;
+ private System.Windows.Forms.ToolStripMenuItem mnuRowDisplayFormat;
+ private System.Windows.Forms.ToolStripMenuItem mnuRowSigned1;
+ private System.Windows.Forms.ToolStripMenuItem mnuRowSigned2;
+ private System.Windows.Forms.ToolStripMenuItem mnuRowSigned3;
+ private System.Windows.Forms.ToolStripSeparator toolStripMenuItem6;
+ private System.Windows.Forms.ToolStripMenuItem mnuRowHex1;
+ private System.Windows.Forms.ToolStripMenuItem mnuRowHex2;
+ private System.Windows.Forms.ToolStripSeparator toolStripMenuItem7;
+ private System.Windows.Forms.ToolStripMenuItem mnuRowUnsigned;
+ private System.Windows.Forms.ToolStripMenuItem mnuRowBinary;
+ private System.Windows.Forms.ToolStripSeparator toolStripMenuItem8;
+ private System.Windows.Forms.ToolStripMenuItem mnuRowHex3;
+ private System.Windows.Forms.ToolStripSeparator toolStripMenuItem9;
+ private System.Windows.Forms.ToolStripMenuItem mnuRowClearFormat;
+ }
+}
diff --git a/UI/Debugger/Controls/ctrlWatch.resx b/UI/Debugger/Controls/ctrlWatch.resx
new file mode 100644
index 0000000..865fe76
--- /dev/null
+++ b/UI/Debugger/Controls/ctrlWatch.resx
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ 17, 17
+
+
\ No newline at end of file
diff --git a/UI/Debugger/WatchManager.cs b/UI/Debugger/WatchManager.cs
new file mode 100644
index 0000000..a8af40c
--- /dev/null
+++ b/UI/Debugger/WatchManager.cs
@@ -0,0 +1,226 @@
+using Mesen.GUI.Config;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+
+namespace Mesen.GUI.Debugger
+{
+ class WatchManager
+ {
+ public static event EventHandler WatchChanged;
+ private static List _watchEntries = new List();
+ private static Regex _arrayWatchRegex = new Regex(@"\[((\$[0-9A-Fa-f]+)|(\d+)|([@_a-zA-Z0-9]+))\s*,\s*(\d+)\]", RegexOptions.Compiled);
+ public static Regex FormatSuffixRegex = new Regex(@"^(.*),\s*([B|H|S|U])([\d]){0,1}$", RegexOptions.Compiled);
+
+ public static List WatchEntries
+ {
+ get { return _watchEntries; }
+ set
+ {
+ _watchEntries = new List(value);
+ WatchChanged?.Invoke(null, EventArgs.Empty);
+ }
+ }
+
+ public static List GetWatchContent(List previousValues)
+ {
+ WatchFormatStyle defaultStyle = ConfigManager.Config.Debug.WatchFormat;
+ int defaultByteLength = 1;
+ if(defaultStyle == WatchFormatStyle.Signed) {
+ defaultByteLength = 4;
+ }
+
+ var list = new List();
+ for(int i = 0; i < _watchEntries.Count; i++) {
+ string expression = _watchEntries[i].Trim();
+ string newValue = "";
+ EvalResultType resultType;
+
+ string exprToEvaluate = expression;
+ WatchFormatStyle style = defaultStyle;
+ int byteLength = defaultByteLength;
+ if(expression.StartsWith("{") && expression.EndsWith("}")) {
+ //Default to 2-byte values when using {} syntax
+ byteLength = 2;
+ }
+
+ ProcessFormatSpecifier(ref exprToEvaluate, ref style, ref byteLength);
+
+ bool forceHasChanged = false;
+ Match match = _arrayWatchRegex.Match(expression);
+ if(match.Success) {
+ //Watch expression matches the array display syntax (e.g: [$300,10] = display 10 bytes starting from $300)
+ newValue = ProcessArrayDisplaySyntax(style, ref forceHasChanged, match);
+ } else {
+ Int32 result = DebugApi.EvaluateExpression(exprToEvaluate, out resultType, true);
+ switch(resultType) {
+ case EvalResultType.Numeric: newValue = FormatValue(result, style, byteLength); break;
+ case EvalResultType.Boolean: newValue = result == 0 ? "false" : "true"; break;
+ case EvalResultType.Invalid: newValue = ""; forceHasChanged = true; break;
+ case EvalResultType.DivideBy0: newValue = ""; forceHasChanged = true; break;
+ case EvalResultType.OutOfScope: newValue = "