Debugger: Watch - Added format specifiers

+ Added help icon to watch window
This commit is contained in:
Sour 2019-01-31 19:45:25 -05:00
parent b6e7ce4a9b
commit fac849315f
10 changed files with 449 additions and 54 deletions

View file

@ -105,24 +105,33 @@ While execution is paused, most fields are editable. Altering the value of any f
This section lets you force certain buttons to be held down on the NES' controller. This is often useful when trying to debug input-related code.
Clicking on a button on the mini NES controllers will toggle its state - green buttons are currently being held down.
## Watch Window ##
## Watch List/Window ##
<div class="imgBox"><div>
<img src="/images/WatchList.png" />
<img src="/images/WatchWindow.png" />
<span>Watch Window</span>
</div></div>
The watch window allows you to evaluate expression and see their value. Mesen supports complex expressions in C/C++ style syntax.
<div class="imgBox" style="margin-top: 10px"><div>
<img src="/images/WatchList.png" />
<span>Watch List</span>
</div></div>
**To add a new watch expression**, click on the last empty line in the list and start typing.
**To edit a watch expression**, double-click on it and start typing.
**To switch between hex and decimal**, right-click in the watch and toggle the **Hexadecimal Display** option.
The watch window and watch list allow you to evaluate expression and see their value. The `Watch Window` is a standalone window that can be resized and moved independently from everything else, whereas the `Watch List` is a part of the main debugger window for quick access to watch expressions.
**To add a new watch expression**, click on the last empty line in the list to enter edit mode.
**To edit a watch expression**, double-click on it to enter edit mode.
You can use the right-click context menu to delete or move entries, as well as select formatting options.
An import and export feature is also available to save/load watch expressions from a plain text file.
### Syntax ###
The used syntax is identical to C/C++ syntax (e.g && for and, || for or, etc.) and have the same operator precedence as C/C++.
The syntax is identical to C/C++ (e.g `&&` for AND, `||` for OR) and uses the same operator precedence as well.
**Note:** Use the $ prefix to denote hexadecimal values.
{{% notice tip %}}
Use the $ prefix to denote hexadecimal values (e.g: `$FF`) or the % prefix for binary values (e.g: `%1101`)
{{% /notice %}}
#### Special values ####
@ -153,6 +162,30 @@ The following "variables" can be used in both the watch window and contional bre
* **SpriteOverflow**: true if the PPU's "Sprite Overflow" flag is set
* **VerticalBlank**: true if the PPU's "Vertical Blank" flag is set
#### Formatting ####
It is possible to customize the format of each entry by adding a suffix to the expression.
Suffixes contain a single letter and are optionally followed by a number indicating the number of bytes expected in the return value (up to 4).
The available suffixes are:
* `S` - Signed decimal value
* `U` - Unsigned decimal value
* `H` - Hexadecimal
* `B` - Binary
For example, suffixing an expression with:
* `, H2` will display the result as a 2-byte hexadecimal value (e.g: `26, H2` will display as `$001A`)
* `, B` will display the result as a binary value (e.g: `141,B` will display as `%10001101`)
* `, S2` will display the result as a 16-bit signed decimal value (e.g: `$FE4F, S2` will display as `-433`)
* `, U` will display the result as an unsigned decimal value (e.g: `180, U` will display as `180`)
You can select the default format to use for entries without prefixes by right-clicking and choosing between:
* **Decimal Display** (equivalent to `S4` to all entries - displays the result as 32-bit signed decimal values)
* **Hexadecimal Display** (equivalent to `H1` to all entries)
* **Binary Display** (equivalent to `B1` to all entries)
#### Usage Examples ####
```
@ -162,6 +195,7 @@ scanline == 10 && (cycle >= 55 && cycle <= 100)
x == [$150] || y == [10]
[[$15] + y] //Reads the value at address $15, adds Y to it and reads the value at the resulting address.
{$FFFA} //Returns the NMI handler's address.
[$14] | ([$15] << 8), H2 //Display the value of the 2-byte variable stored at $14 in hexadecimal format.
```
**Using labels**

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -148,7 +148,7 @@ namespace Mesen.GUI.Config
public bool AlwaysScrollToCenter = false;
public bool SplitView = false;
public bool VerticalLayout = false;
public bool HexDisplay = true;
public WatchFormatStyle WatchFormat = WatchFormatStyle.Hex;
public bool ShowBreakpointLabels = true;
public Size EventViewerSize = new Size(0, 0);

