/* 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 3 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, see .
*
*/
// Console module
#include "common/md5.h"
#include "sci/sci.h"
#include "sci/console.h"
#include "sci/debug.h"
#include "sci/event.h"
#include "sci/resource/resource.h"
#include "sci/engine/state.h"
#include "sci/engine/kernel.h"
#include "sci/engine/selector.h"
#include "sci/engine/savegame.h"
#include "sci/engine/gc.h"
#include "sci/engine/features.h"
#include "sci/engine/scriptdebug.h"
#include "sci/sound/midiparser_sci.h"
#include "sci/sound/music.h"
#include "sci/sound/drivers/mididriver.h"
#include "sci/sound/drivers/map-mt32-to-gm.h"
#include "sci/graphics/animate.h"
#include "sci/graphics/cache.h"
#include "sci/graphics/cursor.h"
#include "sci/graphics/screen.h"
#include "sci/graphics/paint16.h"
#include "sci/graphics/palette.h"
#include "sci/graphics/ports.h"
#include "sci/graphics/view.h"
#include "sci/parser/vocabulary.h"
#include "video/avi_decoder.h"
#include "sci/video/seq_decoder.h"
#ifdef ENABLE_SCI32
#include "common/memstream.h"
#include "sci/graphics/frameout.h"
#include "sci/graphics/paint32.h"
#include "sci/graphics/palette32.h"
#include "sci/sound/decoders/sol.h"
#include "video/coktel_decoder.h"
#endif
#include "common/file.h"
#include "common/savefile.h"
#include "engines/util.h"
namespace Sci {
int g_debug_sleeptime_factor = 1;
int g_debug_simulated_key = 0;
bool g_debug_track_mouse_clicks = false;
// Refer to the "addresses" command on how to pass address parameters
static int parse_reg_t(EngineState *s, const char *str, reg_t *dest);
Console::Console(SciEngine *engine) : GUI::Debugger(),
_engine(engine), _debugState(engine->_debugState), _videoFrameDelay(0),
_gameFlagsGlobal(_engine->_features->getGameFlagsGlobal()) {
assert(_engine);
assert(_engine->_gamestate);
// Variables
registerVar("sleeptime_factor", &g_debug_sleeptime_factor);
registerVar("gc_interval", &engine->_gamestate->scriptGCInterval);
registerVar("simulated_key", &g_debug_simulated_key);
registerVar("track_mouse_clicks", &g_debug_track_mouse_clicks);
// FIXME: This actually passes an enum type instead of an integer but no
// precaution is taken to assure that all assigned values are in the range
// of the enum type. We should handle this more carefully...
registerVar("script_abort_flag", (int *)&_engine->_gamestate->abortScriptProcessing);
// General
registerCmd("help", WRAP_METHOD(Console, cmdHelp));
// Kernel
// registerCmd("classes", WRAP_METHOD(Console, cmdClasses)); // TODO
registerCmd("opcodes", WRAP_METHOD(Console, cmdOpcodes));
registerCmd("selector", WRAP_METHOD(Console, cmdSelector));
registerCmd("selectors", WRAP_METHOD(Console, cmdSelectors));
registerCmd("functions", WRAP_METHOD(Console, cmdKernelFunctions));
registerCmd("class_table", WRAP_METHOD(Console, cmdClassTable));
// Parser
registerCmd("suffixes", WRAP_METHOD(Console, cmdSuffixes));
registerCmd("parse_grammar", WRAP_METHOD(Console, cmdParseGrammar));
registerCmd("parser_nodes", WRAP_METHOD(Console, cmdParserNodes));
registerCmd("parser_words", WRAP_METHOD(Console, cmdParserWords));
registerCmd("sentence_fragments", WRAP_METHOD(Console, cmdSentenceFragments));
registerCmd("parse", WRAP_METHOD(Console, cmdParse));
registerCmd("set_parse_nodes", WRAP_METHOD(Console, cmdSetParseNodes));
registerCmd("said", WRAP_METHOD(Console, cmdSaid));
// Resources
registerCmd("diskdump", WRAP_METHOD(Console, cmdDiskDump));
registerCmd("hexdump", WRAP_METHOD(Console, cmdHexDump));
registerCmd("resource_id", WRAP_METHOD(Console, cmdResourceId));
registerCmd("resource_info", WRAP_METHOD(Console, cmdResourceInfo));
registerCmd("resource_types", WRAP_METHOD(Console, cmdResourceTypes));
registerCmd("list", WRAP_METHOD(Console, cmdList));
registerCmd("alloc_list", WRAP_METHOD(Console, cmdAllocList));
registerCmd("hexgrep", WRAP_METHOD(Console, cmdHexgrep));
registerCmd("verify_scripts", WRAP_METHOD(Console, cmdVerifyScripts));
registerCmd("integrity_dump", WRAP_METHOD(Console, cmdResourceIntegrityDump));
// Game
registerCmd("save_game", WRAP_METHOD(Console, cmdSaveGame));
registerCmd("restore_game", WRAP_METHOD(Console, cmdRestoreGame));
registerCmd("restart_game", WRAP_METHOD(Console, cmdRestartGame));
registerCmd("version", WRAP_METHOD(Console, cmdGetVersion));
registerCmd("room", WRAP_METHOD(Console, cmdRoomNumber));
registerCmd("quit", WRAP_METHOD(Console, cmdQuit));
registerCmd("list_saves", WRAP_METHOD(Console, cmdListSaves));
// Graphics
registerCmd("show_map", WRAP_METHOD(Console, cmdShowMap));
registerCmd("set_palette", WRAP_METHOD(Console, cmdSetPalette));
registerCmd("draw_pic", WRAP_METHOD(Console, cmdDrawPic));
registerCmd("draw_cel", WRAP_METHOD(Console, cmdDrawCel));
registerCmd("undither", WRAP_METHOD(Console, cmdUndither));
registerCmd("pic_visualize", WRAP_METHOD(Console, cmdPicVisualize));
registerCmd("play_video", WRAP_METHOD(Console, cmdPlayVideo));
registerCmd("animate_list", WRAP_METHOD(Console, cmdAnimateList));
registerCmd("al", WRAP_METHOD(Console, cmdAnimateList)); // alias
registerCmd("window_list", WRAP_METHOD(Console, cmdWindowList));
registerCmd("wl", WRAP_METHOD(Console, cmdWindowList)); // alias
registerCmd("plane_list", WRAP_METHOD(Console, cmdPlaneList));
registerCmd("pl", WRAP_METHOD(Console, cmdPlaneList)); // alias
registerCmd("visible_plane_list", WRAP_METHOD(Console, cmdVisiblePlaneList));
registerCmd("vpl", WRAP_METHOD(Console, cmdVisiblePlaneList)); // alias
registerCmd("plane_items", WRAP_METHOD(Console, cmdPlaneItemList));
registerCmd("pi", WRAP_METHOD(Console, cmdPlaneItemList)); // alias
registerCmd("visible_plane_items", WRAP_METHOD(Console, cmdVisiblePlaneItemList));
registerCmd("vpi", WRAP_METHOD(Console, cmdVisiblePlaneItemList)); // alias
registerCmd("saved_bits", WRAP_METHOD(Console, cmdSavedBits));
registerCmd("show_saved_bits", WRAP_METHOD(Console, cmdShowSavedBits));
// Segments
registerCmd("segment_table", WRAP_METHOD(Console, cmdPrintSegmentTable));
registerCmd("segtable", WRAP_METHOD(Console, cmdPrintSegmentTable)); // alias
registerCmd("segment_info", WRAP_METHOD(Console, cmdSegmentInfo));
registerCmd("seginfo", WRAP_METHOD(Console, cmdSegmentInfo)); // alias
registerCmd("segment_kill", WRAP_METHOD(Console, cmdKillSegment));
registerCmd("segkill", WRAP_METHOD(Console, cmdKillSegment)); // alias
// Garbage collection
registerCmd("gc", WRAP_METHOD(Console, cmdGCInvoke));
registerCmd("gc_objects", WRAP_METHOD(Console, cmdGCObjects));
registerCmd("gc_reachable", WRAP_METHOD(Console, cmdGCShowReachable));
registerCmd("gc_freeable", WRAP_METHOD(Console, cmdGCShowFreeable));
registerCmd("gc_normalize", WRAP_METHOD(Console, cmdGCNormalize));
// Music/SFX
registerCmd("songlib", WRAP_METHOD(Console, cmdSongLib));
registerCmd("songinfo", WRAP_METHOD(Console, cmdSongInfo));
registerCmd("is_sample", WRAP_METHOD(Console, cmdIsSample));
registerCmd("startsound", WRAP_METHOD(Console, cmdStartSound));
registerCmd("togglesound", WRAP_METHOD(Console, cmdToggleSound));
registerCmd("stopallsounds", WRAP_METHOD(Console, cmdStopAllSounds));
registerCmd("sfx01_header", WRAP_METHOD(Console, cmdSfx01Header));
registerCmd("sfx01_track", WRAP_METHOD(Console, cmdSfx01Track));
registerCmd("show_instruments", WRAP_METHOD(Console, cmdShowInstruments));
registerCmd("map_instrument", WRAP_METHOD(Console, cmdMapInstrument));
registerCmd("audio_list", WRAP_METHOD(Console, cmdAudioList));
registerCmd("audio_dump", WRAP_METHOD(Console, cmdAudioDump));
// Script
registerCmd("addresses", WRAP_METHOD(Console, cmdAddresses));
registerCmd("registers", WRAP_METHOD(Console, cmdRegisters));
registerCmd("reg", WRAP_METHOD(Console, cmdRegisters));
registerCmd("dissect_script", WRAP_METHOD(Console, cmdDissectScript));
registerCmd("backtrace", WRAP_METHOD(Console, cmdBacktrace));
registerCmd("bt", WRAP_METHOD(Console, cmdBacktrace)); // alias
registerCmd("trace", WRAP_METHOD(Console, cmdTrace));
registerCmd("t", WRAP_METHOD(Console, cmdTrace)); // alias
registerCmd("s", WRAP_METHOD(Console, cmdTrace)); // alias
registerCmd("stepover", WRAP_METHOD(Console, cmdStepOver));
registerCmd("p", WRAP_METHOD(Console, cmdStepOver)); // alias
registerCmd("step_ret", WRAP_METHOD(Console, cmdStepRet));
registerCmd("pret", WRAP_METHOD(Console, cmdStepRet)); // alias
registerCmd("step_event", WRAP_METHOD(Console, cmdStepEvent));
registerCmd("se", WRAP_METHOD(Console, cmdStepEvent)); // alias
registerCmd("step_global", WRAP_METHOD(Console, cmdStepGlobal));
registerCmd("sg", WRAP_METHOD(Console, cmdStepGlobal)); // alias
registerCmd("step_callk", WRAP_METHOD(Console, cmdStepCallk));
registerCmd("snk", WRAP_METHOD(Console, cmdStepCallk)); // alias
registerCmd("disasm", WRAP_METHOD(Console, cmdDisassemble));
registerCmd("disasm_addr", WRAP_METHOD(Console, cmdDisassembleAddress));
registerCmd("find_callk", WRAP_METHOD(Console, cmdFindKernelFunctionCall));
registerCmd("send", WRAP_METHOD(Console, cmdSend));
registerCmd("go", WRAP_METHOD(Console, cmdGo));
registerCmd("logkernel", WRAP_METHOD(Console, cmdLogKernel));
registerCmd("vocab994", WRAP_METHOD(Console, cmdMapVocab994));
registerCmd("gameflags_init", WRAP_METHOD(Console, cmdGameFlagsInit));
registerCmd("gameflags_test", WRAP_METHOD(Console, cmdGameFlagsTest));
registerCmd("tf", WRAP_METHOD(Console, cmdGameFlagsTest));
registerCmd("gameflags_set", WRAP_METHOD(Console, cmdGameFlagsSet));
registerCmd("sf", WRAP_METHOD(Console, cmdGameFlagsSet));
registerCmd("gameflags_clear", WRAP_METHOD(Console, cmdGameFlagsClear));
registerCmd("cf", WRAP_METHOD(Console, cmdGameFlagsClear));
// Breakpoints
registerCmd("bp_list", WRAP_METHOD(Console, cmdBreakpointList));
registerCmd("bplist", WRAP_METHOD(Console, cmdBreakpointList)); // alias
registerCmd("bl", WRAP_METHOD(Console, cmdBreakpointList)); // alias
registerCmd("bp_del", WRAP_METHOD(Console, cmdBreakpointDelete));
registerCmd("bpdel", WRAP_METHOD(Console, cmdBreakpointDelete)); // alias
registerCmd("bc", WRAP_METHOD(Console, cmdBreakpointDelete)); // alias
registerCmd("bp_action", WRAP_METHOD(Console, cmdBreakpointAction));
registerCmd("bpact", WRAP_METHOD(Console, cmdBreakpointAction)); // alias
registerCmd("bp_address", WRAP_METHOD(Console, cmdBreakpointAddress));
registerCmd("bpa", WRAP_METHOD(Console, cmdBreakpointAddress)); // alias
registerCmd("bp_method", WRAP_METHOD(Console, cmdBreakpointMethod));
registerCmd("bpx", WRAP_METHOD(Console, cmdBreakpointMethod)); // alias
registerCmd("bp_read", WRAP_METHOD(Console, cmdBreakpointRead));
registerCmd("bpr", WRAP_METHOD(Console, cmdBreakpointRead)); // alias
registerCmd("bp_write", WRAP_METHOD(Console, cmdBreakpointWrite));
registerCmd("bpw", WRAP_METHOD(Console, cmdBreakpointWrite)); // alias
registerCmd("bp_kernel", WRAP_METHOD(Console, cmdBreakpointKernel));
registerCmd("bpk", WRAP_METHOD(Console, cmdBreakpointKernel)); // alias
registerCmd("bp_function", WRAP_METHOD(Console, cmdBreakpointFunction));
registerCmd("bpe", WRAP_METHOD(Console, cmdBreakpointFunction)); // alias
// VM
registerCmd("script_steps", WRAP_METHOD(Console, cmdScriptSteps));
registerCmd("script_objects", WRAP_METHOD(Console, cmdScriptObjects));
registerCmd("scro", WRAP_METHOD(Console, cmdScriptObjects));
registerCmd("script_strings", WRAP_METHOD(Console, cmdScriptStrings));
registerCmd("scrs", WRAP_METHOD(Console, cmdScriptStrings));
registerCmd("script_said", WRAP_METHOD(Console, cmdScriptSaid));
registerCmd("vm_varlist", WRAP_METHOD(Console, cmdVMVarlist));
registerCmd("vmvarlist", WRAP_METHOD(Console, cmdVMVarlist)); // alias
registerCmd("vl", WRAP_METHOD(Console, cmdVMVarlist)); // alias
registerCmd("vm_vars", WRAP_METHOD(Console, cmdVMVars));
registerCmd("vmvars", WRAP_METHOD(Console, cmdVMVars)); // alias
registerCmd("vv", WRAP_METHOD(Console, cmdVMVars)); // alias
registerCmd("stack", WRAP_METHOD(Console, cmdStack));
registerCmd("st", WRAP_METHOD(Console, cmdStack)); // alias
registerCmd("value_type", WRAP_METHOD(Console, cmdValueType));
registerCmd("view_listnode", WRAP_METHOD(Console, cmdViewListNode));
registerCmd("view_reference", WRAP_METHOD(Console, cmdViewReference));
registerCmd("vr", WRAP_METHOD(Console, cmdViewReference)); // alias
registerCmd("dump_reference", WRAP_METHOD(Console, cmdDumpReference));
registerCmd("dr", WRAP_METHOD(Console, cmdDumpReference)); // alias
registerCmd("view_object", WRAP_METHOD(Console, cmdViewObject));
registerCmd("vo", WRAP_METHOD(Console, cmdViewObject)); // alias
registerCmd("active_object", WRAP_METHOD(Console, cmdViewActiveObject));
registerCmd("acc_object", WRAP_METHOD(Console, cmdViewAccumulatorObject));
_debugState.seeking = kDebugSeekNothing;
_debugState.seekLevel = 0;
_debugState.runningStep = 0;
_debugState.stopOnEvent = false;
_debugState.debugging = false;
_debugState.breakpointWasHit = false;
_debugState._breakpoints.clear(); // No breakpoints defined
_debugState._activeBreakpointTypes = 0;
}
Console::~Console() {
}
void Console::attach(const char *entry) {
if (entry) {
// Attaching to display a severe error, let the engine know
_engine->severeError();
}
GUI::Debugger::attach(entry);
}
void Console::preEnter() {
GUI::Debugger::preEnter();
}
extern void playVideo(Video::VideoDecoder &videoDecoder);
void Console::postEnter() {
if (!_videoFile.empty()) {
Common::ScopedPtr videoDecoder;
if (_videoFile.hasSuffix(".seq")) {
videoDecoder.reset(new SEQDecoder(_videoFrameDelay));
} else if (_videoFile.hasSuffix(".avi")) {
videoDecoder.reset(new Video::AVIDecoder());
} else {
warning("Unrecognized video type");
}
if (videoDecoder && videoDecoder->loadFile(_videoFile)) {
_engine->_gfxCursor->kernelHide();
playVideo(*videoDecoder);
_engine->_gfxCursor->kernelShow();
} else
warning("Could not play video %s\n", _videoFile.c_str());
_videoFile.clear();
_videoFrameDelay = 0;
}
GUI::Debugger::postEnter();
}
bool Console::cmdHelp(int argc, const char **argv) {
debugPrintf("\n");
debugPrintf("Variables\n");
debugPrintf("---------\n");
debugPrintf("sleeptime_factor: Factor to multiply with wait times in kWait()\n");
debugPrintf("gc_interval: Number of kernel calls in between garbage collections\n");
debugPrintf("simulated_key: Add a key with the specified scan code to the event list\n");
debugPrintf("track_mouse_clicks: Toggles mouse click tracking to the console\n");
debugPrintf("script_abort_flag: Set to 1 to abort script execution. Set to 2 to force a replay afterwards\n");
debugPrintf("\n");
debugPrintf("Debug flags\n");
debugPrintf("-----------\n");
debugPrintf("debugflag_list - Lists the available debug flags and their status\n");
debugPrintf("debugflag_enable - Enables a debug flag\n");
debugPrintf("debugflag_disable - Disables a debug flag\n");
debugPrintf("debuglevel - Shows or sets debug level\n");
debugPrintf("\n");
debugPrintf("Commands\n");
debugPrintf("--------\n");
debugPrintf("Kernel:\n");
debugPrintf(" opcodes - Lists the opcode names\n");
debugPrintf(" selectors - Lists the selector names\n");
debugPrintf(" selector - Attempts to find the requested selector by name\n");
debugPrintf(" functions - Lists the kernel functions\n");
debugPrintf(" class_table - Shows the available classes\n");
debugPrintf("\n");
debugPrintf("Parser:\n");
debugPrintf(" suffixes - Lists the vocabulary suffixes\n");
debugPrintf(" parse_grammar - Shows the parse grammar, in strict GNF\n");
debugPrintf(" parser_nodes - Shows the specified number of nodes from the parse node tree\n");
debugPrintf(" parser_words - Shows the words from the parse node tree\n");
debugPrintf(" sentence_fragments - Shows the sentence fragments (used to build Parse trees)\n");
debugPrintf(" parse - Parses a sequence of words and prints the resulting parse tree\n");
debugPrintf(" set_parse_nodes - Sets the contents of all parse nodes\n");
debugPrintf(" said - Match a string against a said spec\n");
debugPrintf("\n");
debugPrintf("Resources:\n");
debugPrintf(" diskdump - Dumps the specified resource to disk as a patch file\n");
debugPrintf(" hexdump - Dumps the specified resource to standard output\n");
debugPrintf(" resource_id - Identifies a resource number by splitting it up in resource type and resource number\n");
debugPrintf(" resource_info - Shows info about a resource\n");
debugPrintf(" resource_types - Shows the valid resource types\n");
debugPrintf(" list - Lists all the resources of a given type\n");
debugPrintf(" alloc_list - Lists all allocated resources\n");
debugPrintf(" hexgrep - Searches some resources for a particular sequence of bytes, represented as hexadecimal numbers\n");
debugPrintf(" verify_scripts - Performs sanity checks on SCI1.1-SCI2.1 game scripts (e.g. if they're up to 64KB in total)\n");
debugPrintf(" integrity_dump - Dumps integrity data about resources in the current game to disk\n");
debugPrintf("\n");
debugPrintf("Game:\n");
debugPrintf(" save_game - Saves the current game state to the hard disk\n");
debugPrintf(" restore_game - Restores a saved game from the hard disk\n");
debugPrintf(" list_saves - List all saved games including filenames\n");
debugPrintf(" restart_game - Restarts the game\n");
debugPrintf(" version - Shows the resource and interpreter versions\n");
debugPrintf(" room - Gets or sets the current room number\n");
debugPrintf(" quit - Quits the game\n");
debugPrintf("\n");
debugPrintf("Graphics:\n");
debugPrintf(" show_map - Switches to visual, priority, control or display screen\n");
debugPrintf(" set_palette - Sets a palette resource\n");
debugPrintf(" draw_pic - Draws a pic resource\n");
debugPrintf(" draw_cel - Draws a cel from a view resource\n");
debugPrintf(" pic_visualize - Enables visualization of the drawing process of EGA pictures\n");
debugPrintf(" undither - Enable/disable undithering\n");
debugPrintf(" play_video - Plays a SEQ, AVI, VMD, RBT or DUK video\n");
debugPrintf(" animate_list / al - Shows the current list of objects in kAnimate's draw list (SCI0 - SCI1.1)\n");
debugPrintf(" window_list / wl - Shows a list of all the windows (ports) in the draw list (SCI0 - SCI1.1)\n");
debugPrintf(" plane_list / pl - Shows a list of all the planes in the draw list (SCI2+)\n");
debugPrintf(" visible_plane_list / vpl - Shows a list of all the planes in the visible draw list (SCI2+)\n");
debugPrintf(" plane_items / pi - Shows a list of all items for a plane (SCI2+)\n");
debugPrintf(" visible_plane_items / vpi - Shows a list of all items for a plane in the visible draw list (SCI2+)\n");
debugPrintf(" saved_bits - List saved bits on the hunk\n");
debugPrintf(" show_saved_bits - Display saved bits\n");
debugPrintf("\n");
debugPrintf("Segments:\n");
debugPrintf(" segment_table / segtable - Lists all segments\n");
debugPrintf(" segment_info / seginfo - Provides information on the specified segment\n");
debugPrintf(" segment_kill / segkill - Deletes the specified segment\n");
debugPrintf("\n");
debugPrintf("Garbage collection:\n");
debugPrintf(" gc - Invokes the garbage collector\n");
debugPrintf(" gc_objects - Lists all reachable objects, normalized\n");
debugPrintf(" gc_reachable - Lists all addresses directly reachable from a given memory object\n");
debugPrintf(" gc_freeable - Lists all addresses freeable in a given segment\n");
debugPrintf(" gc_normalize - Prints the \"normal\" address of a given address\n");
debugPrintf("\n");
debugPrintf("Music/SFX:\n");
debugPrintf(" songlib - Shows the song library\n");
debugPrintf(" songinfo - Shows information about a specified song in the song library\n");
debugPrintf(" togglesound - Starts/stops a sound in the song library\n");
debugPrintf(" stopallsounds - Stops all sounds in the playlist\n");
debugPrintf(" startsound - Starts the specified sound resource, replacing the first song in the song library\n");
debugPrintf(" is_sample - Shows information on a given sound resource, if it's a PCM sample\n");
debugPrintf(" sfx01_header - Dumps the header of a SCI01 song\n");
debugPrintf(" sfx01_track - Dumps a track of a SCI01 song\n");
debugPrintf(" show_instruments - Shows the instruments of a specific song, or all songs\n");
debugPrintf(" map_instrument - Dynamically maps an MT-32 instrument to a GM instrument\n");
debugPrintf(" audio_list - Lists currently active digital audio samples (SCI2+)\n");
debugPrintf(" audio_dump - Dumps the requested audio resource as an uncompressed wave file (SCI2+)\n");
debugPrintf("\n");
debugPrintf("Script:\n");
debugPrintf(" addresses - Provides information on how to pass addresses\n");
debugPrintf(" registers / reg - Shows the current register values\n");
debugPrintf(" dissect_script - Examines a script\n");
debugPrintf(" backtrace / bt - Dumps the send/self/super/call/calle/callb stack\n");
debugPrintf(" trace / t / s - Executes one operation (no parameters) or several operations (specified as a parameter) \n");
debugPrintf(" stepover / p - Executes one operation, skips over call/send\n");
debugPrintf(" step_ret / pret - Steps forward until ret is called on the current execution stack level.\n");
debugPrintf(" step_event / se - Steps forward until a SCI event is received.\n");
debugPrintf(" step_global / sg - Steps until the global variable with the specified index is modified.\n");
debugPrintf(" step_callk / snk - Steps forward until it hits the next callk operation, or a specific callk (specified as a parameter)\n");
debugPrintf(" disasm - Disassembles a method by name\n");
debugPrintf(" disasm_addr - Disassembles one or more commands\n");
debugPrintf(" send - Sends a message to an object\n");
debugPrintf(" go - Executes the script\n");
debugPrintf(" logkernel - Logs kernel calls\n");
debugPrintf(" gameflags_init - Initialize gameflag commands if necessary\n");
debugPrintf(" gameflags_test / tf - Test game flags\n");
debugPrintf(" gameflags_set / sf - Sets game flags\n");
debugPrintf(" gameflags_clear / cf - Clears game flags\n");
debugPrintf("\n");
debugPrintf("Breakpoints:\n");
debugPrintf(" bp_list / bplist / bl - Lists the current breakpoints\n");
debugPrintf(" bp_del / bpdel / bc - Deletes a breakpoint with the specified index\n");
debugPrintf(" bp_action / bpact - Set action to be performed when breakpoint is triggered\n");
debugPrintf(" bp_address / bpa - Sets a breakpoint on a script address\n");
debugPrintf(" bp_method / bpx - Sets a breakpoint on the execution of a specified method/selector\n");
debugPrintf(" bp_read / bpr - Sets a breakpoint on reading of a specified selector\n");
debugPrintf(" bp_write / bpw - Sets a breakpoint on writing to a specified selector\n");
debugPrintf(" bp_kernel / bpk - Sets a breakpoint on execution of a kernel function\n");
debugPrintf(" bp_function / bpe - Sets a breakpoint on the execution of the specified exported function\n");
debugPrintf("\n");
debugPrintf("VM:\n");
debugPrintf(" script_steps - Shows the number of executed SCI operations\n");
debugPrintf(" script_objects / scro - Shows all objects inside a specified script\n");
debugPrintf(" script_strings / scrs - Shows all strings inside a specified script\n");
debugPrintf(" script_said - Shows all said - strings inside a specified script\n");
debugPrintf(" vm_varlist / vmvarlist / vl - Shows the addresses of variables in the VM\n");
debugPrintf(" vm_vars / vmvars / vv - Displays or changes variables in the VM\n");
debugPrintf(" stack / st - Lists the specified number of stack elements\n");
debugPrintf(" value_type - Determines the type of a value\n");
debugPrintf(" view_listnode - Examines the list node at the given address\n");
debugPrintf(" view_reference / vr - Examines an arbitrary reference\n");
debugPrintf(" dump_reference / dr - Dumps an arbitrary reference to disk\n");
debugPrintf(" view_object / vo - Examines the object at the given address\n");
debugPrintf(" active_object - Shows information on the currently active object or class\n");
debugPrintf(" acc_object - Shows information on the object or class at the address indexed by the accumulator\n");
debugPrintf("\n");
return true;
}
ResourceType parseResourceType(const char *resid) {
// Gets the resource number of a resource string, or returns -1
ResourceType res = kResourceTypeInvalid;
for (int i = 0; i < kResourceTypeInvalid; i++)
if (strcmp(getResourceTypeName((ResourceType)i), resid) == 0)
res = (ResourceType)i;
return res;
}
bool Console::cmdGetVersion(int argc, const char **argv) {
const char *viewTypeDesc[] = { "Unknown", "EGA", "Amiga ECS 32 colors", "Amiga AGA 64 colors", "VGA", "VGA SCI1.1" };
bool hasVocab997 = g_sci->getResMan()->testResource(ResourceId(kResourceTypeVocab, VOCAB_RESOURCE_SELECTORS)) ? true : false;
Common::String gameVersion = "N/A";
Common::File versionFile;
if (versionFile.open("VERSION")) {
gameVersion = versionFile.readLine();
versionFile.close();
}
debugPrintf("Game ID: %s\n", _engine->getGameIdStr());
debugPrintf("Emulated interpreter version: %s\n", getSciVersionDesc(getSciVersion()));
debugPrintf("\n");
debugPrintf("Detected features:\n");
debugPrintf("------------------\n");
debugPrintf("Sound type: %s\n", getSciVersionDesc(_engine->_features->detectDoSoundType()));
debugPrintf("Graphics functions type: %s\n", getSciVersionDesc(_engine->_features->detectGfxFunctionsType()));
debugPrintf("Lofs type: %s\n", getSciVersionDesc(_engine->_features->detectLofsType()));
debugPrintf("Move count type: %s\n", (_engine->_features->handleMoveCount()) ? "increment" : "ignore");
debugPrintf("SetCursor type: %s\n", getSciVersionDesc(_engine->_features->detectSetCursorType()));
debugPrintf("PseudoMouse ability: %s\n", _engine->_features->detectPseudoMouseAbility() == kPseudoMouseAbilityTrue ? "yes" : "no");
#ifdef ENABLE_SCI32
if ((getSciVersion() >= SCI_VERSION_2_1_EARLY) && (getSciVersion() <= SCI_VERSION_2_1_LATE))
debugPrintf("SCI2.1 kernel table: %s\n", (_engine->_features->detectSci21KernelType() == SCI_VERSION_2) ? "modified SCI2 (old)" : "SCI2.1 (new)");
#endif
debugPrintf("View type: %s\n", viewTypeDesc[g_sci->getResMan()->getViewType()]);
if (getSciVersion() <= SCI_VERSION_1_1) {
debugPrintf("kAnimate fastCast enabled: %s\n", g_sci->_gfxAnimate->isFastCastEnabled() ? "yes" : "no");
}
if (getSciVersion() < SCI_VERSION_2) {
debugPrintf("Uses palette merging: %s\n", g_sci->_gfxPalette16->isMerging() ? "yes" : "no");
debugPrintf("Uses 16 bit color matching: %s\n", g_sci->_gfxPalette16->isUsing16bitColorMatch() ? "yes" : "no");
}
debugPrintf("Resource volume version: %s\n", g_sci->getResMan()->getVolVersionDesc());
debugPrintf("Resource map version: %s\n", g_sci->getResMan()->getMapVersionDesc());
debugPrintf("Contains selector vocabulary (vocab.997): %s\n", hasVocab997 ? "yes" : "no");
debugPrintf("Has CantBeHere selector: %s\n", g_sci->getKernel()->_selectorCache.cantBeHere != -1 ? "yes" : "no");
if (getSciVersion() >= SCI_VERSION_2) {
debugPrintf("Plane id base: %d\n", g_sci->_features->detectPlaneIdBase());
}
debugPrintf("Game version (VERSION file): %s\n", gameVersion.c_str());
debugPrintf("\n");
return true;
}
bool Console::cmdOpcodes(int argc, const char **argv) {
// Load the opcode table from vocab.998 if it exists, to obtain the opcode names
Resource *r = _engine->getResMan()->findResource(ResourceId(kResourceTypeVocab, 998), 0);
// If the resource couldn't be loaded, leave
if (!r) {
debugPrintf("unable to load vocab.998\n");
return true;
}
int count = r->getUint16LEAt(0);
debugPrintf("Opcode names in numeric order [index: type name]:\n");
for (int i = 0; i < count; i++) {
int offset = r->getUint16LEAt(2 + i * 2);
int len = r->getUint16LEAt(offset) - 2;
int type = r->getUint16LEAt(offset + 2);
// QFG3 has empty opcodes
Common::String name = len > 0 ? r->getStringAt(offset + 4, len) : "Dummy";
debugPrintf("%03x: %03x %15s | ", i, type, name.c_str());
if ((i % 3) == 2)
debugPrintf("\n");
}
debugPrintf("\n");
return true;
}
bool Console::cmdSelector(int argc, const char **argv) {
if (argc < 2) {
debugPrintf("Attempts to find the requested selector by name.\n");
debugPrintf("Usage: %s \n", argv[0]);
return true;
}
Common::String name = argv[1];
int seeker = _engine->getKernel()->findSelector(name.c_str());
if (seeker >= 0) {
debugPrintf("Selector %s found at %03x (%d)\n", name.c_str(), seeker, seeker);
return true;
}
debugPrintf("Selector %s wasn't found\n", name.c_str());
return true;
}
bool Console::cmdSelectors(int argc, const char **argv) {
debugPrintf("Selector names in numeric order:\n");
Common::String selectorName;
for (uint seeker = 0; seeker < _engine->getKernel()->getSelectorNamesSize(); seeker++) {
selectorName = _engine->getKernel()->getSelectorName(seeker);
if (selectorName != "BAD SELECTOR")
debugPrintf("%03x: %20s | ", seeker, selectorName.c_str());
else
continue;
if ((seeker % 3) == 2)
debugPrintf("\n");
}
debugPrintf("\n");
#if 0
// For debug/development
// If we ever need to modify static_selectors.cpp, this code will print the selectors
// in a ready to use format
Common::DumpFile *outFile = new Common::DumpFile();
outFile->open("selectors.txt");
char buf[50];
Common::String selName;
uint totalSize = _engine->getKernel()->getSelectorNamesSize();
uint seeker = 0;
while (seeker < totalSize) {
selName = "\"" + _engine->getKernel()->getSelectorName(seeker) + "\"";
sprintf(buf, "%15s, ", selName.c_str());
outFile->writeString(buf);
if (!((seeker + 1) % 5) && seeker)
outFile->writeByte('\n');
seeker++;
}
outFile->finalize();
outFile->close();
#endif
return true;
}
bool Console::cmdKernelFunctions(int argc, const char **argv) {
debugPrintf("Kernel function names in numeric order:\n");
uint column = 0;
for (uint seeker = 0; seeker < _engine->getKernel()->getKernelNamesSize(); seeker++) {
const Common::String &kernelName = _engine->getKernel()->getKernelName(seeker);
if (kernelName == "Dummy")
continue;
debugPrintf("%03x: %20s | ", seeker, kernelName.c_str());
if ((column++ % 3) == 2)
debugPrintf("\n");
}
debugPrintf("\n");
return true;
}
bool Console::cmdSuffixes(int argc, const char **argv) {
_engine->getVocabulary()->printSuffixes();
return true;
}
bool Console::cmdParserWords(int argc, const char **argv) {
_engine->getVocabulary()->printParserWords();
return true;
}
bool Console::cmdSetParseNodes(int argc, const char **argv) {
if (argc < 2) {
debugPrintf("Sets the contents of all parse nodes.\n");
debugPrintf("Usage: %s ... \n", argv[0]);
debugPrintf("Tokens should be separated by blanks and enclosed in parentheses\n");
return true;
}
int i = 0;
int pos = -1;
int nextToken = 0, nextValue = 0;
const char *token = argv[i++];
if (!strcmp(token, "(")) {
nextToken = kParseOpeningParenthesis;
} else if (!strcmp(token, ")")) {
nextToken = kParseClosingParenthesis;
} else if (!strcmp(token, "nil")) {
nextToken = kParseNil;
} else {
nextValue = strtol(token, nullptr, 0);
nextToken = kParseNumber;
}
if (_engine->getVocabulary()->parseNodes(&i, &pos, nextToken, nextValue, argc, argv) == -1)
return 1;
_engine->getVocabulary()->dumpParseTree();
return true;
}
bool Console::cmdRegisters(int argc, const char **argv) {
EngineState *s = _engine->_gamestate;
debugPrintf("Current register values:\n");
debugPrintf("acc=%04x:%04x prev=%04x:%04x &rest=%x\n", PRINT_REG(s->r_acc), PRINT_REG(s->r_prev), s->r_rest);
if (!s->_executionStack.empty()) {
debugPrintf("pc=%04x:%04x obj=%04x:%04x fp=ST:%04x sp=ST:%04x\n",
PRINT_REG(s->xs->addr.pc), PRINT_REG(s->xs->objp),
(unsigned)(s->xs->fp - s->stack_base), (unsigned)(s->xs->sp - s->stack_base));
} else
debugPrintf("\n");
return true;
}
bool Console::parseResourceNumber36(const char *userParameter, uint16 &resourceNumber, uint32 &resourceTuple) {
int userParameterLen = strlen(userParameter);
if (userParameterLen != 10) {
debugPrintf("Audio36/Sync36 resource numbers must be specified as RRRNNVVCCS\n");
debugPrintf("where RRR is the resource number/map\n");
debugPrintf(" NN is the noun\n");
debugPrintf(" VV is the verb\n");
debugPrintf(" CC is the cond\n");
debugPrintf(" S is the seq\n");
return false;
}
// input: RRRNNVVCCS
resourceNumber = strtol(Common::String(userParameter, 3).c_str(), nullptr, 36);
uint16 noun = strtol(Common::String(userParameter + 3, 2).c_str(), nullptr, 36);
uint16 verb = strtol(Common::String(userParameter + 5, 2).c_str(), nullptr, 36);
uint16 cond = strtol(Common::String(userParameter + 7, 2).c_str(), nullptr, 36);
uint16 seq = strtol(Common::String(userParameter + 9, 1).c_str(), nullptr, 36);
resourceTuple = ((noun & 0xff) << 24) | ((verb & 0xff) << 16) | ((cond & 0xff) << 8) | (seq & 0xff);
return true;
}
bool Console::cmdDiskDump(int argc, const char **argv) {
bool resourceAll = false;
uint16 resourceNumber = 0;
uint32 resourceTuple = 0;
if (argc != 3) {
debugPrintf("Dumps the specified resource to disk as a patch file\n");
debugPrintf("Usage: %s \n", argv[0]);
debugPrintf(" may be '*' to dump all resources of given type\n");
cmdResourceTypes(argc, argv);
return true;
}
ResourceType resourceType = parseResourceType(argv[1]);
if (resourceType == kResourceTypeInvalid) {
debugPrintf("Resource type '%s' is not valid\n", argv[1]);
return true;
}
if (strcmp(argv[2], "*") == 0) {
resourceAll = true;
} else {
switch (resourceType) {
case kResourceTypeAudio36:
case kResourceTypeSync36:
if (!parseResourceNumber36(argv[2], resourceNumber, resourceTuple)) {
return true;
}
break;
default:
resourceNumber = atoi(argv[2]);
break;
}
}
if (resourceType == kResourceTypeInvalid) {
debugPrintf("Resource type '%s' is not valid\n", argv[1]);
return true;
}
if (resourceAll) {
// "*" used, dump everything of that type
Common::List resources = _engine->getResMan()->listResources(resourceType, -1);
Common::sort(resources.begin(), resources.end());
Common::List::iterator itr;
for (itr = resources.begin(); itr != resources.end(); ++itr) {
resourceNumber = itr->getNumber();
resourceTuple = itr->getTuple();
cmdDiskDumpWorker(resourceType, resourceNumber, resourceTuple);
}
} else {
// id was given, dump only this resource
cmdDiskDumpWorker(resourceType, resourceNumber, resourceTuple);
}
return true;
}
void Console::cmdDiskDumpWorker(ResourceType resourceType, int resourceNumber, uint32 resourceTuple) {
const char *resourceTypeName = getResourceTypeName(resourceType);
ResourceId resourceId;
Resource *resource = nullptr;
char outFileName[50];
switch (resourceType) {
case kResourceTypeAudio36:
case kResourceTypeSync36: {
resourceId = ResourceId(resourceType, resourceNumber, resourceTuple);
resource = _engine->getResMan()->findResource(resourceId, 0);
sprintf(outFileName, "%s", resourceId.toPatchNameBase36().c_str());
// patch filename is: [type:1 char] [map:3 chars] [noun:2 chars] [verb:2 chars] "." [cond: 2 chars] [seq:1 char]
// e.g. "@5EG0000.014"
break;
}
default:
resourceId = ResourceId(resourceType, resourceNumber);
resource = _engine->getResMan()->findResource(resourceId, 0);
sprintf(outFileName, "%s.%03d", resourceTypeName, resourceNumber);
// patch filename is: [resourcetype].[resourcenumber]
// e.g. "Script.0"
break;
}
if (resource) {
Common::DumpFile *outFile = new Common::DumpFile();
outFile->open(outFileName);
resource->writeToStream(outFile);
outFile->finalize();
outFile->close();
delete outFile;
debugPrintf("Resource %s (located in %s) has been dumped to disk\n", outFileName, resource->getResourceLocation().c_str());
} else {
debugPrintf("Resource %s not found\n", outFileName);
}
}
bool Console::cmdHexDump(int argc, const char **argv) {
if (argc != 3) {
debugPrintf("Dumps the specified resource to standard output\n");
debugPrintf("Usage: %s \n", argv[0]);
cmdResourceTypes(argc, argv);
return true;
}
int resNum = atoi(argv[2]);
ResourceType res = parseResourceType(argv[1]);
if (res == kResourceTypeInvalid)
debugPrintf("Resource type '%s' is not valid\n", argv[1]);
else {
Resource *resource = _engine->getResMan()->findResource(ResourceId(res, resNum), 0);
if (resource) {
Common::hexdump(resource->getUnsafeDataAt(0), resource->size(), 16, 0);
debugPrintf("Resource %s.%03d has been dumped to standard output\n", argv[1], resNum);
} else {
debugPrintf("Resource %s.%03d not found\n", argv[1], resNum);
}
}
return true;
}
bool Console::cmdResourceId(int argc, const char **argv) {
if (argc != 2) {
debugPrintf("Identifies a resource number by splitting it up in resource type and resource number\n");
debugPrintf("Usage: %s \n", argv[0]);
return true;
}
int id = atoi(argv[1]);
debugPrintf("%s.%d (0x%x)\n", getResourceTypeName((ResourceType)(id >> 11)), id & 0x7ff, id & 0x7ff);
return true;
}
bool Console::cmdList(int argc, const char **argv) {
int selectedMapNumber = -1;
Common::List resources;
Common::List::iterator itr;
int displayCount = 0;
int currentMap = -1;
if (argc < 2) {
debugPrintf("Lists all the resources of a given type\n");
cmdResourceTypes(argc, argv);
return true;
}
ResourceType resourceType = parseResourceType(argv[1]);
if (resourceType == kResourceTypeInvalid) {
debugPrintf("Unknown resource type: '%s'\n", argv[1]);
return true;
}
switch (resourceType) {
case kResourceTypeAudio36:
case kResourceTypeSync36:
if (argc != 3) {
debugPrintf("Please specify map number (-1: all maps)\n");
return true;
}
selectedMapNumber = atoi(argv[2]);
resources = _engine->getResMan()->listResources(resourceType, selectedMapNumber);
Common::sort(resources.begin(), resources.end());
for (itr = resources.begin(); itr != resources.end(); ++itr) {
const uint16 map = itr->getNumber();
const uint32 resourceTuple = itr->getTuple();
const uint16 noun = (resourceTuple >> 24) & 0xff;
const uint16 verb = (resourceTuple >> 16) & 0xff;
const uint16 cond = (resourceTuple >> 8) & 0xff;
const uint16 seq = resourceTuple & 0xff;
if (currentMap != map) {
if (displayCount % 3)
debugPrintf("\n");
debugPrintf("Map %04x (%i):\n", map, map);
currentMap = map;
displayCount = 0;
}
if (displayCount % 3 == 0)
debugPrintf(" ");
debugPrintf("%02x %02x %02x %02x (%3i %3i %3i %3i) ", noun, verb, cond, seq, noun, verb, cond, seq);
if (++displayCount % 3 == 0)
debugPrintf("\n");
}
break;
default:
resources = _engine->getResMan()->listResources(resourceType);
Common::sort(resources.begin(), resources.end());
for (itr = resources.begin(); itr != resources.end(); ++itr) {
debugPrintf("%8i", itr->getNumber());
if (++displayCount % 10 == 0)
debugPrintf("\n");
}
break;
}
debugPrintf("\n");
return true;
}
bool Console::cmdResourceIntegrityDump(int argc, const char **argv) {
if (argc < 2) {
debugPrintf("Dumps integrity data about resources in the current game to disk.\n");
debugPrintf("Usage: %s [] []\n", argv[0]);
return true;
}
Common::DumpFile outFile;
if (!outFile.open(argv[1])) {
debugPrintf("Failed to open output file %s.\n", argv[1]);
return true;
}
const bool hashVideoFiles = argc < 3;
const bool videoFiles = argc < 4;
for (int i = 0; i < kResourceTypeInvalid; ++i) {
const ResourceType resType = (ResourceType)i;
// This will list video resources inside of resource bundles even if
// video files are skipped, but this seems fine since those files are
// small because they were intended to load into memory. (This happens
// with VMDs in GK2.)
Common::List resources = _engine->getResMan()->listResources(resType);
const char *extension = "";
if (videoFiles) {
switch (resType) {
case kResourceTypeRobot:
case kResourceTypeVMD:
case kResourceTypeDuck:
case kResourceTypeClut: {
extension = getResourceTypeExtension(resType);
assert(*extension != '\0');
const Common::String filesGlob = Common::String::format("*.%s", extension).c_str();
Common::ArchiveMemberList files;
const int numMatches = SearchMan.listMatchingMembers(files, filesGlob);
if (numMatches > 0) {
Common::ArchiveMemberList::const_iterator it;
for (it = files.begin(); it != files.end(); ++it) {
const uint resNo = atoi((*it)->getName().c_str());
resources.push_back(ResourceId(resType, resNo));
}
}
break;
}
default:
break;
}
}
if (resources.size()) {
Common::sort(resources.begin(), resources.end());
Common::List::const_iterator it;
debugPrintf("%s: ", getResourceTypeName(resType));
for (it = resources.begin(); it != resources.end(); ++it) {
Common::String statusName;
if (resType == kResourceTypeAudio36 || resType == kResourceTypeSync36) {
statusName = it->toPatchNameBase36();
} else {
statusName = Common::String::format("%d", it->getNumber());
}
const Common::String resourceName = it->toString();
Resource *resource = _engine->getResMan()->findResource(*it, false);
if (resource) {
Common::MemoryReadStream stream = resource->toStream();
writeIntegrityDumpLine(statusName, resourceName, outFile, &stream, resource->size(), true);
} else if (videoFiles && *extension != '\0') {
const Common::String fileName = Common::String::format("%u.%s", it->getNumber(), extension);
Common::File file;
Common::ReadStream *stream = nullptr;
if (file.open(fileName)) {
stream = &file;
}
writeIntegrityDumpLine(statusName, resourceName, outFile, stream, file.size(), hashVideoFiles);
}
}
debugPrintf("\n");
}
}
const char *otherVideoFiles[] = { "avi", "seq" };
for (uint i = 0; i < ARRAYSIZE(otherVideoFiles); ++i) {
const char *extension = otherVideoFiles[i];
Common::ArchiveMemberList files;
if (SearchMan.listMatchingMembers(files, Common::String::format("*.%s", extension).c_str()) > 0) {
debugPrintf("%s: ", extension);
Common::sort(files.begin(), files.end(), Common::ArchiveMemberListComparator());
Common::ArchiveMemberList::const_iterator it;
for (it = files.begin(); it != files.end(); ++it) {
const Common::ArchiveMember &file = **it;
Common::ScopedPtr stream(file.createReadStream());
writeIntegrityDumpLine(file.getName(), file.getName(), outFile, stream.get(), stream->size(), hashVideoFiles);
}
debugPrintf("\n");
}
}
return true;
}
bool Console::cmdAllocList(int argc, const char **argv) {
ResourceManager *resMan = _engine->getResMan();
for (int i = 0; i < kResourceTypeInvalid; ++i) {
Common::List resources = _engine->getResMan()->listResources((ResourceType)i);
if (resources.size()) {
Common::sort(resources.begin(), resources.end());
bool hasAlloc = false;
Common::List::const_iterator it;
for (it = resources.begin(); it != resources.end(); ++it) {
Resource *resource = resMan->testResource(*it);
if (resource != nullptr && resource->data() != nullptr) {
if (hasAlloc) {
debugPrintf(", ");
} else {
debugPrintf("%s: ", getResourceTypeName((ResourceType)i));
}
hasAlloc = true;
debugPrintf("%u (%u locks)", resource->getNumber(), resource->getNumLockers());
}
}
if (hasAlloc) {
debugPrintf("\n");
}
}
}
return true;
}
bool Console::cmdDissectScript(int argc, const char **argv) {
if (argc != 2) {
debugPrintf("Examines a script\n");
debugPrintf("Usage: %s