1111 lines
30 KiB
C++
1111 lines
30 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* 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.
|
|
*
|
|
*/
|
|
|
|
#include "glk/alan3/debug.h"
|
|
#include "glk/alan3/alan3.h"
|
|
#include "glk/alan3/class.h"
|
|
#include "glk/alan3/sysdep.h"
|
|
#include "glk/alan3/alan_version.h"
|
|
#include "glk/alan3/compatibility.h"
|
|
#include "glk/alan3/current.h"
|
|
#include "glk/alan3/event.h"
|
|
#include "glk/alan3/exe.h"
|
|
#include "glk/alan3/glkio.h"
|
|
#include "glk/alan3/instance.h"
|
|
#include "glk/alan3/inter.h"
|
|
#include "glk/alan3/lists.h"
|
|
#include "glk/alan3/memory.h"
|
|
#include "glk/alan3/options.h"
|
|
#include "glk/alan3/output.h"
|
|
#include "glk/alan3/sysdep.h"
|
|
#include "glk/alan3/utils.h"
|
|
#include "glk/streams.h"
|
|
|
|
namespace Glk {
|
|
namespace Alan3 {
|
|
|
|
#define BREAKPOINTMAX 50
|
|
|
|
|
|
/* PUBLIC: */
|
|
int breakpointCount = 0;
|
|
Breakpoint breakpoint[BREAKPOINTMAX];
|
|
|
|
#define debugPrefix "adbg: "
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static void showAttributes(AttributeEntry *attrib) {
|
|
AttributeEntry *at;
|
|
int i;
|
|
char str[80];
|
|
|
|
if (attrib == nullptr)
|
|
return;
|
|
|
|
i = 1;
|
|
for (at = attrib; !isEndOfArray(at); at++) {
|
|
sprintf(str, "$i$t%s[%d] = %d", (char *) pointerTo(at->id), at->code, (int)at->value);
|
|
|
|
output(str);
|
|
i++;
|
|
}
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static void showContents(CONTEXT, int cnt) {
|
|
uint i;
|
|
char str[80];
|
|
Abool found = FALSE;
|
|
|
|
output("$iContains:");
|
|
for (i = 1; i <= header->instanceMax; i++) {
|
|
if (isIn(i, cnt, DIRECT)) { /* Yes, it's directly in this container */
|
|
if (!found)
|
|
found = TRUE;
|
|
output("$i$t");
|
|
say(context, i);
|
|
sprintf(str, "[%d] ", i);
|
|
output(str);
|
|
}
|
|
}
|
|
if (!found)
|
|
output("nothing");
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static char *idOfInstance(CONTEXT, int instance) {
|
|
int base = header->instanceTableAddress +
|
|
header->instanceMax * sizeof(InstanceEntry) / sizeof(Aword) + 1;
|
|
return (char *)&memory[memory[base + instance - 1]];
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static void sayInstanceNumberAndName(CONTEXT, int ins) {
|
|
char buf[1000];
|
|
|
|
sprintf(buf, "[%d] %s (\"$$", ins, idOfInstance(context, ins));
|
|
output(buf);
|
|
say(context, ins);
|
|
output("$$\")");
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static void sayLocationOfInstance(CONTEXT, int ins, const char *prefix) {
|
|
if (admin[ins].location == 0)
|
|
return;
|
|
else {
|
|
output(prefix);
|
|
if (isALocation(admin[ins].location)) {
|
|
output("at");
|
|
CALL1(sayInstanceNumberAndName, admin[ins].location)
|
|
CALL2(sayLocationOfInstance, admin[ins].location, prefix)
|
|
} else if (isAContainer(admin[ins].location)) {
|
|
if (isAObject(admin[ins].location))
|
|
output("in");
|
|
else if (isAActor(admin[ins].location))
|
|
output("carried by");
|
|
CALL1(sayInstanceNumberAndName, admin[ins].location)
|
|
CALL2(sayLocationOfInstance, admin[ins].location, prefix)
|
|
} else {
|
|
output("Illegal location!");
|
|
}
|
|
}
|
|
}
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static void listInstance(CONTEXT, int ins) {
|
|
output("$i");
|
|
CALL1(sayInstanceNumberAndName, ins)
|
|
if (instances[ins].container)
|
|
output("(container)");
|
|
CALL2(sayLocationOfInstance, ins, ", ")
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static void listInstances(CONTEXT, char *pattern) {
|
|
uint ins;
|
|
bool found = FALSE;
|
|
|
|
for (ins = 1; ins <= header->instanceMax; ins++) {
|
|
if (pattern == nullptr || (pattern != nullptr && match(pattern, idOfInstance(context, ins)))) {
|
|
if (!found) {
|
|
output("Instances:");
|
|
found = TRUE;
|
|
}
|
|
CALL1(listInstance, ins)
|
|
}
|
|
}
|
|
if (pattern != nullptr && !found)
|
|
output("No instances matched the pattern.");
|
|
}
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static void showInstance(CONTEXT, int ins) {
|
|
char str[80];
|
|
|
|
if (ins > (int)header->instanceMax || ins < 1) {
|
|
sprintf(str, "Instance index %d is out of range.", ins);
|
|
output(str);
|
|
return;
|
|
}
|
|
|
|
output("The");
|
|
CALL1(sayInstanceNumberAndName, ins)
|
|
if (instances[ins].parent) {
|
|
sprintf(str, "Isa %s[%d]", idOfClass(instances[ins].parent), instances[ins].parent);
|
|
output(str);
|
|
}
|
|
|
|
if (!isA(ins, header->locationClassId) || (isA(ins, header->locationClassId) && admin[ins].location != 0)) {
|
|
sprintf(str, "$iLocation:");
|
|
output(str);
|
|
needSpace = TRUE;
|
|
CALL2(sayLocationOfInstance, ins, "")
|
|
}
|
|
|
|
output("$iAttributes:");
|
|
showAttributes(admin[ins].attributes);
|
|
|
|
if (instances[ins].container)
|
|
CALL1(showContents, ins)
|
|
|
|
if (isA(ins, header->actorClassId)) {
|
|
if (admin[ins].script == 0)
|
|
output("$iIs idle");
|
|
else {
|
|
sprintf(str, "$iExecuting script: %d, Step: %d", admin[ins].script, admin[ins].step);
|
|
output(str);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static void listObjects(CONTEXT) {
|
|
uint obj;
|
|
|
|
output("Objects:");
|
|
for (obj = 1; obj <= header->instanceMax; obj++)
|
|
if (isAObject(obj))
|
|
CALL1(listInstance, obj)
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static void showObject(CONTEXT, int obj) {
|
|
char str[80];
|
|
|
|
|
|
if (!isAObject(obj)) {
|
|
sprintf(str, "Instance %d is not an object", obj);
|
|
output(str);
|
|
return;
|
|
}
|
|
|
|
CALL1(showInstance, obj)
|
|
}
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static int sourceFileNumber(char *fileName) {
|
|
SourceFileEntry *entries = (SourceFileEntry *)pointerTo(header->sourceFileTable);
|
|
int n;
|
|
|
|
for (n = 0; * (Aword *)&entries[n] != EOD; n++) {
|
|
char *entryName;
|
|
entryName = getStringFromFile(entries[n].fpos, entries[n].len);
|
|
if (strcmp(entryName, fileName) == 0) return n;
|
|
entryName = baseNameStart(entryName);
|
|
if (strcmp(entryName, fileName) == 0) return n;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static void printClassName(int c) {
|
|
output(idOfClass(c));
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static void showClassInheritance(int c) {
|
|
char str[80];
|
|
|
|
if (classes[c].parent != 0) {
|
|
output(", Isa");
|
|
printClassName(classes[c].parent);
|
|
sprintf(str, "[%d]", classes[c].parent);
|
|
output(str);
|
|
}
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static void showClass(int cla) {
|
|
char str[80];
|
|
|
|
if (cla < 1) {
|
|
sprintf(str, "Class index %d is out of range.", cla);
|
|
output(str);
|
|
return;
|
|
}
|
|
|
|
output("$t");
|
|
printClassName(cla);
|
|
sprintf(str, "[%d]", cla);
|
|
output(str);
|
|
showClassInheritance(cla);
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static void listClass(int c) {
|
|
char str[80];
|
|
|
|
sprintf(str, "%3d: ", c);
|
|
output(str);
|
|
printClassName(c);
|
|
showClassInheritance(c);
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static void showClassHierarchy(int thisItem, int depth) {
|
|
int i;
|
|
uint child;
|
|
|
|
output("$i");
|
|
for (i = 0; i < depth; i++)
|
|
output("$t");
|
|
|
|
listClass(thisItem);
|
|
for (child = 1; child <= header->classMax; child++) {
|
|
if (classes[child].parent == thisItem) {
|
|
showClassHierarchy(child, depth + 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static void listLocations(CONTEXT) {
|
|
uint loc;
|
|
|
|
output("Locations:");
|
|
for (loc = 1; loc <= header->instanceMax; loc++)
|
|
if (isALocation(loc))
|
|
listInstance(context, loc);
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static void showLocation(CONTEXT, int loc) {
|
|
char str[80];
|
|
|
|
|
|
if (!isALocation(loc)) {
|
|
sprintf(str, "Instance %d is not a location.", loc);
|
|
output(str);
|
|
return;
|
|
}
|
|
|
|
output("The ");
|
|
CALL1(say, loc)
|
|
sprintf(str, "(%d) Isa location :", loc);
|
|
output(str);
|
|
|
|
output("$iAttributes =");
|
|
showAttributes(admin[loc].attributes);
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static void listActors(CONTEXT) {
|
|
uint act;
|
|
|
|
output("Actors:");
|
|
for (act = 1; act <= header->instanceMax; act++)
|
|
if (isAActor(act))
|
|
CALL1(listInstance, act)
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static void showActor(CONTEXT, int act) {
|
|
char str[80];
|
|
|
|
if (!isAActor(act)) {
|
|
sprintf(str, "Instance %d is not an actor.", act);
|
|
output(str);
|
|
return;
|
|
}
|
|
|
|
CALL1(showInstance, act)
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static void showEvents(CONTEXT) {
|
|
uint event;
|
|
int i;
|
|
char str[80];
|
|
bool scheduled;
|
|
|
|
output("Events:");
|
|
for (event = 1; event <= header->eventMax; event++) {
|
|
sprintf(str, "$i%d [%s]:", event, (char *)pointerTo(events[event].id));
|
|
|
|
output(str);
|
|
scheduled = FALSE;
|
|
for (i = 0; i < eventQueueTop; i++)
|
|
if ((scheduled = (eventQueue[i].event == (int)event)))
|
|
break;
|
|
if (scheduled) {
|
|
sprintf(str, "Scheduled for +%d, at ", eventQueue[i].after);
|
|
output(str);
|
|
CALL1(say, eventQueue[i].where)
|
|
} else
|
|
output("Not scheduled.");
|
|
}
|
|
}
|
|
|
|
|
|
/*======================================================================*/
|
|
char *sourceFileName(int fileNumber) {
|
|
SourceFileEntry *entries = (SourceFileEntry *)pointerTo(header->sourceFileTable);
|
|
|
|
return getStringFromFile(entries[fileNumber].fpos, entries[fileNumber].len);
|
|
}
|
|
|
|
|
|
/*======================================================================*/
|
|
bool readLine(Common::SeekableReadStream *rs, char *line, int maxLen) {
|
|
if (rs->pos() < rs->size()) {
|
|
line[maxLen - 1] = '\0';
|
|
|
|
char c;
|
|
do {
|
|
c = rs->readByte();
|
|
*line++ = c;
|
|
} while (--maxLen > 1);
|
|
}
|
|
|
|
return rs->pos() < rs->size();
|
|
}
|
|
|
|
|
|
/*======================================================================*/
|
|
char *readSourceLine(int file, int line) {
|
|
int count;
|
|
#define SOURCELINELENGTH 1000
|
|
static char buffer[SOURCELINELENGTH];
|
|
|
|
frefid_t sourceFileRef = g_vm->glk_fileref_create_by_name(fileusage_TextMode, sourceFileName(file), 0);
|
|
strid_t sourceFile = g_vm->glk_stream_open_file(sourceFileRef, filemode_Read, 0);
|
|
|
|
if (sourceFile != nullptr) {
|
|
for (count = 0; count < line; count++) {
|
|
if (!readLine(*sourceFile, buffer, SOURCELINELENGTH))
|
|
return nullptr;
|
|
|
|
// If not read the whole line, or no newline, try to read again
|
|
while (strchr(buffer, '\n') == nullptr) {
|
|
if (!readLine(*sourceFile, buffer, SOURCELINELENGTH))
|
|
break;
|
|
}
|
|
}
|
|
|
|
delete sourceFile;
|
|
return buffer;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
/*======================================================================*/
|
|
void showSourceLine(int fileNumber, int line) {
|
|
char *buffer = readSourceLine(fileNumber, line);
|
|
if (buffer != nullptr) {
|
|
if (buffer[strlen(buffer) - 1] == '\n')
|
|
buffer[strlen(buffer) - 1] = '\0';
|
|
printf("<%05d>: %s", line, buffer);
|
|
}
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static void listFiles() {
|
|
SourceFileEntry *entry;
|
|
int i = 0;
|
|
for (entry = (SourceFileEntry *)pointerTo(header->sourceFileTable); * ((Aword *)entry) != EOD; entry++) {
|
|
printf(" %2d : %s\n", i, sourceFileName(i));
|
|
i++;
|
|
}
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static int findSourceLineIndex(SourceLineEntry *entry, int file, int line) {
|
|
/* Will return index to the closest line available */
|
|
int i = 0;
|
|
|
|
while (!isEndOfArray(&entry[i]) && entry[i].file != file)
|
|
i++;
|
|
while (!isEndOfArray(&entry[i]) && entry[i].file == file && entry[i].line < line)
|
|
i++;
|
|
if (isEndOfArray(entry) || entry[i].file != file)
|
|
return i - 1;
|
|
else
|
|
return i;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static void listBreakpoints() {
|
|
int i;
|
|
bool found = FALSE;
|
|
|
|
for (i = 0; i < BREAKPOINTMAX; i++)
|
|
if (breakpoint[i].line != 0) {
|
|
if (!found)
|
|
printf("Breakpoints set:\n");
|
|
found = TRUE;
|
|
printf(" %s:%d\n", sourceFileName(breakpoint[i].file), breakpoint[i].line);
|
|
}
|
|
if (!found)
|
|
printf("No breakpoints set\n");
|
|
}
|
|
|
|
|
|
/*======================================================================*/
|
|
int breakpointIndex(int file, int line) {
|
|
int i;
|
|
|
|
for (i = 0; i < BREAKPOINTMAX; i++)
|
|
if (breakpoint[i].line == line && breakpoint[i].file == file)
|
|
return i;
|
|
return -1;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static int availableBreakpointSlot() {
|
|
int i;
|
|
|
|
for (i = 0; i < BREAKPOINTMAX; i++)
|
|
if (breakpoint[i].line == 0)
|
|
return i;
|
|
return -1;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static void setBreakpoint(int file, int line) {
|
|
int i = breakpointIndex(file, line);
|
|
|
|
if (i != -1)
|
|
printf("Breakpoint already set at %s:%d\n", sourceFileName(file), line);
|
|
else {
|
|
i = availableBreakpointSlot();
|
|
if (i == -1)
|
|
printf("No room for more breakpoints. Delete one first.\n");
|
|
else {
|
|
int lineIndex = findSourceLineIndex((SourceLineEntry *)pointerTo(header->sourceLineTable), file, line);
|
|
SourceLineEntry *entry = (SourceLineEntry *)pointerTo(header->sourceLineTable);
|
|
char leadingText[100] = "Breakpoint";
|
|
if (entry[lineIndex].file == (Aint)EOD) {
|
|
printf("Line %d not available\n", line);
|
|
} else {
|
|
if (entry[lineIndex].line != line)
|
|
sprintf(leadingText, "Line %d not available, breakpoint instead", line);
|
|
breakpoint[i].file = entry[lineIndex].file;
|
|
breakpoint[i].line = entry[lineIndex].line;
|
|
printf("%s set at %s:%d\n", leadingText, sourceFileName(entry[lineIndex].file), entry[lineIndex].line);
|
|
showSourceLine(entry[lineIndex].file, entry[lineIndex].line);
|
|
printf("\n");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static void deleteBreakpoint(int line, int file) {
|
|
int i = breakpointIndex(file, line);
|
|
|
|
if (i == -1)
|
|
printf("No breakpoint set at %s:%d\n", sourceFileName(file), line);
|
|
else {
|
|
breakpoint[i].line = 0;
|
|
printf("Breakpoint at %s:%d deleted\n", sourceFileName(file), line);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static bool saved_traceSection, saved_traceInstruction, saved_capitilize, saved_tracePush, saved_traceStack, saved_traceSource;
|
|
static int loc;
|
|
|
|
/*======================================================================*/
|
|
void saveInfo(void) {
|
|
/* Save some important things */
|
|
saved_capitilize = capitalize;
|
|
capitalize = FALSE;
|
|
saved_traceSection = traceSectionOption;
|
|
traceSectionOption = FALSE;
|
|
saved_traceSource = traceSourceOption;
|
|
traceSourceOption = FALSE;
|
|
saved_traceInstruction = traceInstructionOption;
|
|
traceInstructionOption = FALSE;
|
|
saved_tracePush = tracePushOption;
|
|
tracePushOption = FALSE;
|
|
saved_traceStack = traceStackOption;
|
|
traceStackOption = FALSE;
|
|
loc = current.location;
|
|
current.location = where(HERO, DIRECT);
|
|
}
|
|
|
|
|
|
/*======================================================================*/
|
|
void restoreInfo(void) {
|
|
/* Restore! */
|
|
capitalize = saved_capitilize;
|
|
traceSectionOption = saved_traceSection;
|
|
traceInstructionOption = saved_traceInstruction;
|
|
traceSourceOption = saved_traceSource;
|
|
tracePushOption = saved_tracePush;
|
|
traceStackOption = saved_traceStack;
|
|
current.location = loc;
|
|
}
|
|
|
|
#define HELP_COMMAND 'H'
|
|
#define QUIT_COMMAND 'Q'
|
|
#define EXIT_COMMAND 'X'
|
|
#define GO_COMMAND 'G'
|
|
#define FILES_COMMAND 'F'
|
|
#define INSTANCES_COMMAND 'I'
|
|
#define CLASSES_COMMAND 'C'
|
|
#define OBJECTS_COMMAND 'O'
|
|
#define ACTORS_COMMAND 'A'
|
|
#define LOCATIONS_COMMAND 'L'
|
|
#define EVENTS_COMMAND 'E'
|
|
#define BREAK_COMMAND 'B'
|
|
#define DELETE_COMMAND 'D'
|
|
#define TRACE_COMMAND 'R'
|
|
#define SECTION_TRACE_COMMAND 'T'
|
|
#define INSTRUCTION_TRACE_COMMAND 'S'
|
|
#define NEXT_COMMAND 'N'
|
|
#define UNKNOWN_COMMAND '?'
|
|
#define AMBIGUOUS_COMMAND '-'
|
|
#define TRACE_SOURCE_COMMAND 's'
|
|
#define TRACE_SECTION_COMMAND 'e'
|
|
#define TRACE_INSTRUCTION_COMMAND 'i'
|
|
#define TRACE_PUSH_COMMAND 'p'
|
|
#define TRACE_STACK_COMMAND 't'
|
|
|
|
typedef struct DebugParseEntry {
|
|
const char *command;
|
|
const char *parameter;
|
|
char code;
|
|
const char *helpText;
|
|
} DebugParseEntry;
|
|
|
|
static const DebugParseEntry commandEntries[] = {
|
|
{"help", "", HELP_COMMAND, "this help"},
|
|
{"?", "", HELP_COMMAND, "d:o"},
|
|
{"break", "[[file:]n]", BREAK_COMMAND, "set breakpoint at source line [n] (optionally in [file])"},
|
|
{"delete", "[[file:]n]", DELETE_COMMAND, "delete breakpoint at source line [n] (optionally in [file])"},
|
|
{"files", "", FILES_COMMAND, "list source files"},
|
|
{"events", "", EVENTS_COMMAND, "list events"},
|
|
{"classes", "", CLASSES_COMMAND, "list class hierarchy"},
|
|
{"instances", "[n]", INSTANCES_COMMAND, "list instance(s), all, wildcard, number or name"},
|
|
{"objects", "[n]", OBJECTS_COMMAND, "list instance(s) that are objects"},
|
|
{"actors", "[n]", ACTORS_COMMAND, "list instance(s) that are actors"},
|
|
{"locations", "[n]", LOCATIONS_COMMAND, "list instances that are locations"},
|
|
{"trace", "('source'|'section'|'instruction'|'push'|'stack')", TRACE_COMMAND, "toggle various traces"},
|
|
{"next", "", NEXT_COMMAND, "run game and stop at next source line"},
|
|
{"go", "", GO_COMMAND, "go another player turn"},
|
|
{"exit", "", EXIT_COMMAND, "exit to game, enter 'debug' to get back"},
|
|
{"x", "", EXIT_COMMAND, "d:o"},
|
|
{"quit", "", QUIT_COMMAND, "quit game"},
|
|
{nullptr, nullptr, '\0', nullptr}
|
|
};
|
|
|
|
static const DebugParseEntry traceSubcommand[] = {
|
|
{"source", "", TRACE_SOURCE_COMMAND, ""},
|
|
{"section", "", TRACE_SECTION_COMMAND, ""},
|
|
{"instructions", "", TRACE_INSTRUCTION_COMMAND, ""},
|
|
{"pushs", "", TRACE_PUSH_COMMAND, ""},
|
|
{"stacks", "", TRACE_STACK_COMMAND, ""},
|
|
{nullptr, nullptr, '\0', nullptr}
|
|
};
|
|
|
|
|
|
static char *spaces(int length) {
|
|
static char buf[200];
|
|
int i;
|
|
|
|
for (i = 0; i < length; i++)
|
|
buf[i] = ' ';
|
|
buf[i] = '\0';
|
|
return buf;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static char *padding(const DebugParseEntry *entry, int maxLength) {
|
|
return spaces(maxLength - strlen(entry->command) - strlen(entry->parameter));
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static void handleHelpCommand() {
|
|
if (!regressionTestOption)
|
|
output(alan.longHeader);
|
|
|
|
const DebugParseEntry *entry = commandEntries;
|
|
|
|
int maxLength = 0;
|
|
for (entry = commandEntries; entry->command != nullptr; entry++) {
|
|
if (strlen(entry->command) + strlen(entry->parameter) > (uint)maxLength)
|
|
maxLength = strlen(entry->command) + strlen(entry->parameter);
|
|
}
|
|
|
|
output("$nADBG Commands (can be abbreviated):");
|
|
for (entry = commandEntries; entry->command != nullptr; entry++) {
|
|
char buf[200];
|
|
sprintf(buf, "$i%s %s %s$n$t$t-- %s", entry->command, entry->parameter, padding(entry, maxLength), entry->helpText);
|
|
output(buf);
|
|
}
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static const DebugParseEntry *findEntry(char *command, const DebugParseEntry *entry) {
|
|
while (entry->command != nullptr) {
|
|
if (scumm_strnicmp(command, entry->command, strlen(command)) == 0)
|
|
return entry;
|
|
entry++;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static char parseDebugCommand(char *command) {
|
|
const DebugParseEntry *entry = findEntry(command, commandEntries);
|
|
if (entry != nullptr) {
|
|
if (strlen(command) < strlen(entry->command)) {
|
|
/* See if there are any more partial matches */
|
|
if (findEntry(command, entry + 1) != nullptr)
|
|
/* TODO: we should list the possible matches somehow */
|
|
return AMBIGUOUS_COMMAND;
|
|
}
|
|
return entry->code;
|
|
} else
|
|
return UNKNOWN_COMMAND;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static void readCommand(CONTEXT, char buf[], size_t maxLen) {
|
|
char c;
|
|
bool flag;
|
|
|
|
capitalize = FALSE;
|
|
if (anyOutput) newline();
|
|
do {
|
|
output("adbg> ");
|
|
|
|
FUNC2(g_io->readLine, flag, buf, maxLen)
|
|
if (!flag) {
|
|
newline();
|
|
CALL0(quitGame)
|
|
}
|
|
lin = 1;
|
|
c = buf[0];
|
|
} while (c == '\0');
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static void displaySourceLocation(int line, int fileNumber) {
|
|
const char *cause;
|
|
if (anyOutput) newline();
|
|
if (breakpointIndex(fileNumber, line) != -1)
|
|
cause = "Breakpoint hit at";
|
|
else
|
|
cause = "Stepping to";
|
|
printf("%s %s %s:%d\n", debugPrefix, cause, sourceFileName(fileNumber), line);
|
|
showSourceLine(fileNumber, line);
|
|
printf("\n");
|
|
anyOutput = FALSE;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static void toggleSectionTrace() {
|
|
if ((saved_traceSection = !saved_traceSection))
|
|
printf("Section trace on.");
|
|
else
|
|
printf("Section trace off.");
|
|
}
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static void toggleInstructionTrace() {
|
|
if ((saved_traceInstruction = !saved_traceInstruction))
|
|
printf("Single instruction trace on.");
|
|
else
|
|
printf("Single instruction trace off.");
|
|
}
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static void toggleSourceTrace() {
|
|
if ((saved_traceSource = !saved_traceSource))
|
|
printf("Source code trace on.");
|
|
else
|
|
printf("Source code trace off.");
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static void togglePushTrace() {
|
|
if ((saved_tracePush = !saved_tracePush))
|
|
printf("Stack Push trace on.");
|
|
else
|
|
printf("Stack Push trace off.");
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static void toggleStackTrace() {
|
|
if ((saved_traceStack = !saved_traceStack))
|
|
printf("Full stack trace on.");
|
|
else
|
|
printf("Full stack trace off.");
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static int parseTraceCommand() {
|
|
char *subcommand = strtok(nullptr, "");
|
|
const DebugParseEntry *entry;
|
|
if (subcommand == nullptr)
|
|
return UNKNOWN_COMMAND;
|
|
else {
|
|
entry = findEntry(subcommand, traceSubcommand);
|
|
if (entry != nullptr) {
|
|
if (strlen(subcommand) < strlen(entry->command)) {
|
|
if (findEntry(subcommand, entry + 1) != nullptr)
|
|
return AMBIGUOUS_COMMAND;
|
|
}
|
|
return entry->code;
|
|
} else
|
|
return UNKNOWN_COMMAND;
|
|
}
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static const char *printTraceState(bool state) {
|
|
if (state)
|
|
return "on - Traces";
|
|
else
|
|
return "off - Doesn't trace";
|
|
}
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static void printTrace(void) {
|
|
printf("Trace section : %s entry to every section (check, description, event, actor, ...)\n", printTraceState(saved_traceSection));
|
|
printf("Trace source : %s every source line executed\n", printTraceState(saved_traceSource));
|
|
printf("Trace instruction : %s every Amachine instruction executed\n", printTraceState(saved_traceInstruction));
|
|
printf("Trace push : %s every push onto the Amachine stack\n", printTraceState(saved_tracePush));
|
|
printf("Trace stack : %s the complete stack every time\n", printTraceState(saved_traceStack));
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static void handleTraceCommand() {
|
|
char subcommand = parseTraceCommand();
|
|
|
|
switch (subcommand) {
|
|
case TRACE_SECTION_COMMAND:
|
|
toggleSectionTrace();
|
|
break;
|
|
case TRACE_SOURCE_COMMAND:
|
|
toggleSourceTrace();
|
|
break;
|
|
case TRACE_INSTRUCTION_COMMAND:
|
|
toggleInstructionTrace();
|
|
break;
|
|
case TRACE_PUSH_COMMAND:
|
|
togglePushTrace();
|
|
break;
|
|
case TRACE_STACK_COMMAND:
|
|
toggleStackTrace();
|
|
break;
|
|
case AMBIGUOUS_COMMAND:
|
|
output("Ambiguous Trace subcommand abbreviation. ? for help.");
|
|
break;
|
|
default:
|
|
printTrace();
|
|
}
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static void handleBreakCommand(int fileNumber) {
|
|
char *parameter = strtok(nullptr, ":");
|
|
if (parameter != nullptr && Common::isAlpha((int)parameter[0])) {
|
|
fileNumber = sourceFileNumber(parameter);
|
|
if (fileNumber == -1) {
|
|
printf("No such file: '%s'\n", parameter);
|
|
return;
|
|
}
|
|
parameter = strtok(nullptr, "");
|
|
}
|
|
if (parameter == nullptr)
|
|
listBreakpoints();
|
|
else
|
|
setBreakpoint(fileNumber, atoi(parameter));
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static void handleDeleteCommand(bool calledFromBreakpoint, int line, int fileNumber) {
|
|
char *parameter = strtok(nullptr, "");
|
|
if (parameter == nullptr) {
|
|
if (calledFromBreakpoint)
|
|
deleteBreakpoint(line, fileNumber);
|
|
else
|
|
printf("No current breakpoint to delete\n");
|
|
} else
|
|
deleteBreakpoint(atoi(parameter), fileNumber);
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static void handleNextCommand(bool calledFromBreakpoint) {
|
|
stopAtNextLine = TRUE;
|
|
debugOption = FALSE;
|
|
if (!calledFromBreakpoint)
|
|
current.sourceLine = 0;
|
|
restoreInfo();
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static void handleLocationsCommand(CONTEXT) {
|
|
char *parameter = strtok(nullptr, "");
|
|
if (parameter == nullptr)
|
|
listLocations(context);
|
|
else
|
|
showLocation(context, atoi(parameter));
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static void handleActorsCommand(CONTEXT) {
|
|
char *parameter = strtok(nullptr, "");
|
|
if (parameter == nullptr)
|
|
listActors(context);
|
|
else
|
|
showActor(context, atoi(parameter));
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static void handleClassesCommand(CONTEXT) {
|
|
char *parameter = strtok(nullptr, "");
|
|
if (parameter == nullptr || strchr(parameter, '*') != nullptr) {
|
|
output("Classes:");
|
|
showClassHierarchy(1, 0);
|
|
listInstances(context, parameter);
|
|
} else if (Common::isDigit((int)parameter[0]))
|
|
showClass(atoi(parameter));
|
|
else {
|
|
printf("You have to give a class index to display. You can't use names (yet).");
|
|
}
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static void handleObjectsCommand(CONTEXT) {
|
|
char *parameter = strtok(nullptr, "");
|
|
if (parameter == nullptr)
|
|
listObjects(context);
|
|
else
|
|
showObject(context, atoi(parameter));
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static void handleInstancesCommand(CONTEXT) {
|
|
char *parameter = strtok(nullptr, "");
|
|
uint i;
|
|
|
|
if (parameter == nullptr || strchr(parameter, '*') != nullptr)
|
|
listInstances(context, parameter);
|
|
else if (Common::isDigit((int)parameter[0]))
|
|
showInstance(context, atoi(parameter));
|
|
else {
|
|
for (i = 1; i < header->instanceMax; i++)
|
|
if (strcmp(parameter, idOfInstance(context, i)) == 0) {
|
|
showInstance(context, i);
|
|
return;
|
|
}
|
|
printf("No instance named '%s'.", parameter);
|
|
}
|
|
}
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
static bool exactSameVersion() {
|
|
return header->version[3] == alan.version.version
|
|
&& header->version[2] == alan.version.revision
|
|
&& header->version[1] == alan.version.correction
|
|
&& header->version[0] == alan.version.state[0];
|
|
}
|
|
|
|
|
|
/*======================================================================*/
|
|
void debug(CONTEXT, bool calledFromBreakpoint, int line, int fileNumber) {
|
|
static bool warned = FALSE;
|
|
|
|
saveInfo();
|
|
g_vm->glk_set_style(style_Preformatted);
|
|
|
|
if (calledFromBreakpoint)
|
|
displaySourceLocation(line, fileNumber);
|
|
else {
|
|
if (!exactSameVersion() && !warned && !regressionTestOption) {
|
|
printf("<WARNING: You are debugging a game which has version %s.>\n",
|
|
decodedGameVersion(header->version));
|
|
printf("<That is not exactly the same as this interpreter (%s).>\n", alan.version.string);
|
|
printf("<This might cause a lot of trouble. Cross your fingers...>\n");
|
|
warned = TRUE;
|
|
}
|
|
}
|
|
|
|
while (TRUE) {
|
|
char commandLine[200];
|
|
CALL2(readCommand, commandLine, 200)
|
|
|
|
char *command = strtok(commandLine, " ");
|
|
char commandCode = parseDebugCommand(command);
|
|
|
|
switch (commandCode) {
|
|
case AMBIGUOUS_COMMAND:
|
|
output("Ambiguous ADBG command abbreviation. ? for help.");
|
|
break;
|
|
case ACTORS_COMMAND:
|
|
handleActorsCommand(context);
|
|
break;
|
|
case BREAK_COMMAND:
|
|
handleBreakCommand(fileNumber);
|
|
break;
|
|
case CLASSES_COMMAND:
|
|
handleClassesCommand(context);
|
|
break;
|
|
case DELETE_COMMAND:
|
|
handleDeleteCommand(calledFromBreakpoint, line, fileNumber);
|
|
break;
|
|
case EVENTS_COMMAND:
|
|
showEvents(context);
|
|
break;
|
|
case EXIT_COMMAND:
|
|
debugOption = FALSE;
|
|
restoreInfo();
|
|
goto exit_debug;
|
|
case FILES_COMMAND:
|
|
listFiles();
|
|
break;
|
|
case GO_COMMAND:
|
|
restoreInfo();
|
|
goto exit_debug;
|
|
case HELP_COMMAND:
|
|
handleHelpCommand();
|
|
break;
|
|
case INSTANCES_COMMAND:
|
|
handleInstancesCommand(context);
|
|
break;
|
|
case TRACE_COMMAND:
|
|
handleTraceCommand();
|
|
break;
|
|
case INSTRUCTION_TRACE_COMMAND:
|
|
toggleInstructionTrace();
|
|
break;
|
|
case LOCATIONS_COMMAND:
|
|
handleLocationsCommand(context);
|
|
break;
|
|
case NEXT_COMMAND:
|
|
handleNextCommand(calledFromBreakpoint);
|
|
goto exit_debug;
|
|
case OBJECTS_COMMAND:
|
|
handleObjectsCommand(context);
|
|
break;
|
|
case QUIT_COMMAND:
|
|
CALL1(terminate, 0)
|
|
break;
|
|
case SECTION_TRACE_COMMAND:
|
|
toggleSectionTrace();
|
|
break;
|
|
default:
|
|
output("Unknown ADBG command. ? for help.");
|
|
break;
|
|
}
|
|
}
|
|
|
|
exit_debug:
|
|
g_vm->glk_set_style(style_Normal);
|
|
}
|
|
|
|
|
|
/*======================================================================*/
|
|
void traceSay(CONTEXT, int item) {
|
|
/*
|
|
Say something, but make sure we don't disturb anything and that it is
|
|
shown to the player. Needed for tracing. During debugging things are
|
|
set up to avoid this problem.
|
|
*/
|
|
|
|
saveInfo();
|
|
needSpace = FALSE;
|
|
col = 1;
|
|
if (item == 0) {
|
|
printf("$null$");
|
|
} else {
|
|
CALL1(say, item)
|
|
}
|
|
|
|
needSpace = FALSE;
|
|
col = 1;
|
|
restoreInfo();
|
|
}
|
|
|
|
} // End of namespace Alan3
|
|
} // End of namespace Glk
|