fceux/drivers/win/tracer.cpp

590 lines
17 KiB
C++

/* FCE Ultra - NES/Famicom Emulator
*
* Copyright notice for this file:
* Copyright (C) 2002 Ben Parnell
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "common.h"
#include "debugger.h"
#include "..\..\x6502.h"
#include "..\..\fceu.h"
#include "..\..\cart.h" //mbg merge 7/19/06 moved after fceu.h
#include "cdlogger.h"
#include "..\..\file.h"
#include "..\..\debug.h"
#include "tracer.h"
#include "..\..\memview.h"
//#define LOG_SKIP_UNMAPPED 4
//#define LOG_ADD_PERIODS 8
//int logaxy = 1, logopdata = 1; //deleteme
int logging_options = -1;
int log_update_window = 0;
//int tracer_open=0;
volatile int logtofile = 0, logging = 0;
HWND hTracer;
HFONT hlogFont, hlogNewFont;
int log_optn_intlst[LOG_OPTION_SIZE] = {10000,8000,6000,4000,2000,1000,700,400,200,100};
char *log_optn_strlst[LOG_OPTION_SIZE] = {"10000","8000","6000","4000","2000","1000","700","400","200","100"};
char *logfilename = 0;
int oldcodecount, olddatacount;
SCROLLINFO tracesi;
char **tracelogbuf;
int tracelogbufsize, tracelogbufpos;
int tracelogbufusedsize;
FILE *LOG_FP;
void ShowLogDirDialog(void);
void BeginLoggingSequence(void);
void LogInstruction(void);
void OutputLogLine(char *str);
void EndLoggingSequence(void);
//void PauseLoggingSequence(void);
void UpdateLogWindow(void);
void UpdateLogText(void);
void EnableTracerMenuItems(void);
int PromptForCDLogger(void);
BOOL CALLBACK TracerCallB(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
int i;
LOGFONT lf;
switch(uMsg) {
case WM_INITDIALOG:
//CenterWindow(hwndDlg);
hTracer = hwndDlg;
//setup font
hlogFont = (HFONT)SendMessage(hwndDlg, WM_GETFONT, 0, 0);
GetObject(hlogFont, sizeof(LOGFONT), &lf);
strcpy(lf.lfFaceName,"Courier");
hlogNewFont = CreateFontIndirect(&lf);
SendDlgItemMessage(hwndDlg,100,WM_SETFONT,(WPARAM)hlogNewFont,FALSE);
//check the disabled radio button
CheckRadioButton(hwndDlg,103,104,103);
//EnableWindow(GetDlgItem(hwndDlg,101),FALSE);
//fill in the options for the log size
for(i = 0;i < LOG_OPTION_SIZE;i++){
SendDlgItemMessage(hwndDlg,105,CB_INSERTSTRING,-1,(LPARAM)(LPSTR)log_optn_strlst[i]);
}
SendDlgItemMessage(hwndDlg,105,CB_SETCURSEL,0,0);
SetDlgItemText(hwndDlg, 100, "Welcome to the Trace Logger.");
logtofile = 0;
if(logging_options == -1){
logging_options = (LOG_REGISTERS | LOG_PROCESSOR_STATUS);
CheckDlgButton(hwndDlg, 110, BST_CHECKED);
CheckDlgButton(hwndDlg, 111, BST_CHECKED);
} else{
if(logging_options&LOG_REGISTERS)CheckDlgButton(hwndDlg, 110, BST_CHECKED);
if(logging_options&LOG_PROCESSOR_STATUS)CheckDlgButton(hwndDlg, 111, BST_CHECKED);
}
EnableWindow(GetDlgItem(hwndDlg,105),TRUE);
EnableWindow(GetDlgItem(hwndDlg,112),FALSE);
if(log_update_window)CheckDlgButton(hwndDlg, 116, BST_CHECKED);
EnableTracerMenuItems();
break;
case WM_CLOSE:
case WM_QUIT:
if(logging)EndLoggingSequence();
DeleteObject(hlogNewFont);
logging_options &= 3;
hTracer = 0;
EndDialog(hwndDlg,0);
break;
case WM_COMMAND:
switch(HIWORD(wParam)) {
case BN_CLICKED:
switch(LOWORD(wParam)) {
case 102: //start logging
if(logging)EndLoggingSequence();
else BeginLoggingSequence();
EnableTracerMenuItems();
break;
case 103:
logtofile = 0;
EnableTracerMenuItems();
break;
case 104:
logtofile = 1;
EnableTracerMenuItems();
break;
case 110:
logging_options ^= LOG_REGISTERS;
break;
case 111:
logging_options ^= LOG_PROCESSOR_STATUS;
break;
case 114:
logging_options ^= LOG_NEW_INSTRUCTIONS;
if(logging && (!PromptForCDLogger())){
logging_options ^= LOG_NEW_INSTRUCTIONS; //turn it back off
CheckDlgButton(hTracer, 114, BST_UNCHECKED);
}
//EnableTracerMenuItems();
break;
case 115:
logging_options ^= LOG_NEW_DATA;
if(logging && (!PromptForCDLogger())){
logging_options ^= LOG_NEW_DATA; //turn it back off
CheckDlgButton(hTracer, 115, BST_UNCHECKED);
}
break;
case 116:
//todo: if this gets unchecked then we need to clear out the window
log_update_window ^= 1;
if(!FCEUI_EmulationPaused() && !log_update_window) //mbg merge 7/19/06 changed to use EmulationPaused()
SetDlgItemText(hTracer, 100, "Press F2 to pause the game, or snap \r\nthe debugger to update this window.\r\n");
//PauseLoggingSequence();
break;
case 112:
ShowLogDirDialog();
break;
}
break;
}
break;
case WM_MOVING:
StopSound();
break;
}
//if (logging && !logtofile) {
switch(uMsg) {
case WM_VSCROLL:
if (lParam) {
//if ((!logging) || (logging && logtofile))break;
//if(!(logging && !logtofile))break;
if ((!logging) || logtofile) break;
if(!FCEUI_EmulationPaused() && !log_update_window) break; //mbg merge 7/19/06 changd to use EmulationPaused()
StopSound();
GetScrollInfo((HWND)lParam,SB_CTL,&tracesi);
switch(LOWORD(wParam)) {
case SB_ENDSCROLL:
case SB_TOP:
case SB_BOTTOM: break;
case SB_LINEUP: tracesi.nPos--; break;
case SB_LINEDOWN:tracesi.nPos++; break;
case SB_PAGEUP: tracesi.nPos-=tracesi.nPage; break;
case SB_PAGEDOWN: tracesi.nPos+=tracesi.nPage; break;
case SB_THUMBPOSITION: //break;
case SB_THUMBTRACK: tracesi.nPos = tracesi.nTrackPos; break;
}
if(tracesi.nPos < tracesi.nMin) tracesi.nPos = tracesi.nMin;
if((tracesi.nPos+(int)tracesi.nPage) > tracesi.nMax) tracesi.nPos = tracesi.nMax-(int)tracesi.nPage; //mbg merge 7/19/06 added casts
if(tracesi.nPos < 0)tracesi.nPos = 0; //this fixes a bad crash that occurs if you scroll up when only a few isntructions were logged
SetScrollInfo((HWND)lParam,SB_CTL,&tracesi,TRUE);
UpdateLogText();
}
break;
}
//}
return FALSE;
}
void BeginLoggingSequence(void){
char str[2048], str2[100];
int i, j;
if(!PromptForCDLogger())return; //do nothing if user selected no and CD Logger is needed
if(logtofile){
LOG_FP = fopen(logfilename,"w");
if(LOG_FP == NULL){
sprintf(str,"Error Opening File %s",logfilename);
MessageBox(hTracer,str, "File Error", MB_OK);
return;
}
fprintf(LOG_FP,FCEU_NAME_AND_VERSION" - Trace Log File\n"); //mbg merge 7/19/06 changed string
} else {
strcpy(str,"Allocating Memory...\r\n");
SetDlgItemText(hTracer, 100, str);
tracelogbufsize = j = log_optn_intlst[SendDlgItemMessage(hTracer,105,CB_GETCURSEL,0,0)];
tracelogbuf = (char**)malloc(j*sizeof(char *)); //mbg merge 7/19/06 added cast
for(i = 0;i < j;i++){
tracelogbuf[i] = (char*)malloc(80); //mbg merge 7/19/06 added cast
tracelogbuf[i][0] = 0;
}
sprintf(str2,"%d Bytes Allocated...\r\n",j*80);
strcat(str,str2);
strcat(str,"Press F2 to pause the game, or snap \r\nthe debugger to update this window.\r\n");
SetDlgItemText(hTracer, 100, str);
tracelogbufpos = tracelogbufusedsize = 0;
}
oldcodecount = codecount;
olddatacount = datacount;
logging=1;
SetDlgItemText(hTracer, 102,"Stop Logging");
return;
}
/*
void LogInstructionOld(){
char str[96], chr[32];
int addr=X.PC;
int size, j;
uint8 opcode[3];
sprintf(str, "$%04X:", addr);
if ((size = opsize[GetMem(addr)]) == 0) {
sprintf(chr, "%02X UNDEFINED", GetMem(addr)); addr++;
strcat(str,chr);
}
else {
if ((addr+size) > 0xFFFF) {
sprintf(chr, "%02X OVERFLOW", GetMem(addr));
strcat(str,chr);
goto done;
}
for (j = 0; j < size; j++) {
sprintf(chr, "%02X ", opcode[j] = GetMem(addr)); addr++;
strcat(str,chr);
}
while (size < 3) {
strcat(str," "); //pad output to align ASM
size++;
}
strcat(strcat(str," "),BinToASM(addr,opcode));
}
done:
strcat(str,"\n");
if(logtofile){
fprintf(LOG_FP,str);
}
}*/
//todo: really speed this up
void FCEUD_TraceInstruction(){
if(!logging) return;
char address[7], data[11], disassembly[28], axystate[16], procstatus[12];
char str[96];
int addr=X.PC;
int size, j;
uint8 opcode[3], tmp;
static int unloggedlines;
if(((logging_options & LOG_NEW_INSTRUCTIONS) && (oldcodecount != codecount)) ||
((logging_options & LOG_NEW_DATA) && (olddatacount != datacount))){//something new was logged
oldcodecount = codecount;
olddatacount = datacount;
if(unloggedlines > 0){
sprintf(str,"(%d lines skipped)",unloggedlines);
OutputLogLine(str);
unloggedlines = 0;
}
} else {
if((logging_options & LOG_NEW_INSTRUCTIONS) ||
(logging_options & LOG_NEW_DATA)){
if(FCEUI_GetLoggingCD())unloggedlines++;
return;
}
}
sprintf(address, "$%04X:", addr);
axystate[0] = str[0] = 0;
size = opsize[GetMem(addr)];
if ((addr+size) > 0xFFFF){
sprintf(data, "%02X ", GetMem(addr&0xFFFF));
sprintf(disassembly,"OVERFLOW");
} else {
switch(size){
case 0:
sprintf(data, "%02X ", GetMem(addr));
sprintf(disassembly,"UNDEFINED");
break;
case 1:
opcode[0]=GetMem(addr++);
sprintf(data, "%02X ", opcode[0]);
strcpy(disassembly,BinToASM(addr,opcode));
break;
case 2:
opcode[0]=GetMem(addr++);
opcode[1]=GetMem(addr++);
sprintf(data, "%02X %02X ", opcode[0],opcode[1]);
strcpy(disassembly,BinToASM(addr,opcode));
break;
case 3:
opcode[0]=GetMem(addr++);
opcode[1]=GetMem(addr++);
opcode[2]=GetMem(addr++);
sprintf(data, "%02X %02X %02X ", opcode[0],opcode[1],opcode[2]);
strcpy(disassembly,BinToASM(addr,opcode));
break;
}
}
//stretch the disassembly string out if we have to output other stuff.
if(logging_options & (LOG_REGISTERS|LOG_PROCESSOR_STATUS)){
for(j = strlen(disassembly);j < 27;j++)disassembly[j] = ' ';
disassembly[27] = 0;
}
if(logging_options & LOG_REGISTERS){
sprintf(axystate,"A:%02X X:%02X Y:%02X",(X.A),(X.X),(X.Y));
}
if(logging_options & LOG_PROCESSOR_STATUS){
tmp = X.P^0xFF;
sprintf(procstatus,"P:%c%c%c%c%c%c%c%c",
'N'|(tmp&0x80)>>2,
'V'|(tmp&0x40)>>1,
'U'|(tmp&0x20),
'B'|(tmp&0x10)<<1,
'D'|(tmp&0x08)<<2,
'I'|(tmp&0x04)<<3,
'Z'|(tmp&0x02)<<4,
'C'|(tmp&0x01)<<5
);
}
strcat(str,address);
strcat(str,data);
strcat(str,disassembly);
if(logging_options & LOG_REGISTERS)strcat(str,axystate);
if((logging_options & LOG_REGISTERS) && (logging_options & LOG_PROCESSOR_STATUS))strcat(str," ");
if(logging_options & LOG_PROCESSOR_STATUS)strcat(str,procstatus);
OutputLogLine(str);
return;
}
void OutputLogLine(char *str){
if(logtofile){
strcat(str,"\n");
fputs(str,LOG_FP);
fflush(LOG_FP);
}else{
strcat(str,"\r\n");
if(strlen(str) < 80)strcpy(tracelogbuf[tracelogbufpos],str);
tracelogbufpos++;
if(tracelogbufusedsize < tracelogbufsize)tracelogbufusedsize++;
tracelogbufpos%=tracelogbufsize;
}
}
void EndLoggingSequence(void){
int j, i;
if(logtofile){
fclose(LOG_FP);
} else {
j = tracelogbufsize;
for(i = 0;i < j;i++){
free(tracelogbuf[i]);
}
free(tracelogbuf);
SetDlgItemText(hTracer, 100, "Welcome to the Trace Logger.");
}
logging=0;
SetDlgItemText(hTracer, 102,"Start Logging");
}
//void PauseLoggingSequence(void){
void UpdateLogWindow(void){
//if((tracelogbufpos == 0) || (tracelogbuf[tracelogbufpos][0]))
// tracesi.nMax = tracelogbufsize;
//else tracesi.nMax = tracelogbufpos;
//todo: fix this up.
//if(!log_update_window && !userpause){
// SetDlgItemText(hTracer, 100, "Press F2 to pause the game, or snap \r\nthe debugger to update this window.\r\n");
// return;
//}
//
//we don't want to continue if the trace logger isn't logging, or if its logging to a file.
if((!logging) || logtofile)return;
//if the game isn't paused, and the option to update the log window while running isn't checked, then halt here.
if(!FCEUI_EmulationPaused() && !log_update_window){ //mbg merge 7/19/06 changd to use EmulationPaused()
return;
}
tracesi.cbSize = sizeof(SCROLLINFO);
tracesi.fMask = SIF_ALL;
tracesi.nPage = 21;
tracesi.nMin = 0;
tracesi.nMax = tracelogbufusedsize; //todo: try -2
tracesi.nPos = tracesi.nMax-tracesi.nPage;
if (tracesi.nPos < tracesi.nMin) tracesi.nPos = tracesi.nMin;
SetScrollInfo(GetDlgItem(hTracer,101),SB_CTL,&tracesi,TRUE);
UpdateLogText();
return;
}
void UpdateLogText(void){
int i, j;
char str[2048];
str[0] = 0;
/*
for(i = 21;i > 0;i--){
j = tracelogbufpos-i-1;
if((tracelogbufusedsize == tracelogbufsize) && (j < 0))j = tracelogbufsize+j;
if(j >= 0)strcat(str,tracelogbuf[j]);
}
*/
for(i = tracesi.nPos;i < min(tracesi.nMax,tracesi.nPos+21);i++){
j = i;
if(tracelogbufusedsize == tracelogbufsize){
j = (tracelogbufpos+i)%tracelogbufsize;
}
strcat(str,tracelogbuf[j]);
}
SetDlgItemText(hTracer, 100, str);
sprintf(str,"nPage = %d, nPos = %d, nMax = %d, nMin = %d",tracesi.nPage,tracesi.nPos,tracesi.nMax,tracesi.nMin);
SetDlgItemText(hTracer, 1002, str);
return;
}
void EnableTracerMenuItems(void){
//if(logging_options & LOG_NEW_INSTRUCTIONS){
//EnableWindow(GetDlgItem(hTracer,115),TRUE);
//} else {
// CheckDlgButton(hTracer, 115, BST_UNCHECKED);
//EnableWindow(GetDlgItem(hTracer,115),FALSE);
//}
if(logging){
EnableWindow(GetDlgItem(hTracer,103),FALSE);
EnableWindow(GetDlgItem(hTracer,104),FALSE);
EnableWindow(GetDlgItem(hTracer,105),FALSE);
//EnableWindow(GetDlgItem(hTracer,110),FALSE);
//EnableWindow(GetDlgItem(hTracer,111),FALSE);
EnableWindow(GetDlgItem(hTracer,112),FALSE);
//EnableWindow(GetDlgItem(hTracer,114),FALSE);
//EnableWindow(GetDlgItem(hTracer,115),FALSE);
return;
}
EnableWindow(GetDlgItem(hTracer,103),TRUE);
EnableWindow(GetDlgItem(hTracer,104),TRUE);
EnableWindow(GetDlgItem(hTracer,105),TRUE);
//EnableWindow(GetDlgItem(hTracer,110),TRUE);
//EnableWindow(GetDlgItem(hTracer,111),TRUE); //uncomment me
EnableWindow(GetDlgItem(hTracer,112),TRUE);
EnableWindow(GetDlgItem(hTracer,114),TRUE);
if(logtofile){
EnableWindow(GetDlgItem(hTracer,105),FALSE);
EnableWindow(GetDlgItem(hTracer,112),TRUE);
CheckDlgButton(hTracer, 116, BST_UNCHECKED);
log_update_window = 0;
EnableWindow(GetDlgItem(hTracer,116),FALSE);
} else{
EnableWindow(GetDlgItem(hTracer,105),TRUE);
EnableWindow(GetDlgItem(hTracer,112),FALSE);
EnableWindow(GetDlgItem(hTracer,116),TRUE);
}
/*
if(FCEUI_GetLoggingCD()){
EnableWindow(GetDlgItem(hTracer,114),TRUE);
if(logging_options & LOG_NEW_INSTRUCTIONS){
EnableWindow(GetDlgItem(hTracer,115),TRUE);
} else EnableWindow(GetDlgItem(hTracer,115),FALSE);
}
else{
EnableWindow(GetDlgItem(hTracer,114),FALSE);
EnableWindow(GetDlgItem(hTracer,115),FALSE);
CheckDlgButton(hTracer, 114, BST_UNCHECKED);
CheckDlgButton(hTracer, 115, BST_UNCHECKED);
logging_options &= 3;
}
*/
return;
}
//this returns 1 if the CD logger is activated when needed, or 0 if the user selected no, not to activate it
int PromptForCDLogger(void){
if((logging_options & (LOG_NEW_INSTRUCTIONS|LOG_NEW_DATA)) && (!FCEUI_GetLoggingCD())){
StopSound();
if(MessageBox(hTracer,"In order for some of the features you have selected to take effect,\
the Code/Data Logger must also be running.\
Would you like to Start the Code/Data Logger Now?","Start Code/Data Logger?",
MB_YESNO) == IDYES){
DoCDLogger();
FCEUI_SetLoggingCD(1);
//EnableTracerMenuItems();
SetDlgItemText(hCDLogger, 105, "Pause");
return 1;
}
return 0; //user selected no so 0 is returned
}
return 1;
}
void ShowLogDirDialog(void){
const char filter[]="6502 Trace Log File(*.log,*.txt)\0*.log;*.txt\0";
char nameo[2048];
OPENFILENAME ofn;
StopSound();
memset(&ofn,0,sizeof(ofn));
ofn.lStructSize=sizeof(ofn);
ofn.hInstance=fceu_hInstance;
ofn.lpstrTitle="Log Trace As...";
ofn.lpstrFilter=filter;
nameo[0]=0;
ofn.lpstrFile=nameo;
ofn.nMaxFile=256;
ofn.Flags=OFN_EXPLORER|OFN_FILEMUSTEXIST|OFN_HIDEREADONLY;
ofn.hwndOwner = hTracer;
GetSaveFileName(&ofn);
if(nameo[0]){
if(logfilename)free(logfilename);
logfilename = (char*)malloc(strlen(nameo)+1); //mbg merge 7/19/06 added cast
strcpy(logfilename,nameo);
}
return;
}
void DoTracer(){
if (!GI) {
FCEUD_PrintError("You must have a game loaded before you can use the Trace Logger.");
return;
}
if (GI->type==GIT_NSF) { //todo: NSF support!
FCEUD_PrintError("Sorry, you can't yet use the Trace Logger with NSFs.");
return;
}
if(!hTracer){
CreateDialog(fceu_hInstance,"TRACER",NULL,TracerCallB);
//hTracer gets set in WM_INITDIALOG
} else {
SetWindowPos(hTracer,HWND_TOP,0,0,0,0,SWP_NOSIZE|SWP_NOMOVE|SWP_NOOWNERZORDER);
}
}