View file

@ -42,11 +42,28 @@
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.txtEdit = new System.Windows.Forms.TextBox();
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();
//
@ -95,12 +112,16 @@
this.mnuMoveUp,
this.mnuMoveDown,
this.toolStripMenuItem2,
this.mnuHexDisplay,
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, 226);
this.contextMenuWatch.Size = new System.Drawing.Size(194, 298);
this.contextMenuWatch.Opening += new System.ComponentModel.CancelEventHandler(this.contextMenuWatch_Opening);
//
// mnuRemoveWatch
@ -158,25 +179,137 @@
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.CheckOnClick = 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);
//
// txtEdit
// mnuBinaryDisplay
//
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);
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
//
@ -199,6 +332,16 @@
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);
@ -232,5 +375,22 @@
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;
}
}

View file

@ -38,7 +38,6 @@ namespace Mesen.GUI.Debugger
{
base.OnLoad(e);
if(!IsDesignMode) {
this.mnuHexDisplay.Checked = ConfigManager.Config.DebugInfo.HexDisplay;
WatchManager.WatchChanged += WatchManager_WatchChanged;
mnuRemoveWatch.InitShortcut(this, nameof(DebuggerShortcutsConfig.WatchList_Delete));
mnuEditInMemoryViewer.InitShortcut(this, nameof(DebuggerShortcutsConfig.CodeWindow_EditInMemoryViewer));
@ -48,6 +47,16 @@ namespace Mesen.GUI.Debugger
}
}
public string GetTooltipText()
{
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) {
@ -90,13 +99,12 @@ namespace Mesen.GUI.Debugger
public void UpdateWatch(bool autoResizeColumns = true)
{
List<WatchValueInfo> watchContent = WatchManager.GetWatchContent(mnuHexDisplay.Checked, _previousValues);
List<WatchValueInfo> watchContent = WatchManager.GetWatchContent(_previousValues);
_previousValues = watchContent;
int currentSelection = lstWatch.FocusedItem?.Selected == true ? (lstWatch.FocusedItem?.Index ?? -1) : -1;
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();
@ -111,6 +119,9 @@ namespace Mesen.GUI.Debugger
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++) {
@ -144,19 +155,8 @@ namespace Mesen.GUI.Debugger
}
lstWatch.EndUpdate();
}
if(currentSelection >= 0 && lstWatch.Items.Count > currentSelection) {
SetSelectedItem(currentSelection);
}
}
private void mnuHexDisplay_Click(object sender, EventArgs e)
{
ConfigManager.Config.DebugInfo.HexDisplay = this.mnuHexDisplay.Checked;
ConfigManager.ApplyChanges();
UpdateWatch();
}
private void lstWatch_SelectedIndexChanged(object sender, EventArgs e)
{
mnuRemoveWatch.Enabled = lstWatch.SelectedItems.Count >= 1;
@ -165,6 +165,11 @@ namespace Mesen.GUI.Debugger
private void UpdateActions()
{
mnuHexDisplay.Checked = ConfigManager.Config.DebugInfo.WatchFormat == WatchFormatStyle.Hex;
mnuDecimalDisplay.Checked = ConfigManager.Config.DebugInfo.WatchFormat == WatchFormatStyle.Signed;
mnuBinaryDisplay.Checked = ConfigManager.Config.DebugInfo.WatchFormat == WatchFormatStyle.Binary;
mnuRowDisplayFormat.Enabled = lstWatch.SelectedItems.Count > 0;
mnuEditInMemoryViewer.Enabled = false;
mnuViewInDisassembly.Enabled = false;
mnuMoveUp.Enabled = false;
@ -396,5 +401,107 @@ namespace Mesen.GUI.Debugger
}
}
}
private void mnuHexDisplay_Click(object sender, EventArgs e)
{
ConfigManager.Config.DebugInfo.WatchFormat = WatchFormatStyle.Hex;
ConfigManager.ApplyChanges();
UpdateWatch();
}
private void mnuDecimalDisplay_Click(object sender, EventArgs e)
{
ConfigManager.Config.DebugInfo.WatchFormat = WatchFormatStyle.Signed;
ConfigManager.ApplyChanges();
UpdateWatch();
}
private void mnuBinaryDisplay_Click(object sender, EventArgs e)
{
ConfigManager.Config.DebugInfo.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<string> 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("");
}
}
}

