mirror of
https://github.com/RetroDECK/Supermodel.git
synced 2024-11-22 22:05:38 +00:00
97ac46c82a
- Added CPrintBreakpoint Changes to console-based debugger: - Added new scp command and tidied up some other commands - Tidied up event logging - Sorted commands into common groups - Added ReadMe.txt help file
421 lines
10 KiB
C++
421 lines
10 KiB
C++
#ifdef SUPERMODEL_DEBUGGER
|
|
|
|
#include "Supermodel.h"
|
|
|
|
#include "ConsoleDebugger.h"
|
|
#include "CPUDebug.h"
|
|
#include "Label.h"
|
|
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
|
|
namespace Debugger
|
|
{
|
|
CSupermodelDebugger::CSupermodelDebugger(::CModel3 *model3, ::CInputs *inputs, ::CLogger *logger) :
|
|
CConsoleDebugger(), m_model3(model3), m_inputs(inputs), m_logger(logger),
|
|
m_loadEmuState(false), m_saveEmuState(false), m_resetEmu(false)
|
|
{
|
|
AddCPU(new CPPCDebug());
|
|
#ifdef SUPERMODEL_SOUND
|
|
AddCPU(new C68KDebug());
|
|
#endif
|
|
}
|
|
|
|
void CSupermodelDebugger::WaitCommand(CCPUDebug *cpu)
|
|
{
|
|
m_inputs->GetInputSystem()->UngrabMouse();
|
|
|
|
CConsoleDebugger::WaitCommand(cpu);
|
|
|
|
m_inputs->GetInputSystem()->GrabMouse();
|
|
}
|
|
|
|
bool CSupermodelDebugger::ProcessToken(const char *token, const char *cmd)
|
|
{
|
|
//
|
|
// Emulator
|
|
//
|
|
if (CheckToken(token, "les", "loademustate")) // loademustate <filename>
|
|
{
|
|
// Parse arguments
|
|
token = strtok(NULL, " ");
|
|
if (token == NULL)
|
|
{
|
|
Print("Missing filename.\n");
|
|
return false;
|
|
}
|
|
|
|
strncpy(m_stateFile, token, 254);
|
|
m_stateFile[254] = '\0';
|
|
m_loadEmuState = true;
|
|
return true;
|
|
}
|
|
else if (CheckToken(token, "ses", "saveemustate")) // saveemustate <filename>
|
|
{
|
|
// Parse arguments
|
|
token = strtok(NULL, " ");
|
|
if (token == NULL)
|
|
{
|
|
Print("Missing filename.\n");
|
|
return false;
|
|
}
|
|
|
|
strncpy(m_stateFile, token, 254);
|
|
m_stateFile[254] = '\0';
|
|
m_saveEmuState = true;
|
|
return true;
|
|
}
|
|
else if (CheckToken(token, "res", "resetemu")) // resetemu
|
|
{
|
|
m_resetEmu = true;
|
|
return true;
|
|
}
|
|
//
|
|
// Inputs
|
|
//
|
|
else if (CheckToken(token, "lip", "listinputs")) // listinputs
|
|
{
|
|
ListInputs();
|
|
return false;
|
|
}
|
|
else if (CheckToken(token, "pip", "printinput")) // printinput (<id>|<label>)
|
|
{
|
|
// Parse arguments
|
|
token = strtok(NULL, " ");
|
|
if (token == NULL)
|
|
{
|
|
Print("Mising input name.\n");
|
|
return false;
|
|
}
|
|
::CInput *input = (*m_inputs)[token];
|
|
if (input == NULL)
|
|
{
|
|
Print("No input with id or label '%s'.\n", token);
|
|
return false;
|
|
}
|
|
if (!InputIsValid(input))
|
|
{
|
|
Print("Input '%s' is not valid for current game.\n", token);
|
|
return false;
|
|
}
|
|
|
|
if (!input->IsVirtual())
|
|
{
|
|
char mapTrunc[41];
|
|
Truncate(mapTrunc, 40, input->GetMapping());
|
|
Print("Input %s (%s) [%s] = %04X (%d)\n", input->id, input->label, mapTrunc, input->value, input->value);
|
|
}
|
|
else
|
|
Print("Input %s (%s) = %04X (%d)\n", input->id, input->label, input->value, input->value);
|
|
return false;
|
|
}
|
|
else if (CheckToken(token, "sip", "setinput")) // setinput (<id>|<label>) <mapping>
|
|
{
|
|
// Parse arguments
|
|
token = strtok(NULL, " ");
|
|
if (token == NULL)
|
|
{
|
|
Print("Mising input id or label.\n");
|
|
return false;
|
|
}
|
|
::CInput *input = (*m_inputs)[token];
|
|
if (input == NULL)
|
|
{
|
|
Print("No input with id or label '%s'.\n", token);
|
|
return false;
|
|
}
|
|
if (!InputIsValid(input))
|
|
{
|
|
Print("Input '%s' is not valid for current game.\n", token);
|
|
return false;
|
|
}
|
|
token = strtok(NULL, " ");
|
|
if (token == NULL)
|
|
{
|
|
Print("Missing mapping to set.\n");
|
|
return false;
|
|
}
|
|
|
|
input->SetMapping(token);
|
|
|
|
Print("Set input %s (%s) to [%s]\n", input->id, input->label, input->GetMapping());
|
|
return false;
|
|
}
|
|
else if (CheckToken(token, "rip", "resetinput")) // resetinput (<id>|<label>)
|
|
{
|
|
// Parse arguments
|
|
token = strtok(NULL, " ");
|
|
if (token == NULL)
|
|
{
|
|
Print("Mising input id or label.\n");
|
|
return false;
|
|
}
|
|
::CInput *input = (*m_inputs)[token];
|
|
if (input == NULL)
|
|
{
|
|
Print("No input with id or label '%s'.\n", token);
|
|
return false;
|
|
}
|
|
if (!InputIsValid(input))
|
|
{
|
|
Print("Input '%s' is not valid for current game.\n", token);
|
|
return false;
|
|
}
|
|
|
|
input->ResetToDefaultMapping();
|
|
|
|
Print("Reset input %s (%s) to [%s]\n", input->id, input->label, input->GetMapping());
|
|
return false;
|
|
}
|
|
else if (CheckToken(token, "cip", "configinput")) // configinput (<id>|<label>) [(s)et|(a)ppend]
|
|
{
|
|
// Parse arguments
|
|
token = strtok(NULL, " ");
|
|
if (token == NULL)
|
|
{
|
|
Print("Mising input id or label.\n");
|
|
return false;
|
|
}
|
|
CInput *input = (*m_inputs)[token];
|
|
if (input == NULL)
|
|
{
|
|
Print("No input with id or label '%s'.\n", token);
|
|
return false;
|
|
}
|
|
if (!InputIsValid(input))
|
|
{
|
|
Print("Input '%s' is not valid for current game.\n", token);
|
|
return false;
|
|
}
|
|
token = strtok(NULL, " ");
|
|
bool append;
|
|
if (token == NULL || CheckToken(token, "s", "set"))
|
|
append = false;
|
|
else if (CheckToken(token, "a", "append"))
|
|
append = true;
|
|
else
|
|
{
|
|
Print("Enter a valid mode (s)et or (a)ppend.\n");
|
|
return false;
|
|
}
|
|
|
|
Print("Configure input %s [%s]: %s...", input->label, input->GetMapping(), (append ? "Appending" : "Setting"));
|
|
fflush(stdout); // required on terminals that use buffering
|
|
|
|
// Configure the input
|
|
if (input->Configure(append, "KEY_ESCAPE"))
|
|
Print(" %s\n", input->GetMapping());
|
|
else
|
|
Print(" [Cancelled]\n");
|
|
return false;
|
|
}
|
|
else if (CheckToken(token, "caip", "configallinputs")) // configallinputs
|
|
{
|
|
m_inputs->ConfigureInputs(m_model3->GetGameInfo());
|
|
return false;
|
|
}
|
|
//
|
|
// Help
|
|
//
|
|
else if (CheckToken(token, "h", "help"))
|
|
{
|
|
CConsoleDebugger::ProcessToken(token, cmd);
|
|
|
|
const char *fmt = " %-6s %-25s %s\n";
|
|
Print(" Emulator:\n");
|
|
Print(fmt, "les", "loademustate", "<filename>");
|
|
Print(fmt, "ses", "saveemustate", "<filename>");
|
|
Print(fmt, "res", "resetemu", "");
|
|
Print(" Inputs:\n");
|
|
Print(fmt, "lip", "listinputs", "");
|
|
Print(fmt, "pip", "printinput", "(<id>|<label>)");
|
|
Print(fmt, "sip", "setinput", "(<id>|<label>) <mapping>");
|
|
Print(fmt, "rip", "resetinput", "(<id>|<label>)");
|
|
Print(fmt, "cip", "configinput", "(<id>|<label>) [(s)et|(a)ppend]");
|
|
Print(fmt, "caip", "configallinputs", "");
|
|
return false;
|
|
}
|
|
else
|
|
return CConsoleDebugger::ProcessToken(token, cmd);
|
|
}
|
|
|
|
bool CSupermodelDebugger::InputIsValid(::CInput *input)
|
|
{
|
|
return input->IsUIInput() || (input->gameFlags & m_model3->GetGameInfo()->inputFlags);
|
|
}
|
|
|
|
void CSupermodelDebugger::ListInputs()
|
|
{
|
|
Print("Inputs:\n");
|
|
if (m_inputs->Count() == 0)
|
|
Print(" None\n");
|
|
|
|
// Get maximum id, label and mapping widths
|
|
size_t idAndLabelWidth = 0;
|
|
size_t mappingWidth = 0;
|
|
for (unsigned i = 0; i < m_inputs->Count(); i++)
|
|
{
|
|
::CInput *input = (*m_inputs)[i];
|
|
if (!InputIsValid(input))
|
|
continue;
|
|
|
|
idAndLabelWidth = max<size_t>(idAndLabelWidth, strlen(input->id) + strlen(input->label) + 3);
|
|
if (!input->IsVirtual())
|
|
mappingWidth = max<size_t>(mappingWidth, strlen(input->GetMapping()));
|
|
}
|
|
mappingWidth = min<size_t>(mappingWidth, 20);
|
|
|
|
// Print labels, mappings and values for each input
|
|
const char *groupLabel = NULL;
|
|
char idAndLabel[255];
|
|
char mapping[21];
|
|
for (unsigned i = 0; i < m_inputs->Count(); i++)
|
|
{
|
|
::CInput *input = (*m_inputs)[i];
|
|
if (!InputIsValid(input))
|
|
continue;
|
|
|
|
if (groupLabel == NULL || stricmp(groupLabel, input->GetInputGroup()) != 0)
|
|
{
|
|
groupLabel = input->GetInputGroup();
|
|
Print(" %s:\n", groupLabel);
|
|
}
|
|
|
|
sprintf(idAndLabel, "%s (%s)", input->id, input->label);
|
|
Print(" %-*s", (int)idAndLabelWidth, idAndLabel);
|
|
if (!input->IsVirtual())
|
|
Truncate(mapping, 20, input->GetMapping());
|
|
else
|
|
mapping[0] = '\0';
|
|
Print(" %-*s %04X (%d)\n", (int)mappingWidth, mapping, input->value, input->value);
|
|
}
|
|
}
|
|
|
|
void CSupermodelDebugger::Attached()
|
|
{
|
|
CConsoleDebugger::Attached();
|
|
|
|
char fileName[25];
|
|
sprintf(fileName, "Debug/%s.ds", m_model3->GetGameInfo()->id);
|
|
LoadState(fileName);
|
|
}
|
|
|
|
void CSupermodelDebugger::Detaching()
|
|
{
|
|
char fileName[25];
|
|
sprintf(fileName, "Debug/%s.ds", m_model3->GetGameInfo()->id);
|
|
SaveState(fileName);
|
|
|
|
CConsoleDebugger::Detaching();
|
|
}
|
|
|
|
bool CSupermodelDebugger::LoadModel3State(const char *fileName)
|
|
{
|
|
// Open file and find header
|
|
CBlockFile state;
|
|
if (state.Load(fileName) != OKAY)
|
|
return false;
|
|
if (state.FindBlock("Debugger Model3 State") != OKAY)
|
|
{
|
|
state.Close();
|
|
return false;
|
|
}
|
|
|
|
// Check version in header matches
|
|
unsigned version;
|
|
state.Read(&version, sizeof(version));
|
|
if (version != MODEL3_STATEFILE_VERSION)
|
|
{
|
|
state.Close();
|
|
return false;
|
|
}
|
|
|
|
// Load Model3 state
|
|
m_model3->LoadState(&state);
|
|
state.Close();
|
|
|
|
// Reset debugger
|
|
Reset();
|
|
return true;
|
|
}
|
|
|
|
bool CSupermodelDebugger::SaveModel3State(const char *fileName)
|
|
{
|
|
// Create file with header
|
|
CBlockFile state;
|
|
if (state.Create(fileName, "Debugger Model3 State", __FILE__) != OKAY)
|
|
return false;
|
|
|
|
// Write out version in header
|
|
unsigned version = MODEL3_STATEFILE_VERSION;
|
|
state.Write(&version, sizeof(version));
|
|
|
|
// Save Model3 state
|
|
m_model3->SaveState(&state);
|
|
state.Close();
|
|
return true;
|
|
}
|
|
|
|
void CSupermodelDebugger::ResetModel3()
|
|
{
|
|
// Reset Model3
|
|
m_model3->Reset();
|
|
|
|
// Reset debugger
|
|
Reset();
|
|
}
|
|
|
|
void CSupermodelDebugger::DebugLog(const char *fmt, va_list vl)
|
|
{
|
|
// Use the supplied logger, if any
|
|
if (m_logger != NULL)
|
|
m_logger->DebugLog(fmt, vl);
|
|
else
|
|
CConsoleDebugger::DebugLog(fmt, vl);
|
|
}
|
|
|
|
void CSupermodelDebugger::InfoLog(const char *fmt, va_list vl)
|
|
{
|
|
// Use the supplied logger, if any
|
|
if (m_logger != NULL)
|
|
m_logger->InfoLog(fmt, vl);
|
|
else
|
|
CConsoleDebugger::InfoLog(fmt, vl);
|
|
}
|
|
|
|
void CSupermodelDebugger::ErrorLog(const char *fmt, va_list vl)
|
|
{
|
|
// Use the supplied logger, if any
|
|
if (m_logger != NULL)
|
|
m_logger->ErrorLog(fmt, vl);
|
|
else
|
|
CConsoleDebugger::ErrorLog(fmt, vl);
|
|
}
|
|
|
|
void CSupermodelDebugger::Poll()
|
|
{
|
|
CConsoleDebugger::Poll();
|
|
|
|
// Load/saving of emulator state and resetting emulator must be done here
|
|
if (m_loadEmuState)
|
|
{
|
|
LoadModel3State(m_stateFile);
|
|
m_loadEmuState = false;
|
|
ForceBreak(false);
|
|
}
|
|
else if (m_saveEmuState)
|
|
{
|
|
SaveModel3State(m_stateFile);
|
|
m_saveEmuState = false;
|
|
ForceBreak(false);
|
|
}
|
|
else if (m_resetEmu)
|
|
{
|
|
ResetModel3();
|
|
m_resetEmu = false;
|
|
ForceBreak(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif // SUPERMODEL_DEBUGGER
|