scummvm/engines/dgds/console.cpp
2025-03-27 13:47:57 +08:00

514 lines
13 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 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 <http://www.gnu.org/licenses/>.
*
*/
#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 <file>\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 <file to search>\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 <file> [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 <imagefilename> [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 <imagefilename> <frameno> [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 <num> <val>\n", argv[0]);
debugPrintf("%s <num> returns the value of a global\n", argv[0]);
debugPrintf("%s <num> <val> 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 <num> 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 <num> <val>\n", argv[0]);
debugPrintf("%s <num> returns the status of a trigger\n", argv[0]);
debugPrintf("%s <num> <val> 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 <filename>\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