View file

@ -1,4 +1,5 @@
using System;
using Mesen.GUI.Config;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@ -13,6 +14,7 @@ namespace Mesen.GUI.Debugger
public static event EventHandler WatchChanged;
private static List<string> _watchEntries = new List<string>();
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<string> WatchEntries
{
@ -24,28 +26,39 @@ namespace Mesen.GUI.Debugger
}
}
public static List<WatchValueInfo> GetWatchContent(bool useHex, List<WatchValueInfo> previousValues)
public static List<WatchValueInfo> GetWatchContent(List<WatchValueInfo> previousValues)
{
WatchFormatStyle defaultStyle = ConfigManager.Config.DebugInfo.WatchFormat;
int defaultByteLength = 1;
if(defaultStyle == WatchFormatStyle.Signed) {
defaultByteLength = 4;
}
var list = new List<WatchValueInfo>();
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(useHex, ref forceHasChanged, match);
newValue = ProcessArrayDisplaySyntax(style, ref forceHasChanged, match);
} else {
Int32 result = InteropEmu.DebugEvaluateExpression(expression, out resultType, true);
Int32 result = InteropEmu.DebugEvaluateExpression(exprToEvaluate, out resultType, true);
switch(resultType) {
case EvalResultType.Numeric:
//When using {$00} syntax to show the value of a word, always display 4 hex characters.
bool displayAsWord = expression.StartsWith("{") && expression.EndsWith("}");
newValue = useHex ? ("$" + result.ToString(displayAsWord ? "X4" : "X2")) : result.ToString();
break;
case EvalResultType.Numeric: newValue = FormatValue(result, style, byteLength); break;
case EvalResultType.Boolean: newValue = result == 0 ? "false" : "true"; break;
case EvalResultType.Invalid: newValue = "<invalid expression>"; forceHasChanged = true; break;
case EvalResultType.DivideBy0: newValue = "<division by zero>"; forceHasChanged = true; break;
@ -59,7 +72,67 @@ namespace Mesen.GUI.Debugger
return list;
}
private static string ProcessArrayDisplaySyntax(bool useHex, ref bool forceHasChanged, Match match)
private static string FormatValue(int value, WatchFormatStyle style, int byteLength)
{
switch(style) {
case WatchFormatStyle.Unsigned: return ((UInt32)value).ToString();
case WatchFormatStyle.Hex: return "$" + value.ToString("X" + byteLength * 2);
case WatchFormatStyle.Binary:
string binary = Convert.ToString(value, 2).PadLeft(byteLength * 8, '0');
for(int i = binary.Length - 4; i > 0; i -= 4) {
binary = binary.Insert(i, ".");
}
return "%" + binary;
case WatchFormatStyle.Signed:
int bitCount = byteLength * 8;
if(bitCount < 32) {
if(((value >> (bitCount - 1)) & 0x01) == 0x01) {
//Negative value
return (value | (-(1 << bitCount))).ToString();
} else {
//Position value
return value.ToString();
}
} else {
return value.ToString();
}
default: throw new Exception("Unsupported format");
}
}
public static bool IsArraySyntax(string expression)
{
return _arrayWatchRegex.IsMatch(expression);
}
private static bool ProcessFormatSpecifier(ref string expression, ref WatchFormatStyle style, ref int byteLength)
{
Match match = WatchManager.FormatSuffixRegex.Match(expression);
if(!match.Success) {
return false;
}
string format = match.Groups[2].Value.ToUpperInvariant();
switch(format[0]) {
case 'S': style = WatchFormatStyle.Signed; break;
case 'H': style = WatchFormatStyle.Hex; break;
case 'B': style = WatchFormatStyle.Binary; break;
case 'U': style = WatchFormatStyle.Unsigned; break;
default: throw new Exception("Invalid format");
}
if(match.Groups[3].Success) {
byteLength = Math.Max(Math.Min(Int32.Parse(match.Groups[3].Value), 4), 1);
} else {
byteLength = 1;
}
expression = match.Groups[1].Value;
return true;
}
private static string ProcessArrayDisplaySyntax(WatchFormatStyle style, ref bool forceHasChanged, Match match)
{
string newValue;
int address;
@ -81,7 +154,7 @@ namespace Mesen.GUI.Debugger
List<string> values = new List<string>(elemCount);
for(int j = address, end = address + elemCount; j < end; j++) {
int memValue = InteropEmu.DebugGetMemoryValue(DebugMemoryType.CpuMemory, (uint)j);
values.Add(useHex ? memValue.ToString("X2") : memValue.ToString());
values.Add(FormatValue(memValue, style, 1));
}
newValue = string.Join(" ", values);
} else {
@ -141,4 +214,12 @@ namespace Mesen.GUI.Debugger
public string Value { get; set; }
public bool HasChanged { get; set; }
}
public enum WatchFormatStyle
{
Unsigned,
Signed,
Hex,
Binary
}
}

