2016-01-10 19:56:40 -05:00
using System ;
using System.Collections.Generic ;
using System.ComponentModel ;
using System.Data ;
using System.Drawing ;
2016-09-03 21:52:59 -04:00
using System.IO ;
2016-01-10 19:56:40 -05:00
using System.Linq ;
using System.Text ;
using System.Threading.Tasks ;
using System.Windows.Forms ;
2016-09-03 21:52:59 -04:00
using Mesen.GUI.Config ;
2017-03-04 15:18:00 -05:00
using Mesen.GUI.Controls ;
2016-01-10 19:56:40 -05:00
using Mesen.GUI.Forms ;
namespace Mesen.GUI.Debugger
{
public partial class frmTraceLogger : BaseForm
{
2017-03-04 15:18:00 -05:00
private int _lineCount ;
2016-09-03 21:52:59 -04:00
private bool _loggingEnabled = false ;
2017-03-04 15:18:00 -05:00
private string _lastFilename ;
2017-03-16 21:34:28 -04:00
private EntityBinder _entityBinder = new EntityBinder ( ) ;
2017-08-06 16:23:22 -04:00
private int _previousCycleCount ;
private string _previousTrace ;
2017-08-06 17:53:18 -04:00
private volatile bool _refreshRunning ;
2018-05-26 01:14:37 -04:00
private bool _initialized ;
2016-09-03 21:52:59 -04:00
2016-01-10 19:56:40 -05:00
public frmTraceLogger ( )
{
InitializeComponent ( ) ;
2017-03-04 15:18:00 -05:00
DebugInfo debugInfo = ConfigManager . Config . DebugInfo ;
2017-08-06 16:23:22 -04:00
if ( ! debugInfo . TraceLoggerSize . IsEmpty ) {
2018-06-05 18:35:47 -04:00
this . StartPosition = FormStartPosition . Manual ;
2017-08-06 16:23:22 -04:00
this . Size = debugInfo . TraceLoggerSize ;
2018-06-05 18:35:47 -04:00
this . Location = debugInfo . TraceLoggerLocation ;
2017-08-06 16:23:22 -04:00
}
2018-03-03 11:14:57 -05:00
txtTraceLog . BaseFont = new Font ( debugInfo . TraceFontFamily , debugInfo . TraceFontSize , debugInfo . TraceFontStyle ) ;
2018-03-03 10:41:59 -05:00
txtTraceLog . TextZoom = debugInfo . TraceTextZoom ;
2017-03-04 15:18:00 -05:00
mnuAutoRefresh . Checked = debugInfo . TraceAutoRefresh ;
_lineCount = debugInfo . TraceLineCount ;
2017-03-16 21:34:28 -04:00
_entityBinder . Entity = debugInfo . TraceLoggerOptions ;
_entityBinder . AddBinding ( "ShowByteCode" , chkShowByteCode ) ;
_entityBinder . AddBinding ( "ShowCpuCycles" , chkShowCpuCycles ) ;
_entityBinder . AddBinding ( "ShowEffectiveAddresses" , chkShowEffectiveAddresses ) ;
2018-02-14 21:46:24 -05:00
_entityBinder . AddBinding ( "ShowMemoryValues" , chkShowMemoryValues ) ;
2017-03-16 21:34:28 -04:00
_entityBinder . AddBinding ( "ShowExtraInfo" , chkShowExtraInfo ) ;
_entityBinder . AddBinding ( "ShowPpuFrames" , chkShowFrameCount ) ;
_entityBinder . AddBinding ( "ShowPpuCycles" , chkShowPpuCycles ) ;
_entityBinder . AddBinding ( "ShowPpuScanline" , chkShowPpuScanline ) ;
_entityBinder . AddBinding ( "ShowRegisters" , chkShowRegisters ) ;
_entityBinder . AddBinding ( "IndentCode" , chkIndentCode ) ;
_entityBinder . AddBinding ( "UseLabels" , chkUseLabels ) ;
2018-05-26 01:14:37 -04:00
_entityBinder . AddBinding ( "ExtendZeroPage" , chkExtendZeroPage ) ;
_entityBinder . AddBinding ( "UseWindowsEol" , chkUseWindowsEol ) ;
2017-03-16 21:34:28 -04:00
_entityBinder . AddBinding ( "StatusFormat" , cboStatusFlagFormat ) ;
2018-05-26 01:14:37 -04:00
_entityBinder . AddBinding ( "OverrideFormat" , chkOverrideFormat ) ;
2017-03-16 21:34:28 -04:00
_entityBinder . UpdateUI ( ) ;
2017-03-04 15:18:00 -05:00
2017-11-15 19:30:29 -05:00
this . toolTip . SetToolTip ( this . picExpressionWarning , "Condition contains invalid syntax or symbols." ) ;
this . toolTip . SetToolTip ( this . picHelp , "When a condition is given, instructions will only be logged by the trace logger if the condition returns a value not equal to 0 or false." + Environment . NewLine + Environment . NewLine + frmBreakpoint . GetConditionTooltip ( false ) ) ;
2018-05-26 01:14:37 -04:00
this . toolTip . SetToolTip ( this . picFormatHelp ,
"You can customize the trace logger's output by enabling the 'Override' option and altering the format." + Environment . NewLine + Environment . NewLine +
"The following tags are available: " + Environment . NewLine +
"[ByteCode]: The byte code for the instruction (1 to 3 bytes)." + Environment . NewLine +
"[Disassembly]: The disassembly for the current instruction." + Environment . NewLine +
"[EffectiveAddress]: The effective address used for indirect addressing modes." + Environment . NewLine +
"[MemoryValue]: The value stored at the memory location referred to by the instruction." + Environment . NewLine +
"[PC]: Program Counter" + Environment . NewLine +
"[A]: A register" + Environment . NewLine +
"[X]: X register" + Environment . NewLine +
"[Y]: Y register" + Environment . NewLine +
"[SP]: Stack Pointer" + Environment . NewLine +
"[P]: Processor Flags" + Environment . NewLine +
"[Cycle]: The current PPU cycle." + Environment . NewLine +
"[Scanline]: The current PPU scanline." + Environment . NewLine +
"[FrameCount]: The current PPU frame." + Environment . NewLine +
"[CycleCount]: The current CPU cycle (32-bit signed value, resets to 0 at power on)" + Environment . NewLine + Environment . NewLine +
"You can also specify some options by using a comma. e.g:" + Environment . NewLine +
"[Cycle,3] will display the cycle and pad out the output to always be 3 characters wide." + Environment . NewLine +
"[Scanline,h] will display the scanline in hexadecimal." + Environment . NewLine +
"[Align,50]: Align is a special tag that is useful when trying to align some content. [Align,50] will make the next tag start on column 50."
) ;
2018-03-10 09:58:24 -05:00
2018-05-26 01:14:37 -04:00
this . _initialized = true ;
2018-03-10 09:58:24 -05:00
}
private void InitShortcuts ( )
{
mnuIncreaseFontSize . InitShortcut ( this , nameof ( DebuggerShortcutsConfig . IncreaseFontSize ) ) ;
mnuDecreaseFontSize . InitShortcut ( this , nameof ( DebuggerShortcutsConfig . DecreaseFontSize ) ) ;
mnuResetFontSize . InitShortcut ( this , nameof ( DebuggerShortcutsConfig . ResetFontSize ) ) ;
mnuRefresh . InitShortcut ( this , nameof ( DebuggerShortcutsConfig . Refresh ) ) ;
2017-11-15 19:30:29 -05:00
}
protected override void OnLoad ( EventArgs e )
{
base . OnLoad ( e ) ;
2017-03-04 15:18:00 -05:00
UpdateMenu ( ) ;
tmrUpdateLog . Start ( ) ;
2018-05-26 01:14:37 -04:00
RefreshLog ( true , true ) ;
2018-06-09 17:54:34 -04:00
InitShortcuts ( ) ;
2016-01-10 19:56:40 -05:00
}
2016-09-03 21:52:59 -04:00
protected override void OnFormClosing ( FormClosingEventArgs e )
{
2017-08-06 17:53:18 -04:00
tmrUpdateLog . Stop ( ) ;
while ( _refreshRunning ) {
System . Threading . Thread . Sleep ( 50 ) ;
}
2016-09-03 21:52:59 -04:00
base . OnFormClosing ( e ) ;
2017-03-04 15:18:00 -05:00
DebugInfo debugInfo = ConfigManager . Config . DebugInfo ;
debugInfo . TraceAutoRefresh = mnuAutoRefresh . Checked ;
debugInfo . TraceLineCount = _lineCount ;
2018-06-14 20:08:03 -04:00
debugInfo . TraceLoggerSize = this . WindowState ! = FormWindowState . Normal ? this . RestoreBounds . Size : this . Size ;
debugInfo . TraceLoggerLocation = this . WindowState ! = FormWindowState . Normal ? this . RestoreBounds . Location : this . Location ;
2018-03-03 10:41:59 -05:00
2018-03-03 13:11:45 -05:00
debugInfo . TraceFontFamily = txtTraceLog . BaseFont . FontFamily . Name ;
debugInfo . TraceFontSize = txtTraceLog . BaseFont . Size ;
debugInfo . TraceFontStyle = txtTraceLog . BaseFont . Style ;
2018-03-03 10:41:59 -05:00
debugInfo . TraceTextZoom = txtTraceLog . TextZoom ;
2017-03-16 21:34:28 -04:00
_entityBinder . UpdateObject ( ) ;
2017-03-04 15:18:00 -05:00
ConfigManager . ApplyChanges ( ) ;
2016-09-03 21:52:59 -04:00
if ( _loggingEnabled ) {
InteropEmu . DebugStopTraceLogger ( ) ;
}
}
2018-05-26 01:14:37 -04:00
protected void UpdateFormatOptions ( )
{
if ( ! chkOverrideFormat . Checked ) {
string format = "[PC,h] " ;
if ( chkShowByteCode . Checked ) {
format + = "[ByteCode,11h] " ;
}
format + = "[Disassembly]" ;
int alignValue = 40 ;
if ( chkShowEffectiveAddresses . Checked ) {
format + = "[EffectiveAddress]" ;
alignValue + = 8 ;
}
if ( chkShowMemoryValues . Checked ) {
format + = " [MemoryValue,h]" ;
alignValue + = 6 ;
}
format + = "[Align," + alignValue . ToString ( ) + "] " ;
if ( chkShowRegisters . Checked ) {
format + = "A:[A,h] X:[X,h] Y:[Y,h] " ;
switch ( cboStatusFlagFormat . GetEnumValue < StatusFlagFormat > ( ) ) {
case StatusFlagFormat . Hexadecimal : format + = "P:[P,h]" ; break ;
case StatusFlagFormat . CompactText : format + = "P:[P]" ; break ;
case StatusFlagFormat . Text : format + = "P:[P,8]" ; break ;
}
format + = " SP:[SP,h] " ;
}
if ( chkShowPpuCycles . Checked ) {
format + = "CYC:[Cycle,3] " ;
}
if ( chkShowPpuScanline . Checked ) {
format + = "SL:[Scanline,3] " ;
}
if ( chkShowFrameCount . Checked ) {
format + = "FC:[FrameCount] " ;
}
if ( chkShowCpuCycles . Checked ) {
format + = "CPU Cycle:[CycleCount]" ;
}
txtFormat . Text = format . Trim ( ) ;
}
txtFormat . ReadOnly = ! chkOverrideFormat . Checked ;
chkShowByteCode . Enabled = ! chkOverrideFormat . Checked ;
chkShowRegisters . Enabled = ! chkOverrideFormat . Checked ;
chkShowPpuCycles . Enabled = ! chkOverrideFormat . Checked ;
chkShowPpuScanline . Enabled = ! chkOverrideFormat . Checked ;
chkShowFrameCount . Enabled = ! chkOverrideFormat . Checked ;
chkShowCpuCycles . Enabled = ! chkOverrideFormat . Checked ;
chkShowEffectiveAddresses . Enabled = ! chkOverrideFormat . Checked ;
chkShowMemoryValues . Enabled = ! chkOverrideFormat . Checked ;
cboStatusFlagFormat . Enabled = ! chkOverrideFormat . Checked ;
if ( _initialized ) {
RefreshLog ( false , true ) ;
}
}
2017-03-04 15:18:00 -05:00
private void SetOptions ( )
2016-01-10 19:56:40 -05:00
{
2017-03-16 21:34:28 -04:00
_entityBinder . UpdateObject ( ) ;
2017-08-05 12:13:53 -04:00
TraceLoggerOptions options = ( TraceLoggerOptions ) _entityBinder . Entity ;
2018-05-26 01:14:37 -04:00
InteropTraceLoggerOptions interopOptions = new InteropTraceLoggerOptions ( ) ;
interopOptions . IndentCode = options . IndentCode ;
interopOptions . ShowExtraInfo = options . ShowExtraInfo ;
interopOptions . UseLabels = options . UseLabels ;
interopOptions . UseWindowsEol = options . UseWindowsEol ;
interopOptions . ExtendZeroPage = options . ExtendZeroPage ;
interopOptions . Condition = Encoding . UTF8 . GetBytes ( txtCondition . Text ) ;
Array . Resize ( ref interopOptions . Condition , 1000 ) ;
interopOptions . Format = Encoding . UTF8 . GetBytes ( txtFormat . Text . Replace ( "\t" , "\\t" ) ) ;
Array . Resize ( ref interopOptions . Format , 1000 ) ;
InteropEmu . DebugSetTraceOptions ( interopOptions ) ;
2017-03-04 15:18:00 -05:00
}
2016-09-03 21:52:59 -04:00
2017-03-04 15:18:00 -05:00
private void btnStartLogging_Click ( object sender , EventArgs e )
{
using ( SaveFileDialog sfd = new SaveFileDialog ( ) ) {
sfd . FileName = "Trace logs (*.txt)|*.txt" ;
sfd . FileName = "Trace - " + InteropEmu . GetRomInfo ( ) . GetRomName ( ) + ".txt" ;
sfd . InitialDirectory = ConfigManager . DebuggerFolder ;
if ( sfd . ShowDialog ( ) = = DialogResult . OK ) {
_lastFilename = sfd . FileName ;
SetOptions ( ) ;
InteropEmu . DebugStartTraceLogger ( sfd . FileName ) ;
btnStartLogging . Enabled = false ;
btnStopLogging . Enabled = true ;
btnOpenTrace . Enabled = false ;
2016-09-03 21:52:59 -04:00
2017-03-04 15:18:00 -05:00
_loggingEnabled = true ;
}
}
2016-01-10 19:56:40 -05:00
}
private void btnStopLogging_Click ( object sender , EventArgs e )
{
InteropEmu . DebugStopTraceLogger ( ) ;
2016-09-03 21:52:59 -04:00
btnStartLogging . Enabled = true ;
btnStopLogging . Enabled = false ;
2017-03-04 15:18:00 -05:00
btnOpenTrace . Enabled = true ;
2016-09-03 21:52:59 -04:00
}
private void btnOpenTrace_Click ( object sender , EventArgs e )
{
try {
2017-03-04 15:18:00 -05:00
System . Diagnostics . Process . Start ( _lastFilename ) ;
2016-09-03 21:52:59 -04:00
} catch { }
2016-01-10 19:56:40 -05:00
}
2017-03-04 15:18:00 -05:00
2017-08-06 16:23:22 -04:00
protected override bool ProcessCmdKey ( ref Message msg , Keys keyData )
2017-03-04 15:18:00 -05:00
{
2018-03-10 09:58:24 -05:00
if ( keyData = = ConfigManager . Config . DebugInfo . Shortcuts . Copy ) {
if ( _previousTrace ! = null & & ! this . txtCondition . Focused & & txtTraceLog . SelectionStart > = 0 ) {
string [ ] lines = _previousTrace . Split ( '\n' ) ;
StringBuilder sb = new StringBuilder ( ) ;
for ( int i = txtTraceLog . SelectionStart , end = txtTraceLog . SelectionStart + txtTraceLog . SelectionLength ; i < = end & & i < lines . Length ; i + + ) {
sb . AppendLine ( lines [ i ] ) ;
2017-08-06 16:23:22 -04:00
}
2018-03-10 09:58:24 -05:00
Clipboard . SetText ( sb . ToString ( ) ) ;
return true ;
}
2017-08-06 16:23:22 -04:00
}
return base . ProcessCmdKey ( ref msg , keyData ) ;
}
2017-08-06 17:53:18 -04:00
2018-05-26 01:14:37 -04:00
private void RefreshLog ( bool scrollToBottom , bool forceUpdate )
2017-08-06 16:23:22 -04:00
{
if ( _refreshRunning ) {
return ;
2017-03-04 15:18:00 -05:00
}
2017-08-06 16:23:22 -04:00
2017-08-14 23:44:01 -04:00
//Make sure labels are up to date
DebugWorkspaceManager . GetWorkspace ( ) ;
2017-08-06 16:23:22 -04:00
_refreshRunning = true ;
SetOptions ( ) ;
Task . Run ( ( ) = > {
//Update trace log in another thread for performance
DebugState state = new DebugState ( ) ;
InteropEmu . DebugGetState ( ref state ) ;
2018-05-26 01:14:37 -04:00
if ( _previousCycleCount ! = state . CPU . CycleCount | | forceUpdate ) {
2017-08-06 16:23:22 -04:00
string newTrace = InteropEmu . DebugGetExecutionTrace ( ( UInt32 ) _lineCount ) ;
_previousCycleCount = state . CPU . CycleCount ;
_previousTrace = newTrace ;
int index = 0 ;
string line = null ;
Func < bool > readLine = ( ) = > {
if ( index < newTrace . Length ) {
int endOfLineIndex = newTrace . IndexOf ( '\n' , index ) ;
line = newTrace . Substring ( index , endOfLineIndex - index ) ;
index = endOfLineIndex + 1 ;
return true ;
} else {
return false ;
}
} ;
List < int > programCounter = new List < int > ( 30000 ) ;
List < string > byteCode = new List < string > ( 30000 ) ;
List < string > lineContent = new List < string > ( 30000 ) ;
List < int > indent = new List < int > ( 30000 ) ;
2018-05-26 01:14:37 -04:00
bool showByteCode = false ;
2017-08-06 16:23:22 -04:00
while ( readLine ( ) ) {
2018-05-26 01:14:37 -04:00
string [ ] parts = line . Split ( ' \ x1 ' ) ;
programCounter . Add ( Int32 . Parse ( parts [ 0 ] , System . Globalization . NumberStyles . HexNumber ) ) ;
byteCode . Add ( parts [ 1 ] ) ;
string content = parts [ 2 ] ;
while ( true ) {
string str = content . TrimStart ( ) ;
if ( str . StartsWith ( parts [ 0 ] ) ) {
content = str . Substring ( 4 ) ;
} else if ( str . StartsWith ( parts [ 1 ] ) ) {
content = str . Substring ( 11 ) ;
showByteCode = true ;
} else if ( str . StartsWith ( parts [ 1 ] . Replace ( "$" , "" ) ) ) {
content = str . Substring ( 8 ) ;
showByteCode = true ;
} else {
break ;
}
}
lineContent . Add ( content ) ;
indent . Add ( 0 ) ;
2017-08-06 16:23:22 -04:00
}
this . BeginInvoke ( ( Action ) ( ( ) = > {
2018-05-26 01:14:37 -04:00
txtTraceLog . ShowContentNotes = showByteCode ;
txtTraceLog . ShowSingleContentLineNotes = showByteCode ;
2017-08-06 16:23:22 -04:00
txtTraceLog . LineIndentations = indent . ToArray ( ) ;
txtTraceLog . LineNumbers = programCounter . ToArray ( ) ;
txtTraceLog . TextLineNotes = byteCode . ToArray ( ) ;
2017-09-12 19:39:03 -04:00
txtTraceLog . TextLines = lineContent . ToArray ( ) ;
2017-08-06 16:23:22 -04:00
if ( scrollToBottom ) {
txtTraceLog . ScrollToLineIndex ( txtTraceLog . LineCount - 1 ) ;
}
} ) ) ;
}
_refreshRunning = false ;
} ) ;
2017-03-04 15:18:00 -05:00
}
private void UpdateMenu ( )
{
mnu30000Lines . Checked = _lineCount = = 30000 ;
2018-06-14 23:49:59 -04:00
mnu15000Lines . Checked = _lineCount = = 15000 ;
2017-03-04 15:18:00 -05:00
mnu10000Lines . Checked = _lineCount = = 10000 ;
2018-06-14 23:49:59 -04:00
mnu5000Lines . Checked = _lineCount = = 5000 ;
2017-03-04 15:18:00 -05:00
mnu1000Lines . Checked = _lineCount = = 1000 ;
mnu100Lines . Checked = _lineCount = = 100 ;
2017-08-06 16:23:22 -04:00
if ( _lineCount > = 1000 ) {
2017-03-04 15:18:00 -05:00
tmrUpdateLog . Interval = 250 ;
} else {
tmrUpdateLog . Interval = 150 ;
}
}
private void tmrUpdateLog_Tick ( object sender , EventArgs e )
{
2017-08-05 12:13:53 -04:00
if ( txtCondition . Text . Length > 0 ) {
EvalResultType resultType ;
InteropEmu . DebugEvaluateExpression ( txtCondition . Text , out resultType ) ;
picExpressionWarning . Visible = ( resultType = = EvalResultType . Invalid ) ;
} else {
picExpressionWarning . Visible = false ;
}
2017-03-04 15:18:00 -05:00
if ( mnuAutoRefresh . Checked ) {
2018-05-26 01:14:37 -04:00
RefreshLog ( false , false ) ;
2017-03-04 15:18:00 -05:00
}
}
2018-06-14 23:49:59 -04:00
private void SetLineCount ( int count )
2017-03-04 15:18:00 -05:00
{
2018-06-14 23:49:59 -04:00
_lineCount = count ;
2017-03-04 15:18:00 -05:00
UpdateMenu ( ) ;
2018-06-14 23:49:59 -04:00
RefreshLog ( false , true ) ;
}
private void mnu30000Lines_Click ( object sender , EventArgs e )
{
SetLineCount ( 30000 ) ;
}
private void mnu15000Lines_Click ( object sender , EventArgs e )
{
SetLineCount ( 15000 ) ;
2017-03-04 15:18:00 -05:00
}
private void mnu10000Lines_Click ( object sender , EventArgs e )
{
2018-06-14 23:49:59 -04:00
SetLineCount ( 10000 ) ;
}
private void mnu5000Lines_Click ( object sender , EventArgs e )
{
SetLineCount ( 5000 ) ;
2017-03-04 15:18:00 -05:00
}
private void mnu1000Lines_Click ( object sender , EventArgs e )
{
2018-06-14 23:49:59 -04:00
SetLineCount ( 1000 ) ;
2017-03-04 15:18:00 -05:00
}
private void mnu100Lines_Click ( object sender , EventArgs e )
{
2018-06-14 23:49:59 -04:00
SetLineCount ( 100 ) ;
2017-03-04 15:18:00 -05:00
}
private void mnuRefresh_Click ( object sender , EventArgs e )
{
2018-05-26 01:14:37 -04:00
RefreshLog ( false , true ) ;
2017-03-04 15:18:00 -05:00
}
2018-03-03 10:41:59 -05:00
private void mnuIncreaseFontSize_Click ( object sender , EventArgs e )
{
txtTraceLog . TextZoom + = 10 ;
}
private void mnuDecreaseFontSize_Click ( object sender , EventArgs e )
{
txtTraceLog . TextZoom - = 10 ;
}
private void mnuResetFontSize_Click ( object sender , EventArgs e )
{
txtTraceLog . TextZoom = 100 ;
}
private void mnuSelectFont_Click ( object sender , EventArgs e )
{
2018-03-03 11:14:57 -05:00
txtTraceLog . BaseFont = FontDialogHelper . SelectFont ( txtTraceLog . BaseFont ) ;
2018-03-03 10:41:59 -05:00
}
2018-05-26 01:14:37 -04:00
private void chkOptions_CheckedChanged ( object sender , EventArgs e )
{
UpdateFormatOptions ( ) ;
}
private void cboStatusFlagFormat_SelectedIndexChanged ( object sender , EventArgs e )
{
UpdateFormatOptions ( ) ;
}
private void txtFormat_TextChanged ( object sender , EventArgs e )
{
if ( chkOverrideFormat . Checked ) {
//Only save format string when override flag is set
( ( TraceLoggerOptions ) _entityBinder . Entity ) . Format = txtFormat . Text ;
}
}
private void chkOverrideFormat_CheckedChanged ( object sender , EventArgs e )
{
if ( chkOverrideFormat . Checked ) {
string format = ( ( TraceLoggerOptions ) _entityBinder . Entity ) . Format ;
if ( string . IsNullOrWhiteSpace ( format ) ) {
format = txtFormat . Text ;
}
txtFormat . Text = format ;
}
UpdateFormatOptions ( ) ;
}
2016-01-10 19:56:40 -05:00
}
}