/* 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. * */ // 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.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 "sci/graphics/frameout.h" #include "sci/graphics/paint32.h" #include "sci/graphics/palette32.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, bool mayBeValue); Console::Console(SciEngine *engine) : GUI::Debugger(), _engine(engine), _debugState(engine->_debugState) { 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)); // Script registerCmd("addresses", WRAP_METHOD(Console, cmdAddresses)); registerCmd("registers", 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)); // 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("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::preEnter() { _engine->pauseEngine(true); } extern void playVideo(Video::VideoDecoder *videoDecoder, VideoState videoState); void Console::postEnter() { if (!_videoFile.empty()) { Video::VideoDecoder *videoDecoder = 0; #ifdef ENABLE_SCI32 bool duckMode = false; #endif if (_videoFile.hasSuffix(".seq")) { videoDecoder = new SEQDecoder(_videoFrameDelay); #ifdef ENABLE_SCI32 } else if (_videoFile.hasSuffix(".vmd")) { videoDecoder = new Video::AdvancedVMDDecoder(); } else if (_videoFile.hasSuffix(".duk")) { duckMode = true; videoDecoder = new Video::AVIDecoder(); #endif } else if (_videoFile.hasSuffix(".avi")) { videoDecoder = new Video::AVIDecoder(); } else { warning("Unrecognized video type"); } if (videoDecoder && videoDecoder->loadFile(_videoFile)) { _engine->_gfxCursor->kernelHide(); #ifdef ENABLE_SCI32 // Duck videos are 16bpp, so we need to change pixel formats int oldWidth = g_system->getWidth(); int oldHeight = g_system->getHeight(); if (duckMode) { Common::List formats; formats.push_back(videoDecoder->getPixelFormat()); initGraphics(640, 480, true, formats); if (g_system->getScreenFormat().bytesPerPixel != videoDecoder->getPixelFormat().bytesPerPixel) error("Could not switch screen format for the duck video"); } #endif VideoState emptyState; emptyState.reset(); emptyState.fileName = _videoFile; emptyState.flags = kDoubled; // always allow the videos to be double sized playVideo(videoDecoder, emptyState); #ifdef ENABLE_SCI32 // Switch back to 8bpp if we played a duck video if (duckMode) initGraphics(oldWidth, oldHeight, oldWidth > 320); #endif _engine->_gfxCursor->kernelShow(); } else warning("Could not play video %s\n", _videoFile.c_str()); _videoFile.clear(); _videoFrameDelay = 0; } _engine->pauseEngine(false); } 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("weak_validations: Turns some validation errors into warnings\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("\n"); debugPrintf("Script:\n"); debugPrintf(" addresses - Provides information on how to pass addresses\n"); debugPrintf(" registers - 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("\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(" 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 - 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"); 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"); 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 %20s | ", 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"); for (uint seeker = 0; seeker < _engine->getKernel()->getKernelNamesSize(); seeker++) { debugPrintf("%03x: %20s | ", seeker, _engine->getKernel()->getKernelName(seeker).c_str()); if ((seeker % 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, NULL, 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(), 0, 36); uint16 noun = strtol(Common::String(userParameter + 3, 2).c_str(), 0, 36); uint16 verb = strtol(Common::String(userParameter + 5, 2).c_str(), 0, 36); uint16 cond = strtol(Common::String(userParameter + 7, 2).c_str(), 0, 36); uint16 seq = strtol(Common::String(userParameter + 9, 1).c_str(), 0, 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 = NULL; 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: extension = ""; } } 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