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 = "