/* 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 "lastexpress/debug.h" // Data #include "lastexpress/data/animation.h" #include "lastexpress/data/background.h" #include "lastexpress/data/cursor.h" #include "lastexpress/data/scene.h" #include "lastexpress/data/sequence.h" #include "lastexpress/data/subtitle.h" #include "lastexpress/fight/fight.h" #include "lastexpress/game/action.h" #include "lastexpress/game/beetle.h" #include "lastexpress/game/entities.h" #include "lastexpress/game/inventory.h" #include "lastexpress/game/logic.h" #include "lastexpress/game/object.h" #include "lastexpress/game/savegame.h" #include "lastexpress/game/savepoint.h" #include "lastexpress/game/scenes.h" #include "lastexpress/game/state.h" #include "lastexpress/sound/queue.h" #include "lastexpress/graphics.h" #include "lastexpress/lastexpress.h" #include "lastexpress/resource.h" #include "common/debug-channels.h" #include "common/md5.h" namespace LastExpress { Debugger::Debugger(LastExpressEngine *engine) : _engine(engine), _command(NULL), _numParams(0), _commandParams(NULL) { ////////////////////////////////////////////////////////////////////////// // Register the debugger commands // General DCmd_Register("help", WRAP_METHOD(Debugger, cmdHelp)); // Data DCmd_Register("ls", WRAP_METHOD(Debugger, cmdListFiles)); DCmd_Register("dump", WRAP_METHOD(Debugger, cmdDumpFiles)); DCmd_Register("showframe", WRAP_METHOD(Debugger, cmdShowFrame)); DCmd_Register("showbg", WRAP_METHOD(Debugger, cmdShowBg)); DCmd_Register("playseq", WRAP_METHOD(Debugger, cmdPlaySeq)); DCmd_Register("playsnd", WRAP_METHOD(Debugger, cmdPlaySnd)); DCmd_Register("playsbe", WRAP_METHOD(Debugger, cmdPlaySbe)); DCmd_Register("playnis", WRAP_METHOD(Debugger, cmdPlayNis)); // Scene & interaction DCmd_Register("loadscene", WRAP_METHOD(Debugger, cmdLoadScene)); DCmd_Register("fight", WRAP_METHOD(Debugger, cmdFight)); DCmd_Register("beetle", WRAP_METHOD(Debugger, cmdBeetle)); // Game DCmd_Register("delta", WRAP_METHOD(Debugger, cmdTimeDelta)); DCmd_Register("time", WRAP_METHOD(Debugger, cmdTime)); DCmd_Register("show", WRAP_METHOD(Debugger, cmdShow)); DCmd_Register("entity", WRAP_METHOD(Debugger, cmdEntity)); // Misc DCmd_Register("loadgame", WRAP_METHOD(Debugger, cmdLoadGame)); DCmd_Register("chapter", WRAP_METHOD(Debugger, cmdSwitchChapter)); DCmd_Register("clear", WRAP_METHOD(Debugger, cmdClear)); resetCommand(); _soundStream = new StreamedSound(); } Debugger::~Debugger() { DebugMan.clearAllDebugChannels(); SAFE_DELETE(_soundStream); resetCommand(); _command = NULL; _commandParams = NULL; // Zero passed pointers _engine = NULL; } ////////////////////////////////////////////////////////////////////////// // Helper functions ////////////////////////////////////////////////////////////////////////// bool Debugger::hasCommand() const { return (_numParams != 0); } void Debugger::resetCommand() { SAFE_DELETE(_command); if (_commandParams) for (int i = 0; i < _numParams; i++) free(_commandParams[i]); free(_commandParams); _commandParams = NULL; _numParams = 0; } int Debugger::getNumber(const char *arg) const { return strtol(arg, (char **)NULL, 0); } void Debugger::copyCommand(int argc, const char **argv) { _commandParams = (char **)malloc(sizeof(char *) * (uint)argc); if (!_commandParams) return; _numParams = argc; for (int i = 0; i < _numParams; i++) { _commandParams[i] = (char *)malloc(strlen(argv[i]) + 1); if (_commandParams[i] == NULL) error("[Debugger::copyCommand] Cannot allocate memory for command parameters"); memset(_commandParams[i], 0, strlen(argv[i]) + 1); strcpy(_commandParams[i], argv[i]); } // Exit the debugger! Cmd_Exit(0, 0); } void Debugger::callCommand() { if (_command) (*_command)(_numParams, const_cast(_commandParams)); } bool Debugger::loadArchive(ArchiveIndex index) { if (index < 1 || index > 3) { DebugPrintf("Invalid cd number (was: %d, valid: [1-3])\n", index); return false; } if (!_engine->getResourceManager()->loadArchive(index)) return false; getScenes()->loadSceneDataFile(index); return true; } // Restore loaded archive void Debugger::restoreArchive() const { ArchiveIndex index = kArchiveCd1; switch (getProgress().chapter) { default: case kChapter1: index = kArchiveCd1; break; case kChapter2: case kChapter3: index = kArchiveCd2; break; case kChapter4: case kChapter5: index = kArchiveCd3; break; } _engine->getResourceManager()->loadArchive(index); getScenes()->loadSceneDataFile(index); } ////////////////////////////////////////////////////////////////////////// // Debugger commands ////////////////////////////////////////////////////////////////////////// bool Debugger::cmdHelp(int, const char **) { 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("\n"); DebugPrintf("Commands\n"); DebugPrintf("--------\n"); DebugPrintf(" ls - list files in the archive\n"); DebugPrintf(" dump - dump a list of files in all archives\n"); DebugPrintf("\n"); DebugPrintf(" showframe - show a frame from a sequence\n"); DebugPrintf(" showbg - show a background\n"); DebugPrintf(" playseq - play a sequence\n"); DebugPrintf(" playsnd - play a sound\n"); DebugPrintf(" playsbe - play a subtitle\n"); DebugPrintf(" playnis - play an animation\n"); DebugPrintf("\n"); DebugPrintf(" loadscene - load a scene\n"); DebugPrintf(" fight - start a fight\n"); DebugPrintf(" beetle - start the beetle game\n"); DebugPrintf("\n"); DebugPrintf(" delta - Adjust the time delta\n"); DebugPrintf(" show - show game data\n"); DebugPrintf(" entity - show entity data\n"); DebugPrintf("\n"); DebugPrintf(" loadgame - load a saved game\n"); DebugPrintf(" chapter - switch to a specific chapter\n"); DebugPrintf(" clear - clear the screen\n"); DebugPrintf("\n"); return true; } /** * Command: list files in archive * * @param argc The argument count. * @param argv The values. * * @return true if it was handled, false otherwise */ bool Debugger::cmdListFiles(int argc, const char **argv) { if (argc == 2 || argc == 3) { Common::String filter(const_cast(argv[1])); // Load the proper archive if (argc == 3) { if (!loadArchive((ArchiveIndex)getNumber(argv[2]))) return true; } Common::ArchiveMemberList list; int count = _engine->getResourceManager()->listMatchingMembers(list, filter); DebugPrintf("Number of matches: %d\n", count); for (Common::ArchiveMemberList::iterator it = list.begin(); it != list.end(); ++it) DebugPrintf(" %s\n", (*it)->getName().c_str()); // Restore archive if (argc == 3) restoreArchive(); } else { DebugPrintf("Syntax: ls (use * for all) ()\n"); } return true; } /** * Command: Dump the list of files in the archive * * @param argc The argument count. * @param argv The values. * * @return true if it was handled, false otherwise */ bool Debugger::cmdDumpFiles(int argc, const char **) { #define OUTPUT_ARCHIVE_FILES(name, filename) { \ _engine->getResourceManager()->reset(); \ _engine->getResourceManager()->loadArchive(filename); \ Common::ArchiveMemberList list; \ int count = _engine->getResourceManager()->listMatchingMembers(list, "*"); \ debugC(1, kLastExpressDebugResource, "\n\n--------------------------------------------------------------------\n"); \ debugC(1, kLastExpressDebugResource, "-- " #name " (%d files)\n", count); \ debugC(1, kLastExpressDebugResource, "--------------------------------------------------------------------\n\n"); \ debugC(1, kLastExpressDebugResource, "Filename,Size,MD5\n"); \ for (Common::ArchiveMemberList::iterator it = list.begin(); it != list.end(); ++it) { \ Common::SeekableReadStream *stream = getArchive((*it)->getName()); \ if (!stream) { \ DebugPrintf("ERROR: Cannot create stream for file: %s\n", (*it)->getName().c_str()); \ restoreArchive(); \ return true; \ } \ Common::String md5str = Common::computeStreamMD5AsString(*stream); \ debugC(1, kLastExpressDebugResource, "%s, %d, %s", (*it)->getName().c_str(), stream->size(), md5str.c_str()); \ delete stream; \ } \ } if (argc == 1) { // For each archive file, dump the list of files if (_engine->isDemo()) { OUTPUT_ARCHIVE_FILES("DEMO", "DEMO.HPF"); } else { OUTPUT_ARCHIVE_FILES("HD", "HD.HPF"); OUTPUT_ARCHIVE_FILES("CD 1", "CD1.HPF"); OUTPUT_ARCHIVE_FILES("CD 2", "CD2.HPF"); OUTPUT_ARCHIVE_FILES("CD 3", "CD3.HPF"); } // Restore current loaded archive restoreArchive(); } else { DebugPrintf("Syntax: dump"); } return true; } /** * Command: Shows a frame * * @param argc The argument count. * @param argv The values. * * @return true if it was handled, false otherwise */ bool Debugger::cmdShowFrame(int argc, const char **argv) { if (argc == 3 || argc == 4) { Common::String filename(const_cast(argv[1])); filename += ".seq"; if (argc == 4) { if (!loadArchive((ArchiveIndex)getNumber(argv[3]))) return true; } if (!_engine->getResourceManager()->hasFile(filename)) { DebugPrintf("Cannot find file: %s\n", filename.c_str()); return true; } // Store command if (!hasCommand()) { _command = WRAP_METHOD(Debugger, cmdShowFrame); copyCommand(argc, argv); return Cmd_Exit(0, 0); } else { Sequence sequence(filename); if (sequence.load(getArchive(filename))) { _engine->getCursor()->show(false); clearBg(GraphicsManager::kBackgroundOverlay); AnimFrame *frame = sequence.getFrame((uint16)getNumber(argv[2])); if (!frame) { DebugPrintf("Invalid frame index '%s'\n", argv[2]); resetCommand(); return true; } _engine->getGraphicsManager()->draw(frame, GraphicsManager::kBackgroundOverlay); delete frame; askForRedraw(); redrawScreen(); _engine->_system->delayMillis(1000); _engine->getCursor()->show(true); } resetCommand(); if (argc == 4) restoreArchive(); } } else { DebugPrintf("Syntax: cmd_showframe ()\n"); } return true; } /** * Command: shows a background * * @param argc The argument count. * @param argv The values. * * @return true if it was handled, false otherwise */ bool Debugger::cmdShowBg(int argc, const char **argv) { if (argc == 2 || argc == 3) { Common::String filename(const_cast(argv[1])); if (argc == 3) { if (!loadArchive((ArchiveIndex)getNumber(argv[2]))) return true; } if (!_engine->getResourceManager()->hasFile(filename + ".BG")) { DebugPrintf("Cannot find file: %s\n", (filename + ".BG").c_str()); return true; } // Store command if (!hasCommand()) { _command = WRAP_METHOD(Debugger, cmdShowBg); copyCommand(argc, argv); return Cmd_Exit(0, 0); } else { clearBg(GraphicsManager::kBackgroundC); Background *background = _engine->getResourceManager()->loadBackground(filename); if (background) { _engine->getGraphicsManager()->draw(background, GraphicsManager::kBackgroundC); delete background; askForRedraw(); } redrawScreen(); if (argc == 3) restoreArchive(); // Pause for a second to be able to see the background _engine->_system->delayMillis(1000); resetCommand(); } } else { DebugPrintf("Syntax: showbg ()\n"); } return true; } /** * Command: plays a sequence. * * @param argc The argument count. * @param argv The values. * * @return true if it was handled, false otherwise */ bool Debugger::cmdPlaySeq(int argc, const char **argv) { if (argc == 2 || argc == 3) { Common::String filename(const_cast(argv[1])); filename += ".seq"; if (argc == 3) { if (!loadArchive((ArchiveIndex)getNumber(argv[2]))) return true; } if (!_engine->getResourceManager()->hasFile(filename)) { DebugPrintf("Cannot find file: %s\n", filename.c_str()); return true; } // Store command if (!hasCommand()) { _command = WRAP_METHOD(Debugger, cmdPlaySeq); copyCommand(argc, argv); return Cmd_Exit(0, 0); } else { Sequence *sequence = new Sequence(filename); if (sequence->load(getArchive(filename))) { // Check that we have at least a frame to show if (sequence->count() == 0) { delete sequence; return false; } _engine->getCursor()->show(false); SequenceFrame player(sequence, 0, true); do { // Clear screen clearBg(GraphicsManager::kBackgroundA); _engine->getGraphicsManager()->draw(&player, GraphicsManager::kBackgroundA); askForRedraw(); redrawScreen(); // Handle right-click to interrupt sequence Common::Event ev; _engine->getEventManager()->pollEvent(ev); if (ev.type == Common::EVENT_RBUTTONUP) break; _engine->_system->delayMillis(175); // go to the next frame } while (player.nextFrame()); _engine->getCursor()->show(true); } else { // Sequence player is deleting his reference to the sequence, but we need to take care of it if the // sequence could not be loaded delete sequence; } resetCommand(); if (argc == 3) restoreArchive(); } } else { DebugPrintf("Syntax: playseq ()\n"); } return true; } /** * Command: plays a sound * * @param argc The argument count. * @param argv The values. * * @return true if it was handled, false otherwise */ bool Debugger::cmdPlaySnd(int argc, const char **argv) { if (argc == 2 || argc == 3) { if (argc == 3) { if (!loadArchive((ArchiveIndex)getNumber(argv[2]))) return true; } // Add .SND at the end of the filename if needed Common::String name(const_cast(argv[1])); if (!name.contains('.')) name += ".SND"; if (!_engine->getResourceManager()->hasFile(name)) { DebugPrintf("Cannot find file: %s\n", name.c_str()); return true; } _engine->_system->getMixer()->stopAll(); _soundStream->load(getArchive(name), 16); if (argc == 3) restoreArchive(); } else { DebugPrintf("Syntax: playsnd ()\n"); } return true; } /** * Command: plays subtitles * * @param argc The argument count. * @param argv The values. * * @return true if it was handled, false otherwise */ bool Debugger::cmdPlaySbe(int argc, const char **argv) { if (argc == 2 || argc == 3) { Common::String filename(const_cast(argv[1])); if (argc == 3) { if (!loadArchive((ArchiveIndex)getNumber(argv[2]))) return true; } filename += ".sbe"; if (!_engine->getResourceManager()->hasFile(filename)) { DebugPrintf("Cannot find file: %s\n", filename.c_str()); return true; } // Store command if (!hasCommand()) { _command = WRAP_METHOD(Debugger, cmdPlaySbe); copyCommand(argc, argv); return Cmd_Exit(0, 0); } else { SubtitleManager subtitle(_engine->getFont()); if (subtitle.load(getArchive(filename))) { _engine->getCursor()->show(false); for (uint16 i = 0; i < subtitle.getMaxTime(); i += 25) { clearBg(GraphicsManager::kBackgroundAll); subtitle.setTime(i); _engine->getGraphicsManager()->draw(&subtitle, GraphicsManager::kBackgroundOverlay); askForRedraw(); redrawScreen(); // Handle right-click to interrupt sequence Common::Event ev; _engine->getEventManager()->pollEvent(ev); if (ev.type == Common::EVENT_RBUTTONUP) break; _engine->_system->delayMillis(500); } _engine->getCursor()->show(true); } if (argc == 3) restoreArchive(); resetCommand(); } } else { DebugPrintf("Syntax: playsbe ()\n"); } return true; } /** * Command: plays a NIS animation sequence. * * @param argc The argument count. * @param argv The values. * * @return true if it was handled, false otherwise */ bool Debugger::cmdPlayNis(int argc, const char **argv) { if (argc == 2 || argc == 3) { Common::String name(const_cast(argv[1])); if (argc == 3) { if (!loadArchive((ArchiveIndex)getNumber(argv[2]))) return true; } // If we got a nis filename, check that the file exists if (name.contains('.') && !_engine->getResourceManager()->hasFile(name)) { DebugPrintf("Cannot find file: %s\n", name.c_str()); return true; } // Store command if (!hasCommand()) { _command = WRAP_METHOD(Debugger, cmdPlayNis); copyCommand(argc, argv); return Cmd_Exit(0, 0); } else { // Make sure we are not called in a loop _numParams = 0; // Check if we got a nis filename or an animation index if (name.contains('.')) { Animation animation; if (animation.load(getArchive(name))) { _engine->getCursor()->show(false); animation.play(); _engine->getCursor()->show(true); } } else { getAction()->playAnimation((EventIndex)atoi(name.c_str()), true); } if (argc == 3) restoreArchive(); resetCommand(); } } else { DebugPrintf("Syntax: playnis ()\n"); } return true; } /** * Command: loads a scene * * @param argc The argument count. * @param argv The values. * * @return true if it was handled, false otherwise */ bool Debugger::cmdLoadScene(int argc, const char **argv) { if (argc == 2 || argc == 3) { int cd = 1; SceneIndex index = (SceneIndex)getNumber(argv[1]); // Check args if (argc == 3) { if (!loadArchive((ArchiveIndex)getNumber(argv[2]))) return true; } if (index > 2500) { DebugPrintf("Error: invalid index value (0-2500)"); return true; } // Store command if (!hasCommand()) { _command = WRAP_METHOD(Debugger, cmdLoadScene); copyCommand(argc, argv); return Cmd_Exit(0, 0); } else { clearBg(GraphicsManager::kBackgroundAll); /************ DEBUG *************************/ // Use to find scenes with certain values //for (int i = index; i < 2500; i++) { // loadSceneObject(scene, i); // if (scene.getHeader() && scene.getHeader()->car == 5 && scene.getHeader()->position == 81) { // DebugPrintf("Found scene: %d", i); // // Draw scene found // _engine->getGraphicsManager()->draw(&scene, GraphicsManager::kBackgroundC); // askForRedraw(); // redrawScreen(); // _engine->_system->delayMillis(500); // break; // } //} //delete _sceneLoader; //resetCommand(); //return true; /*********************************************/ Scene *scene = getScenes()->get(index); if (!scene) { DebugPrintf("Cannot load scene %i from CD %i", index, cd); resetCommand(); return true; } _engine->getGraphicsManager()->draw(scene, GraphicsManager::kBackgroundC); askForRedraw(); redrawScreen(); // Pause for a second to be able to see the scene _engine->_system->delayMillis(500); if (argc == 3) restoreArchive(); resetCommand(); } } else { DebugPrintf("Syntax: loadscene ()\n"); } return true; } /** * Command: starts a fight sequence * * @param argc The argument count. * @param argv The values. * * @return true if it was handled, false otherwise */ bool Debugger::cmdFight(int argc, const char **argv) { if (argc == 2) { FightType type = (FightType)getNumber(argv[1]); // Load proper data file ArchiveIndex index = kArchiveCd1; switch (type) { default: goto error; case kFightMilos: index = kArchiveCd1; break; case kFightAnna: index = kArchiveCd2; break; case kFightIvo: case kFightSalko: case kFightVesna: index = kArchiveCd3; break; } loadArchive(index); // Store command if (!hasCommand()) { _command = WRAP_METHOD(Debugger, cmdFight); copyCommand(argc, argv); return false; } else { // Make sure we are not called in a loop _numParams = 0; clearBg(GraphicsManager::kBackgroundAll); askForRedraw(); redrawScreen(); SceneIndex lastScene = getState()->scene; getFight()->setup(type) ? DebugPrintf("Lost fight!\n") : DebugPrintf("Won fight!\n"); // Pause for a second to be able to see the final scene _engine->_system->delayMillis(1000); // Restore loaded archive restoreArchive(); // Stop audio and restore scene getSoundQueue()->stopAllSound(); clearBg(GraphicsManager::kBackgroundAll); Scene *scene = getScenes()->get(lastScene); _engine->getGraphicsManager()->draw(scene, GraphicsManager::kBackgroundC); askForRedraw(); redrawScreen(); resetCommand(); } } else { error: DebugPrintf("Syntax: fight (id=2001-2005)\n"); } return true; } /** * Command: starts the beetle sequence * * @param argc The argument count. * @param argv The values. * * @return true if it was handled, false otherwise */ bool Debugger::cmdBeetle(int argc, const char **argv) { if (argc == 1) { // Load proper data file (beetle game in in Cd2) loadArchive(kArchiveCd2); // Store command if (!hasCommand()) { _command = WRAP_METHOD(Debugger, cmdBeetle); copyCommand(argc, argv); return false; } else { clearBg(GraphicsManager::kBackgroundAll); askForRedraw(); redrawScreen(); // Save current state SceneIndex previousScene = getState()->scene; ObjectLocation previousLocation = getInventory()->get(kItemBeetle)->location; ChapterIndex previousChapter = (ChapterIndex)getProgress().chapter; // Setup scene & inventory getProgress().chapter = kChapter2; Scene *scene = getScenes()->get(kSceneBeetle); getInventory()->get(kItemBeetle)->location = kObjectLocation3; askForRedraw(); redrawScreen(); // Load the beetle game Action *action = NULL; Beetle *beetle = new Beetle(_engine); if (!beetle->isLoaded()) beetle->load(); // Play the game Common::Event ev; bool playgame = true; while (playgame) { // Update beetle beetle->update(); askForRedraw(); redrawScreen(); while (g_system->getEventManager()->pollEvent(ev)) { switch (ev.type) { default: break; case Common::EVENT_KEYDOWN: // Exit beetle game on escape if (ev.kbd.keycode == Common::KEYCODE_ESCAPE) playgame = false; break; case Common::EVENT_MOUSEMOVE: { // Update cursor CursorStyle style = kCursorNormal; SceneHotspot *hotspot = NULL; if (scene->checkHotSpot(ev.mouse, &hotspot)) { if (!action) action = new Action(_engine); style = action->getCursor(*hotspot); } _engine->getCursor()->setStyle(style); break; } case Common::EVENT_LBUTTONUP: case Common::EVENT_RBUTTONUP: // Update coordinates getLogic()->getGameState()->setCoordinates(ev.mouse); if (beetle->catchBeetle()) playgame = false; break; } _engine->_system->delayMillis(10); } } // Cleanup beetle->unload(); delete beetle; delete action; // Pause for a second to be able to see the final scene _engine->_system->delayMillis(1000); // Restore state getProgress().chapter = previousChapter; getInventory()->get(kItemBeetle)->location = previousLocation; // Restore loaded archive restoreArchive(); // Stop audio and restore scene getSoundQueue()->stopAllSound(); clearBg(GraphicsManager::kBackgroundAll); Scene *oldScene = getScenes()->get(previousScene); _engine->getGraphicsManager()->draw(oldScene, GraphicsManager::kBackgroundC); askForRedraw(); redrawScreen(); resetCommand(); } } else { DebugPrintf("Syntax: beetle\n"); } return true; } /** * Command: adjusts the time delta * * @param argc The argument count. * @param argv The values. * * @return true if it was handled, false otherwise */ bool Debugger::cmdTimeDelta(int argc, const char **argv) { if (argc == 2) { int delta = getNumber(argv[1]); if (delta <= 0 || delta > 500) goto label_error; getState()->timeDelta = (uint)delta; } else { label_error: DebugPrintf("Syntax: delta