/* 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 . * */ #include "common/memstream.h" #include "common/system.h" #include "graphics/palette.h" #include "graphics/paletteman.h" #include "image/png.h" #include "dgds/ads.h" #include "dgds/console.h" #include "dgds/dgds.h" #include "dgds/globals.h" #include "dgds/includes.h" #include "dgds/image.h" #include "dgds/parser.h" #include "dgds/resource.h" #include "dgds/scene.h" #include "dgds/game_palettes.h" #include "gui/debugger.h" namespace Dgds { Console::Console(DgdsEngine *vm) : _vm(vm) { registerCmd("fileinfo", WRAP_METHOD(Console, cmdFileInfo)); registerCmd("filesearch", WRAP_METHOD(Console, cmdFileSearch)); registerCmd("filedump", WRAP_METHOD(Console, cmdFileDump)); registerCmd("imagedump", WRAP_METHOD(Console, cmdImageDump)); registerCmd("imagedumpall", WRAP_METHOD(Console, cmdImageDumpAll)); registerCmd("global", WRAP_METHOD(Console, cmdGlobal)); registerCmd("scene", WRAP_METHOD(Console, cmdScene)); registerCmd("scriptdump", WRAP_METHOD(Console, cmdScriptDump)); registerCmd("trigger", WRAP_METHOD(Console, cmdTrigger)); registerCmd("hotarea", WRAP_METHOD(Console, cmdSetHotAreaDebug)); } bool Console::cmdFileInfo(int argc, const char **argv) { if (argc != 2) { debugPrintf("Usage: %s \n", argv[0]); return true; } const Resource &res = _vm->getResourceManager()->getResourceInfo(argv[1]); debugPrintf("Resource volume: %d, position: %d, size: %d, checksum: %d\n", res.volume, res.pos, res.size, res.checksum); return true; } bool Console::cmdFileSearch(int argc, const char **argv) { if (argc != 2) { debugPrintf("Usage: %s \n", argv[0]); return true; } const ResourceList &resources = _vm->getResourceManager()->getResources(); for (const auto &resource : resources) { if (resource._key.contains(argv[1])) { Resource res = resource._value; debugPrintf("Resource: %s, volume: %d, position: %d, size: %d, checksum: %d\n", resource._key.c_str(), res.volume, res.pos, res.size, res.checksum); } } return true; } bool Console::cmdFileDump(int argc, const char **argv) { if (argc < 2) { debugPrintf("Usage: %s [ignore patches] [unpack] [outputpath] [chunktype]\n", argv[0]); debugPrintf(" eg: %s CLLIVING.ADH 0 1 clliving_unpack.adscript SCR:\n", argv[0]); return true; } Common::String fileName = argv[1]; bool ignorePatches = (argc > 2) && (!scumm_stricmp(argv[2], "true") || !strcmp(argv[2], "1")); bool unpack = (argc > 3) && (!scumm_stricmp(argv[3], "true") || !strcmp(argv[3], "1")); Common::String dstPath = (argc > 4) ? argv[4] : ""; Common::String chunkType = (argc > 5) ? argv[5] : ""; Common::SeekableReadStream *resStream = _vm->getResource(fileName, ignorePatches); if (resStream == nullptr) { debugPrintf("Resource not found\n"); return true; } int32 size = resStream->size(); byte *data; if (!unpack) { data = new byte[size]; resStream->read(data, size); } else { data = new byte[2000000]; // about 2MB, but maximum container size is around 1.5MB byte *ptr = data; const char *dot; DGDS_EX ex = 0; size = 0; if ((dot = strrchr(fileName.c_str(), '.'))) { ex = MKTAG24(toupper(dot[1]), toupper(dot[2]), toupper(dot[3])); } DgdsChunkReader chunk(resStream); while (chunk.readNextHeader(ex, fileName)) { if (!chunkType.empty() && !chunkType.equals(chunk.getIdStr())) { if (!chunk.isContainer()) chunk.skipContent(); continue; } bool readSuccess = chunk.readContent(_vm->getDecompressor()); if (readSuccess) { memcpy(ptr, chunk.getIdStr(), 4); ptr += 4; chunk.getContent()->read(ptr, chunk.getContent()->size()); ptr += chunk.getContent()->size(); size += 4 + chunk.getContent()->size(); } else { warning("Failed to read content for chunk with id %s", chunk.getIdStr()); } } } delete resStream; Common::DumpFile out; out.open(Common::Path(dstPath + fileName)); if (!out.isOpen()) { warning("Couldn't open path %s%s", dstPath.c_str(), fileName.c_str()); } else { out.write(data, size); out.flush(); out.close(); } delete[] data; return true; } bool Console::dumpImageFrame(const char *fname, int frameno, const char *outpath) { Image img(_vm->getResourceManager(), _vm->getDecompressor()); int maxframe = img.frameCount(fname); if (frameno > maxframe) { debugPrintf("Image only has %d frames\n", maxframe); return false; } GamePalettes pal(_vm->getResourceManager(), _vm->getDecompressor()); img.loadBitmap(fname); int width = img.width(frameno); int height = img.height(frameno); if (!width || !height) { debugPrintf("Image %s:%d not valid\n", fname, frameno); return false; } Common::DumpFile outf; Common::String outfname = Common::String::format("%s-%d.png", fname, frameno); if (outpath) { Common::Path path(outpath); path.joinInPlace(outfname); outf.open(path); } else { outf.open(Common::Path(outfname)); } if (!outf.isOpen()) { debugPrintf("Couldn't open %s\n", outfname.c_str()); return false; } byte palbuf[768]; g_system->getPaletteManager()->grabPalette(palbuf, 0, 256); ::Image::writePNG(outf, *(img.getSurface(frameno)->surfacePtr()), palbuf); outf.close(); debugPrintf("wrote %dx%d png to %s\n", width, height, outfname.c_str()); return true; } bool Console::cmdImageDumpAll(int argc, const char **argv) { #ifdef USE_PNG if (argc < 2) { debugPrintf("Usage: %s [outputdir]\n", argv[0]); debugPrintf(" eg: %s CLGAME2.BMP /tmp\n", argv[0]); return true; } const char *fname = argv[1]; if (!_vm->getResourceManager()->hasResource(fname)) { debugPrintf("Resource %s not found\n", fname); return true; } Image img(_vm->getResourceManager(), _vm->getDecompressor()); int maxframe = img.frameCount(fname); const char *outpath = (argc > 2 ? argv[2] : nullptr); for (int i = 0; i < maxframe; i++) { if (!dumpImageFrame(fname, i, outpath)) break; } return true; #else warning("dumpimage needs png support"); return true; #endif // USE_PNG } bool Console::cmdImageDump(int argc, const char **argv) { #ifdef USE_PNG if (argc < 3) { debugPrintf("Usage: %s [outputdir]\n", argv[0]); debugPrintf(" eg: %s CLGAME2.BMP 2 /tmp\n", argv[0]); return true; } const char *fname = argv[1]; int frameno = atoi(argv[2]); if (!_vm->getResourceManager()->hasResource(fname)) { debugPrintf("Resource %s not found\n", fname); return true; } const char *outpath = (argc > 3 ? argv[3] : nullptr); dumpImageFrame(fname, frameno, outpath); return true; #else warning("dumpimage needs png support"); return true; #endif // USE_PNG } bool Console::cmdGlobal(int argc, const char **argv) { if (argc < 2) { debugPrintf("Usage: %s \n", argv[0]); debugPrintf("%s returns the value of a global\n", argv[0]); debugPrintf("%s sets the value of a global\n", argv[0]); return true; } GDSScene *scene = _vm->getGDSScene(); int num = atoi(argv[1]); if (argc == 2) { int16 val = scene->getGlobal(num); debugPrintf("Global %d is %d\n", num, val); } else if (argc == 3) { int16 newVal = atoi(argv[2]); scene->setGlobal(num, newVal); debugPrintf("Global %d set to %d\n", num, newVal); } return true; } bool Console::cmdScene(int argc, const char **argv) { if (argc == 1) { int curSceneNum = _vm->getScene()->getNum(); debugPrintf("Current scene is %d\n", curSceneNum); debugPrintf("Use %s to change the scene\n", argv[0]); } else { int num = atoi(argv[1]); _vm->changeScene(num); debugPrintf("Scene changed to %d\n", num); } return true; } bool Console::cmdTrigger(int argc, const char **argv) { if (argc < 2) { debugPrintf("Usage: %s \n", argv[0]); debugPrintf("%s returns the status of a trigger\n", argv[0]); debugPrintf("%s sets the status of a trigger\n", argv[0]); return true; } SDSScene *scene = _vm->getScene(); int num = atoi(argv[1]); if (argc == 2) { bool val = scene->isTriggerEnabled(num); debugPrintf("Trigger %d is %d\n", num, val); } else if (argc == 3) { bool enable = atoi(argv[2]); scene->enableTrigger(0, num, enable); debugPrintf("Trigger %d set to %d\n", num, enable); } return true; } void Console::printOp(int indent, const char *text) { for (int i = 0; i < indent; i++) { debugPrintf("\t"); } debugPrintf("%s", text); } bool Console::cmdScriptDump(int argc, const char **argv) { if (argc < 2) { debugPrintf("Usage: %s \n", argv[0]); debugPrintf(" eg: %s CLBATH.ADS\n", argv[0]); return true; } Common::String fname(argv[1]); Common::SeekableReadStream *resStream = _vm->getResource(fname, false); if (resStream == nullptr) { debugPrintf("Resource %s not found\n", fname.c_str()); return true; } ADSData adsData; ADSInterpreter interp(_vm); ADSParser dgds(_vm->getResourceManager(), _vm->getDecompressor()); dgds.parse(&adsData, fname); adsData.scr->seek(0); uint8 segno = 0; uint8 indent = 1; uint16 opcode; uint16 tag = 1; while (!adsData.scr->eos()) { opcode = adsData.scr->readUint16LE(); switch (opcode) { case 0xFFFF: printOp(indent, "END\n"); tag = adsData.scr->readUint16LE(); segno++; // Fall through case 0x0001: if (adsData._tags.contains(tag)) debugPrintf("\n%d: %s\n", segno, adsData._tags[tag].c_str()); continue; case 0x0005: printOp(indent, "init"); break; case 0x1010: printOp(indent++, "WHILE runtype"); break; case 0x1020: printOp(indent++, "WHILE not runtype"); break; case 0x1030: printOp(indent++, "WHILE NOT_PLAYED"); break; case 0x1040: printOp(indent++, "WHILE PLAYED"); break; case 0x1050: printOp(indent++, "WHILE FINISHED"); break; case 0x1060: printOp(indent++, "WHILE NOT_RUNNING"); break; case 0x1070: printOp(indent++, "WHILE RUNNING"); break; case 0x1080: printOp(indent++, "WHILE count?"); break; case 0x1090: printOp(indent++, "WHILE ??"); break; case 0x1310: printOp(indent++, "IF PAUSED"); break; case 0x1320: printOp(indent++, "IF NOT_PAUSED"); break; case 0x1330: printOp(indent++, "IF NOT_PLAYED"); break; case 0x1340: printOp(indent++, "IF PLAYED"); break; case 0x1350: printOp(indent++, "IF FINISHED"); break; case 0x1360: printOp(indent++, "IF NOT_RUNNING"); break; case 0x1370: printOp(indent++, "IF RUNNING"); break; case 0x1380: printOp(indent++, "IF DETAIL LEVEL <="); break; case 0x1390: printOp(indent++, "IF DETAIL LEVEL >="); break; case 0x1500: printOp(--indent, "ELSE / Skip to end-if"); break; case 0x1510: printOp(--indent, "END IF"); break; case 0x1520: printOp(--indent, "END WHILE"); break; case 0x2000: printOp(indent, "ADD sequence (restart)"); break; case 0x2005: printOp(indent, "ADD sequence (continue)"); break; case 0x2010: printOp(indent, "STOP SCENE"); break; case 0x2015: printOp(indent, "PAUSE SEQ"); break; case 0x2020: printOp(indent, "RESET SEQ"); break; case 0x3020: printOp(indent, "RANDOM_NOOP"); break; case 0x3010: printOp(indent++, "RANDOM_START"); break; case 0x30FF: printOp(--indent, "RANDOM_END"); break; case 0x4000: printOp(indent, "MOVE SEQ TO BACK"); break; case 0x4010: printOp(indent, "MOVE SEQ TO FRONT"); break; case 0xF000: printOp(indent, "SET STATE 2"); break; case 0xF010: printOp(indent, "FADE_OUT"); break; case 0xF200: printOp(indent, "RUN_SCRIPT"); break; case 0xF210: printOp(indent, "RESTART_SCRIPT"); break; case 0x1420: printOp(--indent, "AND"); break; case 0x1430: printOp(--indent, "OR"); break; case 0xFF10: printOp(indent, "UNKNOWN"); break; case 0xFFF0: printOp(indent, "END_IF"); break; default: printOp(indent, "UNKNOWN"); debugPrintf("(%04x)", opcode); break; } int numArgs = interp.numArgs(opcode); for (int i = 0; i < numArgs; i++) { debugPrintf(" %d", adsData.scr->readUint16LE()); } debugPrintf("\n"); } delete resStream; return true; } bool Console::cmdSetHotAreaDebug(int argc, const char **argv) { if (argc > 1) { debugPrintf("Usage: %s\n", argv[0]); debugPrintf("Toggle whether to show debug boxes around scene hot areas\n"); return true; } DgdsEngine *engine = DgdsEngine::getInstance(); bool enabled = engine->getDebugShowHotAreas(); debugPrintf("Turned %s hot area debug\n", enabled ? "OFF" : "ON"); engine->setDebugShowHotAreas(!enabled); return true; } } // End of namespace Dgds