2594 lines
60 KiB
C++
2594 lines
60 KiB
C++
/* FCE Ultra - NES/Famicom Emulator
|
|
*
|
|
* Copyright notice for this file:
|
|
* Copyright (C) 2020 mjbudd77
|
|
*
|
|
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
//
|
|
// TraceLogger.cpp
|
|
//
|
|
#include <stdio.h>
|
|
#include <math.h>
|
|
|
|
#ifdef WIN32
|
|
#include <windows.h>
|
|
#include "../win/TraceFileWriter.h"
|
|
#else
|
|
#include <unistd.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#endif
|
|
|
|
#include <QDir>
|
|
#include <QMenu>
|
|
#include <QMenuBar>
|
|
#include <QAction>
|
|
#include <QSettings>
|
|
#include <QFileDialog>
|
|
#include <QInputDialog>
|
|
#include <QMessageBox>
|
|
#include <QShortcut>
|
|
#include <QPainter>
|
|
#include <QGuiApplication>
|
|
|
|
#include "../../types.h"
|
|
#include "../../fceu.h"
|
|
#include "../../cart.h"
|
|
#include "../../x6502.h"
|
|
#include "../../debug.h"
|
|
#include "../../asm.h"
|
|
#include "../../ppu.h"
|
|
#include "../../ines.h"
|
|
#include "../../nsf.h"
|
|
#include "../../movie.h"
|
|
|
|
#include "common/os_utils.h"
|
|
#include "utils/StringBuilder.h"
|
|
|
|
#include "Qt/ConsoleDebugger.h"
|
|
#include "Qt/ConsoleWindow.h"
|
|
#include "Qt/ConsoleUtilities.h"
|
|
#include "Qt/TraceLogger.h"
|
|
#include "Qt/main.h"
|
|
#include "Qt/dface.h"
|
|
#include "Qt/input.h"
|
|
#include "Qt/config.h"
|
|
#include "Qt/SymbolicDebug.h"
|
|
#include "Qt/fceuWrapper.h"
|
|
|
|
#define LOG_REGISTERS 0x00000001
|
|
#define LOG_PROCESSOR_STATUS 0x00000002
|
|
#define LOG_NEW_INSTRUCTIONS 0x00000004
|
|
#define LOG_NEW_DATA 0x00000008
|
|
#define LOG_TO_THE_LEFT 0x00000010
|
|
#define LOG_FRAMES_COUNT 0x00000020
|
|
#define LOG_MESSAGES 0x00000040
|
|
#define LOG_BREAKPOINTS 0x00000080
|
|
#define LOG_SYMBOLIC 0x00000100
|
|
#define LOG_CODE_TABBING 0x00000200
|
|
#define LOG_CYCLES_COUNT 0x00000400
|
|
#define LOG_INSTRUCTIONS_COUNT 0x00000800
|
|
#define LOG_BANK_NUMBER 0x00001000
|
|
|
|
#define LOG_LINE_MAX_LEN 160
|
|
// Frames count - 1+6+1 symbols
|
|
// Cycles count - 1+11+1 symbols
|
|
// Instructions count - 1+11+1 symbols
|
|
// AXYS state - 20
|
|
// Processor status - 11
|
|
// Tabs - 31
|
|
// Address - 6
|
|
// Data - 10
|
|
// Disassembly - 45
|
|
// EOL (/0) - 1
|
|
// ------------------------
|
|
// 148 symbols total
|
|
#define LOG_AXYSTATE_MAX_LEN 21
|
|
#define LOG_PROCSTATUS_MAX_LEN 12
|
|
#define LOG_TABS_MASK 31
|
|
#define LOG_ADDRESS_MAX_LEN 13
|
|
#define LOG_DATA_MAX_LEN 11
|
|
#define LOG_DISASSEMBLY_MAX_LEN 46
|
|
#define NL_MAX_MULTILINE_COMMENT_LEN 1000
|
|
|
|
static int logging = 0;
|
|
static int logging_options = LOG_REGISTERS | LOG_PROCESSOR_STATUS | LOG_TO_THE_LEFT | LOG_MESSAGES | LOG_BREAKPOINTS | LOG_CODE_TABBING;
|
|
static int oldcodecount = 0, olddatacount = 0;
|
|
|
|
static traceRecord_t *recBuf = NULL;
|
|
static int recBufMax = 0;
|
|
static int recBufHead = 0;
|
|
static int recBufNum = 0;
|
|
static traceRecord_t *logBuf = NULL;
|
|
static int logBufMax = 3000000;
|
|
// logBufHead and logBufTail are volatile because they are shared use by both the emulation and disk logger threads.
|
|
// Ensure that the compiler doesn't do any thread caching optimizations on them so that changes to these
|
|
// variables are immediately visible by the other thread.
|
|
static volatile int logBufHead = 0;
|
|
static volatile int logBufTail = 0;
|
|
static bool overrunWarningArmed = true;
|
|
static TraceLoggerDialog_t *traceLogWindow = NULL;
|
|
static void pushMsgToLogBuffer(const char *msg);
|
|
#ifdef WIN32
|
|
#include <windows.h>
|
|
static HANDLE logFile = INVALID_HANDLE_VALUE;
|
|
#else
|
|
static int logFile = -1;
|
|
#endif
|
|
static std::string logFilePath;
|
|
//----------------------------------------------------
|
|
static void initLogOption( const char *name, int bitmask )
|
|
{
|
|
int opt;
|
|
|
|
g_config->getOption(name, &opt);
|
|
|
|
if ( opt )
|
|
{
|
|
logging_options |= bitmask;
|
|
}
|
|
else
|
|
{
|
|
logging_options &= ~bitmask;
|
|
}
|
|
}
|
|
//----------------------------------------------------
|
|
TraceLoggerDialog_t::TraceLoggerDialog_t(QWidget *parent)
|
|
: QDialog(parent, Qt::Window)
|
|
{
|
|
QSettings settings;
|
|
QVBoxLayout *mainLayout;
|
|
QHBoxLayout *hbox;
|
|
QGridLayout *grid;
|
|
QGroupBox *frame;
|
|
QMenuBar *menuBar;
|
|
QMenu *fileMenu;
|
|
QAction *act;
|
|
QLabel *lbl;
|
|
int opt, useNativeMenuBar;
|
|
QShortcut *shortcut;
|
|
|
|
if (recBufMax == 0)
|
|
{
|
|
initTraceLogBuffer(1000000);
|
|
}
|
|
|
|
setWindowTitle(tr("Trace Logger"));
|
|
|
|
menuBar = new QMenuBar(this);
|
|
|
|
// This is needed for menu bar to show up on MacOS
|
|
g_config->getOption( "SDL.UseNativeMenuBar", &useNativeMenuBar );
|
|
|
|
menuBar->setNativeMenuBar( useNativeMenuBar ? true : false );
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Menu Start
|
|
//-----------------------------------------------------------------------
|
|
// File
|
|
fileMenu = menuBar->addMenu(tr("&File"));
|
|
|
|
// File -> Close
|
|
act = new QAction(tr("&Close"), this);
|
|
act->setShortcut(QKeySequence::Close);
|
|
act->setStatusTip(tr("Close Window"));
|
|
connect(act, SIGNAL(triggered()), this, SLOT(closeWindow(void)) );
|
|
|
|
fileMenu->addAction(act);
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Menu End
|
|
//-----------------------------------------------------------------------
|
|
|
|
mainLayout = new QVBoxLayout();
|
|
|
|
mainLayout->setMenuBar( menuBar );
|
|
|
|
grid = new QGridLayout();
|
|
mainLayout->addLayout(grid, 100);
|
|
|
|
traceView = new QTraceLogView(this);
|
|
vbar = new QScrollBar(Qt::Vertical, this);
|
|
hbar = new QScrollBar(Qt::Horizontal, this);
|
|
|
|
connect(hbar, SIGNAL(valueChanged(int)), this, SLOT(hbarChanged(int)));
|
|
connect(vbar, SIGNAL(valueChanged(int)), this, SLOT(vbarChanged(int)));
|
|
connect(vbar, SIGNAL(actionTriggered(int)), this, SLOT(vbarActionTriggered(int)));
|
|
|
|
shortcut = new QShortcut( QKeySequence("Page Up"), this );
|
|
connect( shortcut, SIGNAL(activated(void)), this, SLOT(pageUpActivated(void)) );
|
|
shortcut = new QShortcut( QKeySequence("Page Down"), this );
|
|
connect( shortcut, SIGNAL(activated(void)), this, SLOT(pageDnActivated(void)) );
|
|
|
|
traceView->setScrollBars(hbar, vbar);
|
|
traceView->setMinimumHeight(256);
|
|
hbar->setMinimum(0);
|
|
hbar->setMaximum(100);
|
|
vbar->setMinimum(0);
|
|
vbar->setMaximum(0);
|
|
vbar->setValue(0);
|
|
vbar->setInvertedAppearance(true);
|
|
|
|
grid->addWidget(traceView, 0, 0);
|
|
grid->addWidget(vbar, 0, 1);
|
|
grid->addWidget(hbar, 1, 0);
|
|
|
|
grid = new QGridLayout();
|
|
mainLayout->addLayout(grid, 1);
|
|
|
|
lbl = new QLabel(tr("Lines"));
|
|
logLastLbl = new QLabel(tr("Log Last"));
|
|
logMaxLinesComboBox = new QComboBox();
|
|
|
|
logMaxLinesComboBox->addItem(tr("3,000,000"), 3000000);
|
|
logMaxLinesComboBox->addItem(tr("1,000,000"), 1000000);
|
|
logMaxLinesComboBox->addItem(tr("300,000"), 300000);
|
|
logMaxLinesComboBox->addItem(tr("100,000"), 100000);
|
|
logMaxLinesComboBox->addItem(tr("30,000"), 30000);
|
|
logMaxLinesComboBox->addItem(tr("10,000"), 10000);
|
|
logMaxLinesComboBox->addItem(tr("3,000"), 3000);
|
|
logMaxLinesComboBox->addItem(tr("1,000"), 1000);
|
|
|
|
for (int i = 0; i < logMaxLinesComboBox->count(); i++)
|
|
{
|
|
if (logMaxLinesComboBox->itemData(i).toInt() == recBufMax)
|
|
{
|
|
logMaxLinesComboBox->setCurrentIndex(i);
|
|
}
|
|
}
|
|
connect(logMaxLinesComboBox, SIGNAL(activated(int)), this, SLOT(logMaxLinesChanged(int)));
|
|
|
|
logFileCbox = new QCheckBox(tr("Log to File"));
|
|
selLogFileButton = new QPushButton(tr("Browse..."));
|
|
startStopButton = new QPushButton(tr("Start Logging"));
|
|
autoUpdateCbox = new QCheckBox(tr("Automatically update this window while logging"));
|
|
clearButton = new QPushButton(tr("Clear Log"));
|
|
|
|
g_config->getOption("SDL.TraceLogSaveFilePath", &logFilePath);
|
|
|
|
g_config->getOption("SDL.TraceLogSaveToFile", &opt );
|
|
logFileCbox->setChecked( opt );
|
|
connect(logFileCbox, SIGNAL(stateChanged(int)), this, SLOT(logToFileStateChanged(int)));
|
|
|
|
g_config->getOption("SDL.TraceLogPeriodicWindowUpdate", &opt );
|
|
autoUpdateCbox->setChecked( opt );
|
|
connect(autoUpdateCbox, SIGNAL(stateChanged(int)), this, SLOT(autoUpdateStateChanged(int)));
|
|
|
|
if (logging)
|
|
{
|
|
startStopButton->setText(tr("Stop Logging"));
|
|
startStopButton->setIcon( style()->standardIcon( QStyle::SP_MediaStop ) );
|
|
}
|
|
else
|
|
{
|
|
startStopButton->setIcon( style()->standardIcon( QStyle::SP_MediaPlay ) );
|
|
}
|
|
clearButton->setIcon( style()->standardIcon( QStyle::SP_DialogResetButton ) );
|
|
|
|
connect( startStopButton, SIGNAL(clicked(void)), this, SLOT(toggleLoggingOnOff(void)));
|
|
connect(selLogFileButton, SIGNAL(clicked(void)), this, SLOT(openLogFile(void)));
|
|
connect( clearButton, SIGNAL(clicked(void)), this, SLOT(clearLog(void)));
|
|
|
|
hbox = new QHBoxLayout();
|
|
hbox->addWidget(logLastLbl);
|
|
hbox->addWidget(logMaxLinesComboBox);
|
|
hbox->addWidget(lbl);
|
|
|
|
grid->addLayout(hbox, 0, 0, Qt::AlignLeft);
|
|
grid->addWidget(startStopButton, 0, 1, Qt::AlignLeft);
|
|
grid->addWidget( clearButton, 0, 2, Qt::AlignLeft);
|
|
|
|
hbox = new QHBoxLayout();
|
|
hbox->addWidget(logFileCbox);
|
|
hbox->addWidget(selLogFileButton);
|
|
|
|
grid->addLayout(hbox, 1, 0, Qt::AlignLeft);
|
|
grid->addWidget(autoUpdateCbox, 1, 1, Qt::AlignLeft);
|
|
|
|
grid = new QGridLayout();
|
|
frame = new QGroupBox(tr("Log Options"));
|
|
frame->setLayout(grid);
|
|
|
|
logRegCbox = new QCheckBox(tr("Log State of Registers"));
|
|
logFrameCbox = new QCheckBox(tr("Log Frames Count"));
|
|
logEmuMsgCbox = new QCheckBox(tr("Log Emulator Messages"));
|
|
symTraceEnaCbox = new QCheckBox(tr("Symbolic Trace"));
|
|
logProcStatFlagCbox = new QCheckBox(tr("Log Processor Status Flags"));
|
|
logCyclesCountCbox = new QCheckBox(tr("Log Cycles Count"));
|
|
logBreakpointCbox = new QCheckBox(tr("Log Breakpoint Hits"));
|
|
useStackPointerCbox = new QCheckBox(tr("Use Stack Pointer for Code Tabbing (Nesting Visualization)"));
|
|
toLeftDisassemblyCbox = new QCheckBox(tr("To the Left from Disassembly"));
|
|
logInstrCountCbox = new QCheckBox(tr("Log Instructions Count"));
|
|
logBankNumCbox = new QCheckBox(tr("Log Bank Number"));
|
|
|
|
initLogOption("SDL.TraceLogRegisterState", LOG_REGISTERS );
|
|
initLogOption("SDL.TraceLogProcessorState", LOG_PROCESSOR_STATUS );
|
|
initLogOption("SDL.TraceLogNewInstructions", LOG_NEW_INSTRUCTIONS );
|
|
initLogOption("SDL.TraceLogNewData", LOG_NEW_DATA );
|
|
initLogOption("SDL.TraceLogFrameCount", LOG_FRAMES_COUNT );
|
|
initLogOption("SDL.TraceLogCycleCount", LOG_CYCLES_COUNT );
|
|
initLogOption("SDL.TraceLogInstructionCount", LOG_INSTRUCTIONS_COUNT );
|
|
initLogOption("SDL.TraceLogMessages", LOG_MESSAGES );
|
|
initLogOption("SDL.TraceLogBreakpointHits", LOG_BREAKPOINTS );
|
|
initLogOption("SDL.TraceLogBankNumber", LOG_BANK_NUMBER );
|
|
initLogOption("SDL.TraceLogSymbolic", LOG_SYMBOLIC );
|
|
initLogOption("SDL.TraceLogStackTabbing", LOG_CODE_TABBING );
|
|
initLogOption("SDL.TraceLogLeftDisassembly", LOG_TO_THE_LEFT );
|
|
|
|
logRegCbox->setChecked((logging_options & LOG_REGISTERS) ? true : false);
|
|
logFrameCbox->setChecked((logging_options & LOG_FRAMES_COUNT) ? true : false);
|
|
logEmuMsgCbox->setChecked((logging_options & LOG_MESSAGES) ? true : false);
|
|
symTraceEnaCbox->setChecked((logging_options & LOG_SYMBOLIC) ? true : false);
|
|
logProcStatFlagCbox->setChecked((logging_options & LOG_PROCESSOR_STATUS) ? true : false);
|
|
logCyclesCountCbox->setChecked((logging_options & LOG_CYCLES_COUNT) ? true : false);
|
|
logBreakpointCbox->setChecked((logging_options & LOG_BREAKPOINTS) ? true : false);
|
|
useStackPointerCbox->setChecked((logging_options & LOG_CODE_TABBING) ? true : false);
|
|
toLeftDisassemblyCbox->setChecked((logging_options & LOG_TO_THE_LEFT) ? true : false);
|
|
logInstrCountCbox->setChecked((logging_options & LOG_INSTRUCTIONS_COUNT) ? true : false);
|
|
logBankNumCbox->setChecked((logging_options & LOG_BANK_NUMBER) ? true : false);
|
|
|
|
connect(logRegCbox, SIGNAL(stateChanged(int)), this, SLOT(logRegStateChanged(int)));
|
|
connect(logFrameCbox, SIGNAL(stateChanged(int)), this, SLOT(logFrameStateChanged(int)));
|
|
connect(logEmuMsgCbox, SIGNAL(stateChanged(int)), this, SLOT(logEmuMsgStateChanged(int)));
|
|
connect(symTraceEnaCbox, SIGNAL(stateChanged(int)), this, SLOT(symTraceEnaStateChanged(int)));
|
|
connect(logProcStatFlagCbox, SIGNAL(stateChanged(int)), this, SLOT(logProcStatFlagStateChanged(int)));
|
|
connect(logCyclesCountCbox, SIGNAL(stateChanged(int)), this, SLOT(logCyclesCountStateChanged(int)));
|
|
connect(logBreakpointCbox, SIGNAL(stateChanged(int)), this, SLOT(logBreakpointStateChanged(int)));
|
|
connect(useStackPointerCbox, SIGNAL(stateChanged(int)), this, SLOT(useStackPointerStateChanged(int)));
|
|
connect(toLeftDisassemblyCbox, SIGNAL(stateChanged(int)), this, SLOT(toLeftDisassemblyStateChanged(int)));
|
|
connect(logInstrCountCbox, SIGNAL(stateChanged(int)), this, SLOT(logInstrCountStateChanged(int)));
|
|
connect(logBankNumCbox, SIGNAL(stateChanged(int)), this, SLOT(logBankNumStateChanged(int)));
|
|
|
|
grid->addWidget(logRegCbox, 0, 0, Qt::AlignLeft);
|
|
grid->addWidget(logFrameCbox, 1, 0, Qt::AlignLeft);
|
|
grid->addWidget(logEmuMsgCbox, 2, 0, Qt::AlignLeft);
|
|
grid->addWidget(symTraceEnaCbox, 3, 0, Qt::AlignLeft);
|
|
grid->addWidget(logProcStatFlagCbox, 0, 1, Qt::AlignLeft);
|
|
grid->addWidget(logCyclesCountCbox, 1, 1, Qt::AlignLeft);
|
|
grid->addWidget(logBreakpointCbox, 2, 1, Qt::AlignLeft);
|
|
grid->addWidget(useStackPointerCbox, 3, 1, 1, 2, Qt::AlignLeft);
|
|
grid->addWidget(toLeftDisassemblyCbox, 0, 2, Qt::AlignLeft);
|
|
grid->addWidget(logInstrCountCbox, 1, 2, Qt::AlignLeft);
|
|
grid->addWidget(logBankNumCbox, 2, 2, Qt::AlignLeft);
|
|
|
|
mainLayout->addWidget(frame, 1);
|
|
|
|
grid = new QGridLayout();
|
|
frame = new QGroupBox(tr("Extra Log Options that work with the Code/Data Logger"));
|
|
frame->setLayout(grid);
|
|
|
|
logNewMapCodeCbox = new QCheckBox(tr("Only Log Newly Mapped Code"));
|
|
logNewMapDataCbox = new QCheckBox(tr("Only Log that Accesses Newly Mapped Data"));
|
|
|
|
logNewMapCodeCbox->setChecked((logging_options & LOG_NEW_INSTRUCTIONS) ? true : false);
|
|
logNewMapDataCbox->setChecked((logging_options & LOG_NEW_DATA) ? true : false);
|
|
|
|
connect(logNewMapCodeCbox, SIGNAL(stateChanged(int)), this, SLOT(logNewMapCodeChanged(int)));
|
|
connect(logNewMapDataCbox, SIGNAL(stateChanged(int)), this, SLOT(logNewMapDataChanged(int)));
|
|
|
|
grid->addWidget(logNewMapCodeCbox, 0, 0, Qt::AlignLeft);
|
|
grid->addWidget(logNewMapDataCbox, 0, 1, Qt::AlignLeft);
|
|
|
|
mainLayout->addWidget(frame, 1);
|
|
|
|
setLayout(mainLayout);
|
|
|
|
traceViewCounter = 0;
|
|
recbufHeadLp = recBufHead;
|
|
|
|
updateTimer = new QTimer(this);
|
|
|
|
connect(updateTimer, &QTimer::timeout, this, &TraceLoggerDialog_t::updatePeriodic);
|
|
|
|
updateTimer->start(50); // 20hz
|
|
|
|
diskThread = new TraceLogDiskThread_t(this);
|
|
|
|
restoreGeometry(settings.value("traceLogger/geometry").toByteArray());
|
|
}
|
|
//----------------------------------------------------
|
|
TraceLoggerDialog_t::~TraceLoggerDialog_t(void)
|
|
{
|
|
updateTimer->stop();
|
|
|
|
//logging = 0;
|
|
msleep(1);
|
|
diskThread->requestInterruption();
|
|
diskThread->quit();
|
|
diskThread->wait( 1000000 );
|
|
|
|
traceLogWindow = NULL;
|
|
|
|
//printf("Trace Logger Window Deleted\n");
|
|
}
|
|
//----------------------------------------------------
|
|
void TraceLoggerDialog_t::closeEvent(QCloseEvent *event)
|
|
{
|
|
QSettings settings;
|
|
//printf("Trace Logger Close Window Event\n");
|
|
settings.setValue("traceLogger/geometry", saveGeometry());
|
|
done(0);
|
|
deleteLater();
|
|
event->accept();
|
|
}
|
|
//----------------------------------------------------
|
|
void TraceLoggerDialog_t::closeWindow(void)
|
|
{
|
|
QSettings settings;
|
|
//printf("Trace Logger Close Window\n");
|
|
settings.setValue("traceLogger/geometry", saveGeometry());
|
|
done(0);
|
|
deleteLater();
|
|
}
|
|
//----------------------------------------------------
|
|
void TraceLoggerDialog_t::updatePeriodic(void)
|
|
{
|
|
char traceViewDrawEnable;
|
|
|
|
if (FCEUI_EmulationPaused())
|
|
{
|
|
traceViewDrawEnable = 1;
|
|
}
|
|
else
|
|
{
|
|
traceViewDrawEnable = autoUpdateCbox->isChecked();
|
|
}
|
|
|
|
if ( !logging || !logFileCbox->isChecked())
|
|
{
|
|
overrunWarningArmed = true;
|
|
}
|
|
|
|
if (traceViewCounter > 5)
|
|
{
|
|
if (recBufHead != recbufHeadLp)
|
|
{
|
|
traceView->highlightClear();
|
|
}
|
|
recbufHeadLp = recBufHead;
|
|
|
|
if (traceViewDrawEnable)
|
|
{
|
|
traceView->update();
|
|
}
|
|
traceViewCounter = 0;
|
|
}
|
|
traceViewCounter++;
|
|
}
|
|
//----------------------------------------------------
|
|
void TraceLoggerDialog_t::logMaxLinesChanged(int index)
|
|
{
|
|
int logPrev;
|
|
int maxLines = logMaxLinesComboBox->itemData(index).toInt();
|
|
|
|
logPrev = logging;
|
|
logging = 0;
|
|
|
|
msleep(1);
|
|
|
|
initTraceLogBuffer(maxLines);
|
|
|
|
vbar->setMaximum(0);
|
|
vbar->setValue(0);
|
|
|
|
logging = logPrev;
|
|
|
|
traceView->update();
|
|
}
|
|
//----------------------------------------------------
|
|
void TraceLoggerDialog_t::clearLog(void)
|
|
{
|
|
int logPrev;
|
|
|
|
logPrev = logging;
|
|
logging = 0;
|
|
|
|
msleep(1);
|
|
|
|
recBufNum = recBufHead = 0;
|
|
|
|
vbar->setMaximum(0);
|
|
vbar->setValue(0);
|
|
|
|
logging = logPrev;
|
|
|
|
traceView->update();
|
|
}
|
|
//----------------------------------------------------
|
|
void TraceLoggerDialog_t::toggleLoggingOnOff(void)
|
|
{
|
|
if (logging)
|
|
{
|
|
logging = 0;
|
|
msleep(1);
|
|
pushMsgToLogBuffer("Logging Finished");
|
|
startStopButton->setText(tr("Start Logging"));
|
|
startStopButton->setIcon( style()->standardIcon( QStyle::SP_MediaPlay ) );
|
|
|
|
diskThread->requestInterruption();
|
|
diskThread->quit();
|
|
diskThread->wait(1000);
|
|
|
|
traceView->update();
|
|
}
|
|
else
|
|
{
|
|
if (logFileCbox->isChecked())
|
|
{
|
|
if ( logFilePath.size() == 0 )
|
|
{
|
|
openLogFile();
|
|
}
|
|
diskThread->start();
|
|
msleep(100);
|
|
}
|
|
pushMsgToLogBuffer("Log Start");
|
|
startStopButton->setText(tr("Stop Logging"));
|
|
startStopButton->setIcon( style()->standardIcon( QStyle::SP_MediaStop ) );
|
|
logging = 1;
|
|
}
|
|
}
|
|
//----------------------------------------------------
|
|
void TraceLoggerDialog_t::showBufferWarning(void)
|
|
{
|
|
const char *msg = "\
|
|
Error: Trace Logger Circular Buffer Overrun has been detected!\n\n\
|
|
This means that some instructions have not been written to the log\
|
|
file and resulting log of instructions is incomplete.\n\n\
|
|
Recommend increasing buffer size (max lines) to at least 1,000,000 lines.\n\n\
|
|
This message won't show again until logging is stopped and started again.";
|
|
|
|
if ( consoleWindow )
|
|
{
|
|
consoleWindow->QueueErrorMsgWindow(msg);
|
|
}
|
|
}
|
|
//----------------------------------------------------
|
|
void TraceLoggerDialog_t::openLogFile(void)
|
|
{
|
|
const char *romFile;
|
|
int ret, useNativeFileDialogVal;
|
|
QString filename;
|
|
QFileDialog dialog(this, tr("Select Log File"));
|
|
|
|
//printf("Log File Select\n");
|
|
|
|
dialog.setFileMode(QFileDialog::AnyFile);
|
|
|
|
dialog.setNameFilter(tr("LOG files (*.log *.LOG) ;; All files (*)"));
|
|
|
|
dialog.setViewMode(QFileDialog::List);
|
|
dialog.setFilter(QDir::AllEntries | QDir::AllDirs | QDir::Hidden);
|
|
dialog.setLabelText(QFileDialog::Accept, tr("Open"));
|
|
dialog.setDefaultSuffix(tr(".log"));
|
|
|
|
romFile = getRomFile();
|
|
|
|
if (romFile != NULL)
|
|
{
|
|
std::string dir;
|
|
getDirFromFile(romFile, dir);
|
|
dialog.setDirectory(tr(dir.c_str()));
|
|
}
|
|
|
|
if ( logFilePath.size() != 0 )
|
|
{
|
|
std::string dir;
|
|
getDirFromFile(logFilePath.c_str(), dir);
|
|
dialog.setDirectory(tr(dir.c_str()));
|
|
}
|
|
|
|
// Check config option to use native file dialog or not
|
|
g_config->getOption("SDL.UseNativeFileDialog", &useNativeFileDialogVal);
|
|
|
|
dialog.setOption(QFileDialog::DontUseNativeDialog, !useNativeFileDialogVal);
|
|
|
|
ret = dialog.exec();
|
|
|
|
if (ret)
|
|
{
|
|
QStringList fileList;
|
|
fileList = dialog.selectedFiles();
|
|
|
|
if (fileList.size() > 0)
|
|
{
|
|
filename = fileList[0];
|
|
}
|
|
}
|
|
|
|
if (filename.isNull())
|
|
{
|
|
return;
|
|
}
|
|
//qDebug() << "selected file path : " << filename.toLocal8Bit();
|
|
|
|
logFilePath = filename.toLocal8Bit().constData();
|
|
|
|
g_config->setOption("SDL.TraceLogSaveFilePath", logFilePath);
|
|
|
|
return;
|
|
}
|
|
//----------------------------------------------------
|
|
void TraceLoggerDialog_t::hbarChanged(int val)
|
|
{
|
|
traceView->update();
|
|
}
|
|
//----------------------------------------------------
|
|
void TraceLoggerDialog_t::vbarChanged(int val)
|
|
{
|
|
//printf("VBAR: %i \n", val );
|
|
traceView->update();
|
|
}
|
|
//----------------------------------------------------
|
|
void TraceLoggerDialog_t::vbarActionTriggered(int act)
|
|
{
|
|
int val = vbar->value();
|
|
int max = vbar->maximum();
|
|
|
|
if ( act == QAbstractSlider::SliderSingleStepAdd )
|
|
{
|
|
val = val - vbar->singleStep();
|
|
|
|
if ( val < 0 )
|
|
{
|
|
val = 0;
|
|
}
|
|
vbar->setSliderPosition(val);
|
|
}
|
|
else if ( act == QAbstractSlider::SliderSingleStepSub )
|
|
{
|
|
val = val + vbar->singleStep();
|
|
|
|
if ( val > max )
|
|
{
|
|
val = max;
|
|
}
|
|
vbar->setSliderPosition(val);
|
|
}
|
|
else if ( act == QAbstractSlider::SliderPageStepAdd )
|
|
{
|
|
val = val - vbar->pageStep();
|
|
|
|
if ( val < 0 )
|
|
{
|
|
val = 0;
|
|
}
|
|
vbar->setSliderPosition(val);
|
|
}
|
|
else if ( act == QAbstractSlider::SliderPageStepSub )
|
|
{
|
|
val = val + vbar->pageStep();
|
|
|
|
if ( val > max )
|
|
{
|
|
val = max;
|
|
}
|
|
vbar->setSliderPosition(val);
|
|
}
|
|
//printf("ACT:%i\n", act);
|
|
}
|
|
//----------------------------------------------------
|
|
void TraceLoggerDialog_t::pageUpActivated(void)
|
|
{
|
|
int val = vbar->value();
|
|
int max = vbar->maximum();
|
|
|
|
val = val + vbar->pageStep();
|
|
|
|
if ( val > max )
|
|
{
|
|
val = max;
|
|
}
|
|
vbar->setSliderPosition(val);
|
|
}
|
|
//----------------------------------------------------
|
|
void TraceLoggerDialog_t::pageDnActivated(void)
|
|
{
|
|
int val = vbar->value();
|
|
|
|
val = val - vbar->pageStep();
|
|
|
|
if ( val < 0 )
|
|
{
|
|
val = 0;
|
|
}
|
|
vbar->setSliderPosition(val);
|
|
}
|
|
//----------------------------------------------------
|
|
void TraceLoggerDialog_t::logToFileStateChanged(int state)
|
|
{
|
|
g_config->setOption("SDL.TraceLogSaveToFile", state != Qt::Unchecked );
|
|
}
|
|
//----------------------------------------------------
|
|
void TraceLoggerDialog_t::autoUpdateStateChanged(int state)
|
|
{
|
|
g_config->setOption("SDL.TraceLogPeriodicWindowUpdate", state != Qt::Unchecked );
|
|
}
|
|
//----------------------------------------------------
|
|
void TraceLoggerDialog_t::logRegStateChanged(int state)
|
|
{
|
|
if (state == Qt::Unchecked)
|
|
{
|
|
logging_options &= ~LOG_REGISTERS;
|
|
}
|
|
else
|
|
{
|
|
logging_options |= LOG_REGISTERS;
|
|
}
|
|
g_config->setOption("SDL.TraceLogRegisterState", (logging_options & LOG_REGISTERS) ? 1 : 0 );
|
|
}
|
|
//----------------------------------------------------
|
|
void TraceLoggerDialog_t::logFrameStateChanged(int state)
|
|
{
|
|
if (state == Qt::Unchecked)
|
|
{
|
|
logging_options &= ~LOG_FRAMES_COUNT;
|
|
}
|
|
else
|
|
{
|
|
logging_options |= LOG_FRAMES_COUNT;
|
|
}
|
|
g_config->setOption("SDL.TraceLogFrameCount", (logging_options & LOG_FRAMES_COUNT) ? 1 : 0 );
|
|
}
|
|
//----------------------------------------------------
|
|
void TraceLoggerDialog_t::logEmuMsgStateChanged(int state)
|
|
{
|
|
if (state == Qt::Unchecked)
|
|
{
|
|
logging_options &= ~LOG_MESSAGES;
|
|
}
|
|
else
|
|
{
|
|
logging_options |= LOG_MESSAGES;
|
|
}
|
|
g_config->setOption("SDL.TraceLogMessages", (logging_options & LOG_MESSAGES) ? 1 : 0 );
|
|
}
|
|
//----------------------------------------------------
|
|
void TraceLoggerDialog_t::symTraceEnaStateChanged(int state)
|
|
{
|
|
if (state == Qt::Unchecked)
|
|
{
|
|
logging_options &= ~LOG_SYMBOLIC;
|
|
}
|
|
else
|
|
{
|
|
logging_options |= LOG_SYMBOLIC;
|
|
}
|
|
g_config->setOption("SDL.TraceLogSymbolic", (logging_options & LOG_SYMBOLIC) ? 1 : 0 );
|
|
}
|
|
//----------------------------------------------------
|
|
void TraceLoggerDialog_t::logProcStatFlagStateChanged(int state)
|
|
{
|
|
if (state == Qt::Unchecked)
|
|
{
|
|
logging_options &= ~LOG_PROCESSOR_STATUS;
|
|
}
|
|
else
|
|
{
|
|
logging_options |= LOG_PROCESSOR_STATUS;
|
|
}
|
|
g_config->setOption("SDL.TraceLogProcessorState", (logging_options & LOG_PROCESSOR_STATUS) ? 1 : 0 );
|
|
}
|
|
//----------------------------------------------------
|
|
void TraceLoggerDialog_t::logCyclesCountStateChanged(int state)
|
|
{
|
|
if (state == Qt::Unchecked)
|
|
{
|
|
logging_options &= ~LOG_CYCLES_COUNT;
|
|
}
|
|
else
|
|
{
|
|
logging_options |= LOG_CYCLES_COUNT;
|
|
}
|
|
g_config->setOption("SDL.TraceLogCycleCount", (logging_options & LOG_CYCLES_COUNT) ? 1 : 0 );
|
|
}
|
|
//----------------------------------------------------
|
|
void TraceLoggerDialog_t::logBreakpointStateChanged(int state)
|
|
{
|
|
if (state == Qt::Unchecked)
|
|
{
|
|
logging_options &= ~LOG_BREAKPOINTS;
|
|
}
|
|
else
|
|
{
|
|
logging_options |= LOG_BREAKPOINTS;
|
|
}
|
|
g_config->setOption("SDL.TraceLogBreakpointHits", (logging_options & LOG_BREAKPOINTS) ? 1 : 0 );
|
|
}
|
|
//----------------------------------------------------
|
|
void TraceLoggerDialog_t::useStackPointerStateChanged(int state)
|
|
{
|
|
if (state == Qt::Unchecked)
|
|
{
|
|
logging_options &= ~LOG_CODE_TABBING;
|
|
}
|
|
else
|
|
{
|
|
logging_options |= LOG_CODE_TABBING;
|
|
}
|
|
g_config->setOption("SDL.TraceLogStackTabbing", (logging_options & LOG_CODE_TABBING) ? 1 : 0 );
|
|
}
|
|
//----------------------------------------------------
|
|
void TraceLoggerDialog_t::toLeftDisassemblyStateChanged(int state)
|
|
{
|
|
if (state == Qt::Unchecked)
|
|
{
|
|
logging_options &= ~LOG_TO_THE_LEFT;
|
|
}
|
|
else
|
|
{
|
|
logging_options |= LOG_TO_THE_LEFT;
|
|
}
|
|
g_config->setOption("SDL.TraceLogLeftDisassembly", (logging_options & LOG_TO_THE_LEFT) ? 1 : 0 );
|
|
}
|
|
//----------------------------------------------------
|
|
void TraceLoggerDialog_t::logInstrCountStateChanged(int state)
|
|
{
|
|
if (state == Qt::Unchecked)
|
|
{
|
|
logging_options &= ~LOG_INSTRUCTIONS_COUNT;
|
|
}
|
|
else
|
|
{
|
|
logging_options |= LOG_INSTRUCTIONS_COUNT;
|
|
}
|
|
g_config->setOption("SDL.TraceLogInstructionCount", (logging_options & LOG_INSTRUCTIONS_COUNT) ? 1 : 0 );
|
|
}
|
|
//----------------------------------------------------
|
|
void TraceLoggerDialog_t::logBankNumStateChanged(int state)
|
|
{
|
|
if (state == Qt::Unchecked)
|
|
{
|
|
logging_options &= ~LOG_BANK_NUMBER;
|
|
}
|
|
else
|
|
{
|
|
logging_options |= LOG_BANK_NUMBER;
|
|
}
|
|
g_config->setOption("SDL.TraceLogBankNumber", (logging_options & LOG_BANK_NUMBER) ? 1 : 0 );
|
|
}
|
|
//----------------------------------------------------
|
|
void TraceLoggerDialog_t::logNewMapCodeChanged(int state)
|
|
{
|
|
if (state == Qt::Unchecked)
|
|
{
|
|
logging_options &= ~LOG_NEW_INSTRUCTIONS;
|
|
}
|
|
else
|
|
{
|
|
logging_options |= LOG_NEW_INSTRUCTIONS;
|
|
}
|
|
g_config->setOption("SDL.TraceLogNewInstructions", (logging_options & LOG_NEW_INSTRUCTIONS) ? 1 : 0 );
|
|
}
|
|
//----------------------------------------------------
|
|
void TraceLoggerDialog_t::logNewMapDataChanged(int state)
|
|
{
|
|
if (state == Qt::Unchecked)
|
|
{
|
|
logging_options &= ~LOG_NEW_DATA;
|
|
}
|
|
else
|
|
{
|
|
logging_options |= LOG_NEW_DATA;
|
|
}
|
|
g_config->setOption("SDL.TraceLogNewData", (logging_options & LOG_NEW_DATA) ? 1 : 0 );
|
|
}
|
|
//----------------------------------------------------
|
|
traceRecord_t::traceRecord_t(void)
|
|
{
|
|
cpu.PC = 0;
|
|
cpu.A = 0;
|
|
cpu.X = 0;
|
|
cpu.Y = 0;
|
|
cpu.S = 0;
|
|
cpu.P = 0;
|
|
|
|
opCode[0] = 0;
|
|
opCode[1] = 0;
|
|
opCode[2] = 0;
|
|
opSize = 0;
|
|
asmTxtSize = 0;
|
|
asmTxt[0] = 0;
|
|
|
|
cycleCount = 0;
|
|
instrCount = 0;
|
|
flags = 0;
|
|
|
|
callAddr = -1;
|
|
romAddr = -1;
|
|
bank = -1;
|
|
skippedLines = 0;
|
|
}
|
|
//----------------------------------------------------
|
|
int traceRecord_t::appendAsmText(const char *txt)
|
|
{
|
|
size_t len = strlen(txt);
|
|
memcpy(asmTxt + asmTxtSize, txt, len + 1);
|
|
|
|
asmTxtSize += len;
|
|
|
|
return 0;
|
|
}
|
|
//----------------------------------------------------
|
|
int traceRecord_t::convToText(char *txt, int *len)
|
|
{
|
|
int i = 0, j = 0;
|
|
char str_axystate[32], str_procstatus[32];
|
|
|
|
str_axystate[0] = 0;
|
|
str_procstatus[0] = 0;
|
|
|
|
txt[0] = 0;
|
|
if (opSize == 0)
|
|
{
|
|
strcpy(txt, asmTxt);
|
|
|
|
return -1;
|
|
}
|
|
|
|
StringBuilder sb(txt + i);
|
|
if (skippedLines > 0)
|
|
sb << '(' << sb_dec(skippedLines) << " lines skipped) ";
|
|
|
|
// Start filling the str_temp line: Frame count, Cycles count, Instructions count, AXYS state, Processor status, Tabs, Address, Data, Disassembly
|
|
if (logging_options & LOG_FRAMES_COUNT)
|
|
sb << 'f' << sb_dec(frameCount, -6);
|
|
|
|
if (logging_options & LOG_CYCLES_COUNT)
|
|
sb << 'c' << sb_dec(cycleCount, -11);
|
|
|
|
if (logging_options & LOG_INSTRUCTIONS_COUNT)
|
|
sb << 'i' << sb_dec(instrCount, -11);
|
|
|
|
if (logging_options & LOG_REGISTERS)
|
|
{
|
|
StringBuilder sb(str_axystate);
|
|
sb << "A:" << sb_hex(cpu.A, 2)
|
|
<< " X:" << sb_hex(cpu.X, 2)
|
|
<< " Y:" << sb_hex(cpu.Y, 2)
|
|
<< " S:" << sb_hex(cpu.S, 2)
|
|
<< ' ';
|
|
}
|
|
|
|
if (logging_options & LOG_PROCESSOR_STATUS)
|
|
{
|
|
char *s = str_procstatus;
|
|
*(s++) = cpu.P & 0x80 ? 'N' : 'n';
|
|
*(s++) = cpu.P & 0x40 ? 'V' : 'v';
|
|
*(s++) = cpu.P & 0x20 ? 'U' : 'u';
|
|
*(s++) = cpu.P & 0x10 ? 'B' : 'b';
|
|
*(s++) = cpu.P & 0x08 ? 'D' : 'd';
|
|
*(s++) = cpu.P & 0x04 ? 'I' : 'i';
|
|
*(s++) = cpu.P & 0x02 ? 'Z' : 'z';
|
|
*(s++) = cpu.P & 0x01 ? 'C' : 'c';
|
|
*(s++) = ' ';
|
|
*(s++) = '\0';
|
|
}
|
|
|
|
if (logging_options & LOG_TO_THE_LEFT)
|
|
{
|
|
if (logging_options & LOG_REGISTERS)
|
|
sb << str_axystate;
|
|
if (logging_options & LOG_PROCESSOR_STATUS)
|
|
sb << str_procstatus;
|
|
}
|
|
|
|
if (logging_options & LOG_CODE_TABBING)
|
|
{
|
|
// add spaces at the beginning of the line according to stack pointer
|
|
int spaces = (0xFF - cpu.S) & LOG_TABS_MASK;
|
|
|
|
for (; spaces > 0; spaces--)
|
|
sb << ' ';
|
|
}
|
|
else if (logging_options & LOG_TO_THE_LEFT)
|
|
sb << ' ';
|
|
|
|
if (logging_options & LOG_BANK_NUMBER)
|
|
{
|
|
if (cpu.PC >= 0x8000)
|
|
sb << sb_addr((uint8_t)bank, 2) << ':';
|
|
else
|
|
sb << " $";
|
|
}
|
|
else
|
|
sb << '$';
|
|
|
|
sb << sb_hex(cpu.PC, 4) << ": ";
|
|
|
|
for (j = 0; j < opSize; j++)
|
|
sb << sb_hex(opCode[j], 2) << ' ';
|
|
for (; j < 3; j++)
|
|
sb << " ";
|
|
|
|
sb << asmTxt;
|
|
|
|
if (callAddr >= 0)
|
|
sb << " (from " << sb_addr((uint16_t)callAddr) << ')';
|
|
|
|
if (!(logging_options & LOG_TO_THE_LEFT))
|
|
{
|
|
if (logging_options & LOG_REGISTERS)
|
|
sb << str_axystate;
|
|
if (logging_options & LOG_PROCESSOR_STATUS)
|
|
sb << str_procstatus;
|
|
}
|
|
|
|
i = int(sb.str() + sb.size() - txt);
|
|
txt[i] = '\0';
|
|
|
|
if (len)
|
|
{
|
|
*len = i;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
//----------------------------------------------------
|
|
int initTraceLogBuffer(int maxRecs)
|
|
{
|
|
if (maxRecs != recBufMax)
|
|
{
|
|
size_t size;
|
|
|
|
size = maxRecs * sizeof(traceRecord_t);
|
|
|
|
if ( recBuf != NULL )
|
|
{
|
|
free(recBuf); recBuf = NULL;
|
|
}
|
|
|
|
recBuf = (traceRecord_t *)malloc(size);
|
|
|
|
if (recBuf)
|
|
{
|
|
memset((void *)recBuf, 0, size);
|
|
recBufMax = maxRecs;
|
|
}
|
|
else
|
|
{
|
|
recBufMax = 0;
|
|
}
|
|
recBufNum = recBufHead = 0;
|
|
}
|
|
return recBuf == NULL;
|
|
}
|
|
//----------------------------------------------------
|
|
void openTraceLoggerWindow(QWidget *parent)
|
|
{
|
|
// Only allow one trace logger window to be open
|
|
if (traceLogWindow != NULL)
|
|
{
|
|
traceLogWindow->activateWindow();
|
|
traceLogWindow->raise();
|
|
return;
|
|
}
|
|
//printf("Open Trace Logger Window\n");
|
|
|
|
traceLogWindow = new TraceLoggerDialog_t(parent);
|
|
|
|
traceLogWindow->show();
|
|
}
|
|
//----------------------------------------------------
|
|
static void pushToLogBuffer(traceRecord_t &rec)
|
|
{
|
|
|
|
recBuf[recBufHead] = rec;
|
|
recBufHead = (recBufHead + 1) % recBufMax;
|
|
|
|
if ( recBufNum < recBufMax )
|
|
{
|
|
recBufNum++;
|
|
}
|
|
|
|
if ( logBuf )
|
|
{
|
|
int nextHead, delayCount = 0;
|
|
logBuf[logBufHead] = rec;
|
|
nextHead = (logBufHead + 1) % logBufMax;
|
|
|
|
while (nextHead == logBufTail)
|
|
{
|
|
SDL_Delay(1);
|
|
|
|
delayCount++;
|
|
|
|
if ( delayCount > 10000 )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool overrun = nextHead == logBufTail;
|
|
|
|
logBufHead = nextHead;
|
|
|
|
if ( overrunWarningArmed )
|
|
{ // Don't spam with buffer overrun warning messages,
|
|
// we will print once if this happens.
|
|
if (overrun)
|
|
{
|
|
if ( traceLogWindow )
|
|
{
|
|
traceLogWindow->showBufferWarning();
|
|
}
|
|
printf("Trace Log Overrun!!!\n");
|
|
overrunWarningArmed = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//----------------------------------------------------
|
|
static void pushMsgToLogBuffer(const char *msg)
|
|
{
|
|
traceRecord_t rec;
|
|
|
|
strncpy(rec.asmTxt, msg, sizeof(rec.asmTxt));
|
|
|
|
rec.asmTxt[sizeof(rec.asmTxt) - 1] = 0;
|
|
|
|
pushToLogBuffer(rec);
|
|
}
|
|
//----------------------------------------------------
|
|
int FCEUD_TraceLoggerStart(void)
|
|
{
|
|
if ( !logging )
|
|
{
|
|
if (recBufMax == 0)
|
|
{
|
|
initTraceLogBuffer(1000000);
|
|
}
|
|
logging = 1;
|
|
}
|
|
return logging;
|
|
}
|
|
//----------------------------------------------------
|
|
int FCEUD_TraceLoggerStop(void)
|
|
{
|
|
if (!logging)
|
|
{
|
|
return 0;
|
|
}
|
|
if (traceLogWindow)
|
|
{
|
|
traceLogWindow->toggleLoggingOnOff();
|
|
}
|
|
else
|
|
{
|
|
logging = 0;
|
|
msleep(1);
|
|
pushMsgToLogBuffer("Logging Finished");
|
|
}
|
|
return logging;
|
|
}
|
|
//----------------------------------------------------
|
|
int FCEUD_TraceLoggerRunning(void)
|
|
{
|
|
return logging;
|
|
}
|
|
|
|
void FCEUD_FlushTrace()
|
|
{
|
|
//not needed, since it's doing something in a thread, i guess.
|
|
//seems weird that there's no way to rendezvous with it
|
|
}
|
|
|
|
//----------------------------------------------------
|
|
//todo: really speed this up
|
|
void FCEUD_TraceInstruction(uint8 *opcode, int size)
|
|
{
|
|
if (!logging)
|
|
return;
|
|
|
|
traceRecord_t rec;
|
|
|
|
char asmTxt[256];
|
|
unsigned int addr = X.PC;
|
|
static int unloggedlines = 0;
|
|
int asmFlags = ASM_DEBUG_TRACES;
|
|
|
|
rec.cpu.PC = X.PC;
|
|
rec.cpu.A = X.A;
|
|
rec.cpu.X = X.X;
|
|
rec.cpu.Y = X.Y;
|
|
rec.cpu.S = X.S;
|
|
rec.cpu.P = X.P;
|
|
|
|
for (int i = 0; i < size; i++)
|
|
{
|
|
rec.opCode[i] = opcode[i];
|
|
}
|
|
rec.opSize = size;
|
|
rec.romAddr = GetPRGAddress(addr);
|
|
rec.bank = getBank(addr);
|
|
|
|
rec.frameCount = currFrameCounter;
|
|
rec.instrCount = total_instructions;
|
|
|
|
int64 counter_value = timestampbase + (uint64)timestamp - total_cycles_base;
|
|
if (counter_value < 0) // sanity check
|
|
{
|
|
ResetDebugStatisticsCounters();
|
|
counter_value = 0;
|
|
}
|
|
rec.cycleCount = counter_value;
|
|
|
|
if (logging_options & LOG_SYMBOLIC)
|
|
{
|
|
asmFlags |= ASM_DEBUG_SYMS | ASM_DEBUG_REGS;
|
|
}
|
|
|
|
// if instruction executed from the RAM, skip this, log all instead
|
|
// TODO: loops folding mame-lyke style
|
|
if (rec.romAddr != -1)
|
|
{
|
|
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)
|
|
{
|
|
//snprintf(str_result, "(%d lines skipped)", unloggedlines);
|
|
rec.skippedLines = unloggedlines;
|
|
unloggedlines = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ((logging_options & LOG_NEW_INSTRUCTIONS) ||
|
|
(logging_options & LOG_NEW_DATA))
|
|
{
|
|
if (FCEUI_GetLoggingCD())
|
|
{
|
|
unloggedlines++;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((addr + size) > 0xFFFF)
|
|
{
|
|
//snprintf(str_data, "%02X ", opcode[0]);
|
|
//snprintf(str_disassembly, "OVERFLOW");
|
|
rec.flags |= 0x01;
|
|
}
|
|
else
|
|
{
|
|
char *a = 0;
|
|
switch (size)
|
|
{
|
|
case 0:
|
|
//snprintf(str_disassembly,"UNDEFINED");
|
|
rec.flags |= 0x02;
|
|
break;
|
|
case 1:
|
|
{
|
|
DisassembleWithDebug(addr + 1, opcode, asmFlags, asmTxt);
|
|
// special case: an RTS opcode
|
|
if (opcode[0] == 0x60)
|
|
{
|
|
// add the beginning address of the subroutine that we exit from
|
|
unsigned int caller_addr = GetMem(((X.S) + 1) | 0x0100) + (GetMem(((X.S) + 2) | 0x0100) << 8) - 0x2;
|
|
if (GetMem(caller_addr) == 0x20)
|
|
{
|
|
// this was a JSR instruction - take the subroutine address from it
|
|
unsigned int call_addr = GetMem(caller_addr + 1) + (GetMem(caller_addr + 2) << 8);
|
|
rec.callAddr = call_addr;
|
|
}
|
|
}
|
|
a = asmTxt;
|
|
break;
|
|
}
|
|
case 2:
|
|
DisassembleWithDebug(addr + 2, opcode, asmFlags, asmTxt);
|
|
a = asmTxt;
|
|
break;
|
|
case 3:
|
|
DisassembleWithDebug(addr + 3, opcode, asmFlags, asmTxt);
|
|
a = asmTxt;
|
|
break;
|
|
}
|
|
|
|
if (a)
|
|
{
|
|
rec.appendAsmText(a);
|
|
}
|
|
}
|
|
|
|
switch ( opcode[0] )
|
|
{
|
|
// Zero page
|
|
case 0x85: // STA - Store Accumulator
|
|
case 0x86: // STX - Store X Register
|
|
case 0x84: // STY - Store Y Register
|
|
case 0xC6: // DEC - Decrement Memory
|
|
case 0xE6: // INC - Increment Memory
|
|
rec.writeAddr = opcode[1];
|
|
break;
|
|
// Absolute
|
|
case 0x8D: // STA - Store Accumulator
|
|
case 0x8E: // STX - Store X Register
|
|
case 0x8C: // STY - Store Y Register
|
|
case 0xCE: // DEC - Decrement Memory
|
|
case 0xEE: // INC - Increment Memory
|
|
rec.writeAddr = opcode[1] | opcode[2] << 8;
|
|
break;
|
|
// Indirect X
|
|
case 0x81: // STA - Store A Register
|
|
rec.writeAddr = (opcode[1] + X.X) & 0xFF;
|
|
rec.writeAddr = GetMem((rec.writeAddr)) | (GetMem(((rec.writeAddr)+1)&0xff))<<8;
|
|
break;
|
|
// Indirect Y
|
|
case 0x91: // STA - Store A Register
|
|
rec.writeAddr = GetMem(opcode[1]) | (GetMem((opcode[1]+1)&0xff))<<8;
|
|
rec.writeAddr += X.Y;
|
|
break;
|
|
// Zero Page X
|
|
case 0x95: // STA - Store Accumulator
|
|
case 0x94: // STY - Store Y Register
|
|
case 0xD6: // DEC - Decrement Memory
|
|
case 0xF6: // INC - Increment Memory
|
|
rec.writeAddr = (opcode[1]+ X.X) & 0xFF;
|
|
break;
|
|
// Zero Page Y
|
|
case 0x96: // STX - Store X Register
|
|
rec.writeAddr = (opcode[1]+ X.Y) & 0xFF;
|
|
break;
|
|
default:
|
|
rec.writeAddr = -1;
|
|
break;
|
|
}
|
|
|
|
if ( rec.writeAddr >= 0 )
|
|
{
|
|
rec.preWriteVal = GetMem( rec.writeAddr );
|
|
}
|
|
else
|
|
{
|
|
rec.preWriteVal = 0;
|
|
}
|
|
|
|
pushToLogBuffer(rec);
|
|
|
|
return; // TEST
|
|
// All of the following log text creation is very cpu intensive, to keep emulation
|
|
// running realtime save data and have a separate thread do this translation.
|
|
|
|
//if (size == 1 && GetMem(addr) == 0x60)
|
|
//{
|
|
// // special case: an RTS opcode
|
|
// // add "----------" to emphasize the end of subroutine
|
|
// static const char* emphasize = " -------------------------------------------------------------------------------------------------------------------------";
|
|
// strncat(str_disassembly, emphasize, LOG_DISASSEMBLY_MAX_LEN - strlen(str_disassembly) - 1);
|
|
//}
|
|
|
|
return;
|
|
}
|
|
//----------------------------------------------------
|
|
QTraceLogView::QTraceLogView(QWidget *parent)
|
|
: QWidget(parent)
|
|
{
|
|
QPalette pal;
|
|
QColor fg("black"), bg("white");
|
|
QColor c;
|
|
bool useDarkTheme = false;
|
|
|
|
font.setFamily("Courier New");
|
|
font.setStyle(QFont::StyleNormal);
|
|
font.setStyleHint(QFont::Monospace);
|
|
|
|
pal = this->palette();
|
|
|
|
// Figure out if we are using a light or dark theme by checking the
|
|
// default window text grayscale color. If more white, then we will
|
|
// use white text on black background, else we do the opposite.
|
|
c = pal.color(QPalette::WindowText);
|
|
|
|
if (qGray(c.red(), c.green(), c.blue()) > 128)
|
|
{
|
|
useDarkTheme = true;
|
|
}
|
|
|
|
if (useDarkTheme)
|
|
{
|
|
pal.setColor(QPalette::Base, fg);
|
|
pal.setColor(QPalette::Window, fg);
|
|
pal.setColor(QPalette::WindowText, bg);
|
|
}
|
|
else
|
|
{
|
|
pal.setColor(QPalette::Base, bg);
|
|
pal.setColor(QPalette::Window, bg);
|
|
pal.setColor(QPalette::WindowText, fg);
|
|
}
|
|
|
|
this->setPalette(pal);
|
|
this->setMouseTracking(true);
|
|
this->setFocusPolicy(Qt::StrongFocus);
|
|
|
|
calcFontData();
|
|
|
|
vbar = NULL;
|
|
hbar = NULL;
|
|
|
|
wheelPixelCounter = 0;
|
|
mouseLeftBtnDown = false;
|
|
txtHlgtAnchorLine = -1;
|
|
txtHlgtAnchorChar = -1;
|
|
txtHlgtStartChar = -1;
|
|
txtHlgtStartLine = -1;
|
|
txtHlgtEndChar = -1;
|
|
txtHlgtEndLine = -1;
|
|
captureHighLightText = false;
|
|
|
|
selAddrIdx = -1;
|
|
selAddrLine = -1;
|
|
selAddrChar = -1;
|
|
selAddrWidth = -1;
|
|
selAddrValue = -1;
|
|
memset(selAddrText, 0, sizeof(selAddrText));
|
|
|
|
for (int i = 0; i < 64; i++)
|
|
{
|
|
lineBufIdx[i] = -1;
|
|
}
|
|
}
|
|
//----------------------------------------------------
|
|
QTraceLogView::~QTraceLogView(void)
|
|
{
|
|
}
|
|
//----------------------------------------------------
|
|
void QTraceLogView::calcFontData(void)
|
|
{
|
|
this->setFont(font);
|
|
QFontMetrics metrics(font);
|
|
#if QT_VERSION > QT_VERSION_CHECK(5, 11, 0)
|
|
pxCharWidth = metrics.horizontalAdvance(QLatin1Char('2'));
|
|
#else
|
|
pxCharWidth = metrics.width(QLatin1Char('2'));
|
|
#endif
|
|
pxCharHeight = metrics.height();
|
|
pxLineSpacing = metrics.lineSpacing() * 1.25;
|
|
pxLineLead = pxLineSpacing - pxCharHeight;
|
|
pxCursorHeight = pxCharHeight;
|
|
pxLineWidth = pxCharWidth * 20;
|
|
|
|
viewLines = (viewHeight / pxLineSpacing) + 1;
|
|
}
|
|
//----------------------------------------------------------------------------
|
|
void QTraceLogView::setScrollBars(QScrollBar *h, QScrollBar *v)
|
|
{
|
|
hbar = h;
|
|
vbar = v;
|
|
}
|
|
//----------------------------------------------------
|
|
void QTraceLogView::highlightClear(void)
|
|
{
|
|
txtHlgtEndLine = txtHlgtStartLine = txtHlgtAnchorLine;
|
|
txtHlgtEndChar = txtHlgtStartChar = txtHlgtAnchorChar;
|
|
|
|
selAddrIdx = -1;
|
|
selAddrLine = -1;
|
|
selAddrChar = -1;
|
|
selAddrWidth = -1;
|
|
selAddrValue = -1;
|
|
selAddrText[0] = 0;
|
|
}
|
|
//----------------------------------------------------
|
|
QPoint QTraceLogView::convPixToCursor(QPoint p)
|
|
{
|
|
QPoint c(0, 0);
|
|
|
|
if (p.x() < 0)
|
|
{
|
|
c.setX(0);
|
|
}
|
|
else
|
|
{
|
|
float x = (float)p.x() / pxCharWidth;
|
|
|
|
c.setX((int)x);
|
|
}
|
|
|
|
if (p.y() < 0)
|
|
{
|
|
c.setY(0);
|
|
}
|
|
else
|
|
{
|
|
float ly = ((float)pxLineLead / (float)pxLineSpacing);
|
|
float py = ((float)p.y()) / (float)pxLineSpacing;
|
|
float ry = fmod(py, 1.0);
|
|
|
|
if (ry < ly)
|
|
{
|
|
c.setY(((int)py) - 1);
|
|
}
|
|
else
|
|
{
|
|
c.setY((int)py);
|
|
}
|
|
}
|
|
return c;
|
|
}
|
|
//----------------------------------------------------------------------------
|
|
void QTraceLogView::calcTextSel(int x, int y)
|
|
{
|
|
int i, j;
|
|
char id[128];
|
|
//printf("Line: '%s' Char: %c\n", lineText[y].c_str(), lineText[y][x] );
|
|
|
|
selAddrIdx = -1;
|
|
selAddrLine = -1;
|
|
selAddrChar = -1;
|
|
selAddrWidth = -1;
|
|
selAddrValue = -1;
|
|
selAddrText[0] = 0;
|
|
|
|
if ( static_cast<size_t>(x) < lineText[y].size())
|
|
{
|
|
int ax = x;
|
|
|
|
if (isxdigit(lineText[y][ax]))
|
|
{
|
|
while ((ax >= 0) && isxdigit(lineText[y][ax]))
|
|
{
|
|
ax--;
|
|
}
|
|
if ((ax >= 0) && ((lineText[y][ax] == '$') || (lineText[y][ax] == ':')))
|
|
{
|
|
ax--;
|
|
if (lineText[y][ax] != '#')
|
|
{
|
|
i = 0;
|
|
ax += 2;
|
|
j = ax;
|
|
while (isxdigit(lineText[y][j]))
|
|
{
|
|
id[i] = lineText[y][j];
|
|
i++;
|
|
j++;
|
|
}
|
|
id[i] = 0;
|
|
|
|
selAddrIdx = lineBufIdx[y];
|
|
selAddrLine = y;
|
|
selAddrChar = ax;
|
|
selAddrWidth = i;
|
|
selAddrValue = strtol(id, NULL, 16);
|
|
strcpy(selAddrText, id);
|
|
|
|
//printf("Sel Addr: $%04X \n", selAddrValue );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//----------------------------------------------------------------------------
|
|
bool QTraceLogView::textIsHighlighted(void)
|
|
{
|
|
bool set = false;
|
|
|
|
if (txtHlgtStartLine == txtHlgtEndLine)
|
|
{
|
|
set = (txtHlgtStartChar != txtHlgtEndChar);
|
|
}
|
|
else
|
|
{
|
|
set = true;
|
|
}
|
|
return set;
|
|
}
|
|
//----------------------------------------------------------------------------
|
|
void QTraceLogView::setHighlightEndCoord(int x, int y)
|
|
{
|
|
|
|
if (txtHlgtAnchorLine < y)
|
|
{
|
|
txtHlgtStartLine = txtHlgtAnchorLine;
|
|
txtHlgtStartChar = txtHlgtAnchorChar;
|
|
txtHlgtEndLine = y;
|
|
txtHlgtEndChar = x;
|
|
}
|
|
else if (txtHlgtAnchorLine > y)
|
|
{
|
|
txtHlgtStartLine = y;
|
|
txtHlgtStartChar = x;
|
|
txtHlgtEndLine = txtHlgtAnchorLine;
|
|
txtHlgtEndChar = txtHlgtAnchorChar;
|
|
}
|
|
else
|
|
{
|
|
txtHlgtStartLine = txtHlgtAnchorLine;
|
|
txtHlgtEndLine = txtHlgtAnchorLine;
|
|
|
|
if (txtHlgtAnchorChar < x)
|
|
{
|
|
txtHlgtStartChar = txtHlgtAnchorChar;
|
|
txtHlgtEndChar = x;
|
|
}
|
|
else if (txtHlgtAnchorChar > x)
|
|
{
|
|
txtHlgtStartChar = x;
|
|
txtHlgtEndChar = txtHlgtAnchorChar;
|
|
}
|
|
else
|
|
{
|
|
txtHlgtStartChar = txtHlgtAnchorChar;
|
|
txtHlgtEndChar = txtHlgtAnchorChar;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
//----------------------------------------------------------------------------
|
|
void QTraceLogView::loadClipboard(const char *txt)
|
|
{
|
|
QClipboard *clipboard = QGuiApplication::clipboard();
|
|
|
|
clipboard->setText(tr(txt), QClipboard::Clipboard);
|
|
|
|
if (clipboard->supportsSelection())
|
|
{
|
|
clipboard->setText(tr(txt), QClipboard::Selection);
|
|
}
|
|
}
|
|
//----------------------------------------------------
|
|
void QTraceLogView::keyPressEvent(QKeyEvent *event)
|
|
{
|
|
//printf("Key Press: %i \n", event->key() );
|
|
|
|
if (!textIsHighlighted())
|
|
{
|
|
if (selAddrIdx >= 0)
|
|
{
|
|
if (event->key() == Qt::Key_B)
|
|
{
|
|
ctxMenuAddBP();
|
|
event->accept();
|
|
}
|
|
else if (event->key() == Qt::Key_S)
|
|
{
|
|
ctxMenuAddSym();
|
|
event->accept();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//----------------------------------------------------
|
|
void QTraceLogView::mouseMoveEvent(QMouseEvent *event)
|
|
{
|
|
QPoint c = convPixToCursor(event->pos());
|
|
|
|
if (mouseLeftBtnDown)
|
|
{
|
|
//printf("Left Button Move: (%i,%i)\n", c.x(), c.y() );
|
|
setHighlightEndCoord(c.x(), c.y());
|
|
}
|
|
}
|
|
//----------------------------------------------------
|
|
void QTraceLogView::mouseReleaseEvent(QMouseEvent *event)
|
|
{
|
|
QPoint c = convPixToCursor(event->pos());
|
|
|
|
if (event->button() == Qt::LeftButton)
|
|
{
|
|
//printf("Left Button Release: (%i,%i)\n", c.x(), c.y() );
|
|
mouseLeftBtnDown = false;
|
|
setHighlightEndCoord(c.x(), c.y());
|
|
|
|
captureHighLightText = true;
|
|
|
|
if (!textIsHighlighted())
|
|
{
|
|
calcTextSel(c.x(), c.y());
|
|
}
|
|
}
|
|
}
|
|
//----------------------------------------------------
|
|
void QTraceLogView::mousePressEvent(QMouseEvent *event)
|
|
{
|
|
QPoint c = convPixToCursor(event->pos());
|
|
|
|
//printf("Line: %i,%i\n", c.x(), c.y() );
|
|
|
|
if (event->button() == Qt::LeftButton)
|
|
{
|
|
//printf("Left Button Pressed: (%i,%i)\n", c.x(), c.y() );
|
|
mouseLeftBtnDown = true;
|
|
txtHlgtAnchorChar = c.x();
|
|
txtHlgtAnchorLine = c.y();
|
|
|
|
setHighlightEndCoord(c.x(), c.y());
|
|
}
|
|
}
|
|
//----------------------------------------------------
|
|
void QTraceLogView::wheelEvent(QWheelEvent *event)
|
|
{
|
|
int lineOffset;
|
|
|
|
QPoint numPixels = event->pixelDelta();
|
|
QPoint numDegrees = event->angleDelta();
|
|
|
|
lineOffset = vbar->value();
|
|
|
|
if (!numPixels.isNull())
|
|
{
|
|
wheelPixelCounter += numPixels.y();
|
|
//printf("numPixels: (%i,%i) \n", numPixels.x(), numPixels.y() );
|
|
}
|
|
else if (!numDegrees.isNull())
|
|
{
|
|
//QPoint numSteps = numDegrees / 15;
|
|
//printf("numSteps: (%i,%i) \n", numSteps.x(), numSteps.y() );
|
|
//printf("numDegrees: (%i,%i) %i\n", numDegrees.x(), numDegrees.y(), pxLineSpacing );
|
|
wheelPixelCounter += (pxLineSpacing * numDegrees.y()) / (15 * 8);
|
|
}
|
|
//printf("Wheel Event: %i\n", wheelPixelCounter);
|
|
|
|
if (wheelPixelCounter >= pxLineSpacing)
|
|
{
|
|
lineOffset += (wheelPixelCounter / pxLineSpacing);
|
|
|
|
if (lineOffset > recBufMax)
|
|
{
|
|
lineOffset = recBufMax;
|
|
}
|
|
vbar->setValue(lineOffset);
|
|
|
|
wheelPixelCounter = wheelPixelCounter % pxLineSpacing;
|
|
}
|
|
else if (wheelPixelCounter <= -pxLineSpacing)
|
|
{
|
|
lineOffset += (wheelPixelCounter / pxLineSpacing);
|
|
|
|
if (lineOffset < 0)
|
|
{
|
|
lineOffset = 0;
|
|
}
|
|
vbar->setValue(lineOffset);
|
|
|
|
wheelPixelCounter = wheelPixelCounter % pxLineSpacing;
|
|
}
|
|
|
|
event->accept();
|
|
}
|
|
//----------------------------------------------------
|
|
void QTraceLogView::resizeEvent(QResizeEvent *event)
|
|
{
|
|
viewWidth = event->size().width();
|
|
viewHeight = event->size().height();
|
|
|
|
//printf("QAsmView Resize: %ix%i\n", viewWidth, viewHeight );
|
|
|
|
viewLines = (viewHeight / pxLineSpacing) + 1;
|
|
|
|
//maxLineOffset = 0; // mb.numLines() - viewLines + 1;
|
|
|
|
if (viewWidth >= pxLineWidth)
|
|
{
|
|
pxLineXScroll = 0;
|
|
hbar->hide();
|
|
}
|
|
else
|
|
{
|
|
hbar->setPageStep( viewWidth );
|
|
hbar->setMaximum( pxLineWidth - viewWidth );
|
|
hbar->show();
|
|
pxLineXScroll = hbar->value();
|
|
}
|
|
}
|
|
//----------------------------------------------------------------------------
|
|
void QTraceLogView::openBpEditWindow(int editIdx, watchpointinfo *wp, traceRecord_t *recp)
|
|
{
|
|
int ret;
|
|
QDialog dialog(this);
|
|
QHBoxLayout *hbox;
|
|
QVBoxLayout *mainLayout, *vbox;
|
|
QLabel *lbl;
|
|
QLineEdit *addr1, *addr2, *cond, *name;
|
|
QCheckBox *forbidChkBox, *rbp, *wbp, *xbp, *ebp;
|
|
QGridLayout *grid;
|
|
QFrame *frame;
|
|
QGroupBox *gbox;
|
|
QPushButton *okButton, *cancelButton;
|
|
QRadioButton *cpu_radio, *ppu_radio, *sprite_radio;
|
|
|
|
if (editIdx >= 0)
|
|
{
|
|
dialog.setWindowTitle(tr("Edit Breakpoint"));
|
|
}
|
|
else
|
|
{
|
|
dialog.setWindowTitle(tr("Add Breakpoint"));
|
|
}
|
|
|
|
hbox = new QHBoxLayout();
|
|
mainLayout = new QVBoxLayout();
|
|
|
|
mainLayout->addLayout(hbox);
|
|
|
|
lbl = new QLabel(tr("Address"));
|
|
addr1 = new QLineEdit();
|
|
|
|
hbox->addWidget(lbl);
|
|
hbox->addWidget(addr1);
|
|
|
|
lbl = new QLabel(tr("-"));
|
|
addr2 = new QLineEdit();
|
|
hbox->addWidget(lbl);
|
|
hbox->addWidget(addr2);
|
|
|
|
forbidChkBox = new QCheckBox(tr("Forbid"));
|
|
hbox->addWidget(forbidChkBox);
|
|
|
|
frame = new QFrame();
|
|
vbox = new QVBoxLayout();
|
|
hbox = new QHBoxLayout();
|
|
gbox = new QGroupBox();
|
|
|
|
rbp = new QCheckBox(tr("Read"));
|
|
wbp = new QCheckBox(tr("Write"));
|
|
xbp = new QCheckBox(tr("Execute"));
|
|
ebp = new QCheckBox(tr("Enable"));
|
|
|
|
gbox->setTitle(tr("Memory"));
|
|
mainLayout->addWidget(frame);
|
|
frame->setLayout(vbox);
|
|
frame->setFrameShape(QFrame::Box);
|
|
vbox->addLayout(hbox);
|
|
vbox->addWidget(gbox);
|
|
|
|
hbox->addWidget(rbp);
|
|
hbox->addWidget(wbp);
|
|
hbox->addWidget(xbp);
|
|
hbox->addWidget(ebp);
|
|
|
|
hbox = new QHBoxLayout();
|
|
cpu_radio = new QRadioButton(tr("CPU Mem"));
|
|
ppu_radio = new QRadioButton(tr("PPU Mem"));
|
|
sprite_radio = new QRadioButton(tr("Sprite Mem"));
|
|
cpu_radio->setChecked(true);
|
|
|
|
gbox->setLayout(hbox);
|
|
hbox->addWidget(cpu_radio);
|
|
hbox->addWidget(ppu_radio);
|
|
hbox->addWidget(sprite_radio);
|
|
|
|
grid = new QGridLayout();
|
|
|
|
mainLayout->addLayout(grid);
|
|
lbl = new QLabel(tr("Condition"));
|
|
cond = new QLineEdit();
|
|
|
|
grid->addWidget(lbl, 0, 0);
|
|
grid->addWidget(cond, 0, 1);
|
|
|
|
lbl = new QLabel(tr("Name"));
|
|
name = new QLineEdit();
|
|
|
|
grid->addWidget(lbl, 1, 0);
|
|
grid->addWidget(name, 1, 1);
|
|
|
|
hbox = new QHBoxLayout();
|
|
okButton = new QPushButton(tr("OK"));
|
|
cancelButton = new QPushButton(tr("Cancel"));
|
|
|
|
mainLayout->addLayout(hbox);
|
|
hbox->addWidget(cancelButton);
|
|
hbox->addWidget(okButton);
|
|
|
|
connect(okButton, SIGNAL(clicked(void)), &dialog, SLOT(accept(void)));
|
|
connect(cancelButton, SIGNAL(clicked(void)), &dialog, SLOT(reject(void)));
|
|
|
|
okButton->setDefault(true);
|
|
|
|
if (wp != NULL)
|
|
{
|
|
char stmp[256];
|
|
|
|
if (wp->flags & BT_P)
|
|
{
|
|
ppu_radio->setChecked(true);
|
|
}
|
|
else if (wp->flags & BT_S)
|
|
{
|
|
sprite_radio->setChecked(true);
|
|
}
|
|
|
|
snprintf(stmp, sizeof(stmp), "%04X", wp->address);
|
|
|
|
addr1->setText(tr(stmp));
|
|
|
|
if (wp->endaddress > 0)
|
|
{
|
|
snprintf(stmp, sizeof(stmp), "%04X", wp->endaddress);
|
|
|
|
addr2->setText(tr(stmp));
|
|
}
|
|
|
|
if (wp->flags & WP_R)
|
|
{
|
|
rbp->setChecked(true);
|
|
}
|
|
if (wp->flags & WP_W)
|
|
{
|
|
wbp->setChecked(true);
|
|
}
|
|
if (wp->flags & WP_X)
|
|
{
|
|
xbp->setChecked(true);
|
|
}
|
|
if (wp->flags & WP_F)
|
|
{
|
|
forbidChkBox->setChecked(true);
|
|
}
|
|
if (wp->flags & WP_E)
|
|
{
|
|
ebp->setChecked(true);
|
|
}
|
|
|
|
if (wp->condText)
|
|
{
|
|
cond->setText(tr(wp->condText));
|
|
}
|
|
else
|
|
{
|
|
if (editIdx < 0)
|
|
{
|
|
// If new breakpoint, suggest condition if in ROM Mapping area of memory.
|
|
if (wp->address >= 0x8000)
|
|
{
|
|
char str[64];
|
|
if ((wp->address == recp->cpu.PC) && (recp->bank >= 0))
|
|
{
|
|
snprintf(str, sizeof(str), "K==#%02X", recp->bank);
|
|
}
|
|
else
|
|
{
|
|
snprintf(str, sizeof(str), "K==#%02X", getBank(wp->address));
|
|
}
|
|
cond->setText(tr(str));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (wp->desc)
|
|
{
|
|
name->setText(tr(wp->desc));
|
|
}
|
|
}
|
|
|
|
dialog.setLayout(mainLayout);
|
|
|
|
ret = dialog.exec();
|
|
|
|
if (ret == QDialog::Accepted)
|
|
{
|
|
int start_addr = -1, end_addr = -1, type = 0, enable = 1, slot;
|
|
std::string s;
|
|
|
|
slot = (editIdx < 0) ? numWPs : editIdx;
|
|
|
|
if (cpu_radio->isChecked())
|
|
{
|
|
type |= BT_C;
|
|
}
|
|
else if (ppu_radio->isChecked())
|
|
{
|
|
type |= BT_P;
|
|
}
|
|
else if (sprite_radio->isChecked())
|
|
{
|
|
type |= BT_S;
|
|
}
|
|
|
|
s = addr1->text().toLocal8Bit().constData();
|
|
|
|
if (s.size() > 0)
|
|
{
|
|
start_addr = offsetStringToInt(type, s.c_str());
|
|
}
|
|
|
|
s = addr2->text().toLocal8Bit().constData();
|
|
|
|
if (s.size() > 0)
|
|
{
|
|
end_addr = offsetStringToInt(type, s.c_str());
|
|
}
|
|
|
|
if (rbp->isChecked())
|
|
{
|
|
type |= WP_R;
|
|
}
|
|
if (wbp->isChecked())
|
|
{
|
|
type |= WP_W;
|
|
}
|
|
if (xbp->isChecked())
|
|
{
|
|
type |= WP_X;
|
|
}
|
|
|
|
if (forbidChkBox->isChecked())
|
|
{
|
|
type |= WP_F;
|
|
}
|
|
|
|
enable = ebp->isChecked();
|
|
|
|
if ((start_addr >= 0) && (numWPs < 64))
|
|
{
|
|
unsigned int retval;
|
|
std::string nameString, condString;
|
|
|
|
nameString = name->text().toLocal8Bit().constData();
|
|
condString = cond->text().toLocal8Bit().constData();
|
|
|
|
retval = NewBreak(nameString.c_str(), start_addr, end_addr, type, condString.c_str(), slot, enable);
|
|
|
|
if ((retval == 1) || (retval == 2))
|
|
{
|
|
printf("Breakpoint Add Failed\n");
|
|
}
|
|
else
|
|
{
|
|
if (editIdx < 0)
|
|
{
|
|
numWPs++;
|
|
}
|
|
|
|
updateAllDebuggerWindows(QAsmView::UPDATE_NO_SCROLL);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//----------------------------------------------------------------------------
|
|
void QTraceLogView::ctxMenuAddBP(void)
|
|
{
|
|
watchpointinfo wp;
|
|
traceRecord_t *recp = NULL;
|
|
|
|
wp.address = selAddrValue;
|
|
wp.endaddress = 0;
|
|
wp.flags = WP_X | WP_E;
|
|
wp.condText = 0;
|
|
wp.desc = NULL;
|
|
|
|
if (selAddrLine >= 0)
|
|
{
|
|
recp = &rec[selAddrLine];
|
|
}
|
|
|
|
if (recp != NULL)
|
|
{
|
|
openBpEditWindow(-1, &wp, recp);
|
|
}
|
|
}
|
|
//----------------------------------------------------------------------------
|
|
void QTraceLogView::openDebugSymbolEditWindow(int addr, int bank)
|
|
{
|
|
int ret;
|
|
debugSymbol_t *sym;
|
|
SymbolEditWindow win(this);
|
|
|
|
sym = debugSymbolTable.getSymbolAtBankOffset(bank, addr);
|
|
|
|
win.setAddr(addr);
|
|
win.setBank(bank);
|
|
|
|
win.setSym(sym);
|
|
|
|
ret = win.exec();
|
|
|
|
if (ret == QDialog::Accepted)
|
|
{
|
|
updateAllDebuggerWindows(QAsmView::UPDATE_NO_SCROLL);
|
|
}
|
|
}
|
|
//----------------------------------------------------------------------------
|
|
void QTraceLogView::ctxMenuAddSym(void)
|
|
{
|
|
int addr, bank = -1;
|
|
traceRecord_t *recp = NULL;
|
|
|
|
addr = selAddrValue;
|
|
|
|
if (selAddrLine >= 0)
|
|
{
|
|
recp = &rec[selAddrLine];
|
|
}
|
|
|
|
if (addr < 0x8000)
|
|
{
|
|
bank = -1;
|
|
}
|
|
else
|
|
{
|
|
if (recp != NULL)
|
|
{
|
|
if ((addr == recp->cpu.PC) && (recp->bank >= 0))
|
|
{
|
|
bank = recp->bank;
|
|
}
|
|
else
|
|
{
|
|
bank = getBank(addr);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bank = getBank(addr);
|
|
}
|
|
}
|
|
|
|
openDebugSymbolEditWindow(addr, bank);
|
|
}
|
|
//----------------------------------------------------------------------------
|
|
void QTraceLogView::contextMenuEvent(QContextMenuEvent *event)
|
|
{
|
|
QAction *act;
|
|
QMenu menu(this);
|
|
QPoint c = convPixToCursor(event->pos());
|
|
|
|
if (!textIsHighlighted())
|
|
{
|
|
calcTextSel(c.x(), c.y());
|
|
|
|
if (selAddrIdx >= 0)
|
|
{
|
|
act = new QAction(tr("Add Symbolic Debug Marker"), &menu);
|
|
menu.addAction(act);
|
|
act->setShortcut(QKeySequence(tr("S")));
|
|
connect(act, SIGNAL(triggered(void)), this, SLOT(ctxMenuAddSym(void)));
|
|
|
|
act = new QAction(tr("Add Breakpoint"), &menu);
|
|
menu.addAction(act);
|
|
act->setShortcut(QKeySequence(tr("B")));
|
|
connect(act, SIGNAL(triggered(void)), this, SLOT(ctxMenuAddBP(void)));
|
|
|
|
menu.exec(event->globalPos());
|
|
}
|
|
}
|
|
}
|
|
//----------------------------------------------------
|
|
void QTraceLogView::drawText(QPainter *painter, int x, int y, const char *txt, int maxChars)
|
|
{
|
|
int i = 0;
|
|
char c[2];
|
|
|
|
c[0] = 0;
|
|
c[1] = 0;
|
|
|
|
while ((txt[i] != 0) && (i < maxChars))
|
|
{
|
|
c[0] = txt[i];
|
|
painter->drawText(x, y, tr(c));
|
|
i++;
|
|
x += pxCharWidth;
|
|
}
|
|
}
|
|
//----------------------------------------------------
|
|
void QTraceLogView::paintEvent(QPaintEvent *event)
|
|
{
|
|
int i, x, y, v, row, start, end, nrow, lineLen;
|
|
QPainter painter(this);
|
|
char line[256];
|
|
QColor hlgtFG("white"), hlgtBG("blue");
|
|
|
|
painter.setFont(font);
|
|
viewWidth = event->rect().width();
|
|
viewHeight = event->rect().height();
|
|
|
|
nrow = (viewHeight / pxLineSpacing);
|
|
|
|
if (nrow < 1)
|
|
{
|
|
nrow = 1;
|
|
}
|
|
|
|
viewLines = nrow;
|
|
|
|
painter.fillRect(0, 0, viewWidth, viewHeight, this->palette().color(QPalette::Window));
|
|
|
|
painter.setPen(this->palette().color(QPalette::WindowText));
|
|
|
|
v = vbar->value();
|
|
|
|
if ( viewLines >= recBufNum )
|
|
{
|
|
vbar->hide();
|
|
}
|
|
else
|
|
{
|
|
vbar->setMaximum( recBufNum - viewLines );
|
|
vbar->setPageStep( (viewLines*7)/8 );
|
|
vbar->show();
|
|
}
|
|
|
|
if (viewWidth >= pxLineWidth)
|
|
{
|
|
pxLineXScroll = 0;
|
|
hbar->hide();
|
|
}
|
|
else
|
|
{
|
|
hbar->setPageStep( viewWidth );
|
|
hbar->setMaximum( pxLineWidth - viewWidth );
|
|
hbar->show();
|
|
pxLineXScroll = hbar->value();
|
|
}
|
|
|
|
end = recBufHead - v;
|
|
|
|
if (end < 0)
|
|
end += recBufMax;
|
|
|
|
start = (end - nrow);
|
|
|
|
if (start < 0)
|
|
start += recBufMax;
|
|
|
|
for (i = 0; i < 64; i++)
|
|
{
|
|
lineBufIdx[i] = -1;
|
|
}
|
|
|
|
row = 0;
|
|
while (start != end)
|
|
{
|
|
lineBufIdx[row] = start;
|
|
rec[row] = recBuf[start];
|
|
row++;
|
|
start = (start + 1) % recBufMax;
|
|
}
|
|
|
|
if (captureHighLightText)
|
|
{
|
|
hlgtText.clear();
|
|
}
|
|
|
|
x = -pxLineXScroll;
|
|
y = pxLineSpacing;
|
|
|
|
for (row = 0; row < nrow; row++)
|
|
{
|
|
lineLen = 0;
|
|
|
|
rec[row].convToText(line, &lineLen);
|
|
|
|
lineText[row].assign(line);
|
|
|
|
if ( (lineLen*pxCharWidth) > pxLineWidth )
|
|
{
|
|
pxLineWidth = (lineLen*pxCharWidth);
|
|
}
|
|
//printf("Line %i: '%s'\n", row, line );
|
|
|
|
drawText(&painter, x, y, line, 256);
|
|
|
|
if (textIsHighlighted())
|
|
{
|
|
int l = row;
|
|
|
|
if ((l >= txtHlgtStartLine) && (l <= txtHlgtEndLine))
|
|
{
|
|
int ax, hlgtXs, hlgtXe, hlgtXd;
|
|
|
|
if (l == txtHlgtStartLine)
|
|
{
|
|
hlgtXs = txtHlgtStartChar;
|
|
}
|
|
else
|
|
{
|
|
hlgtXs = 0;
|
|
}
|
|
|
|
if (l == txtHlgtEndLine)
|
|
{
|
|
hlgtXe = txtHlgtEndChar;
|
|
}
|
|
else
|
|
{
|
|
hlgtXe = (viewWidth / pxCharWidth) + 1;
|
|
}
|
|
hlgtXd = (hlgtXe - hlgtXs);
|
|
|
|
ax = x + (hlgtXs * pxCharWidth);
|
|
|
|
painter.fillRect(ax, y - pxLineSpacing + pxLineLead, hlgtXd * pxCharWidth, pxLineSpacing, hlgtBG);
|
|
|
|
if (hlgtXs < lineLen)
|
|
{
|
|
painter.setPen(hlgtFG);
|
|
|
|
drawText(&painter, ax, y, &line[hlgtXs], hlgtXd);
|
|
|
|
painter.setPen(this->palette().color(QPalette::WindowText));
|
|
|
|
for (int i = 0; i < hlgtXd; i++)
|
|
{
|
|
if (line[hlgtXs + i] == 0)
|
|
{
|
|
break;
|
|
}
|
|
hlgtText.append(1, line[hlgtXs + i]);
|
|
}
|
|
}
|
|
if (l != txtHlgtEndLine)
|
|
{
|
|
hlgtText.append("\n");
|
|
}
|
|
}
|
|
}
|
|
else if ((selAddrIdx >= 0) && (selAddrIdx == lineBufIdx[row]))
|
|
{
|
|
if ((selAddrChar >= 0) && (selAddrChar < lineLen))
|
|
{
|
|
if (strncmp(&line[selAddrChar], selAddrText, selAddrWidth) == 0)
|
|
{
|
|
int ax = x + (selAddrChar * pxCharWidth);
|
|
|
|
painter.fillRect(ax, y - pxLineSpacing + pxLineLead, selAddrWidth * pxCharWidth, pxLineSpacing, hlgtBG);
|
|
|
|
painter.setPen(hlgtFG);
|
|
|
|
drawText(&painter, ax, y, selAddrText);
|
|
|
|
painter.setPen(this->palette().color(QPalette::WindowText));
|
|
}
|
|
}
|
|
}
|
|
y += pxLineSpacing;
|
|
}
|
|
|
|
if (captureHighLightText)
|
|
{
|
|
//printf("Highlighted Text:\n%s\n", hlgtText.c_str() );
|
|
|
|
if (textIsHighlighted())
|
|
{
|
|
loadClipboard(hlgtText.c_str());
|
|
}
|
|
captureHighLightText = false;
|
|
}
|
|
}
|
|
//----------------------------------------------------
|
|
TraceLogDiskThread_t::TraceLogDiskThread_t( QObject *parent )
|
|
: QThread(parent)
|
|
{
|
|
setObjectName( QString("TraceLogDiskThread") );
|
|
}
|
|
//----------------------------------------------------
|
|
TraceLogDiskThread_t::~TraceLogDiskThread_t(void)
|
|
{
|
|
//printf("Disk Thread Cleanup\n");
|
|
#ifdef WIN32
|
|
if (logFile != INVALID_HANDLE_VALUE)
|
|
{
|
|
CloseHandle( logFile );
|
|
logFile = INVALID_HANDLE_VALUE;
|
|
}
|
|
#else
|
|
if ( logFile != -1 )
|
|
{
|
|
close(logFile); logFile = -1;
|
|
}
|
|
#endif
|
|
|
|
if ( logBuf )
|
|
{
|
|
free(logBuf);
|
|
logBuf = NULL;
|
|
}
|
|
}
|
|
//----------------------------------------------------
|
|
void TraceLogDiskThread_t::run(void)
|
|
{
|
|
char line[256];
|
|
const unsigned blockSize = 4 * 1024;
|
|
bool dataNeedsFlush = true;
|
|
bool isPaused = false;
|
|
|
|
//printf("Trace Log Disk Start\n");
|
|
|
|
setPriority( QThread::HighestPriority );
|
|
|
|
#ifdef WIN32
|
|
TraceFileWriter tracer;
|
|
if (!tracer.open(logFilePath.c_str(), (bool)FCEUI_EmulationPaused()))
|
|
{
|
|
char stmp[1024];
|
|
snprintf( stmp, sizeof(stmp), "Error: Failed to open log file for writing: %s", logFilePath.c_str() );
|
|
consoleWindow->QueueErrorMsgWindow(stmp);
|
|
return;
|
|
}
|
|
#else
|
|
const unsigned bufSize = blockSize * 2;
|
|
const unsigned flushSize = blockSize;
|
|
char buf[bufSize];
|
|
unsigned int i, idx=0;
|
|
|
|
logFile = open( logFilePath.c_str(), O_CREAT | O_WRONLY | O_TRUNC,
|
|
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH );
|
|
|
|
if ( logFile == -1 )
|
|
{
|
|
char stmp[1024];
|
|
snprintf( stmp, sizeof(stmp), "Error: Failed to open log file for writing: %s", logFilePath.c_str() );
|
|
consoleWindow->QueueErrorMsgWindow(stmp);
|
|
return;
|
|
}
|
|
#endif
|
|
if ( logBuf == NULL )
|
|
{
|
|
size_t size;
|
|
|
|
size = logBufMax * sizeof(traceRecord_t);
|
|
|
|
logBufHead = logBufTail = 0;
|
|
|
|
logBuf = (traceRecord_t *)malloc(size);
|
|
}
|
|
|
|
while ( !isInterruptionRequested() )
|
|
{
|
|
isPaused = FCEUI_EmulationPaused() ? true : false;
|
|
|
|
while (logBufHead != logBufTail)
|
|
{
|
|
logBuf[logBufTail].convToText(line);
|
|
logBufTail = (logBufTail + 1) % logBufMax;
|
|
|
|
#ifdef WIN32
|
|
bool success = tracer.writeLine(line);
|
|
|
|
/// TODO: Do something on error
|
|
#else
|
|
i=0;
|
|
while ( line[i] != 0 )
|
|
{
|
|
buf[idx] = line[i]; i++; idx++;
|
|
}
|
|
buf[idx] = '\n'; idx++;
|
|
|
|
if ( idx >= flushSize )
|
|
{
|
|
if ( write( logFile, buf, idx ) < 0 )
|
|
{
|
|
// HANDLE ERROR TODO
|
|
}
|
|
idx = 0;
|
|
dataNeedsFlush = true;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#ifdef WIN32
|
|
bool success = tracer.setPause(isPaused);
|
|
|
|
/// TODO: Do something on error
|
|
#else
|
|
if (isPaused)
|
|
{
|
|
// If paused, the user might be at a breakpoint or doing some
|
|
// debugging. So make sure all data is flushed to disk for viewing.
|
|
// Only flush data when paused, to keep write efficiency up.
|
|
if ( idx > 0 )
|
|
{
|
|
if ( write( logFile, buf, idx ) < 0 )
|
|
{
|
|
// HANDLE ERROR TODO
|
|
}
|
|
idx = 0;
|
|
dataNeedsFlush = true;
|
|
}
|
|
if (dataNeedsFlush)
|
|
{
|
|
//printf("Flushing Trace Log Disk Buffers\n");
|
|
if ( fsync( logFile ) )
|
|
{
|
|
printf("Trace Log fsync error\n");
|
|
}
|
|
dataNeedsFlush = false;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
SDL_Delay(1);
|
|
}
|
|
|
|
#ifdef WIN32
|
|
tracer.close();
|
|
#else
|
|
if ( idx > 0 )
|
|
{
|
|
if ( write( logFile, buf, idx ) < 0 )
|
|
{
|
|
// HANDLE ERROR TODO
|
|
}
|
|
idx = 0;
|
|
}
|
|
|
|
if ( logFile != -1 )
|
|
{
|
|
close(logFile); logFile = -1;
|
|
}
|
|
#endif
|
|
|
|
//printf("Trace Log Disk Exit\n");
|
|
emit finished();
|
|
}
|
|
//----------------------------------------------------
|
|
//--- Trace Logger BackUp (Undo) Instruction
|
|
//----------------------------------------------------
|
|
static int undoInstruction( traceRecord_t &rec )
|
|
{
|
|
// TODO Undo memory writes
|
|
//printf("BackUp (Undo) Instruction\n");
|
|
X.PC = rec.cpu.PC;
|
|
X.A = rec.cpu.A;
|
|
X.X = rec.cpu.X;
|
|
X.Y = rec.cpu.Y;
|
|
X.S = rec.cpu.S;
|
|
X.P = rec.cpu.P;
|
|
|
|
if ( rec.writeAddr >= 0 )
|
|
{
|
|
if ( rec.writeAddr < 0x8000 )
|
|
{
|
|
writefunc wfunc;
|
|
|
|
wfunc = GetWriteHandler (rec.writeAddr);
|
|
|
|
if (wfunc)
|
|
{
|
|
wfunc ((uint32) rec.writeAddr,
|
|
(uint8) (rec.preWriteVal & 0x000000ff));
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
//----------------------------------------------------
|
|
int FCEUD_TraceLoggerBackUpInstruction(void)
|
|
{
|
|
int ret, idx;
|
|
|
|
if ( recBufNum <= 0 )
|
|
{
|
|
return -1;
|
|
}
|
|
idx = recBufHead - 1;
|
|
|
|
if ( idx < 0 )
|
|
{
|
|
idx += recBufMax;
|
|
}
|
|
ret = undoInstruction( recBuf[idx] );
|
|
|
|
if ( ret == 0 )
|
|
{
|
|
recBufNum--;
|
|
recBufHead = idx;
|
|
}
|
|
return ret;
|
|
}
|
|
//----------------------------------------------------
|