diff --git a/Docs/content/debugging/Debugger.md b/Docs/content/debugging/Debugger.md index 62e0f5c6..d07078ea 100644 --- a/Docs/content/debugging/Debugger.md +++ b/Docs/content/debugging/Debugger.md @@ -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 ##
- + Watch Window
-The watch window allows you to evaluate expression and see their value. Mesen supports complex expressions in C/C++ style syntax. +
+ + Watch List +
-**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** diff --git a/Docs/static/images/WatchList.png b/Docs/static/images/WatchList.png index 6dbf7a07..88cd02ea 100644 Binary files a/Docs/static/images/WatchList.png and b/Docs/static/images/WatchList.png differ diff --git a/Docs/static/images/WatchWindow.png b/Docs/static/images/WatchWindow.png new file mode 100644 index 00000000..4dd1ceac Binary files /dev/null and b/Docs/static/images/WatchWindow.png differ diff --git a/GUI.NET/Config/DebugInfo.cs b/GUI.NET/Config/DebugInfo.cs index 67f5e8b2..180b5fb4 100644 --- a/GUI.NET/Config/DebugInfo.cs +++ b/GUI.NET/Config/DebugInfo.cs @@ -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); diff --git a/GUI.NET/Debugger/Controls/ctrlWatch.Designer.cs b/GUI.NET/Debugger/Controls/ctrlWatch.Designer.cs index f12a719d..a959517d 100644 --- a/GUI.NET/Debugger/Controls/ctrlWatch.Designer.cs +++ b/GUI.NET/Debugger/Controls/ctrlWatch.Designer.cs @@ -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; } } diff --git a/GUI.NET/Debugger/Controls/ctrlWatch.cs b/GUI.NET/Debugger/Controls/ctrlWatch.cs index 2d3dced4..9b455b9d 100644 --- a/GUI.NET/Debugger/Controls/ctrlWatch.cs +++ b/GUI.NET/Debugger/Controls/ctrlWatch.cs @@ -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 watchContent = WatchManager.GetWatchContent(mnuHexDisplay.Checked, _previousValues); + List 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 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/GUI.NET/Debugger/WatchManager.cs b/GUI.NET/Debugger/WatchManager.cs index 37b01a71..449c55bf 100644 --- a/GUI.NET/Debugger/WatchManager.cs +++ b/GUI.NET/Debugger/WatchManager.cs @@ -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 _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 { @@ -24,28 +26,39 @@ namespace Mesen.GUI.Debugger } } - public static List GetWatchContent(bool useHex, List previousValues) + public static List GetWatchContent(List previousValues) { + WatchFormatStyle defaultStyle = ConfigManager.Config.DebugInfo.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(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 = ""; forceHasChanged = true; break; case EvalResultType.DivideBy0: newValue = ""; 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 values = new List(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 + } } diff --git a/GUI.NET/Debugger/frmDebugger.cs b/GUI.NET/Debugger/frmDebugger.cs index e66d1c1a..76b5a94a 100644 --- a/GUI.NET/Debugger/frmDebugger.cs +++ b/GUI.NET/Debugger/frmDebugger.cs @@ -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; diff --git a/GUI.NET/Debugger/frmWatchWindow.Designer.cs b/GUI.NET/Debugger/frmWatchWindow.Designer.cs index 78159151..0bd59cc5 100644 --- a/GUI.NET/Debugger/frmWatchWindow.Designer.cs +++ b/GUI.NET/Debugger/frmWatchWindow.Designer.cs @@ -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; } } \ No newline at end of file diff --git a/GUI.NET/Debugger/frmWatchWindow.cs b/GUI.NET/Debugger/frmWatchWindow.cs index 7d7c62a4..2a4a5a15 100644 --- a/GUI.NET/Debugger/frmWatchWindow.cs +++ b/GUI.NET/Debugger/frmWatchWindow.cs @@ -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()); } }