View file

@ -167,12 +167,7 @@ namespace Mesen.GUI.Debugger
LastCodeWindow = ctrlDebuggerCode;
this.toolTip.SetToolTip(this.picWatchHelp,
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"
);
this.toolTip.SetToolTip(this.picWatchHelp, ctrlWatch.GetTooltipText());
_notifListener = new InteropEmu.NotificationListener(ConfigManager.Config.DebugInfo.DebugConsoleId);
_notifListener.OnNotification += _notifListener_OnNotification;

View file

@ -28,6 +28,8 @@
private void InitializeComponent()
{
this.ctrlWatch = new Mesen.GUI.Debugger.ctrlWatch();
this.picWatchHelp = new System.Windows.Forms.PictureBox();
((System.ComponentModel.ISupportInitialize)(this.picWatchHelp)).BeginInit();
this.SuspendLayout();
//
// ctrlWatch
@ -38,15 +40,28 @@
this.ctrlWatch.Size = new System.Drawing.Size(317, 322);
this.ctrlWatch.TabIndex = 0;
//
// picWatchHelp
//
this.picWatchHelp.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.picWatchHelp.Image = global::Mesen.GUI.Properties.Resources.Help;
this.picWatchHelp.Location = new System.Drawing.Point(297, 4);
this.picWatchHelp.Name = "picWatchHelp";
this.picWatchHelp.Size = new System.Drawing.Size(16, 16);
this.picWatchHelp.SizeMode = System.Windows.Forms.PictureBoxSizeMode.CenterImage;
this.picWatchHelp.TabIndex = 2;
this.picWatchHelp.TabStop = false;
//
// frmWatchWindow
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(317, 322);
this.Controls.Add(this.picWatchHelp);
this.Controls.Add(this.ctrlWatch);
this.MinimumSize = new System.Drawing.Size(248, 137);
this.Name = "frmWatchWindow";
this.Text = "Watch Window";
((System.ComponentModel.ISupportInitialize)(this.picWatchHelp)).EndInit();
this.ResumeLayout(false);
}
@ -54,5 +69,6 @@
#endregion
private ctrlWatch ctrlWatch;
private System.Windows.Forms.PictureBox picWatchHelp;
}
}

View file

@ -26,6 +26,8 @@ namespace Mesen.GUI.Debugger
this.Size = ConfigManager.Config.DebugInfo.WatchWindowSize;
this.Location = ConfigManager.Config.DebugInfo.WatchWindowLocation;
}
this.toolTip.SetToolTip(picWatchHelp, ctrlWatch.GetTooltipText());
}
}