diff options
Diffstat (limited to 'engines')
27 files changed, 5452 insertions, 0 deletions
diff --git a/engines/engines.mk b/engines/engines.mk index 5115ac69aa..4b2780a903 100644 --- a/engines/engines.mk +++ b/engines/engines.mk @@ -42,6 +42,11 @@ DEFINES += -DENABLE_GOB=$(ENABLE_GOB) MODULES += engines/gob endif +ifdef ENABLE_GROOVIE +DEFINES += -DENABLE_GROOVIE=$(ENABLE_GROOVIE) +MODULES += engines/groovie +endif + ifdef ENABLE_IGOR DEFINES += -DENABLE_IGOR=$(ENABLE_IGOR) MODULES += engines/igor diff --git a/engines/groovie/cursor.cpp b/engines/groovie/cursor.cpp new file mode 100644 index 0000000000..3dd1f9bc6e --- /dev/null +++ b/engines/groovie/cursor.cpp @@ -0,0 +1,314 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "groovie/cursor.h" +#include "groovie/groovie.h" + +namespace Groovie { + +// Cursor Manager + +CursorMan::CursorMan(OSystem *system) : + _syst(system), _lastTime(0), _cursor(NULL) { +} + +CursorMan::~CursorMan() { + // Delete the cursors + for (uint cursor = 0; cursor < _cursors.size(); cursor++) { + delete _cursors[cursor]; + } +} + +uint8 CursorMan::getStyle() { + return _current; +} + +void CursorMan::setStyle(uint8 newStyle) { + // Reset the animation + _lastFrame = 254; + _lastTime = 1; + + // Save the new cursor + _current = newStyle; + _cursor = _cursors[newStyle]; + + // Show the first frame + _cursor->enable(); + animate(); +} + +void CursorMan::animate() { + if (_lastTime) { + int newTime = _syst->getMillis(); + if (_lastTime - newTime >= 75) { + _lastFrame++; + _lastFrame %= _cursor->getFrames(); + _cursor->showFrame(_lastFrame); + _lastTime = _syst->getMillis(); + } + } +} + + +// t7g Cursor + +class Cursor_t7g : public Cursor { +public: + Cursor_t7g(OSystem *system, uint8 *img, uint8 *pal); + + void enable(); + void showFrame(uint16 frame); + +private: + OSystem *_syst; + byte *_img; + byte *_pal; +}; + +Cursor_t7g::Cursor_t7g(OSystem *system, uint8 *img, uint8 *pal) : + _syst(system), _pal(pal) { + + _width = img[0]; + _height = img[1]; + _numFrames = img[2]; + uint8 elinor1 = img[3]; + uint8 elinor2 = img[4]; + + _img = img + 5; + + debugC(1, kGroovieDebugCursor | kGroovieDebugAll, "Groovie::Cursor: width: %d, height: %d, frames:%d", _width, _height, _numFrames); + debugC(1, kGroovieDebugCursor | kGroovieDebugUnknown | kGroovieDebugAll, "Groovie::Cursor: elinor: 0x%02X (%d), 0x%02X (%d)", elinor1, elinor1, elinor2, elinor2); +} + +void Cursor_t7g::enable() { + // Apply the palette + _syst->setCursorPalette(_pal, 0, 32); +} + +void Cursor_t7g::showFrame(uint16 frame) { + // Set the mouse cursor + int offset = _width * _height * frame; + _syst->setMouseCursor((const byte *)_img + offset, _width, _height, _width >> 1, _height >> 1, 0); +} + + +// t7g Cursor Manager + +#define NUM_IMGS 9 +static const uint16 cursorDataOffsets[NUM_IMGS] = { +0x0000, 0x182f, 0x3b6d, 0x50cc, 0x6e79, 0x825d, 0x96d7, 0xa455, 0xa776 +}; + +#define NUM_PALS 7 +//Pals: 0xb794, 0xb7f4, 0xb854, 0xb8b4, 0xb914, 0xb974, 0xb9d4 + +#define NUM_STYLES 11 +// pyramid is cursor 8, eyes are 9 & 10 +const uint CursorMan_t7g::_cursorImg[NUM_STYLES] = {3, 5, 4, 3, 1, 0, 2, 6, 7, 8, 8}; +const uint CursorMan_t7g::_cursorPal[NUM_STYLES] = {0, 0, 0, 0, 2, 0, 1, 3, 5, 4, 6}; + +CursorMan_t7g::CursorMan_t7g(OSystem *system) : + CursorMan(system) { + + // Open the cursors file + Common::File robgjd; + if (!robgjd.open("rob.gjd")) { + error("Groovie::Cursor: Couldn't open rob.gjd"); + return; + } + + // Load the images + for (uint imgnum = 0; imgnum < NUM_IMGS; imgnum++) { + robgjd.seek(cursorDataOffsets[imgnum]); + _images.push_back(loadImage(robgjd)); + } + + // Load the palettes + robgjd.seek(-0x60 * NUM_PALS, SEEK_END); + for (uint palnum = 0; palnum < NUM_PALS; palnum++) { + _palettes.push_back(loadPalette(robgjd)); + } + + // Build the cursors + for (uint cursor = 0; cursor < NUM_STYLES; cursor++) { + Cursor *s = new Cursor_t7g(_syst, _images[_cursorImg[cursor]], _palettes[_cursorPal[cursor]]); + _cursors.push_back(s); + } + + robgjd.close(); +} + +CursorMan_t7g::~CursorMan_t7g() { + // Delete the images + for (uint img = 0; img < _images.size(); img++) { + delete[] _images[img]; + } + + // Delete the palettes + for (uint pal = 0; pal < _palettes.size(); pal++) { + delete[] _palettes[pal]; + } +} + +byte *CursorMan_t7g::loadImage(Common::File &file) { + uint16 decompbytes = 0, offset, i, length; + uint8 flagbyte, lengthmask = 0x0F, offsetlen, var_8; + byte *cursorStorage = new byte[65536]; + uint8 *runningcursor = cursorStorage; + + bool finished = false; + while (!(finished || file.eos())) { + flagbyte = file.readByte(); + for (i = 1; i <= 8; i++) { + if (!file.eos()) { + if (flagbyte & 1) { + *(runningcursor++) = file.readByte(); + decompbytes++; + } else { + var_8 = file.readByte(); + offsetlen = file.readByte(); + if (var_8 == 0 && offsetlen == 0) { + finished = true; + break; + } + length = (offsetlen & lengthmask) + 3; + offsetlen >>= 4; + offset = (offsetlen << 8) + var_8; + decompbytes += length; + + for (; length > 0; length--, runningcursor++) { + *(runningcursor) = *(runningcursor - offset); + } + } + flagbyte = flagbyte >> 1; + } + } + } + + return cursorStorage; +} + +byte *CursorMan_t7g::loadPalette(Common::File &file) { + byte *palette = new byte[4 * 32]; + for (uint8 colournum = 0; colournum < 32; colournum++) { + palette[colournum * 4 + 0] = file.readByte(); + palette[colournum * 4 + 1] = file.readByte(); + palette[colournum * 4 + 2] = file.readByte(); + palette[colournum * 4 + 3] = 0; + } + return palette; +} + + +// v2 Cursor + +class Cursor_v2 : public Cursor { +public: + Cursor_v2(Common::File &file); + + void enable(); + void showFrame(uint16 frame); + +private: + //byte *_data; +}; + +Cursor_v2::Cursor_v2(Common::File &file) { + _numFrames = file.readUint16LE(); + _width = file.readUint16LE(); + _height = file.readUint16LE(); + + debugC(1, kGroovieDebugCursor | kGroovieDebugAll, "Groovie::Cursor: width: %d, height: %d, frames:%d", _width, _height, _numFrames); + + uint16 tmp16 = file.readUint16LE(); + debugC(5, kGroovieDebugCursor | kGroovieDebugAll, "hotspot x?: %d\n", tmp16); + tmp16 = file.readUint16LE(); + debugC(5, kGroovieDebugCursor | kGroovieDebugAll, "hotspot y?: %d\n", tmp16); + int loop2count = file.readUint16LE(); + debugC(5, kGroovieDebugCursor | kGroovieDebugAll, "loop2count?: %d\n", loop2count); + for (int l = 0; l < loop2count; l++) { + tmp16 = file.readUint16LE(); + debugC(5, kGroovieDebugCursor | kGroovieDebugAll, "loop2a: %d\n", tmp16); + tmp16 = file.readUint16LE(); + debugC(5, kGroovieDebugCursor | kGroovieDebugAll, "loop2b: %d\n", tmp16); + } + + file.seek(0x20 * 3, SEEK_CUR); + + for (int f = 0; f < _numFrames; f++) { + uint32 tmp32 = file.readUint32LE(); + debugC(5, kGroovieDebugCursor | kGroovieDebugAll, "loop3: %d\n", tmp32); + + //file.seek(tmp32, SEEK_CUR); + byte *data = new byte[tmp32]; + file.read(data, tmp32); + //Common::hexdump(data, tmp32); + delete[] data; + } +} + +void Cursor_v2::enable() { +} + +void Cursor_v2::showFrame(uint16 frame) { +} + + +// v2 Cursor Manager + +CursorMan_v2::CursorMan_v2(OSystem *system) : + CursorMan(system) { + + // Open the icons file + Common::File iconsFile; + if (!iconsFile.open("icons.ph")) { + error("Groovie::Cursor: Couldn't open icons.ph"); + return; + } + + // Verify the signature + uint32 tmp32 = iconsFile.readUint32LE(); + uint16 tmp16 = iconsFile.readUint16LE(); + if (tmp32 != 0x6e6f6369 || tmp16 != 1) { + error("Groovie::Cursor: icons.ph signature failed: %04X %d", tmp32, tmp16); + return; + } + + // Read the number of icons + uint16 nicons = iconsFile.readUint16LE(); + + // Read the icons + for (int i = 0; i < nicons; i++) { + Cursor *s = new Cursor_v2(iconsFile); + _cursors.push_back(s); + } + + iconsFile.close(); +} + +CursorMan_v2::~CursorMan_v2() { +} + +} // End of Groovie namespace diff --git a/engines/groovie/cursor.h b/engines/groovie/cursor.h new file mode 100644 index 0000000000..19694b2a0d --- /dev/null +++ b/engines/groovie/cursor.h @@ -0,0 +1,96 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef GROOVIE_CURSOR_H +#define GROOVIE_CURSOR_H + +#include "common/system.h" +#include "common/file.h" + +namespace Groovie { + +class Cursor { +public: + virtual ~Cursor() {} + uint16 getFrames() { return _numFrames; } + virtual void enable() = 0; + virtual void showFrame(uint16 frame) = 0; + +protected: + uint16 _width; + uint16 _height; + uint16 _numFrames; +}; + +class CursorMan { +public: + CursorMan(OSystem *system); + virtual ~CursorMan(); + + virtual void animate(); + virtual void setStyle(uint8 newStyle); + virtual uint8 getStyle(); + +protected: + OSystem *_syst; + + // Animation variables + uint8 _lastFrame; + uint32 _lastTime; + + // Styles + Common::Array<Cursor *> _cursors; + uint8 _current; + Cursor *_cursor; +}; + +class CursorMan_t7g : public CursorMan { +public: + CursorMan_t7g(OSystem *system); + ~CursorMan_t7g(); + +private: + // Styles data + static const uint _cursorImg[]; + static const uint _cursorPal[]; + + // Cursors data + Common::Array<byte *> _images; + Common::Array<byte *> _palettes; + + // Loading functions + byte *loadImage(Common::File &file); + byte *loadPalette(Common::File &file); +}; + +class CursorMan_v2 : public CursorMan { +public: + CursorMan_v2(OSystem *system); + ~CursorMan_v2(); +}; + +} // End of Groovie namespace + +#endif // GROOVIE_CURSOR_H diff --git a/engines/groovie/debug.cpp b/engines/groovie/debug.cpp new file mode 100644 index 0000000000..8968400fef --- /dev/null +++ b/engines/groovie/debug.cpp @@ -0,0 +1,145 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "groovie/debug.h" +#include "groovie/script.h" +#include "groovie/groovie.h" + +namespace Groovie { + +Debugger::Debugger(GroovieEngine *vm) : + _vm (vm), _script(&_vm->_script), _syst(_vm->_system) { + + // Register the debugger comands + DCmd_Register("step", WRAP_METHOD(Debugger, cmd_step)); + DCmd_Register("go", WRAP_METHOD(Debugger, cmd_go)); + DCmd_Register("pc", WRAP_METHOD(Debugger, cmd_pc)); + DCmd_Register("fg", WRAP_METHOD(Debugger, cmd_fg)); + DCmd_Register("bg", WRAP_METHOD(Debugger, cmd_bg)); + DCmd_Register("mem", WRAP_METHOD(Debugger, cmd_mem)); + DCmd_Register("load", WRAP_METHOD(Debugger, cmd_loadgame)); + DCmd_Register("save", WRAP_METHOD(Debugger, cmd_savegame)); + DCmd_Register("playref", WRAP_METHOD(Debugger, cmd_playref)); + DCmd_Register("dumppal", WRAP_METHOD(Debugger, cmd_dumppal)); +} + +Debugger::~Debugger() { + Common::clearAllSpecialDebugLevels(); +} + +int Debugger::getNumber(const char *arg) { + return strtol(arg, (char **)NULL, 0); +} + +bool Debugger::cmd_step(int argc, const char **argv) { + _script->step(); + return true; +} + +bool Debugger::cmd_go(int argc, const char **argv) { + _script->step(); + return false; +} + +bool Debugger::cmd_fg(int argc, const char **argv) { + _vm->_graphicsMan->updateScreen(&_vm->_graphicsMan->_foreground); + return false; +} + +bool Debugger::cmd_bg(int argc, const char **argv) { + _vm->_graphicsMan->updateScreen(&_vm->_graphicsMan->_background); + return false; +} + +bool Debugger::cmd_pc(int argc, const char **argv) { + if (argc == 2) { + int val = getNumber(argv[1]); + _script->_currentInstruction = val; + } + DebugPrintf("pc = 0x%04X\n", _script->_currentInstruction); + return true; +} + +bool Debugger::cmd_mem(int argc, const char **argv) { + if (argc >= 2) { + int pos = getNumber(argv[1]); + uint8 val; + if (argc >= 3) { + // Set + val = getNumber(argv[2]); + _script->_variables[pos] = val; + } else { + // Get + val = _script->_variables[pos]; + } + DebugPrintf("mem[0x%04X] = 0x%02X\n", pos, val); + } else { + DebugPrintf("Syntax: mem <addr> [<val>]\n"); + } + return true; +} + +bool Debugger::cmd_loadgame(int argc, const char **argv) { + if (argc == 2) { + int slot = getNumber(argv[1]); + _script->loadgame(slot); + } else { + DebugPrintf("Syntax: load <slot>\n"); + } + return true; +} + +bool Debugger::cmd_savegame(int argc, const char **argv) { + if (argc == 2) { + int slot = getNumber(argv[1]); + _script->savegame(slot); + } else { + DebugPrintf("Syntax: save <slot>\n"); + } + return true; +} + +bool Debugger::cmd_playref(int argc, const char **argv) { + if (argc == 2) { + int ref = getNumber(argv[1]); + _script->playvideofromref(ref); + } else { + DebugPrintf("Syntax: playref <videorefnum>\n"); + } + return true; +} + +bool Debugger::cmd_dumppal(int argc, const char **argv) { + uint16 i; + byte palettedump[256 * 4]; + _syst->grabPalette(palettedump, 0, 256); + + for (i = 0; i < 256; i++) { + DebugPrintf("%3d: %3d,%3d,%3d,%3d\n", i, palettedump[(i * 4)], palettedump[(i * 4) + 1], palettedump[(i * 4) + 2], palettedump[(i * 4) + 3]); + } + return true; +} + +} // End of Groovie namespace diff --git a/engines/groovie/debug.h b/engines/groovie/debug.h new file mode 100644 index 0000000000..dadba9482c --- /dev/null +++ b/engines/groovie/debug.h @@ -0,0 +1,63 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef GROOVIE_DEBUG_H +#define GROOVIE_DEBUG_H + +#include "gui/debugger.h" +#include "engines/engine.h" + +namespace Groovie { + +class Script; +class GroovieEngine; + +class Debugger : public GUI::Debugger { +public: + Debugger(GroovieEngine *vm); + ~Debugger(); + +private: + GroovieEngine *_vm; + Script *_script; + OSystem *_syst; + + int getNumber(const char *arg); + + bool cmd_step(int argc, const char **argv); + bool cmd_go(int argc, const char **argv); + bool cmd_pc(int argc, const char **argv); + bool cmd_bg(int argc, const char **argv); + bool cmd_fg(int argc, const char **argv); + bool cmd_mem(int argc, const char **argv); + bool cmd_loadgame(int argc, const char **argv); + bool cmd_savegame(int argc, const char **argv); + bool cmd_playref(int argc, const char **argv); + bool cmd_dumppal(int argc, const char **argv); +}; + +} // End of Groovie namespace + +#endif // GROOVIE_DEBUG_H diff --git a/engines/groovie/detection.cpp b/engines/groovie/detection.cpp new file mode 100644 index 0000000000..80023924dc --- /dev/null +++ b/engines/groovie/detection.cpp @@ -0,0 +1,265 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "common/savefile.h" + +#include "groovie/groovie.h" + +namespace Groovie { + +//#define GROOVIE_EXPERIMENTAL + +static const PlainGameDescriptor groovieGames[] = { + // Games + {"t7g", "The 7th Guest"}, + +#ifdef GROOVIE_EXPERIMENTAL + {"11h", "The 11th Hour: The sequel to The 7th Guest"}, + {"clandestiny", "Clandestiny"}, + {"unclehenry", "Uncle Henry's Playhouse"}, + {"tlc", "Tender Loving Care"}, + + // Extras + {"making11h", "The Making of The 11th Hour"}, + {"clantrailer", "Clandestiny Trailer"}, +#endif + + // Unknown + {"groovie", "Groovie engine game"}, + {0, 0} +}; + +static const GroovieGameDescription gameDescriptions[] = { + + // The 7th Guest DOS English + { + { + "t7g", "", + AD_ENTRY1s("script.grv", "d1b8033b40aa67c076039881eccce90d", 16659), + Common::EN_ANY, Common::kPlatformPC, Common::ADGF_NO_FLAGS + }, + kGroovieT7G, 0 + }, + + // The 7th Guest Mac English + { + { + "t7g", "", + AD_ENTRY1s("script.grv", "6e30b54b1f3bc2262cdcf7961db2ae67", 17191), + Common::EN_ANY, Common::kPlatformMacintosh, Common::ADGF_NO_FLAGS + }, + kGroovieT7G, 0 + }, + +#ifdef GROOVIE_EXPERIMENTAL + // The 11th Hour DOS English + { + { + "11h", "", + AD_ENTRY1s("disk.1", "5c0428cd3659fc7bbcd0aa16485ed5da", 227), + Common::EN_ANY, Common::kPlatformPC, Common::ADGF_NO_FLAGS + }, + kGroovieV2, 1 + }, + + // The Making of The 11th Hour DOS English + { + { + "making11h", "", + AD_ENTRY1s("disk.1", "5c0428cd3659fc7bbcd0aa16485ed5da", 227), + Common::EN_ANY, Common::kPlatformPC, Common::ADGF_NO_FLAGS + }, + kGroovieV2, 2 + }, + + // Clandestiny Trailer DOS English + { + { + "clantrailer", "", + AD_ENTRY1s("disk.1", "5c0428cd3659fc7bbcd0aa16485ed5da", 227), + Common::EN_ANY, Common::kPlatformPC, Common::ADGF_NO_FLAGS + }, + kGroovieV2, 3 + }, + + // Clandestiny DOS English + { + { + "clandestiny", "", + AD_ENTRY1s("disk.1", "f79fc1515174540fef6a34132efc4c53", 76), + Common::EN_ANY, Common::kPlatformPC, Common::ADGF_NO_FLAGS + }, + kGroovieV2, 1 + }, + + // Uncle Henry's Playhouse PC English + { + { + "unclehenry", "", + AD_ENTRY1s("disk.1", "0e1b1d3cecc4fc7efa62a968844d1f7a", 72), + Common::EN_ANY, Common::kPlatformPC, Common::ADGF_NO_FLAGS + }, + kGroovieV2, 1 + }, + + // Tender Loving Care PC English + { + { + "tlc", "", + AD_ENTRY1s("disk.1", "32a1afa68478f1f9d2b25eeea427f2e3", 84), + Common::EN_ANY, Common::kPlatformPC, Common::ADGF_NO_FLAGS + }, + kGroovieV2, 1 + }, +#endif + + {AD_TABLE_END_MARKER, kGroovieT7G, 0} +}; + +static const Common::ADParams detectionParams = { + // Pointer to ADGameDescription or its superset structure + (const byte *)gameDescriptions, + // Size of that superset structure + sizeof(GroovieGameDescription), + // Number of bytes to compute MD5 sum for + 5000, + // List of all engine targets + groovieGames, + // Structure for autoupgrading obsolete targets + 0, + // Name of single gameid (optional) + 0, + // List of files for file-based fallback detection (optional) + 0, + // Flags + 0 +}; + + +class GroovieMetaEngine : public Common::AdvancedMetaEngine { +public: + GroovieMetaEngine() : Common::AdvancedMetaEngine(detectionParams) {} + + const char *getName() const { + return "Groovie Engine"; + } + + const char *getCopyright() const { + return "Groovie Engine (C) 1990-1996 Trilobyte"; + } + + bool createInstance(OSystem *syst, Engine **engine, const Common::ADGameDescription *gd) const; + + bool hasFeature(MetaEngineFeature f) const; + SaveStateList listSaves(const char *target) const; + void removeSaveState(const char *target, int slot) const; +}; + +bool GroovieMetaEngine::createInstance(OSystem *syst, Engine **engine, const Common::ADGameDescription *gd) const { + if (gd) { + *engine = new GroovieEngine(syst, (GroovieGameDescription *)gd); + } + return gd != 0; +} + +bool GroovieMetaEngine::hasFeature(MetaEngineFeature f) const { + return + (f == kSupportsListSaves) || + (f == kSupportsLoadingDuringStartup) || + (f == kSupportsDeleteSave); + //(f == kSavesSupportCreationDate) +} + +SaveStateList GroovieMetaEngine::listSaves(const char *target) const { + Common::SaveFileManager *sfm = g_system->getSavefileManager(); + SaveStateList list; + + // Get the list of savefiles + Common::String pattern = Common::String(target) + ".00?"; + Common::StringList savefiles = sfm->listSavefiles(pattern.c_str()); + + // Sort the list of filenames + sort(savefiles.begin(), savefiles.end()); + + // Fill the information for the existing savegames + Common::StringList::iterator it = savefiles.begin(); + while (it != savefiles.end()) { + int slot = it->lastChar() - '0'; + if (slot >= 0 && slot <= 9) { + Common::InSaveFile *file = sfm->openForLoading(it->c_str()); + + // Read the savegame description + Common::String description; + unsigned char c = 1; + for (int i = 0; (c != 0) && (i < 15); i++) { + c = file->readByte(); + switch (c) { + case 0: + break; + case 16: // @ + c = ' '; + break; + case 244: // $ + c = 0; + break; + default: + c += 0x30; + } + if (c != 0) { + description += c; + } + } + delete file; + + list.push_back(SaveStateDescriptor(slot, description)); + } + it++; + } + + return list; +} + +void GroovieMetaEngine::removeSaveState(const char *target, int slot) const { + if (slot < 0 || slot > 9) { + // Invalid slot, do nothing + return; + } + + char extension[6]; + snprintf(extension, sizeof(extension), ".00%01d", slot); + + Common::String filename = target; + filename += extension; + + g_system->getSavefileManager()->removeSavefile(filename.c_str()); +} + +} // End of namespace Groovie + +#if PLUGIN_ENABLED_DYNAMIC(GROOVIE) + REGISTER_PLUGIN_DYNAMIC(GROOVIE, PLUGIN_TYPE_ENGINE, Groovie::GroovieMetaEngine); +#else + REGISTER_PLUGIN_STATIC(GROOVIE, PLUGIN_TYPE_ENGINE, Groovie::GroovieMetaEngine); +#endif diff --git a/engines/groovie/font.cpp b/engines/groovie/font.cpp new file mode 100644 index 0000000000..cabb7563a1 --- /dev/null +++ b/engines/groovie/font.cpp @@ -0,0 +1,128 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "common/file.h" +#include "graphics/surface.h" + +#include "groovie/font.h" + +namespace Groovie { + +Font::Font(OSystem *syst) : + _syst(syst), _sphinxfnt(NULL) { + + Common::File fontfile; + if (!fontfile.open("sphinx.fnt")) { + error("Groovie::Font: Couldn't open sphinx.fnt"); + } + uint16 fontfilesize = fontfile.size(); + _sphinxfnt = fontfile.readStream(fontfilesize); + fontfile.close(); +} + +Font::~Font() { + delete _sphinxfnt; +} + +void Font::printstring(char *messagein) { + uint16 totalwidth = 0, currxoffset, i; + + char message[15]; + memset(message, 0, 15); + + // Clear the top bar + Common::Rect topbar(640, 80); + Graphics::Surface *gamescreen; + gamescreen = _syst->lockScreen(); + gamescreen->fillRect(topbar, 0); + _syst->unlockScreen(); + + for (i = 0; i < 14; i++) { + char chartocopy = messagein[i]; + if (chartocopy <= 0x00 || chartocopy == 0x24) { + break; + } + message[i] = chartocopy; + } + Common::rtrim(message); + for (i = 0; i < strlen(message); i++) { + totalwidth += letterwidth(message[i]); + } + currxoffset = (640 - totalwidth) / 2; + char *currpos = message; + while (*(currpos) != 0) { + currxoffset += printletter(*(currpos++), currxoffset); + } +} + +uint16 Font::letteroffset(char letter) { + uint16 offset; + offset = letter; + _sphinxfnt->seek(offset); + offset = _sphinxfnt->readByte() * 2 + 128; + _sphinxfnt->seek(offset); + offset = _sphinxfnt->readUint16LE(); + return offset; +} + +uint8 Font::letterwidth(char letter) { + uint16 offset = letteroffset(letter); + _sphinxfnt->seek(offset); + return _sphinxfnt->readByte(); +} + +uint8 Font::letterheight(char letter) { + uint16 offset, width, julia, data, counter = 0; + offset = letteroffset(letter); + _sphinxfnt->seek(offset); + width = _sphinxfnt->readByte(); + julia = _sphinxfnt->readByte(); + data = _sphinxfnt->readByte(); + while (data != 0xFF) { + data = _sphinxfnt->readByte(); + counter++; + } + if (counter % width != 0) assert("font file corrupt"); + return counter / width; +} + + +uint8 Font::printletter(char letter, uint16 xoffset) { + uint16 offset, width, height, julia; + offset = letteroffset(letter); + height = letterheight(letter); + _sphinxfnt->seek(offset); + width = _sphinxfnt->readByte(); + julia = _sphinxfnt->readByte(); + + byte *data = new byte[width * height]; + _sphinxfnt->read(data, width * height); + _syst->copyRectToScreen(data, width, xoffset, 16, width, height); + delete data; + + return width; +} + +} // End of Groovie namespace diff --git a/engines/groovie/font.h b/engines/groovie/font.h new file mode 100644 index 0000000000..1d32f1a50c --- /dev/null +++ b/engines/groovie/font.h @@ -0,0 +1,52 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef GROOVIE_FONT_H +#define GROOVIE_FONT_H + +#include "common/stream.h" +#include "common/system.h" + +namespace Groovie { + +class Font { +public: + Font(OSystem *syst); + ~Font(); + void printstring(char *messagein); + +private: + OSystem *_syst; + Common::MemoryReadStream *_sphinxfnt; + + uint16 letteroffset(char letter); + uint8 letterwidth(char letter); + uint8 letterheight(char letter); + uint8 printletter(char letter, uint16 xoffset); +}; + +} // End of Groovie namespace + +#endif // GROOVIE_FONT_H diff --git a/engines/groovie/graphics.cpp b/engines/groovie/graphics.cpp new file mode 100644 index 0000000000..a7ad88a443 --- /dev/null +++ b/engines/groovie/graphics.cpp @@ -0,0 +1,161 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "groovie/groovie.h" +#include "groovie/graphics.h" + +namespace Groovie { + +GraphicsMan::GraphicsMan(GroovieEngine *vm) : + _vm(vm), _changed(false), _fading(0) { + // Create the game surfaces + _foreground.create(640, 320, 1); + _background.create(640, 320, 1); +} + +GraphicsMan::~GraphicsMan() { + // Free the game surfaces + _foreground.free(); + _background.free(); +} + +void GraphicsMan::update() { + if (_fading) { + // Set the start time + uint32 time = _vm->_system->getMillis() - _fadeStartTime; + + // Scale the time + int step = time / 4; + if (step > 256) { + step = 256; + } + + if (_fading == 1) { + // Apply the fade in + applyFading(step); + } else if (_fading == 2) { + // Apply the fade out + applyFading(256 - step); + + // Clear the buffer when ending the fade out + if (step == 256) + _foreground.fillRect(Common::Rect::Rect(640, 320), 0); + } + + // Check for the end + if (step == 256) { + _fading = 0; + } + } + + // Update the screen if needed and reset the status + if (_changed) { + _vm->_system->updateScreen(); + _changed = false; + } +} + +void GraphicsMan::change() { + _changed = true; +} + +void GraphicsMan::mergeFgAndBg() { + uint32 i; + byte *countf, *countb; + + countf = (byte *)_foreground.getBasePtr(0, 0); + countb = (byte *)_background.getBasePtr(0, 0); + for (i = 640 * 320; i; i--) { + if (255 == *(countf)) { + *(countf) = *(countb); + } + countf++; + countb++; + } +} + + +void GraphicsMan::updateScreen(Graphics::Surface *source) { + _vm->_system->copyRectToScreen((byte *)source->getBasePtr(0, 0), 640, 0, 80, 640, 320); + change(); +} + +bool GraphicsMan::isFading() { + return _fading; +} + +void GraphicsMan::fadeIn(byte *pal) { + // Set the start time + _fadeStartTime = _vm->_system->getMillis(); + + // Copy the target palette + for (int i = 0; i < 256; i++) { + _paletteFull[(i * 4) + 0] = pal[(i * 3) + 0]; + _paletteFull[(i * 4) + 1] = pal[(i * 3) + 1]; + _paletteFull[(i * 4) + 2] = pal[(i * 3) + 2]; + } + + // Apply a black palette right now + applyFading(0); + + // Set the current fading + _fading = 1; +} + +void GraphicsMan::fadeOut() { + // Set the start time + _fadeStartTime = _vm->_system->getMillis(); + + // Get the current palette + _vm->_system->grabPalette(_paletteFull, 0, 256); + + // Set the current fading + _fading = 2; +} + +void GraphicsMan::applyFading(int step) { + // Calculate the fade factor for the given step + int factorR = 256 - (256 - step) * 1; + int factorGB = 256 - (256 - step) * 2; + + if (factorR <= 0) factorR = 0; + if (factorGB <= 0) factorGB = 0; + + // Calculate the new palette + byte newpal[256 * 4]; + for (int i = 0; i < 256; i++) { + newpal[(i * 4) + 0] = (_paletteFull[(i * 4) + 0] * factorR) / 256; + newpal[(i * 4) + 1] = (_paletteFull[(i * 4) + 1] * factorGB) / 256; + newpal[(i * 4) + 2] = (_paletteFull[(i * 4) + 2] * factorGB) / 256; + } + + // Set the screen palette + _vm->_system->setPalette(newpal, 0, 256); + + // Request a screen update + change(); +} + +} // End of Groovie namespace diff --git a/engines/groovie/graphics.h b/engines/groovie/graphics.h new file mode 100644 index 0000000000..ea3261c85f --- /dev/null +++ b/engines/groovie/graphics.h @@ -0,0 +1,65 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef GROOVIE_GRAPHICS_H +#define GROOVIE_GRAPHICS_H + +namespace Groovie { + +class GroovieEngine; + +class GraphicsMan { +public: + GraphicsMan(GroovieEngine *vm); + ~GraphicsMan(); + + // Buffers + void update(); + void change(); + void mergeFgAndBg(); + void updateScreen(Graphics::Surface *source); + Graphics::Surface _foreground; // The main surface that most things are drawn to + Graphics::Surface _background; // Used occasionally, mostly (only?) in puzzles + + // Palette fading + bool isFading(); + void fadeIn(byte *pal); + void fadeOut(); + +private: + GroovieEngine *_vm; + + bool _changed; + + // Palette fading + void applyFading(int step); + int _fading; + byte _paletteFull[256 * 4]; + uint32 _fadeStartTime; +}; + +} // End of Groovie namespace + +#endif // GROOVIE_GRAPHICS_H diff --git a/engines/groovie/groovie.cpp b/engines/groovie/groovie.cpp new file mode 100644 index 0000000000..d49579c0a2 --- /dev/null +++ b/engines/groovie/groovie.cpp @@ -0,0 +1,272 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "common/config-manager.h" +#include "common/events.h" +#include "sound/mixer.h" + +#include "groovie/groovie.h" +#include "groovie/music.h" +#include "groovie/roq.h" +#include "groovie/vdx.h" + +namespace Groovie { + +GroovieEngine::GroovieEngine(OSystem *syst, GroovieGameDescription *gd) : + Engine(syst), _gameDescription(gd), _debugger(NULL), _script(this), + _resMan(NULL), _cursorMan(NULL), _videoPlayer(NULL), _musicPlayer(NULL), + _graphicsMan(NULL), _waitingForInput(false) { + + // Adding the default directories + Common::File::addDefaultDirectory(_gameDataDir.getChild("groovie")); + Common::File::addDefaultDirectory(_gameDataDir.getChild("media")); + Common::File::addDefaultDirectory(_gameDataDir.getChild("system")); + + // Initialize the custom debug levels + Common::addSpecialDebugLevel(kGroovieDebugAll, "All", "Debug everything"); + Common::addSpecialDebugLevel(kGroovieDebugVideo, "Video", "Debug video and audio playback"); + Common::addSpecialDebugLevel(kGroovieDebugResource, "Resource", "Debug resouce management"); + Common::addSpecialDebugLevel(kGroovieDebugScript, "Script", "Debug the scripts"); + Common::addSpecialDebugLevel(kGroovieDebugUnknown, "Unknown", "Report values of unknown data in files"); + Common::addSpecialDebugLevel(kGroovieDebugHotspots, "Hotspots", "Show the hotspots"); + Common::addSpecialDebugLevel(kGroovieDebugCursor, "Cursor", "Debug cursor decompression / switching"); +} + +GroovieEngine::~GroovieEngine() { + // Delete the remaining objects + delete _debugger; + delete _resMan; + delete _cursorMan; + delete _videoPlayer; + delete _musicPlayer; + delete _graphicsMan; +} + +Common::Error GroovieEngine::init() { + // Initialize the graphics + _system->beginGFXTransaction(); + initCommonGFX(true); + _system->initSize(640, 480); + _system->endGFXTransaction(); + + // Create debugger. It requires GFX to be initialized + _debugger = new Debugger(this); + _script.setDebugger(_debugger); + + // Create the graphics manager + _graphicsMan = new GraphicsMan(this); + + // Create the resource and cursor managers and the video player + switch (_gameDescription->version) { + case kGroovieT7G: + _resMan = new ResMan_t7g(); + _cursorMan = new CursorMan_t7g(_system); + _videoPlayer = new VDXPlayer(this); + break; + case kGroovieV2: + _resMan = new ResMan_v2(); + _cursorMan = new CursorMan_v2(_system); + _videoPlayer = new ROQPlayer(this); + break; + } + + // Create the music player + _musicPlayer = new MusicPlayer(this); + + // Load volume levels + syncSoundSettings(); + + // Get the name of the main script + Common::String filename = _gameDescription->desc.filesDescriptions[0].fileName; + if (_gameDescription->version == kGroovieT7G) { + // Run The 7th Guest's demo if requested + if (ConfMan.hasKey("demo_mode") && ConfMan.getBool("demo_mode")) { + filename = Common::String("demo.grv"); + } + } else if (_gameDescription->version == kGroovieV2) { + // Open the disk index + Common::File disk; + if (!disk.open(filename)) { + error("Couldn't open %s", filename.c_str()); + return Common::kNoGameDataFoundError; + } + + // Search the entry + bool found = false; + int index = 0; + while (!found && !disk.eos()) { + Common::String line = disk.readLine(); + if (line.hasPrefix("title: ")) { + // A new entry + index++; + } else if (line.hasPrefix("boot: ") && index == _gameDescription->indexEntry) { + // It's the boot of the entry were looking for, + // get the script filename + filename = line.c_str() + 6; + found = true; + } + } + + // Couldn't find the entry + if (!found) { + error("Couldn't find entry %d in %s", _gameDescription->indexEntry, filename.c_str()); + return Common::kUnknownError; + } + } + + // Check the script file extension + if (!filename.hasSuffix(".grv")) { + error("%s isn't a valid script filename", filename.c_str()); + return Common::kUnknownError; + } + + // Load the script + if (!_script.loadScript(filename)) { + error("Couldn't load the script file %s", filename.c_str()); + return Common::kUnknownError; + } + + // Should I load a saved game? + if (ConfMan.hasKey("save_slot")) { + // Get the requested slot + int slot = ConfMan.getInt("save_slot"); + _script.directGameLoad(slot); + } + + return Common::kNoError; +} + +Common::Error GroovieEngine::go() { + // Check that the game files and the audio tracks aren't together run from + // the same cd + + checkCD(); + + // Initialize the CD + int cd_num = ConfMan.getInt("cdrom"); + if (cd_num >= 0) + _system->openCD(cd_num); + + while (!shouldQuit()) { + // Show the debugger if required + if (_debugger->isAttached()) { + _debugger->onFrame(); + } + + // If there's still a script error after debugging, end the execution + if (_script.haveError()) { + quitGame(); + break; + } + + // Handle input + Common::Event ev; + while (_eventMan->pollEvent(ev)) { + switch (ev.type) { + case Common::EVENT_KEYDOWN: + // CTRL-D: Attach the debugger + if ((ev.kbd.flags & Common::KBD_CTRL) && ev.kbd.keycode == Common::KEYCODE_d) + _debugger->attach(); + + // Send the event to the scripts + _script.setKbdChar(ev.kbd.ascii); + + // Continue the script execution to handle the key + _waitingForInput = false; + break; + + case Common::EVENT_MOUSEMOVE: + // Continue the script execution, the mouse + // pointer may fall inside a hotspot now + _waitingForInput = false; + break; + + case Common::EVENT_LBUTTONDOWN: + // Send the event to the scripts + _script.setMouseClick(); + + // Continue the script execution to handle + // the click + _waitingForInput = false; + break; + + case Common::EVENT_QUIT: + quitGame(); + break; + + default: + break; + } + } + + if (_waitingForInput) { + // Still waiting for input, just update the mouse and wait a bit more + _cursorMan->animate(); + _system->updateScreen(); + _system->delayMillis(50); + } else if (_graphicsMan->isFading()) { + // We're waiting for a fading to end, let the CPU rest + // for a while and continue + _system->delayMillis(30); + } else { + // Everything's fine, execute another script step + _script.step(); + } + + // Update the screen if required + _graphicsMan->update(); + } + + return Common::kNoError; +} + +bool GroovieEngine::hasFeature(EngineFeature f) const { + return + (f == kSupportsRTL) || + (f == kSupportsLoadingDuringRuntime); +} + +void GroovieEngine::syncSoundSettings() { + _musicPlayer->setUserVolume(ConfMan.getInt("music_volume")); + _mixer->setVolumeForSoundType(Audio::Mixer::kPlainSoundType, ConfMan.getInt("speech_volume")); +} + +bool GroovieEngine::canLoadGameStateCurrently() { + // TODO: verify the engine has been initialized + return true; +} + +Common::Error GroovieEngine::loadGameState(int slot) { + _script.directGameLoad(slot); + + // TODO: Use specific error codes + return Common::kNoError; +} + +void GroovieEngine::waitForInput() { + _waitingForInput = true; +} + +} // End of namespace Groovie diff --git a/engines/groovie/groovie.h b/engines/groovie/groovie.h new file mode 100644 index 0000000000..ef5c9a3bda --- /dev/null +++ b/engines/groovie/groovie.h @@ -0,0 +1,103 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef GROOVIE_H +#define GROOVIE_H + +#include "common/advancedDetector.h" +#include "engines/engine.h" +#include "graphics/surface.h" + +#include "groovie/cursor.h" +#include "groovie/debug.h" +#include "groovie/graphics.h" +#include "groovie/player.h" +#include "groovie/resource.h" +#include "groovie/script.h" + +namespace Groovie { + +class MusicPlayer; + +enum kDebugLevels { + kGroovieDebugAll = 1 << 0, + kGroovieDebugVideo = 1 << 1, + kGroovieDebugResource = 1 << 2, + kGroovieDebugScript = 1 << 3, + kGroovieDebugUnknown = 1 << 4, + kGroovieDebugHotspots = 1 << 5, + kGroovieDebugCursor = 1 << 6, + kGroovieDebugMIDI = 1 << 7 + // the current limitation is 32 debug levels (1 << 31 is the last one) +}; + +enum kEngineVersion { + kGroovieT7G, + kGroovieV2 +}; + +struct GroovieGameDescription { + Common::ADGameDescription desc; + + kEngineVersion version; // Version of the engine + int indexEntry; // The index of the entry in disk.1 for V2 games +}; + +class GroovieEngine : public Engine { +public: + GroovieEngine(OSystem *syst, GroovieGameDescription *gd); + ~GroovieEngine(); + +protected: + Common::Error init(); + Common::Error go(); + +public: + bool hasFeature(EngineFeature f) const; + + bool canLoadGameStateCurrently(); + Common::Error loadGameState(int slot); + void syncSoundSettings(); + + Debugger *getDebugger() { return _debugger; } + + void waitForInput(); + + Script _script; + ResMan *_resMan; + CursorMan *_cursorMan; + VideoPlayer *_videoPlayer; + MusicPlayer *_musicPlayer; + GraphicsMan *_graphicsMan; + +private: + GroovieGameDescription *_gameDescription; + Debugger *_debugger; + bool _waitingForInput; +}; + +} // End of namespace Groovie + +#endif // GROOVIE_H diff --git a/engines/groovie/lzss.cpp b/engines/groovie/lzss.cpp new file mode 100644 index 0000000000..750625d70d --- /dev/null +++ b/engines/groovie/lzss.cpp @@ -0,0 +1,99 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "groovie/lzss.h" + +#define OUT_BUFF_SIZE 131072 +#define COMP_THRESH 3 // Compression not attempted if string to be compressed is less than 3 long + +LzssReadStream::LzssReadStream(Common::ReadStream *indata, uint8 lengthmask, uint8 lengthbits) { + /* + TODO: Nasty hack. Make a buffer bigger than I'll ever need... probably. + What should *really* happen is I should define a whole new type of stream + that gets lzss decompressed on the fly + */ + _outLzssBufData = (uint8 *)malloc(OUT_BUFF_SIZE); + _size = decodeLZSS(indata, lengthmask, lengthbits); + _pos = 0; +} + +LzssReadStream::~LzssReadStream() { + free(_outLzssBufData); +} + +uint32 LzssReadStream::decodeLZSS(Common::ReadStream *in, uint8 lengthmask, uint8 lengthbits) { + uint32 N = 1 << (16 - lengthbits); /* History buffer size */ + byte *histbuff = new byte[N]; /* History buffer */ + memset(histbuff, 0, N); + uint32 outstreampos = 0; + uint32 bufpos = 0; + + while (!in->eos()) { + byte flagbyte = in->readByte(); + for (uint32 i = 1; i <= 8; i++) { + if (!in->eos()) { + if ((flagbyte & 1) == 0) { + uint32 offsetlen = in->readUint16LE(); + if (offsetlen == 0) { + break; + } + uint32 length = (offsetlen & lengthmask) + COMP_THRESH; + uint32 offset = (bufpos - (offsetlen >> lengthbits)) & (N - 1); + for (uint32 j = 0; j < length; j++) { + byte tempa = histbuff[(offset + j) & (N - 1)]; + _outLzssBufData[outstreampos++] = tempa; + histbuff[bufpos] = tempa; + bufpos = (bufpos + 1) & (N - 1); + } + } else { + byte tempa = in->readByte(); + if (in->eos()) { + break; + } + _outLzssBufData[outstreampos++] = tempa; + histbuff[bufpos] = tempa; + bufpos = (bufpos + 1) & (N - 1); + } + flagbyte = flagbyte >> 1; + } + } + } + delete[] histbuff; + return outstreampos; +} + +bool LzssReadStream::eos() const { + return _pos >= _size; +} + +uint32 LzssReadStream::read(void *buf, uint32 size) { + if (size > _size - _pos) + size = _size - _pos; + + memcpy(buf, &_outLzssBufData[_pos], size); + _pos += size; + + return size; +} diff --git a/engines/groovie/lzss.h b/engines/groovie/lzss.h new file mode 100644 index 0000000000..2aa0816252 --- /dev/null +++ b/engines/groovie/lzss.h @@ -0,0 +1,42 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "common/stream.h" + +class LzssReadStream : public Common::ReadStream { +private: + uint8 *_outLzssBufData; + uint32 _size; + uint32 _pos; + + uint32 decodeLZSS(Common::ReadStream *in, uint8 lengthmask, uint8 lengthbits); + +public: + LzssReadStream(Common::ReadStream *indata, uint8 lengthmask, uint8 lengthbits); + ~LzssReadStream(); + + bool eos() const; + uint32 read(void *buf, uint32 size); +}; diff --git a/engines/groovie/module.mk b/engines/groovie/module.mk new file mode 100644 index 0000000000..1779c9786e --- /dev/null +++ b/engines/groovie/module.mk @@ -0,0 +1,24 @@ +MODULE := engines/groovie + +MODULE_OBJS := \ + cursor.o \ + debug.o \ + detection.o \ + font.o \ + graphics.o \ + groovie.o \ + lzss.o \ + music.o \ + player.o \ + resource.o \ + roq.o \ + script.o \ + vdx.o + +# This module can be built as a plugin +ifeq ($(ENABLE_GROOVIE), DYNAMIC_PLUGIN) +PLUGIN := 1 +endif + +# Include common rules +include $(srcdir)/rules.mk diff --git a/engines/groovie/music.cpp b/engines/groovie/music.cpp new file mode 100644 index 0000000000..1a7f6a4207 --- /dev/null +++ b/engines/groovie/music.cpp @@ -0,0 +1,211 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "groovie/music.h" +#include "groovie/resource.h" + +namespace Groovie { + +MusicPlayer::MusicPlayer(GroovieEngine *vm) : + _vm(vm), _midiParser(NULL), _data(NULL), _driver(NULL), + _backgroundFileRef(0) { + // Create the parser + _midiParser = MidiParser::createParser_XMIDI(); + + // Create the driver + int driver = detectMusicDriver(MDT_MIDI | MDT_ADLIB | MDT_PREFER_MIDI); + _driver = createMidi(driver); + _driver->open(); + + // Initialize the channel volumes + for (int i = 0; i < 0x10; i++) { + _chanVolumes[i] = 0x7F; + } + + // Set the parser's driver + _midiParser->setMidiDriver(this); + + // Set the timer rate + _midiParser->setTimerRate(_driver->getBaseTempo()); +} + +MusicPlayer::~MusicPlayer() { + // Unload the parser + unload(); + delete _midiParser; + + // Unload the MIDI Driver + _driver->close(); + delete _driver; +} + +void MusicPlayer::playSong(uint16 fileref) { + // Play the referenced file once + play(fileref, false); +} + +void MusicPlayer::setBackgroundSong(uint16 fileref) { + _backgroundFileRef = fileref; +} + +void MusicPlayer::setUserVolume(uint16 volume) { + // Save the new user volume + _userVolume = volume; + if (_userVolume > 0x100) _userVolume = 0x100; + + // Apply it to all the channels + for (int i = 0; i < 0x10; i++) { + updateChanVolume(i); + } + //FIXME: AdlibPercussionChannel::controlChange() is empty + //(can't set the volume for the percusion channel) +} + +void MusicPlayer::setGameVolume(uint16 volume, uint16 time) { + //TODO: Implement volume fading + debugC(5, kGroovieDebugMIDI | kGroovieDebugAll, "setting game volume: %d, %d\n", volume, time); + + // Save the new game volume + _gameVolume = volume; + if (_gameVolume > 100) _gameVolume = 100; + + // Apply it to all the channels + for (int i = 0; i < 0x10; i++) { + updateChanVolume(i); + } +} + +void MusicPlayer::updateChanVolume(byte channel) { + // Generate a MIDI Control change message for the volume + uint32 b = 0x7B0; + + // Specify the channel + b |= (channel & 0xF); + + // Scale by the user and game volumes + uint32 val = (_chanVolumes[channel] * _userVolume * _gameVolume) / 0x100 / 100; + val &= 0x7F; + + // Send it to the driver + _driver->send(b | (val << 16)); +} + +bool MusicPlayer::play(uint16 fileref, bool loop) { + // Unload the previous song + unload(); + + // Set the looping option + _midiParser->property(MidiParser::mpAutoLoop, loop); + + // Load the new file + return load(fileref); +} + +bool MusicPlayer::load(uint16 fileref) { + // Open the song resource + Common::SeekableReadStream *xmidiFile = _vm->_resMan->open(fileref); + if (!xmidiFile) { + error("Groovie::Music: Couldn't resource 0x%04X", fileref); + return false; + } + + // Read the whole file to memory + int length = xmidiFile->size(); + _data = new byte[length]; + xmidiFile->read(_data, length); + delete xmidiFile; + + // Start parsing the data + if (!_midiParser->loadMusic(_data, length)) { + error("Groovie::Music: Invalid XMI file"); + return false; + } + + // Activate the timer source + _driver->setTimerCallback(_midiParser, MidiParser::timerCallback); + + return true; +} + +void MusicPlayer::unload() { + // Unload the parser + _midiParser->unloadMusic(); + + // Unload the xmi file + delete[] _data; + _data = NULL; +} + +int MusicPlayer::open() { + return 0; +} + +void MusicPlayer::close() {} + +void MusicPlayer::send(uint32 b) { + if ((b & 0xFFF0) == 0x07B0) { // Volume change + // Save the specific channel volume + byte chan = b & 0xF; + _chanVolumes[chan] = (b >> 16) & 0x7F; + + // Send the updated value + updateChanVolume(chan); + + return; + } + _driver->send(b); +} + +void MusicPlayer::metaEvent(byte type, byte *data, uint16 length) { + switch (type) { + case 0x2F: + // End of Track, play the background song + if (_backgroundFileRef) { + play(_backgroundFileRef, true); + } + break; + default: + _driver->metaEvent(type, data, length); + break; + } +} + +void MusicPlayer::setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) { + _driver->setTimerCallback(timer_param, timer_proc); +} + +uint32 MusicPlayer::getBaseTempo(void) { + return _driver->getBaseTempo(); +} + +MidiChannel *MusicPlayer::allocateChannel() { + return _driver->allocateChannel(); +} + +MidiChannel *MusicPlayer::getPercussionChannel() { + return _driver->getPercussionChannel(); +} + +} // End of Groovie namespace diff --git a/engines/groovie/music.h b/engines/groovie/music.h new file mode 100644 index 0000000000..6c8e6490b5 --- /dev/null +++ b/engines/groovie/music.h @@ -0,0 +1,78 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef GROOVIE_MUSIC_H +#define GROOVIE_MUSIC_H + +#include "groovie/groovie.h" + +#include "sound/mididrv.h" +#include "sound/midiparser.h" + +namespace Groovie { + +class MusicPlayer : public MidiDriver { +public: + MusicPlayer(GroovieEngine *vm); + ~MusicPlayer(); + void playSong(uint16 fileref); + void setBackgroundSong(uint16 fileref); + + // Volume + void setUserVolume(uint16 volume); + void setGameVolume(uint16 volume, uint16 time); +private: + uint16 _userVolume; + uint16 _gameVolume; + byte _chanVolumes[0x10]; + void updateChanVolume(byte channel); + +public: + // MidiDriver interface + int open(); + void close(); + void send(uint32 b); + void metaEvent(byte type, byte *data, uint16 length); + void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc); + uint32 getBaseTempo(void); + MidiChannel *allocateChannel(); + MidiChannel *getPercussionChannel(); + +private: + GroovieEngine *_vm; + byte *_data; + MidiParser *_midiParser; + MidiDriver *_driver; + + uint16 _backgroundFileRef; + + bool play(uint16 fileref, bool loop); + bool load(uint16 fileref); + void unload(); +}; + +} // End of Groovie namespace + +#endif // GROOVIE_MUSIC_H diff --git a/engines/groovie/player.cpp b/engines/groovie/player.cpp new file mode 100644 index 0000000000..5bac190701 --- /dev/null +++ b/engines/groovie/player.cpp @@ -0,0 +1,98 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "groovie/groovie.h" +#include "groovie/player.h" + +namespace Groovie { + +VideoPlayer::VideoPlayer(GroovieEngine *vm) : + _vm(vm), _syst(vm->_system), _file(NULL), _audioStream(NULL) { +} + +bool VideoPlayer::load(Common::SeekableReadStream *file, uint16 flags) { + _file = file; + _flags = flags; + _audioStream = NULL; + + uint16 fps = loadInternal(); + + if (fps != 0) { + _millisBetweenFrames = 1000 / fps; + _begunPlaying = false; + return true; + } else { + _file = NULL; + return false; + } +} + +bool VideoPlayer::playFrame() { + bool end = true; + + // Process the next frame while the file is open + if (_file) { + end = playFrameInternal(); + } + + // The file has been completely processed + if (end) { + _file = NULL; + + // Wait for pending audio + if (_audioStream) { + if (_audioStream->endOfData()) { + // Mark the audio stream as finished (no more data will be appended) + _audioStream->finish(); + } else { + // Don't end if there's still audio playing + end = false; + } + } + } + + return end; +} + +void VideoPlayer::waitFrame() { + uint32 currTime = _syst->getMillis(); + if (!_begunPlaying) { + _begunPlaying = true; + _lastFrameTime = currTime; + } else { + uint32 millisDiff = currTime - _lastFrameTime; + if (millisDiff < _millisBetweenFrames) { + debugC(7, kGroovieDebugVideo | kGroovieDebugAll, "Groovie::Player: Delaying %d (currTime=%d, _lastFrameTime=%d, millisDiff=%d, _millisBetweenFrame=%d)", + _millisBetweenFrames - millisDiff, currTime, _lastFrameTime, millisDiff, _millisBetweenFrames); + _syst->delayMillis(_millisBetweenFrames - millisDiff); + currTime = _syst->getMillis(); + debugC(7, kGroovieDebugVideo | kGroovieDebugAll, "Groovie::Player: Finished delay at %d", currTime); + } + debugC(6, kGroovieDebugVideo | kGroovieDebugAll, "Groovie::Player: Frame displayed at %d (%f FPS)", currTime, 1000.0 / (currTime - _lastFrameTime)); + _lastFrameTime = currTime; + } +} + +} // End of Groovie namespace diff --git a/engines/groovie/player.h b/engines/groovie/player.h new file mode 100644 index 0000000000..cf2746137e --- /dev/null +++ b/engines/groovie/player.h @@ -0,0 +1,68 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef GROOVIE_PLAYER_H +#define GROOVIE_PLAYER_H + +#include "common/system.h" +#include "sound/audiostream.h" + +namespace Groovie { + +class GroovieEngine; + +class VideoPlayer { +public: + VideoPlayer(GroovieEngine *vm); + virtual ~VideoPlayer() {} + + bool load(Common::SeekableReadStream *file, uint16 flags); + bool playFrame(); + virtual void setOrigin(int16 x, int16 y) {}; + +protected: + // To be implemented by subclasses + virtual uint16 loadInternal() = 0; + virtual bool playFrameInternal() = 0; + + GroovieEngine *_vm; + OSystem *_syst; + Common::SeekableReadStream *_file; + uint16 _flags; + Audio::AppendableAudioStream *_audioStream; + +private: + // Synchronization stuff + bool _begunPlaying; + uint16 _millisBetweenFrames; + uint32 _lastFrameTime; + +protected: + void waitFrame(); +}; + +} // End of Groovie namespace + +#endif // GROOVIE_PLAYER_H diff --git a/engines/groovie/resource.cpp b/engines/groovie/resource.cpp new file mode 100644 index 0000000000..b4ae3ea90c --- /dev/null +++ b/engines/groovie/resource.cpp @@ -0,0 +1,240 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "groovie/groovie.h" +#include "groovie/resource.h" + +namespace Groovie { + +// ResMan + +Common::SeekableReadStream *ResMan::open(uint16 fileRef) { + // Get the information about the resource + ResInfo resInfo; + if (!getResInfo(fileRef, resInfo)) { + return NULL; + } + + // Do we know the name of the required GJD? + if (resInfo.gjd >= _gjds.size()) { + error("Groovie::Resource: Unknown GJD %d", resInfo.gjd); + return NULL; + } + + debugC(1, kGroovieDebugResource | kGroovieDebugAll, "Groovie::Resource: Opening resource 0x%04X (%s, %d, %d)", fileRef, _gjds[resInfo.gjd].c_str(), resInfo.offset, resInfo.size); + + // Does it exist? + if (!Common::File::exists(_gjds[resInfo.gjd])) { + error("Groovie::Resource: %s not found", _gjds[resInfo.gjd].c_str()); + return NULL; + } + + // Open the pack file + Common::File *gjdFile = new Common::File(); + if (!gjdFile->open(_gjds[resInfo.gjd].c_str())) { + delete gjdFile; + error("Groovie::Resource: Couldn't open %s", _gjds[resInfo.gjd].c_str()); + return NULL; + } + + // Save the used gjd file (except xmi and gamwav) + if (resInfo.gjd < 19) { + _lastGjd = resInfo.gjd; + } + + // Returning the resource substream + return new Common::SeekableSubReadStream(gjdFile, resInfo.offset, resInfo.offset + resInfo.size, true); +} + + +// ResMan_t7g + +static const char t7g_gjds[][0x15] = {"at", "b", "ch", "d", "dr", "fh", "ga", "hdisk", "htbd", "intro", "jhek", "k", "la", "li", "mb", "mc", "mu", "n", "p", "xmi", "gamwav"}; + +ResMan_t7g::ResMan_t7g() { + for (int i = 0; i < 0x15; i++) { + // Prepare the filename + Common::String filename = t7g_gjds[i]; + filename += ".gjd"; + + // Append it to the list of GJD files + _gjds.push_back(filename); + } +} + +uint16 ResMan_t7g::getRef(Common::String name, Common::String scriptname) { + // Get the name of the RL file + Common::String rlFileName(t7g_gjds[_lastGjd]); + rlFileName += ".rl"; + + // Open the RL file + Common::File rlFile; + if (!rlFile.open(rlFileName)) { + error("Groovie::Resource: Couldn't open %s", rlFileName.c_str()); + return false; + } + + uint16 resNum; + bool found = false; + for (resNum = 0; !found && !rlFile.ioFailed(); resNum++) { + // Read the resource name + char readname[12]; + rlFile.read(readname, 12); + + // Test whether it's the resource we're searching + Common::String resname(readname, 12); + if (resname.hasPrefix(name.c_str())) { + debugC(2, kGroovieDebugResource | kGroovieDebugAll, "Groovie::Resource: Resource %12s matches %s", readname, name.c_str()); + found = true; + } + + // Skip the rest of resource information + rlFile.read(readname, 8); + } + + // Close the RL file + rlFile.close(); + + // Verify we really found the resource + if (!found) { + error("Groovie::Resource: Couldn't find resource %s in %s", name.c_str(), rlFileName.c_str()); + return (uint16)-1; + } + + return (_lastGjd << 10) | (resNum - 1); +} + +bool ResMan_t7g::getResInfo(uint16 fileRef, ResInfo &resInfo) { + // Calculate the GJD and the resource number + resInfo.gjd = fileRef >> 10; + uint16 resNum = fileRef & 0x3FF; + + // Get the name of the RL file + Common::String rlFileName(t7g_gjds[resInfo.gjd]); + rlFileName += ".rl"; + + // Open the RL file + Common::File rlFile; + if (!rlFile.open(rlFileName)) { + error("Groovie::Resource: Couldn't open %s", rlFileName.c_str()); + return false; + } + + // Seek to the position of the desired resource + rlFile.seek(resNum * 20); + if (rlFile.eos()) { + rlFile.close(); + error("Groovie::Resource: Invalid resource number: 0x%04X (%s)", resNum, rlFileName.c_str()); + return false; + } + + // Read the resource name (just for debugging purposes) + char resname[12]; + rlFile.read(resname, 12); + debugC(2, kGroovieDebugResource | kGroovieDebugAll, "Groovie::Resource: Resource name: %12s", resname); + + // Read the resource information + resInfo.offset = rlFile.readUint32LE(); + resInfo.size = rlFile.readUint32LE(); + + // Close the resource RL file + rlFile.close(); + + return true; +} + + +// ResMan_v2 + +ResMan_v2::ResMan_v2() { + Common::File indexfile; + + // Open the GJD index file + if (!indexfile.open("gjd.gjd")) { + error("Groovie::Resource: Couldn't open gjd.gjd"); + return; + } + + Common::String line = indexfile.readLine(); + while (!indexfile.eos() && !line.empty()) { + // Get the name before the space + Common::String filename; + for (const char *cur = line.c_str(); *cur != ' '; cur++) { + filename += *cur; + } + + // Append it to the list of GJD files + if (!filename.empty()) { + _gjds.push_back(filename); + } + + // Read the next line + line = indexfile.readLine(); + } + + // Close the GJD index file + indexfile.close(); +} + +uint16 ResMan_v2::getRef(Common::String name, Common::String scriptname) { + return 0; +} + +bool ResMan_v2::getResInfo(uint16 fileRef, ResInfo &resInfo) { + // Open the RL file + Common::File rlFile; + if (!rlFile.open("dir.rl")) { + error("Groovie::Resource: Couldn't open dir.rl"); + return false; + } + + // Seek to the position of the desired resource + rlFile.seek(fileRef * 32); + if (rlFile.eos()) { + rlFile.close(); + error("Groovie::Resource: Invalid resource number: 0x%04X", fileRef); + return false; + } + + // Read the resource information + rlFile.readUint32LE(); // Unknown + resInfo.offset = rlFile.readUint32LE(); + resInfo.size = rlFile.readUint32LE(); + resInfo.gjd = rlFile.readUint16LE(); + + // Read the resource name (just for debugging purposes) + char resname[12]; + rlFile.read(resname, 12); + debugC(2, kGroovieDebugResource | kGroovieDebugAll, "Groovie::Resource: Resource name: %12s", resname); + + // 6 padding bytes? (it looks like they're always 0) + + // Close the resource RL file + rlFile.close(); + + return true; +} + +} // End of Groovie namespace diff --git a/engines/groovie/resource.h b/engines/groovie/resource.h new file mode 100644 index 0000000000..09032a9c50 --- /dev/null +++ b/engines/groovie/resource.h @@ -0,0 +1,71 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef GROOVIE_RESOURCE_H +#define GROOVIE_RESOURCE_H + +namespace Groovie { + +struct ResInfo { + uint16 gjd; + uint32 offset; + uint32 size; +}; + +class ResMan { +public: + virtual ~ResMan() {}; + + Common::SeekableReadStream *open(uint16 fileRef); + virtual uint16 getRef(Common::String name, Common::String scriptname = "") = 0; + +protected: + Common::Array<Common::String> _gjds; + virtual bool getResInfo(uint16 fileRef, ResInfo &resInfo) = 0; + + uint16 _lastGjd; +}; + +class ResMan_t7g : public ResMan { +public: + ResMan_t7g(); + ~ResMan_t7g() {}; + + uint16 getRef(Common::String name, Common::String scriptname); + bool getResInfo(uint16 fileRef, ResInfo &resInfo); +}; + +class ResMan_v2 : public ResMan { +public: + ResMan_v2(); + ~ResMan_v2() {}; + + uint16 getRef(Common::String name, Common::String scriptname); + bool getResInfo(uint16 fileRef, ResInfo &resInfo); +}; + +} // End of Groovie namespace + +#endif // GROOVIE_RESOURCE_H diff --git a/engines/groovie/roq.cpp b/engines/groovie/roq.cpp new file mode 100644 index 0000000000..589251da31 --- /dev/null +++ b/engines/groovie/roq.cpp @@ -0,0 +1,403 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "groovie/groovie.h" +#include "groovie/roq.h" + +#include "sound/mixer.h" + +namespace Groovie { + +ROQPlayer::ROQPlayer(GroovieEngine *vm) : + VideoPlayer(vm) { +} + +ROQPlayer::~ROQPlayer() { +} + +uint16 ROQPlayer::loadInternal() { + // Begin reading the file + debugC(1, kGroovieDebugVideo | kGroovieDebugAll, "Groovie::ROQ: Loading video"); + + // Read the file header + ROQBlockHeader blockHeader; + if (!readBlockHeader(blockHeader)) { + return 0; + } + if (blockHeader.type != 0x1084 || blockHeader.size != 0 || blockHeader.param != 0) { + return 0; + } + + // Hardcoded FPS + return 25; +} + +bool ROQPlayer::playFrameInternal() { + debugC(5, kGroovieDebugVideo | kGroovieDebugAll, "Groovie::ROQ: Playing frame"); + + // Process the needed blocks until the next video frame + bool endframe = false; + while (!endframe && !_file->eos()) { + endframe = processBlock(); + } + + // Wait until the current frame can be shown + waitFrame(); + + // Update the screen + _syst->updateScreen(); + + // Return whether the video has ended + return _file->eos(); +} + +bool ROQPlayer::readBlockHeader(ROQBlockHeader &blockHeader) { + if (_file->eos()) { + return false; + } else { + blockHeader.type = _file->readUint16LE(); + blockHeader.size = _file->readUint32LE(); + blockHeader.param = _file->readUint16LE(); + + debugC(10, kGroovieDebugVideo | kGroovieDebugAll, "Groovie::ROQ: Block type = 0x%02X", blockHeader.type); + debugC(10, kGroovieDebugVideo | kGroovieDebugAll, "Groovie::ROQ: Block size = 0x%08X", blockHeader.size); + debugC(10, kGroovieDebugVideo | kGroovieDebugAll, "Groovie::ROQ: Block param = 0x%04X", blockHeader.param); + + return true; + } +} + +bool ROQPlayer::processBlock() { + // Read the header of the block + ROQBlockHeader blockHeader; + if (!readBlockHeader(blockHeader)) { + return true; + } + + bool ok = true; + bool endframe = false; + switch (blockHeader.type) { + case 0x1001: // Video info + ok = processBlockInfo(blockHeader); + break; + case 0x1002: // Quad codebook definition + ok = processBlockQuadCodebook(blockHeader); + break; + case 0x1011: // Quad vector quantised video frame + ok = processBlockQuadVector(blockHeader); + endframe = true; + break; + case 0x1012: // Still image (JPEG) + ok = processBlockStill(blockHeader); + endframe = true; + break; + case 0x1013: // Hang + //warning("Groovie::ROQ: Hang block (skipped)"); + break; + case 0x1020: // Mono sound samples + ok = processBlockSoundMono(blockHeader); + break; + case 0x1021: // Stereo sound samples + ok = processBlockSoundStereo(blockHeader); + break; + case 0x1030: // Audio container + ok = processBlockAudioContainer(blockHeader); + break; + default: + error("Groovie::ROQ: Unknown block type: 0x%04X", blockHeader.type); + ok = false; + } + + // End the frame when the graphics have been modified or when there's an error + return endframe || !ok; +} + +bool ROQPlayer::processBlockInfo(ROQBlockHeader &blockHeader) { + debugC(5, kGroovieDebugVideo | kGroovieDebugAll, "Groovie::ROQ: Processing info block"); + + // Verify the block header + if (blockHeader.type != 0x1001 || blockHeader.size != 8 || blockHeader.param != 0) { + return false; + } + + uint16 tmp; + tmp = _file->readUint16LE(); + debugC(5, kGroovieDebugVideo | kGroovieDebugAll, "w = %d\n", tmp); + if (tmp != 640) { + return false; + } + tmp = _file->readUint16LE(); + debugC(5, kGroovieDebugVideo | kGroovieDebugAll, "h = %d\n", tmp); + if (tmp != 320) { + return false; + } + tmp = _file->readUint16LE(); + debugC(5, kGroovieDebugVideo | kGroovieDebugAll, "unk1 = %d\n", tmp); + if (tmp != 8) { + return false; + } + tmp = _file->readUint16LE(); + debugC(5, kGroovieDebugVideo | kGroovieDebugAll, "unk2 = %d\n", tmp); + if (tmp != 4) { + return false; + } + return true; +} + +bool ROQPlayer::processBlockQuadCodebook(ROQBlockHeader &blockHeader) { + debugC(5, kGroovieDebugVideo | kGroovieDebugAll, "Groovie::ROQ: Processing quad codebook block"); + + // Get the number of 2x2 pixel blocks + _num2blocks = blockHeader.param >> 8; + if (_num2blocks == 0) { + _num2blocks = 256; + } + + // Get the number of 4x4 pixel blocks + _num4blocks = blockHeader.param & 0xFF; + if (_num4blocks == 0 && (blockHeader.size > (uint32)_num2blocks * 6)) { + _num4blocks = 256; + } + + _file->skip(_num2blocks * 6); + _file->skip(_num4blocks * 4); + + return true; +} + +bool ROQPlayer::processBlockQuadVector(ROQBlockHeader &blockHeader) { + debugC(5, kGroovieDebugVideo | kGroovieDebugAll, "Groovie::ROQ: Processing quad vector block"); + _file->skip(blockHeader.size); + return true; + + // Get the mean motion vectors + //byte Mx = blockHeader.param >> 8; + //byte My = blockHeader.param & 0xFF; + + int32 ends =_file->pos() + blockHeader.size; + int numblocks = (640 / 8) * (320 / 8); + for (int j = 0; j < numblocks && ends > _file->pos(); j++) { + debugC(5, kGroovieDebugVideo | kGroovieDebugAll, "doing block %d/%d\n", j, numblocks); + uint16 codingType = _file->readUint16LE(); + for (int i = 0; i < 8; i++) { + switch (codingType >> 14) { + case 0: // MOT: Skip block + //printf("coding type 0\n"); + break; + case 1: { // FCC: Copy an existing block + //printf("coding type 1\n"); + byte argument; + argument = _file->readByte(); + //byte Dx = Mx + (argument >> 4); + //byte Dy = My + (argument & 0x0F); + // Dx = X + 8 - (argument >> 4) - Mx + // Dy = Y + 8 - (argument & 0x0F) - My + break; + } + case 2: { // SLD: Quad vector quantisation + //printf("coding type 2\n"); + byte argument = _file->readByte(); + if (argument > _num4blocks) { + //error("invalid 4x4 block %d of %d", argument, _num4blocks); + } + // Upsample the 4x4 pixel block + break; + } + case 3: // CCC: + //printf("coding type 3:\n"); + processBlockQuadVectorSub(blockHeader); + break; + } + codingType <<= 2; + } + } + debugC(5, kGroovieDebugVideo | kGroovieDebugAll, "Should have ended at %d, and has ended at %d\n", ends, _file->pos()); + return true; +} + +bool ROQPlayer::processBlockQuadVectorSub(ROQBlockHeader &blockHeader) { + debugC(5, kGroovieDebugVideo | kGroovieDebugAll, "Groovie::ROQ: Processing quad vector sub block"); + + // Get the mean motion vectors + //byte Mx = blockHeader.param >> 8; + //byte My = blockHeader.param & 0xFF; + + uint16 codingType = _file->readUint16LE(); + for (int i = 0; i < 4; i++) { + switch (codingType >> 14) { + case 0: // MOT: Skip block + //printf("coding type 0\n"); + break; + case 1: { // FCC: Copy an existing block + //printf("coding type 1\n"); + byte argument; + argument = _file->readByte(); + //byte Dx = Mx + (argument >> 4); + //byte Dy = My + (argument & 0x0F); + // Dx = X + 8 - (argument >> 4) - Mx + // Dy = Y + 8 - (argument & 0x0F) - My + break; + } + case 2: { // SLD: Quad vector quantisation + //printf("coding type 2\n"); + byte argument = _file->readByte(); + if (argument > _num2blocks) { + //error("invalid 2x2 block: %d of %d", argument, _num2blocks); + } + break; + } + case 3: + //printf("coding type 3\n"); + _file->readByte(); + _file->readByte(); + _file->readByte(); + _file->readByte(); + break; + } + codingType <<= 2; + } + return true; +} + +bool ROQPlayer::processBlockStill(ROQBlockHeader &blockHeader) { + debugC(5, kGroovieDebugVideo | kGroovieDebugAll, "Groovie::ROQ: Processing still (JPEG) block"); + //Common::ReadStream *jpegData = new Common::SubReadStream(_file, blockHeader.size); + //Graphics::JPEG jpegFrame; + //jpegFrame.read(jpegData); + /* + Common::File save; + save.open("dump.jpg", Common::File::kFileWriteMode); + save.write(data, blockHeader.size); + save.close(); + */ + error("JPEG!"); + return true; +} + +bool ROQPlayer::processBlockSoundMono(ROQBlockHeader &blockHeader) { + debugC(5, kGroovieDebugVideo | kGroovieDebugAll, "Groovie::ROQ: Processing mono sound block"); + + // Verify the block header + if (blockHeader.type != 0x1020) { + return false; + } + + // Initialize the audio stream if needed + if (!_audioStream) { + byte flags = Audio::Mixer::FLAG_16BITS | Audio::Mixer::FLAG_AUTOFREE; +#ifdef SCUMM_LITTLE_ENDIAN + flags |= Audio::Mixer::FLAG_LITTLE_ENDIAN; +#endif + _audioStream = Audio::makeAppendableAudioStream(22050, flags); + Audio::SoundHandle sound_handle; + ::g_engine->_mixer->playInputStream(Audio::Mixer::kPlainSoundType, &sound_handle, _audioStream); + } + + // Create the audio buffer + int16 *buffer = new int16[blockHeader.size]; + + // Initialize the prediction with the block parameter + int16 prediction = blockHeader.param ^ 0x8000; + + // Process the data + for (uint16 i = 0; i < blockHeader.size; i++) { + int16 data = _file->readByte(); + if (data < 0x80) { + prediction += data * data; + } else { + data -= 0x80; + prediction -= data * data; + } + buffer[i] = prediction; + } + + // Queue the read buffer + _audioStream->queueBuffer((byte *)buffer, blockHeader.size * 2); + + return true; +} + +bool ROQPlayer::processBlockSoundStereo(ROQBlockHeader &blockHeader) { + debugC(5, kGroovieDebugVideo | kGroovieDebugAll, "Groovie::ROQ: Processing stereo sound block"); + + // Verify the block header + if (blockHeader.type != 0x1021) { + return false; + } + + // Initialize the audio stream if needed + if (!_audioStream) { + byte flags = Audio::Mixer::FLAG_16BITS | Audio::Mixer::FLAG_AUTOFREE | Audio::Mixer::FLAG_STEREO; +#ifdef SCUMM_LITTLE_ENDIAN + flags |= Audio::Mixer::FLAG_LITTLE_ENDIAN; +#endif + _audioStream = Audio::makeAppendableAudioStream(22050, flags); + Audio::SoundHandle sound_handle; + ::g_engine->_mixer->playInputStream(Audio::Mixer::kPlainSoundType, &sound_handle, _audioStream); + } + + // Create the audio buffer + int16 *buffer = new int16[blockHeader.size]; + + // Initialize the prediction with the block parameter + int16 predictionLeft = (blockHeader.param & 0xFF00) ^ 0x8000; + int16 predictionRight = (blockHeader.param << 8) ^ 0x8000; + bool left = true; + + // Process the data + for (uint16 i = 0; i < blockHeader.size; i++) { + int16 data = _file->readByte(); + if (left) { + if (data < 0x80) { + predictionLeft += data * data; + } else { + data -= 0x80; + predictionLeft -= data * data; + } + buffer[i] = predictionLeft; + } else { + if (data < 0x80) { + predictionRight += data * data; + } else { + data -= 0x80; + predictionRight -= data * data; + } + buffer[i] = predictionRight; + } + left = !left; + } + + // Queue the read buffer + _audioStream->queueBuffer((byte *)buffer, blockHeader.size * 2); + + return true; +} + +bool ROQPlayer::processBlockAudioContainer(ROQBlockHeader &blockHeader) { + debugC(5, kGroovieDebugVideo | kGroovieDebugAll, "Groovie::ROQ: Processing audio container block: 0x%04X", blockHeader.param); + return true; +} + +} // End of Groovie namespace diff --git a/engines/groovie/roq.h b/engines/groovie/roq.h new file mode 100644 index 0000000000..215974e3bd --- /dev/null +++ b/engines/groovie/roq.h @@ -0,0 +1,69 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef GROOVIE_ROQ_H +#define GROOVIE_ROQ_H + +#include "groovie/player.h" + +namespace Groovie { + +class GroovieEngine; + +struct ROQBlockHeader { + uint16 type; + uint32 size; + uint16 param; +}; + +class ROQPlayer : public VideoPlayer { +public: + ROQPlayer(GroovieEngine *vm); + ~ROQPlayer(); + +protected: + uint16 loadInternal(); + bool playFrameInternal(); + +private: + bool readBlockHeader(ROQBlockHeader &blockHeader); + + bool processBlock(); + bool processBlockInfo(ROQBlockHeader &blockHeader); + bool processBlockQuadCodebook(ROQBlockHeader &blockHeader); + bool processBlockQuadVector(ROQBlockHeader &blockHeader); + bool processBlockQuadVectorSub(ROQBlockHeader &blockHeader); + bool processBlockStill(ROQBlockHeader &blockHeader); + bool processBlockSoundMono(ROQBlockHeader &blockHeader); + bool processBlockSoundStereo(ROQBlockHeader &blockHeader); + bool processBlockAudioContainer(ROQBlockHeader &blockHeader); + + uint16 _num2blocks; + uint16 _num4blocks; +}; + +} // End of Groovie namespace + +#endif // GROOVIE_ROQ_H diff --git a/engines/groovie/script.cpp b/engines/groovie/script.cpp new file mode 100644 index 0000000000..fc4a7fd4f1 --- /dev/null +++ b/engines/groovie/script.cpp @@ -0,0 +1,1566 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "groovie/debug.h" +#include "groovie/music.h" +#include "groovie/script.h" +#include "groovie/groovie.h" + +#include "common/config-manager.h" +#include "common/endian.h" +#include "common/events.h" +#include "common/savefile.h" +#include "sound/audiocd.h" + +#define NUM_OPCODES 90 + +namespace Groovie { + +void debugScript(int level, bool nl, const char *s, ...) { + char buf[STRINGBUFLEN]; + va_list va; + + uint32 engine_level = kGroovieDebugScript | kGroovieDebugAll; + + if (gDebugLevel != 11) + if (!(Common::getEnabledSpecialDebugLevels() & engine_level)) + return; + + va_start(va, s); + vsnprintf(buf, STRINGBUFLEN, s, va); + va_end(va); + + if (nl) + debug(level, buf); + else + debugN(level, buf); +} + +Script::Script(GroovieEngine *vm) : + _code(NULL), _savedCode(NULL), _stacktop(0), + _debugger(NULL), _error(false), _vm(vm), + _videoFile(NULL), _videoRef(0), _font(NULL) { + // Initialize the random source + _vm->_system->getEventManager()->registerRandomSource(_random, "GroovieScripts"); + + // Prepare the variables + _bitflags = 0; + for (int i = 0; i < 0x400; i++) { + _variables[i] = 0; + } + + // Initialize the music type variable + int midiDriver = MidiDriver::detectMusicDriver(MDT_MIDI | MDT_ADLIB | MDT_PREFER_MIDI); + if (midiDriver == MD_ADLIB) { + // MIDI through AdLib + _variables[0x100] = 0; + } else if ((midiDriver == MD_MT32) || ConfMan.getBool("native_mt32")) { + // MT-32 + _variables[0x100] = 2; + } else { + // GM + _variables[0x100] = 1; + } + + _hotspotTopAction = 0; + _hotspotBottomAction = 0; + _hotspotRightAction = 0; + _hotspotLeftAction = 0; + _hotspotCursorOldX = 1000; + _hotspotCursorOldY = 1000; +} + +Script::~Script() { + delete[] _code; + delete[] _savedCode; + + delete _font; + delete _videoFile; +} + +void Script::setDebugger(Debugger *debugger) { + _debugger = debugger; +} + +bool Script::loadScript(Common::String filename) { + // Try to open the script file + Common::File scriptfile; + if (!scriptfile.open(filename)) { + return false; + } + + // Save the script filename + _scriptFile = filename; + + // Load the code + _code = new byte[0x10000]; + scriptfile.read(_code, 0x10000); + scriptfile.close(); + + // Initialize the script + _currentInstruction = 0; + + return true; +} + +void Script::directGameLoad(int slot) { + // Reject invalid slots + if (slot < 0 || slot > 9) { + return; + } + + // TODO: Return to the main script, likely reusing most of o_returnscript() + + // HACK: We set variable 0x19 to the slot to load, and set the current + // instruction to the one that actually loads the saved game specified + // in that variable. This will change in other versions of the game and + // in other games. + _variables[0x19] = slot; + _currentInstruction = 0x287; + + // TODO: We'll probably need to start by running the beginning of the + // script to let it do the soundcard initialization and then do the + // actual loading. +} + +void Script::step() { + // Reset the error status + _error = false; + + // Prepare the base debug string + char debugstring[10]; + sprintf(debugstring, "@0x%04X: ", _currentInstruction); + _debugString = _scriptFile + debugstring; + + // Get the current opcode + byte opcode = readScript8bits(); + _firstbit = ((opcode & 0x80) != 0); + opcode = opcode & 0x7F; + + // Show the opcode debug string + sprintf(debugstring, "op 0x%02X: ", opcode); + _debugString += debugstring; + debugScript(1, false, _debugString.c_str()); + + // Detect invalid opcodes + if (opcode >= NUM_OPCODES) { + o_invalid(); + return; + } + + // Execute the current opcode + OpcodeFunc op = _opcodes[opcode]; + (this->*op)(); +} + +void Script::setMouseClick() { + _eventMouseClicked = true; +} + +void Script::setKbdChar(uint8 c) { + _eventKbdChar = c; +} + +bool Script::haveError() { + return _error; +} + +void Script::error(const char *msg) { + // Prepend the debugging info to the error + Common::String msg2 = _debugString + msg; + + // Print the error message + ::error("ERROR: %s\n", msg2.c_str()); + + // Show it in the debugger + _debugger->attach(msg2.c_str()); + + // Set the error state + _error = true; +} + +uint8 Script::readScript8bits() { + uint8 data = _code[_currentInstruction]; + _currentInstruction++; + return data; +} + +uint8 Script::readScriptVar() { + uint8 data = _variables[readScript8or16bits()]; + return data; +} + +uint16 Script::readScript16bits() { + uint16 data = READ_LE_UINT16(_code + _currentInstruction); + _currentInstruction += 2; + return data; +} + +uint32 Script::readScript32bits() { + uint32 data = READ_LE_UINT32(_code + _currentInstruction); + _currentInstruction += 4; + return data; +} + +uint16 Script::readScript8or16bits() { + if (_firstbit) { + return readScript8bits(); + } else { + return readScript16bits(); + } +} + +uint8 Script::readScriptChar(bool allow7C, bool limitVal, bool limitVar) { + uint8 result; + uint8 data = readScript8bits(); + + if (limitVal) { + data &= 0x7F; + } + + if (allow7C && (data == 0x7C)) { + // Index a bidimensional array + uint8 parta, partb; + parta = readScriptChar(false, false, false); + partb = readScriptChar(false, true, true); + result = _variables[0x0A * parta + partb + 0x19]; + } else if (data == 0x23) { + // Index an array + data = readScript8bits(); + if (limitVar) { + data &= 0x7F; + } + result = _variables[data - 0x61]; + } else { + // Immediate value + result = data - 0x30; + } + return result; +} + +uint16 Script::getVideoRefString() { + Common::String str; + byte c; + + while ((c = readScript8bits())) { + switch (c) { + case 0x23: + c = readScript8bits(); + c = _variables[c - 0x61] + 0x30; + if (c >= 0x41 && c <= 0x5A) { + c += 0x20; + } + break; + case 0x7C: + uint8 parta, partb; + parta = readScriptChar(false, false, false); + partb = readScriptChar(false, false, false); + c = _variables[0x0A * parta + partb + 0x19] + 0x30; + break; + default: + if (c >= 0x41 && c <= 0x5A) { + c += 0x20; + } + } + // Append the current character at the end of the string + str += c; + } + + // Add a trailing dot + str += 0x2E; + + debugScript(0, false, "%s", str.c_str()); + + // Extract the script name. + Common::String scriptname(_scriptFile.c_str(), _scriptFile.size() - 4); + + // Get the fileref of the resource + return _vm->_resMan->getRef(str, scriptname); +} + +bool Script::hotspot(Common::Rect rect, uint16 address, uint8 cursor) { + // Test if the current mouse position is contained in the specified rectangle + Common::Point mousepos = _vm->_system->getEventManager()->getMousePos(); + bool contained = rect.contains(mousepos); + + // Show hotspots when debugging + if (Common::getEnabledSpecialDebugLevels() & (kGroovieDebugHotspots | kGroovieDebugAll)) { + rect.translate(0, -80); + _vm->_graphicsMan->_foreground.frameRect(rect, 250); + _vm->_system->copyRectToScreen((byte*)_vm->_graphicsMan->_foreground.getBasePtr(0, 0), 640, 0, 80, 640, 320); + _vm->_system->updateScreen(); + } + + // If there's an already planned action, do nothing + if (_inputAction != -1) { + return false; + } + + if (contained) { + // Change the mouse cursor + if (_newCursorStyle == 5) { + _newCursorStyle = cursor; + } + + // If clicked with the mouse, jump to the specified address + if (_mouseClicked) { + _inputAction = address; + } + } + + return contained; +} + +void Script::loadgame(uint slot) { + Common::String filename = ConfMan.getActiveDomainName() + ".00" + ('0' + slot); + Common::InSaveFile *file = _vm->_system->getSavefileManager()->openForLoading(filename.c_str()); + + // Loading the variables. It is endian safe because they're byte variables + file->read(_variables, 0x400); + + delete file; +} + +void Script::savegame(uint slot) { + Common::String filename = ConfMan.getActiveDomainName() + ".00" + ('0' + slot); + Common::OutSaveFile *file = _vm->_system->getSavefileManager()->openForSaving(filename.c_str()); + + // Saving the variables. It is endian safe because they're byte variables + file->write(_variables, 0x400); + + delete file; +} + +// OPCODES + +void Script::o_invalid() { + error("Invalid opcode"); +} + +void Script::o_nop() { + debugScript(1, true, "NOP"); +} + +void Script::o_nop8() { + uint8 tmp = readScript8bits(); + debugScript(1, true, "NOP8: 0x%02X", tmp); +} + +void Script::o_nop16() { + uint16 tmp = readScript16bits(); + debugScript(1, true, "NOP16: 0x%04X", tmp); +} + +void Script::o_nop32() { + uint32 tmp = readScript32bits(); + debugScript(1, true, "NOP32: 0x%08X", tmp); +} + +void Script::o_nop8or16() { + uint16 tmp = readScript8or16bits(); + debugScript(1, true, "NOP8OR16: 0x%04X", tmp); +} + +void Script::o_playsong() { // 0x02 + uint16 fileref = readScript16bits(); + debugScript(1, true, "PlaySong(0x%04X): Play xmidi file", fileref); + if (fileref == 0x4C17) { + warning("this song is special somehow"); + // don't save the reference? + } + _vm->_musicPlayer->playSong(fileref); +} + +void Script::o_bf9on() { // 0x03 + debugScript(1, true, "BF9ON: bitflag 9 turned on"); + _bitflags |= 1 << 9; +} + +void Script::o_palfadeout() { + debugScript(1, true, "PALFADEOUT"); + _vm->_graphicsMan->fadeOut(); +} + +void Script::o_bf8on() { // 0x05 + debugScript(1, true, "BF8ON: bitflag 8 turned on"); + _bitflags |= 1 << 8; +} + +void Script::o_bf6on() { // 0x06 + debugScript(1, true, "BF6ON: bitflag 6 turned on"); + _bitflags |= 1 << 6; +} + +void Script::o_bf7on() { // 0x07 + debugScript(1, true, "BF7ON: bitflag 7 turned on"); + _bitflags |= 1 << 7; +} + +void Script::o_setbackgroundsong() { // 0x08 + uint16 fileref = readScript16bits(); + debugScript(1, true, "SetBackgroundSong(0x%04X)", fileref); + _vm->_musicPlayer->setBackgroundSong(fileref); +} + +void Script::o_videofromref() { // 0x09 + uint16 fileref = readScript16bits(); + + // Show the debug information just when starting the playback + if (fileref != _videoRef) { + debugScript(1, false, "VIDEOFROMREF(0x%04X) (Not fully imp): Play video file from ref", fileref); + debugC(5, kGroovieDebugVideo | kGroovieDebugAll, "Playing video 0x%04X via 0x09", fileref); + } + switch (fileref) { + case 0x1C03: // Trilobyte logo + case 0x1C04: // Virgin logo + case 0x1C05: // Credits + if (fileref != _videoRef) { + debugScript(1, true, "Use external file if available"); + } + break; + + case 0x400D: // floating objects in music room + case 0x5060: // a sound from gamwav? + case 0x5098: // a sound from gamwav? + case 0x2402: // House becomes book in intro? + case 0x1426: // Turn to face front in hall: played after intro + case 0x206D: // Cards on table puzzle (bedroom) + case 0x2001: // Coins on table puzzle (bedroom) + if (fileref != _videoRef) { + debugScript(1, false, " (This video is special somehow!)"); + warning("(This video (0x%04X) is special somehow!)", fileref); + } + } + if (fileref != _videoRef) { + debugScript(1, true, ""); + } + // Play the video + if (!playvideofromref(fileref)) { + // Move _currentInstruction back + _currentInstruction -= 3; + } +} + +bool Script::playvideofromref(uint16 fileref) { + // It isn't the current video, open it + if (fileref != _videoRef) { + + // Debug bitflags + debugScript(1, false, "Play video 0x%04X (bitflags:", fileref); + for (int i = 10; i >= 0; i--) { + debugScript(1, false, "%d", _bitflags & (1 << i)? 1 : 0); + } + debugScript(1, true, ")"); + + // Close the previous video file + if (_videoFile) { + _videoRef = 0; + delete _videoFile; + } + + // Try to open the new file + _videoFile = _vm->_resMan->open(fileref); + + if (_videoFile) { + _videoRef = fileref; + _vm->_videoPlayer->load(_videoFile, _bitflags); + } else { + error("Couldn't open file"); + return true; + } + + _bitflags = 0; + } + + // Video available, play one frame + if (_videoFile) { + bool endVideo = _vm->_videoPlayer->playFrame(); + + if (endVideo) { + // Close the file + delete _videoFile; + _videoFile = NULL; + _videoRef = 0; + + // Clear the input events while playing the video + _eventMouseClicked = false; + _eventKbdChar = 0; + + // Newline + debugScript(1, true, ""); + } + + // Let the caller know if the video has ended + return endVideo; + } + + // If the file is closed, finish the playback + return true; +} + +void Script::o_bf5on() { // 0x0A + debugScript(1, true, "BF5ON: bitflag 5 turned on"); + _bitflags |= 1 << 5; +} + +void Script::o_inputloopstart() { + debugScript(5, true, "Input loop start"); + + // Reset the input action and the mouse cursor + _inputAction = -1; + _newCursorStyle = 5; + + // Save the input loop address + _inputLoopAddress = _currentInstruction - 1; + + // Save the current mouse state for the whole loop + _mouseClicked = _eventMouseClicked; + _eventMouseClicked = false; + + // Save the current pressed character for the whole loop + _kbdChar = _eventKbdChar; + _eventKbdChar = 0; +} + +void Script::o_keyboardaction() { + uint8 val = readScript8bits(); + uint16 address = readScript16bits(); + + debugScript(5, true, "Test key == 0x%02X @0x%04X", val, address); + + // If there's an already planned action, do nothing + if (_inputAction != -1) { + return; + } + + // Check the typed key + if (_kbdChar == val) { + // Exit the input loop + _inputLoopAddress = 0; + + // Save the action address + _inputAction = address; + } +} + +void Script::o_hotspot_rect() { + uint16 left = readScript16bits(); + uint16 top = readScript16bits(); + uint16 right = readScript16bits(); + uint16 bottom = readScript16bits(); + uint16 address = readScript16bits(); + uint8 cursor = readScript8bits(); + + debugScript(5, true, "HOTSPOT-RECT(%d,%d,%d,%d) @0x%04X cursor=%d", left, top, right, bottom, address, cursor); + + // Mark the specified rectangle + Common::Rect rect(left, top, right, bottom); + hotspot(rect, address, cursor); +} + +void Script::o_hotspot_left() { + uint16 address = readScript16bits(); + + debugScript(5, true, "HOTSPOT-LEFT @0x%04X", address); + + // Mark the leftmost 100 pixels of the game area + Common::Rect rect(0, 80, 100, 400); + hotspot(rect, address, 1); +} + +void Script::o_hotspot_right() { + uint16 address = readScript16bits(); + + debugScript(5, true, "HOTSPOT-RIGHT @0x%04X", address); + + // Mark the rightmost 100 pixels of the game area + Common::Rect rect(540, 80, 640, 400); + hotspot(rect, address, 2); +} + +void Script::o_hotspot_center() { + uint16 address = readScript16bits(); + + debugScript(5, true, "HOTSPOT-CENTER @0x%04X", address); + + // Mark the centremost 240 pixels of the game area + Common::Rect rect(200, 80, 440, 400); + hotspot(rect, address, 0); +} + +void Script::o_hotspot_current() { + uint16 address = readScript16bits(); + + debugScript(5, true, "HOTSPOT-CURRENT @0x%04X", address); + + // The original interpreter doesn't check the position, so accept the + // whole screen + Common::Rect rect(0, 0, 640, 480); + hotspot(rect, address, 0); +} + +void Script::o_inputloopend() { + debugScript(5, true, "Input loop end"); + + // Handle the predefined hotspots + if (_hotspotTopAction) { + Common::Rect rect(0, 0, 640, 80); + hotspot(rect, _hotspotTopAction, _hotspotTopCursor); + } + if (_hotspotBottomAction) { + Common::Rect rect(0, 400, 640, 480); + hotspot(rect, _hotspotBottomAction, _hotspotBottomCursor); + } + if (_hotspotRightAction) { + Common::Rect rect(560, 0, 640, 480); + hotspot(rect, _hotspotRightAction, 2); + } + if (_hotspotLeftAction) { + Common::Rect rect(0, 0, 80, 480); + hotspot(rect, _hotspotLeftAction, 1); + } + + // Actually execute the planned action + if (_inputAction != -1) { + // Jump to the planned address + _currentInstruction = _inputAction; + + // Exit the input loop + _inputLoopAddress = 0; + _vm->_system->showMouse(false); + } + + // Nothing to do + if (_inputLoopAddress) { + if (_newCursorStyle != _vm->_cursorMan->getStyle()) { + _vm->_cursorMan->setStyle(_newCursorStyle); + } + _vm->_system->showMouse(true); + + // Go back to the begining of the loop + _currentInstruction = _inputLoopAddress; + + // There's nothing to do until we get some input + _vm->waitForInput(); + } +} + +void Script::o_random() { + uint16 varnum = readScript8or16bits(); + uint8 maxnum = readScript8bits(); + + debugScript(1, true, "RANDOM: var[0x%04X] = rand(%d)", varnum, maxnum); + + _variables[varnum] = _random.getRandomNumber(maxnum); +} + +void Script::o_jmp() { + uint16 address = readScript16bits(); + + debugScript(1, true, "JMP @0x%04X", address); + + // Set the current address + _currentInstruction = address; +} + +void Script::o_loadstring() { + uint16 varnum = readScript8or16bits(); + + debugScript(1, false, "LOADSTRING var[0x%04X..] =", varnum); + do { + _variables[varnum++] = readScriptChar(true, true, true); + debugScript(1, false, " 0x%02X", _variables[varnum - 1]); + } while (!(_code[_currentInstruction - 1] & 0x80)); + debugScript(1, true, ""); +} + +void Script::o_ret() { + uint8 val = readScript8bits(); + + debugScript(1, true, "RET %d", val); + + // Set the return value + _variables[0x102] = val; + + // Get the return address + if (_stacktop > 0) { + _stacktop--; + _currentInstruction = _stack[_stacktop]; + } else { + error("Return: Stack is empty"); + } +} + +void Script::o_call() { + uint16 address = readScript16bits(); + + debugScript(1, true, "CALL @0x%04X", address); + + // Save return address in the call stack + _stack[_stacktop] = _currentInstruction; + _stacktop++; + + // Change the current instruction + _currentInstruction = address; +} + +void Script::o_sleep() { + uint16 time = readScript16bits(); + + debugScript(1, true, "SLEEP 0x%04X", time); + + _vm->_system->delayMillis(time * 3); +} + +void Script::o_strcmpnejmp() { // 0x1A + uint16 varnum = readScript8or16bits(); + uint8 val; + uint8 result = 1; + + debugScript(1, false, "STRCMP-NEJMP: var[0x%04X..],", varnum); + + do { + val = readScriptChar(true, true, true); + + if (_variables[varnum] != val) { + result = 0; + } + varnum++; + debugScript(1, false, " 0x%02X", val); + + } while (!(_code[_currentInstruction - 1] & 0x80)); + + uint16 address = readScript16bits(); + if (!result) { + debugScript(1, true, " jumping to @0x%04X", address); + _currentInstruction = address; + } else { + debugScript(1, true, " not jumping"); + } +} + +void Script::o_xor_obfuscate() { + uint16 varnum = readScript8or16bits(); + + debugScript(1, false, "XOR OBFUSCATE: var[0x%04X..] = ", varnum); + do { + uint8 val = readScript8bits(); + _firstbit = ((val & 0x80) != 0); + val &= 0x4F; + + _variables[varnum] ^= val; + debugScript(1, false, "%c", _variables[varnum]); + + varnum++; + } while (!_firstbit); + debugScript(1, true, ""); +} + +void Script::o_vdxtransition() { // 0x1C + uint16 fileref = readScript16bits(); + + // Show the debug information just when starting the playback + if (fileref != _videoRef) { + debugScript(1, true, "VDX transition fileref = 0x%04X", fileref); + debugC(1, kGroovieDebugVideo | kGroovieDebugAll, "Playing video 0x%04X with transition", fileref); + } + + // Set bit 1 + _bitflags |= 1 << 1; + + // Clear bit 7 + _bitflags &= ~(1 << 7); + + // Set bit 2 if _firstbit + if (_firstbit) { + _bitflags |= 1 << 2; + } + + // Play the video + if (!playvideofromref(fileref)) { + // Move _currentInstruction back + _currentInstruction -= 3; + } +} + +void Script::o_swap() { + uint16 varnum1 = readScript8or16bits(); + uint16 varnum2 = readScript16bits(); + + debugScript(1, true, "SWAP var[0x%04X] <-> var[0x%04X]", varnum1, varnum2); + + uint8 tmp = _variables[varnum1]; + _variables[varnum1] = _variables[varnum2]; + _variables[varnum2] = tmp; +} + +void Script::o_inc() { + uint16 varnum = readScript8or16bits(); + + debugScript(1, true, "INC var[0x%04X]", varnum); + + _variables[varnum]++; +} + +void Script::o_dec() { + uint16 varnum = readScript8or16bits(); + + debugScript(1, true, "DEC var[0x%04X]", varnum); + + _variables[varnum]--; +} + +void Script::o_strcmpnejmp_var() { // 0x21 + uint16 data = readScriptVar(); + + if (data > 9) { + data -= 7; + } + data = _variables[data + 0x19]; + bool stringsmatch = 1; + do { + if (_variables[data++] != readScriptChar(true, true, true)) { + stringsmatch = 0; + } + } while (!(_code[_currentInstruction - 1] & 0x80)); + + uint16 offset = readScript16bits(); + if (!stringsmatch) { + _currentInstruction = offset; + } +} + +void Script::o_copybgtofg() { // 0x22 + debugScript(1, true, "COPY_BG_TO_FG"); + memcpy(_vm->_graphicsMan->_foreground.getBasePtr(0, 0), _vm->_graphicsMan->_background.getBasePtr(0, 0), 640 * 320); +} + +void Script::o_strcmpeqjmp() { // 0x23 + uint16 varnum = readScript8or16bits(); + uint8 val; + uint8 result = 1; + + debugScript(1, false, "STRCMP-EQJMP: var[0x%04X..],", varnum); + do { + val = readScriptChar(true, true, true); + + if (_variables[varnum] != val) { + result = 0; + } + varnum++; + debugScript(1, false, " 0x%02X", val); + + } while (!(_code[_currentInstruction - 1] & 0x80)); + + uint16 address = readScript16bits(); + if (result) { + debugScript(1, true, " jumping to @0x%04X", address); + _currentInstruction = address; + } else { + debugScript(1, true, " not jumping"); + } +} + +void Script::o_mov() { + uint16 varnum1 = readScript8or16bits(); + uint16 varnum2 = readScript16bits(); + + debugScript(1, true, "MOV var[0x%04X] = var[0x%04X]", varnum1, varnum2); + + _variables[varnum1] = _variables[varnum2]; +} + +void Script::o_add() { + uint16 varnum1 = readScript8or16bits(); + uint16 varnum2 = readScript16bits(); + + debugScript(1, true, "ADD var[0x%04X] += var[0x%04X]", varnum1, varnum2); + + _variables[varnum1] += _variables[varnum2]; +} + +void Script::o_videofromstring1() { + uint16 instStart = _currentInstruction; + uint16 fileref = getVideoRefString(); + + // Show the debug information just when starting the playback + if (fileref != _videoRef) { + debugScript(0, true, "VIDEOFROMSTRING1 0x%04X", fileref); + } + + // Play the video + if (!playvideofromref(fileref)) { + // Move _currentInstruction back + _currentInstruction = instStart - 1; + } +} + +void Script::o_videofromstring2() { + uint16 instStart = _currentInstruction; + uint16 fileref = getVideoRefString(); + + // Show the debug information just when starting the playback + if (fileref != _videoRef) { + debugScript(0, true, "VIDEOFROMSTRING2 0x%04X", fileref); + } + + // Set bit 1 + _bitflags |= 1 << 1; + + // Set bit 2 if _firstbit + if (_firstbit) { + _bitflags |= 1 << 2; + } + + // Play the video + if (!playvideofromref(fileref)) { + // Move _currentInstruction back + _currentInstruction = instStart - 1; + } +} + +void Script::o_stopmidi() { + debugScript(1, true, "STOPMIDI (TODO)"); +} + +void Script::o_endscript() { + debugScript(1, true, "END OF SCRIPT"); + _error = true; +} + +void Script::o_sethotspottop() { + uint16 address = readScript16bits(); + uint8 cursor = readScript8bits(); + + debugScript(5, true, "SETHOTSPOTTOP @0x%04X cursor=%d", address, cursor); + + _hotspotTopAction = address; + _hotspotTopCursor = cursor; +} + +void Script::o_sethotspotbottom() { + uint16 address = readScript16bits(); + uint8 cursor = readScript8bits(); + + debugScript(5, true, "SETHOTSPOTBOTTOM @0x%04X cursor=%d", address, cursor); + + _hotspotBottomAction = address; + _hotspotBottomCursor = cursor; +} + +void Script::o_loadgame() { + uint16 varnum = readScript8or16bits(); + uint8 slot = _variables[varnum]; + + debugScript(1, true, "LOADGAME var[0x%04X] -> slot=%d (TODO)", varnum, slot); + + loadgame(slot); + _vm->_system->clearScreen(); +} + +void Script::o_savegame() { + uint16 varnum = readScript8or16bits(); + uint8 slot = _variables[varnum]; + + debugScript(1, true, "SAVEGAME var[0x%04X] -> slot=%d (TODO)", varnum, slot); + + savegame(slot); +} + +void Script::o_hotspotbottom_4() { //0x30 + uint16 address = readScript16bits(); + + debugScript(5, true, "HOTSPOT-BOTTOM @0x%04X", address); + + // Mark the 80 pixels under the game area + Common::Rect rect(0, 400, 640, 480); + hotspot(rect, address, 4); +} + +void Script::o_midivolume() { + uint16 arg1 = readScript16bits(); + uint16 arg2 = readScript16bits(); + + debugScript(1, true, "MIDI volume: %d %d", arg1, arg2); + _vm->_musicPlayer->setGameVolume(arg1, arg2); +} + +void Script::o_jne() { + int16 varnum1 = readScript8or16bits(); + uint16 varnum2 = readScript16bits(); + uint16 address = readScript16bits(); + + debugScript(1, false, "JNE: var[var[0x%04X] - 0x31] != var[0x%04X] @0x%04X", varnum1, varnum2, address); + + if (_variables[_variables[varnum1] - 0x31] != _variables[varnum2]) { + _currentInstruction = address; + debugScript(1, true, " jumping to @0x%04X", address); + } else { + debugScript(1, true, " not jumping"); + } +} + +void Script::o_loadstringvar() { + uint16 varnum = readScript8or16bits(); + + varnum = _variables[varnum] - 0x31; + debugScript(1, false, "LOADSTRINGVAR var[0x%04X..] =", varnum); + do { + _variables[varnum++] = readScriptChar(true, true, true); + debugScript(1, false, " 0x%02X", _variables[varnum - 1]); + } while (!(_code[_currentInstruction - 1] & 0x80)); + debugScript(1, true, ""); +} + +void Script::o_chargreatjmp() { + uint16 varnum = readScript8or16bits(); + uint8 val; + uint8 result = 0; + + debugScript(1, false, "CHARGREAT-JMP: var[0x%04X..],", varnum); + do { + val = readScriptChar(true, true, true); + + if (val < _variables[varnum]) { + result = 1; + } + varnum++; + debugScript(1, false, " 0x%02X", val); + } while (!(_code[_currentInstruction - 1] & 0x80)); + + uint16 address = readScript16bits(); + if (result) { + debugScript(1, true, " jumping to @0x%04X", address); + _currentInstruction = address; + } else { + debugScript(1, true, " not jumping"); + } +} + +void Script::o_bf7off() { + debugScript(1, true, "BF7OFF: bitflag 7 turned off"); + _bitflags &= ~(1 << 7); +} + +void Script::o_charlessjmp() { + uint16 varnum = readScript8or16bits(); + uint8 val; + uint8 result = 0; + + debugScript(1, false, "CHARLESS-JMP: var[0x%04X..],", varnum); + do { + val = readScriptChar(true, true, true); + + if (val > _variables[varnum]) { + result = 1; + } + varnum++; + debugScript(1, false, " 0x%02X", val); + } while (!(_code[_currentInstruction - 1] & 0x80)); + + uint16 address = readScript16bits(); + if (result) { + debugScript(1, true, " jumping to @0x%04X", address); + _currentInstruction = address; + } else { + debugScript(1, true, " not jumping"); + } +} + +void Script::o_copyrecttobg() { // 0x37 + uint16 left = readScript16bits(); + uint16 top = readScript16bits(); + uint16 right = readScript16bits(); + uint16 bottom = readScript16bits(); + uint16 i, width = right - left, height = bottom - top; + uint32 offset = 0; + byte *fg, *bg; + + debugScript(1, true, "COPYRECT((%d,%d)->(%d,%d))", left, top, right, bottom); + + fg = (byte *)_vm->_graphicsMan->_foreground.getBasePtr(left, top - 80); + bg = (byte *)_vm->_graphicsMan->_background.getBasePtr(left, top - 80); + for (i = 0; i < height; i++) { + memcpy(bg + offset, fg + offset, width); + offset += 640; + } + _vm->_system->copyRectToScreen((byte *)_vm->_graphicsMan->_background.getBasePtr(left, top - 80), 640, left, top, width, height); + _vm->_graphicsMan->change(); +} + +void Script::o_restorestkpnt() { + debugScript(1, true, "Restore stack pointer from saved (TODO)"); +} + +void Script::o_obscureswap() { + uint16 var1, var2, tmp; + + debugScript(1, true, "OBSCSWAP"); + + // Read the first variable + var1 = readScriptChar(false, true, true) * 10; + var1 += readScriptChar(false, true, true) + 0x19; + + // Read the second variable + var2 = readScriptChar(false, true, true) * 10; + var2 += readScriptChar(false, true, true) + 0x19; + + // Swap the values + tmp = _variables[var1]; + _variables[var1] = _variables[var2]; + _variables[var2] = tmp; +} + +void Script::o_printstring() { + char stringstorage[15], newchar; + uint8 counter = 0; + + debugScript(1, true, "PRINTSTRING"); + + memset(stringstorage, 0, 15); + do { + newchar = readScriptChar(true, true, true) + 0x30; + if (newchar < 0x30 || newchar > 0x39) { // If character is invalid, chuck a space in + if (newchar < 0x41 || newchar > 0x7A) { + newchar = 0x20; + } + } + + stringstorage[counter] = newchar; + counter++; + } while (!(_code[_currentInstruction - 1] & 0x80)); + + stringstorage[counter] = 0; + + // Load the font if required + if (!_font) { + _font = new Font(_vm->_system); + } + _font->printstring(stringstorage); +} + +void Script::o_hotspot_slot() { + uint16 slot = readScript8bits(); + uint16 left = readScript16bits(); + uint16 top = readScript16bits(); + uint16 right = readScript16bits(); + uint16 bottom = readScript16bits(); + uint16 address = readScript16bits(); + uint16 cursor = readScript8bits(); + + debugScript(1, true, "HOTSPOT-SLOT %d (%d,%d,%d,%d) @0x%04X cursor=%d (TODO)", slot, left, top, right, bottom, address, cursor); + + Common::Rect rect(left, top, right, bottom); + if (hotspot(rect, address, cursor)) { + char savename[15]; + + Common::String filename = ConfMan.getActiveDomainName() + ".00" + ('0' + slot); + Common::StringList files = _vm->_system->getSavefileManager()->listSavefiles(filename.c_str()); + if (!files.empty()) { + Common::InSaveFile *file = _vm->_system->getSavefileManager()->openForLoading(filename.c_str()); + if (file) { + uint8 i; + char temp; + + for (i = 0; i < 15; i++) { + file->read(&temp, 1); + savename[i] = temp + 0x30; + } + + delete file; + } else { + strcpy(savename, "ERROR"); + } + } else { + strcpy(savename, "E M P T Y"); + } + + // Load the font if required + if (!_font) { + _font = new Font(_vm->_system); + } + _font->printstring(savename); + } else { + Common::Point mousepos = _vm->_system->getEventManager()->getMousePos(); + if (_hotspotCursorOldX != mousepos.x || _hotspotCursorOldY != mousepos.y ) { + Common::Rect topbar(640, 80); + + Graphics::Surface *gamescreen; + gamescreen = _vm->_system->lockScreen(); + + gamescreen->fillRect(topbar, 0); + + _vm->_system->unlockScreen(); + _hotspotCursorOldX = mousepos.x; + _hotspotCursorOldY = mousepos.y; + } + } +} + +void Script::o_checkvalidsaves() { + debugScript(1, true, "CHECKVALIDSAVES"); + + // Reset the array of valid saves + for (int i = 0; i < 10; i++) { + _variables[i] = 0; + } + + // Get the list of savefiles + Common::String pattern = ConfMan.getActiveDomainName() + ".00?"; + Common::StringList savefiles = _vm->_system->getSavefileManager()->listSavefiles(pattern.c_str()); + + // Mark the existing savefiles as valid + uint count = 0; + Common::StringList::iterator it = savefiles.begin(); + while (it != savefiles.end()) { + int8 n = it->lastChar() - '0'; + if (n >= 0 && n <= 9) { + // TODO: Check the contents of the file? + debugScript(2, true, " Found valid savegame: %s", it->c_str()); + _variables[n] = 1; + count++; + } + it++; + } + + // Save the number of valid saves + _variables[0x104] = count; + debugScript(1, true, " Found %d valid savegames", count); +} + +void Script::o_resetvars() { + debugScript(1, true, "RESETVARS"); + for (int i = 0; i < 0x100; i++) { + _variables[i] = 0; + } +} + +void Script::o_mod() { + uint16 varnum = readScript8or16bits(); + uint8 val = readScript8bits(); + + debugScript(1, true, "MOD var[0x%04X] %%= %d", varnum, val); + + _variables[varnum] %= val; +} + +void Script::o_loadscript() { + Common::String filename; + char c; + + while ((c = readScript8bits())) { + filename += c; + } + debugScript(1, true, "LOADSCRIPT %s", filename.c_str()); + + // Just 1 level of sub-scripts are allowed + if (_savedCode) { + error("Tried to load a level 2 sub-script"); + } + + // Save the current code + _savedCode = _code; + _savedInstruction = _currentInstruction; + + // Save the filename of the current script + _savedScriptFile = _scriptFile; + + // Load the sub-script + if (!loadScript(filename)) { + error("Couldn't load sub-script"); + } + + // Save the current stack top + _savedStacktop = _stacktop; + + // Save the variables + memcpy(_savedVariables, _variables + 0x107, 0x180); +} + +void Script::o_setvideoorigin() { + // Read the two offset arguments + int16 origX = readScript16bits(); + int16 origY = readScript16bits(); + + // Set bitflag 7 + _bitflags |= 1 << 7; + + debugScript(1, true, "SetVideoOrigin(0x%04X,0x%04X) (%d, %d)", origX, origY, origX, origY); + _vm->_videoPlayer->setOrigin(origX, origY); +} + +void Script::o_sub() { + uint16 varnum1 = readScript8or16bits(); + uint16 varnum2 = readScript16bits(); + + debugScript(1, true, "SUB var[0x%04X] -= var[0x%04X]", varnum1, varnum2); + + _variables[varnum1] -= _variables[varnum2]; +} + +void Script::o_othello() { + uint16 arg = readScript8bits(); + byte *scriptBoard = &_variables[0x19]; + byte board[7][7]; + + debugScript(1, true, "OTHELLO var[0x%02X]", arg); + + // Arguments used by the original implementation: (2, arg, scriptBoard) + for (int y = 0; y < 7; y++) { + for (int x = 0; x < 7; x++) { + board[x][y] = 0; + if (*scriptBoard == 0x32) board[x][y] = 1; + if (*scriptBoard == 0x42) board[x][y] = 2; + scriptBoard++; + debugScript(1, false, "%d", board[x][y]); + } + debugScript(1, false, "\n"); + } + + // Set the movement origin + _variables[0] = 6; // y + _variables[1] = 0; // x + // Set the movement destination + _variables[2] = 6; + _variables[3] = 1; +} + +void Script::o_returnscript() { + uint8 val = readScript8bits(); + + debugScript(1, true, "RETURNSCRIPT @0x%02X", val); + + // Are we returning from a sub-script? + if (!_savedCode) { + error("Tried to return from the main script"); + } + + // Set the return value + _variables[0x102] = val; + + // Restore the code + delete[] _code; + _code = _savedCode; + _savedCode = NULL; + _currentInstruction = _savedInstruction; + + // Restore the stack + _stacktop = _savedStacktop; + + // Restore the variables + memcpy(_variables + 0x107, _savedVariables, 0x180); + + // Restore the filename of the script + _scriptFile = _savedScriptFile; + + //TODO: reset script flags and previous video's flag1? + _vm->_videoPlayer->setOrigin(0, 0); +} + +void Script::o_sethotspotright() { + uint16 address = readScript16bits(); + + debugScript(1, true, "SETHOTSPOTRIGHT @0x%04X", address); + + _hotspotRightAction = address; +} + +void Script::o_sethotspotleft() { + uint16 address = readScript16bits(); + + debugScript(1, true, "SETHOTSPOTLEFT @0x%04X", address); + + _hotspotLeftAction = address; +} + +void Script::o_getcd() { + debugScript(1, true, "GETCD"); + + // By default set it to no CD available + int8 cd = -1; + + // Try to open one file from each CD + Common::File cdfile; + if (cdfile.open("b.gjd")) { + cdfile.close(); + cd = 1; + } + if (cdfile.open("at.gjd")) { + cdfile.close(); + if (cd == 1) { + // Both CDs are available + cd = 0; + } else { + cd = 2; + } + } + + _variables[0x106] = cd; +} + +void Script::o_opcode4D() { + // TODO: play alternative vie logo, then playcd + uint8 val = readScript8bits(); + + debugScript(1, true, "PLAYCD? %d", val); + + if (val == 2) { + AudioCD.play(1, 1, 0, 0); + } + +} + +void Script::o_hotspot_outrect() { + uint16 left = readScript16bits(); + uint16 top = readScript16bits(); + uint16 right = readScript16bits(); + uint16 bottom = readScript16bits(); + uint16 address = readScript16bits(); + + debugScript(1, true, "HOTSPOT-OUTRECT(%d,%d,%d,%d) @0x%04X (TODO)", left, top, right, bottom, address); + + // Test if the current mouse position is outside the specified rectangle + Common::Rect rect(left, top, right, bottom); + Common::Point mousepos = _vm->_system->getEventManager()->getMousePos(); + bool contained = rect.contains(mousepos); + + if (!contained) { + error("hotspot-outrect unimplemented!"); + // TODO: what to do with address? + } +} + +void Script::o_stub56() { + uint32 val1 = readScript32bits(); + uint8 val2 = readScript8bits(); + uint8 val3 = readScript8bits(); + + debugScript(1, true, "STUB56: 0x%08X 0x%02X 0x%02X", val1, val2, val3); +} + +void Script::o_stub59() { + uint16 val1 = readScript8or16bits(); + uint8 val2 = readScript8bits(); + + debugScript(1, true, "STUB59: 0x%04X 0x%02X", val1, val2); +} + +Script::OpcodeFunc Script::_opcodes[NUM_OPCODES] = { + &Script::o_nop, // 0x00 + &Script::o_nop, + &Script::o_playsong, + &Script::o_bf9on, + &Script::o_palfadeout, // 0x04 + &Script::o_bf8on, + &Script::o_bf6on, + &Script::o_bf7on, + &Script::o_setbackgroundsong, // 0x08 + &Script::o_videofromref, + &Script::o_bf5on, + &Script::o_inputloopstart, + &Script::o_keyboardaction, // 0x0C + &Script::o_hotspot_rect, + &Script::o_hotspot_left, + &Script::o_hotspot_right, + &Script::o_hotspot_center, // 0x10 + &Script::o_hotspot_center, + &Script::o_hotspot_current, + &Script::o_inputloopend, + &Script::o_random, // 0x14 + &Script::o_jmp, + &Script::o_loadstring, + &Script::o_ret, + &Script::o_call, // 0x18 + &Script::o_sleep, + &Script::o_strcmpnejmp, + &Script::o_xor_obfuscate, + &Script::o_vdxtransition, // 0x1C + &Script::o_swap, + &Script::o_nop8, + &Script::o_inc, + &Script::o_dec, // 0x20 + &Script::o_strcmpnejmp_var, + &Script::o_copybgtofg, + &Script::o_strcmpeqjmp, + &Script::o_mov, // 0x24 + &Script::o_add, + &Script::o_videofromstring1, // Reads a string and then does stuff: used by book in library + &Script::o_videofromstring2, // play vdx file from string, after setting 1 (and 2 if firstbit) + &Script::o_nop16, // 0x28 + &Script::o_stopmidi, + &Script::o_endscript, + &Script::o_nop, + &Script::o_sethotspottop, // 0x2C + &Script::o_sethotspotbottom, + &Script::o_loadgame, + &Script::o_savegame, + &Script::o_hotspotbottom_4, // 0x30 + &Script::o_midivolume, + &Script::o_jne, + &Script::o_loadstringvar, + &Script::o_chargreatjmp, // 0x34 + &Script::o_bf7off, + &Script::o_charlessjmp, + &Script::o_copyrecttobg, + &Script::o_restorestkpnt, // 0x38 + &Script::o_obscureswap, + &Script::o_printstring, + &Script::o_hotspot_slot, + &Script::o_checkvalidsaves, // 0x3C + &Script::o_resetvars, + &Script::o_mod, + &Script::o_loadscript, + &Script::o_setvideoorigin, // 0x40 + &Script::o_sub, + &Script::o_othello, + &Script::o_returnscript, + &Script::o_sethotspotright, // 0x44 + &Script::o_sethotspotleft, + &Script::o_nop, + &Script::o_nop, + &Script::o_nop8, // 0x48 + &Script::o_nop, + &Script::o_nop16, + &Script::o_nop8, + &Script::o_getcd, // 0x4C + &Script::o_opcode4D, + &Script::o_nop16, + &Script::o_nop16, + &Script::o_nop16, // 0x50 + &Script::o_nop16, + //&Script::o_nop8, + &Script::o_invalid, // Do loads with game area, maybe draw dirty areas? + &Script::o_hotspot_outrect, + &Script::o_nop, // 0x54 + &Script::o_nop16, + &Script::o_stub56, + //&Script::o_nop32, + &Script::o_invalid, // completely unimplemented, plays vdx in some way + //&Script::o_nop, // 0x58 + &Script::o_invalid, // 0x58 // like above, but plays from string not ref + &Script::o_stub59 +}; + +} // End of Groovie namespace diff --git a/engines/groovie/script.h b/engines/groovie/script.h new file mode 100644 index 0000000000..3d8fda17af --- /dev/null +++ b/engines/groovie/script.h @@ -0,0 +1,211 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef GROOVIE_SCRIPT_H +#define GROOVIE_SCRIPT_H + +#include "common/file.h" +#include "common/rect.h" + +#include "groovie/font.h" + +namespace Groovie { + +class GroovieEngine; + +class Script { + friend class Debugger; + +public: + Script(GroovieEngine *vm); + ~Script(); + + void setDebugger(Debugger *debugger); + + bool loadScript(Common::String scriptfile); + void directGameLoad(int slot); + void step(); + + void setMouseClick(); + void setKbdChar(uint8 c); + + bool haveError(); + +private: + GroovieEngine *_vm; + + Common::RandomSource _random; + + bool _firstbit; + + // Script filename (for debugging purposes) + Common::String _scriptFile; + Common::String _savedScriptFile; + + // Code + byte *_code; + uint16 _currentInstruction; + byte *_savedCode; + uint16 _savedInstruction; + + // Variables + byte _variables[0x400]; + byte _savedVariables[0x180]; + + // Stack + uint16 _stack[0x20]; + uint8 _stacktop; + uint8 _savedStacktop; + + // Input + bool _mouseClicked; + bool _eventMouseClicked; + uint8 _kbdChar; + uint8 _eventKbdChar; + uint16 _inputLoopAddress; + int16 _inputAction; + uint8 _newCursorStyle; + uint16 _hotspotTopAction; + uint16 _hotspotTopCursor; + uint16 _hotspotBottomAction; + uint16 _hotspotBottomCursor; + uint16 _hotspotRightAction; + uint16 _hotspotLeftAction; + uint16 _hotspotCursorOldX; + uint16 _hotspotCursorOldY; + + // Video + Font *_font; + Common::SeekableReadStream *_videoFile; + uint16 _videoRef; + uint16 _bitflags; + + // Debugging + Debugger *_debugger; + Common::String _debugString; + void error(const char *msg); + bool _error; + + // Helper functions + uint8 readScript8bits(); + uint16 readScript16bits(); + uint32 readScript32bits(); + uint16 readScript8or16bits(); + uint8 readScriptChar(bool allow7C, bool limitVal, bool limitVar); + uint8 readScriptVar(); + uint16 getVideoRefString(); + + bool hotspot(Common::Rect rect, uint16 addr, uint8 cursor); + + void loadgame(uint slot); + void savegame(uint slot); + bool playvideofromref(uint16 fileref); + + // Opcodes + typedef void (Script::*OpcodeFunc)(); + static OpcodeFunc _opcodes[]; + + void o_invalid(); + + void o_nop(); + void o_nop8(); + void o_nop16(); + void o_nop32(); + void o_nop8or16(); + + void o_playsong(); + void o_bf9on(); + void o_palfadeout(); + void o_bf8on(); + void o_bf6on(); + void o_bf7on(); + void o_setbackgroundsong(); + void o_videofromref(); + void o_bf5on(); + void o_inputloopstart(); + void o_keyboardaction(); + void o_hotspot_rect(); + void o_hotspot_left(); + void o_hotspot_right(); + void o_hotspot_center(); + void o_hotspot_current(); + void o_inputloopend(); + void o_random(); + void o_jmp(); + void o_loadstring(); + void o_ret(); + void o_call(); + void o_sleep(); + void o_strcmpnejmp_var(); + void o_copybgtofg(); + void o_strcmpnejmp(); + void o_xor_obfuscate(); + void o_vdxtransition(); + void o_swap(); + void o_inc(); + void o_dec(); + void o_strcmpeqjmp(); + void o_mov(); + void o_add(); + void o_videofromstring1(); + void o_videofromstring2(); + void o_stopmidi(); + void o_endscript(); + void o_sethotspottop(); + void o_sethotspotbottom(); + void o_loadgame(); + void o_savegame(); + void o_hotspotbottom_4(); + void o_midivolume(); + void o_jne(); + void o_loadstringvar(); + void o_chargreatjmp(); + void o_bf7off(); + void o_charlessjmp(); + void o_copyrecttobg(); + void o_restorestkpnt(); + void o_obscureswap(); + void o_printstring(); + void o_hotspot_slot(); + void o_checkvalidsaves(); + void o_resetvars(); + void o_mod(); + void o_loadscript(); + void o_setvideoorigin(); + void o_sub(); + void o_othello(); + void o_returnscript(); + void o_sethotspotright(); + void o_sethotspotleft(); + void o_getcd(); + void o_opcode4D(); + void o_hotspot_outrect(); + void o_stub56(); + void o_stub59(); +}; + +} // End of Groovie namespace + +#endif // GROOVIE_SCRIPT_H diff --git a/engines/groovie/vdx.cpp b/engines/groovie/vdx.cpp new file mode 100644 index 0000000000..4a0198de22 --- /dev/null +++ b/engines/groovie/vdx.cpp @@ -0,0 +1,522 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "groovie/groovie.h" +#include "groovie/lzss.h" +#include "groovie/vdx.h" + +#include "sound/mixer.h" + +#define TILE_SIZE 4 // Size of each tile on the image: only ever seen 4 so far +#define VDX_IDENT 0x9267 // 37479 + +namespace Groovie { + +VDXPlayer::VDXPlayer(GroovieEngine *vm) : + VideoPlayer(vm), _origX(0), _origY(0), _flagOnePrev(false), + _fg(&_vm->_graphicsMan->_foreground), _bg(&_vm->_graphicsMan->_background) { +} + +VDXPlayer::~VDXPlayer() { + //delete _audioStream; +} + +void VDXPlayer::setOrigin(int16 x, int16 y) { + _origX = x; + _origY = y; +} + +uint16 VDXPlayer::loadInternal() { + uint32 engine_level = kGroovieDebugVideo | kGroovieDebugAll; + if ((gDebugLevel == 11) || (Common::getEnabledSpecialDebugLevels() & engine_level)) { + int8 i; + debugN(1, "Groovie::VDX: New VDX: bitflags are "); + for (i = 11; i >= 0; i--) { + debugN(1, "%d", _flags & (1 << i)? 1 : 0); + if (i % 4 == 0) { + debugN(1, " "); + } + } + debug(1, " "); + } + // Flags: + // - 1 Puzzle piece? Skip palette, don't redraw full screen, draw still to b/ack buffer + // - 2 Transparent colour is 0xFF + // - 5 Skip still chunks + // - 7 + // - 8 Just show the first frame + // - 9 Start a palette fade in + _flagZero = ((_flags & (1 << 0)) != 0); + _flagOne = ((_flags & (1 << 1)) != 0); + _flag2Byte = (_flags & (1 << 2)) ? 0xFF : 0x00; + _flagThree = ((_flags & (1 << 3)) != 0); + _flagFour = ((_flags & (1 << 4)) != 0); + _flagFive = ((_flags & (1 << 5)) != 0); + _flagSix = ((_flags & (1 << 6)) != 0); + _flagSeven = ((_flags & (1 << 7)) != 0); + _flagEight = ((_flags & (1 << 8)) != 0); + _flagNine = ((_flags & (1 << 9)) != 0); + + if (_flagOnePrev && !_flagOne && !_flagEight) { + _flagSeven = true; + } + + // Save _flagOne for the next video + _flagOnePrev = _flagOne; + + //_flagTransparent = _flagOne; + _flagFirstFrame = _flagEight; + //_flagSkipPalette = _flagSeven; + _flagSkipPalette = false; + //_flagSkipStill = _flagFive || _flagSeven; + //_flagUpdateStill = _flagNine || _flagSix; + + // Begin reading the file + debugC(1, kGroovieDebugVideo | kGroovieDebugAll, "Groovie::VDX: Playing video"); + + if (_file->readUint16LE() != VDX_IDENT) { + error("Groovie::VDX: This does not appear to be a 7th guest vxd file"); + return 0; + } else { + debugC(5, kGroovieDebugVideo | kGroovieDebugAll, "Groovie::VDX: VDX file identified correctly"); + } + + uint16 tmp; + + // Skip unknown data: 6 bytes, ref Martine + tmp = _file->readUint16LE(); + debugC(2, kGroovieDebugVideo | kGroovieDebugUnknown | kGroovieDebugAll, "Groovie::VDX: Martine1 = 0x%04X", tmp); + tmp = _file->readUint16LE(); + debugC(2, kGroovieDebugVideo | kGroovieDebugUnknown | kGroovieDebugAll, "Groovie::VDX: Martine2 = 0x%04X", tmp); + tmp = _file->readUint16LE(); + debugC(2, kGroovieDebugVideo | kGroovieDebugUnknown | kGroovieDebugAll, "Groovie::VDX: Martine3 (FPS?) = %d", tmp); + + return tmp; +} + +bool VDXPlayer::playFrameInternal() { + byte currRes = 0x80; + while (!_file->eos() && currRes == 0x80) { + currRes = _file->readByte(); + + // Skip unknown data: 1 byte, ref Edward + byte tmp = _file->readByte(); + debugC(5, kGroovieDebugVideo | kGroovieDebugUnknown | kGroovieDebugAll, "Groovie::VDX: Edward = 0x%04X", tmp); + + uint32 compSize = _file->readUint32LE(); + uint8 lengthmask = _file->readByte(); + uint8 lengthbits = _file->readByte(); + + // Read the chunk data and decompress if needed + Common::ReadStream *vdxData = new Common::SubReadStream(_file, compSize); + if (lengthmask && lengthbits) { + Common::ReadStream *decompData = new LzssReadStream(vdxData, lengthmask, lengthbits); + delete vdxData; + vdxData = decompData; + } + + // Use the current chunk + switch (currRes) { + case 0x00: + debugC(6, kGroovieDebugVideo | kGroovieDebugAll, "Groovie::VDX: Replay frame"); + break; + case 0x20: + debugC(5, kGroovieDebugVideo | kGroovieDebugAll, "Groovie::VDX: Still frame"); + getStill(vdxData); + break; + case 0x25: + debugC(5, kGroovieDebugVideo | kGroovieDebugAll, "Groovie::VDX: Animation frame"); + getDelta(vdxData); + break; + case 0x80: + debugC(5, kGroovieDebugVideo | kGroovieDebugAll, "Groovie::VDX: Sound resource"); + chunkSound(vdxData); + break; + default: + error("Groovie::VDX: Invalid resource type: %d", currRes); + } + delete vdxData; + } + + // Wait until the current frame can be shown + waitFrame(); + + // TODO: Move it to a better place + // Update the screen + if (currRes == 0x25) { + //if (_flagSeven) { + //_vm->_graphicsMan->mergeFgAndBg(); + //} + _vm->_graphicsMan->updateScreen(_bg); + } + + // Report the end of the video if we reached the end of the file or if we + // just wanted to play one frame. + return _file->eos() || _flagFirstFrame; +} + +static const uint16 vdxBlockMapLookup[] = { +0xc800, 0xec80, 0xfec8, 0xffec, 0xfffe, 0x3100, 0x7310, 0xf731, 0xff73, 0xfff7, 0x6c80, 0x36c8, 0x136c, 0x6310, 0xc631, 0x8c63, +0xf000, 0xff00, 0xfff0, 0x1111, 0x3333, 0x7777, 0x6666, 0xcccc, 0x0ff0, 0x00ff, 0xffcc, 0x0076, 0xff33, 0x0ee6, 0xccff, 0x6770, +0x33ff, 0x6ee0, 0x4800, 0x2480, 0x1248, 0x0024, 0x0012, 0x2100, 0x4210, 0x8421, 0x0042, 0x0084, 0xf888, 0x0044, 0x0032, 0x111f, +0x22e0, 0x4c00, 0x888f, 0x4470, 0x2300, 0xf111, 0x0e22, 0x00c4, 0xf33f, 0xfccf, 0xff99, 0x99ff, 0x4444, 0x2222, 0xccee, 0x7733, +0x00f8, 0x00f1, 0x00bb, 0x0cdd, 0x0f0f, 0x0f88, 0x13f1, 0x19b3, 0x1f80, 0x226f, 0x27ec, 0x3077, 0x3267, 0x37e4, 0x38e3, 0x3f90, +0x44cf, 0x4cd9, 0x4c99, 0x5555, 0x603f, 0x6077, 0x6237, 0x64c9, 0x64cd, 0x6cd9, 0x70ef, 0x0f00, 0x00f0, 0x0000, 0x4444, 0x2222 +}; + +void VDXPlayer::getDelta(Common::ReadStream *in) { + uint16 j, k, l; + uint32 offset; + uint8 currOpCode, param1, param2, param3; + + // Get the size of the local palette + j = in->readUint16LE(); + + // Load the palette if it isn't empty + if (j) { + uint16 palBitField[16]; + int flag = 1, palIndex; + + // Load the bit field + for (l = 0; l < 16; l++) { + palBitField[l] = in->readUint16LE(); + } + + // Load the actual palette + for (l = 0; l < 16; l++) { + flag = 1 << 15; + for (j = 0; j < 16; j++) { + palIndex = (l * 16) + j; + + if (flag & palBitField[l]) { + for (k = 0; k < 3; k++) { + _palBuf[(palIndex * 3) + k] = in->readByte(); + } + } + flag = flag >> 1; + } + } + + // Apply the palette + if (!_flagSix && !_flagSeven) { + setPalette(_palBuf); + } + } + currOpCode = in->readByte(); + + /* j now becomes the current block line we're dealing with */ + j = 0; + offset = 0; + while (!in->eos()) { + byte colours[16]; + if (currOpCode < 0x60) { + param1 = in->readByte(); + param2 = in->readByte(); + expandColourMap(colours, vdxBlockMapLookup[currOpCode], param1, param2); + decodeBlockDelta(offset, colours, 640); + offset += TILE_SIZE; + } else if (currOpCode > 0x7f) { + param1 = in->readByte(); + param2 = in->readByte(); + param3 = in->readByte(); + expandColourMap(colours, (param1 << 8) + currOpCode, param2, param3); + decodeBlockDelta(offset, colours, 640); + offset += TILE_SIZE; + } else switch (currOpCode) { + case 0x60: /* Fill tile with the 16 colours given as parameters */ + for (l = 0; l < 16; l++) { + colours[l] = in->readByte(); + } + decodeBlockDelta(offset, colours, 640); + offset += TILE_SIZE; + break; + case 0x61: /* Skip to the end of this line, next block is start of next */ + /* Note this is used at the end of EVERY line */ + j++; + offset = j * TILE_SIZE * 640; + break; + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6a: + case 0x6b: /* Skip next param1 blocks (within line) */ + offset += (currOpCode - 0x62) * TILE_SIZE; + break; + case 0x6c: + case 0x6d: + case 0x6e: + case 0x6f: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: /* Next param1 blocks are filled with colour param2 */ + param1 = currOpCode - 0x6b; + param2 = in->readByte(); + for (l = 0; l < 16; l++) { + colours[l] = param2; + } + for (k = 0; k < param1; k++) { + decodeBlockDelta(offset, colours, 640); + offset += TILE_SIZE; + } + break; + case 0x76: + case 0x77: + case 0x78: + case 0x79: + case 0x7a: + case 0x7b: + case 0x7c: + case 0x7d: + case 0x7e: + case 0x7f: /* Next bytes contain colours to fill the next param1 blocks in the current line*/ + param1 = currOpCode - 0x75; + for (k = 0; k < param1; k++) { + param2 = in->readByte(); + for (l = 0; l < 16; l++) { + colours[l] = param2; + } + decodeBlockDelta(offset, colours, 640); + offset += TILE_SIZE; + } + break; + default: + error("Groovie::VDX: Broken somehow"); + } + currOpCode = in->readByte(); + } +} + +void VDXPlayer::getStill(Common::ReadStream *in) { + uint16 numXTiles = in->readUint16LE(); + debugC(5, kGroovieDebugVideo | kGroovieDebugAll, "Groovie::VDX: numXTiles=%d", numXTiles); + uint16 numYTiles = in->readUint16LE(); + debugC(5, kGroovieDebugVideo | kGroovieDebugAll, "Groovie::VDX: numYTiles=%d", numYTiles); + + // It's skipped in the original: + uint16 colourDepth = in->readUint16LE(); + debugC(5, kGroovieDebugVideo | kGroovieDebugAll, "Groovie::VDX: colourDepth=%d", colourDepth); + + uint16 imageWidth = TILE_SIZE * numXTiles; + + uint8 mask = 0; + byte *buf; + if (_flagOne) { + // Paint to the foreground + buf = (byte *)_fg->getBasePtr(0, 0); + if (_flag2Byte) { + mask = 0xff; + } else { + mask = 0; + } + + // TODO: Verify this is the right procedure. Couldn't find it on the + // disassembly, but it's required to work properly + _flagFirstFrame = true; + } else { + // Paint to the background + buf = (byte *)_bg->getBasePtr(0, 0); + } + + // Read the palette + in->read(_palBuf, 3 * 256); + + if (_flagSeven) { + _flagFive = true; + } + + // Skip the frame when flag 5 is set, unless flag 1 is set + if (!_flagFive || _flagOne) { + + byte colours[16]; + for (uint16 j = 0; j < numYTiles; j++) { + for (uint16 i = 0; i < numXTiles; i++) { /* Tile number */ + uint8 colour1 = in->readByte(); + uint8 colour0 = in->readByte(); + uint16 colourMap = in->readUint16LE(); + expandColourMap(colours, colourMap, colour1, colour0); + decodeBlockStill(buf + j * TILE_SIZE * imageWidth + i * TILE_SIZE, colours, 640, mask); + } + } + + // Apply the palette + if (_flagNine) { + // Flag 9 starts a fade in + fadeIn(_palBuf); + } else { + if (!_flagOne && !_flagSeven) { + // Actually apply the palette + setPalette(_palBuf); + } + } + + if (!_flagOne) { + _vm->_graphicsMan->updateScreen(_bg); + } + /* + if (_flagSix) { + if (_flagOne) { + _vm->_graphicsMan->updateScreen(_fg); + } else { + _vm->_graphicsMan->updateScreen(_bg); + } + _flagSix = 0; + } + */ + } else { + // Skip the remaining data + debugC(10, kGroovieDebugVideo | kGroovieDebugAll, "Groovie::VDX: Skipping still frame"); + while (!in->eos()) { + in->readByte(); + } + } +} + +void VDXPlayer::expandColourMap(byte *out, uint16 colourMap, uint8 colour1, uint8 colour0) { + int flag = 1 << 15; + for (int i = 0; i < 16; i++) { + // Set the corresponding colour + out[i] = (colourMap & flag) ? colour1 : colour0; + + // Update the flag to test the next colour + flag >>= 1; + } +} + +void VDXPlayer::decodeBlockStill(byte *buf, byte *colours, uint16 imageWidth, uint8 mask) { + for (int y = 0; y < TILE_SIZE; y++) { + for (int x = 0; x < TILE_SIZE; x++) { + if (_flagOne) { + // 0xff pixels don't modify the buffer + if (*colours != 0xff) { + // Write the colour + *buf = *colours | mask; + // Note: if the mask is 0, it paints the image + // else, it paints the image's mask using 0xff + } + } else { + *buf = *colours; + } + + // Point to the next colour + colours++; + + // Point to the next pixel + buf++; + } + + // Point to the start of the next line + buf += imageWidth - TILE_SIZE; + } +} + +void VDXPlayer::decodeBlockDelta(uint32 offset, byte *colours, uint16 imageWidth) { + byte *fgBuf = (byte *)_fg->getBasePtr(0, 0) + offset; + //byte *bgBuf = (byte *)_bg->getBasePtr(0, 0) + offset; + + byte *dest; + // TODO: Verify just the else block is required + //if (_flagOne) { + // Paint to the foreground + //dest = (byte *)_fg->getBasePtr(0, 0) + offset; + //} else { + dest = (byte *)_bg->getBasePtr(0, 0) + offset; + //} + + int32 off = _origX + _origY * imageWidth; + for (int y = 0; y < TILE_SIZE; y++) { + for (int x = 0; x < TILE_SIZE; x++) { + if (_flagSeven) { + if (fgBuf[off] != 0xff) { + if (*colours == 0xff) { + dest[off] = fgBuf[off]; + } else { + dest[off] = *colours; + } + } + } else { + // Paint directly + dest[off] = *colours; + } + colours++; + off++; + } + + // Prepare the offset of the next line + off += imageWidth - TILE_SIZE; + } +} + +void VDXPlayer::chunkSound(Common::ReadStream *in) { + if (!_audioStream) { + _audioStream = Audio::makeAppendableAudioStream(22050, Audio::Mixer::FLAG_UNSIGNED | Audio::Mixer::FLAG_AUTOFREE); + Audio::SoundHandle sound_handle; + ::g_engine->_mixer->playInputStream(Audio::Mixer::kPlainSoundType, &sound_handle, _audioStream); + } + + byte *data = new byte[60000]; + int chunksize = in->read(data, 60000); + _audioStream->queueBuffer(data, chunksize); +} + +void VDXPlayer::fadeIn(uint8 *targetpal) { + // Don't do anything if we're asked to skip palette changes + if (_flagSkipPalette) + return; + + // TODO: Is it required? If so, move to an appropiate place + // Copy the foreground to the background + memcpy((byte *)_vm->_graphicsMan->_foreground.getBasePtr(0, 0), (byte *)_vm->_graphicsMan->_background.getBasePtr(0, 0), 640 * 320); + + // Start a fadein + _vm->_graphicsMan->fadeIn(targetpal); + + // Show the background + _vm->_graphicsMan->updateScreen(_bg); +} + +void VDXPlayer::setPalette(uint8 *palette) { + if (_flagSkipPalette) + return; + + uint8 palBuf[4 * 256]; + debugC(7, kGroovieDebugVideo | kGroovieDebugAll, "Groovie::VDX: Setting palette"); + for (int i = 0; i < 256; i++) { + palBuf[(i * 4) + 0] = palette[(i * 3) + 0]; + palBuf[(i * 4) + 1] = palette[(i * 3) + 1]; + palBuf[(i * 4) + 2] = palette[(i * 3) + 2]; + palBuf[(i * 4) + 3] = 0; + } + _syst->setPalette(palBuf, 0, 256); +} + +} // End of Groovie namespace diff --git a/engines/groovie/vdx.h b/engines/groovie/vdx.h new file mode 100644 index 0000000000..226cc63843 --- /dev/null +++ b/engines/groovie/vdx.h @@ -0,0 +1,81 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef GROOVIE_VDX_H +#define GROOVIE_VDX_H + +#include "groovie/player.h" + +namespace Groovie { + +class VDXPlayer : public VideoPlayer { +public: + VDXPlayer(GroovieEngine *vm); + ~VDXPlayer(); + void setOrigin(int16 x, int16 y); + +protected: + uint16 loadInternal(); + bool playFrameInternal(); + +private: + Graphics::Surface *_fg, *_bg; + uint8 _palBuf[3 * 256]; + + // Origin + int16 _origX, _origY; + + // Video flags + bool _flagZero; + bool _flagOne; + bool _flagOnePrev; + byte _flag2Byte; + bool _flagThree; + bool _flagFour; + bool _flagFive; + bool _flagSix; + bool _flagSeven; + bool _flagEight; + bool _flagNine; + + bool _flagSkipStill; + bool _flagSkipPalette; + bool _flagFirstFrame; + bool _flagTransparent; + bool _flagUpdateStill; + + void getStill(Common::ReadStream *in); + void getDelta(Common::ReadStream *in); + void expandColourMap(byte *out, uint16 colourMap, uint8 colour1, uint8 colour0); + void decodeBlockStill(byte *buf, byte *colours, uint16 imageWidth, uint8 mask); + void decodeBlockDelta(uint32 offset, byte *colours, uint16 imageWidth); + void chunkSound(Common::ReadStream *in); + void setPalette(uint8 *palette); + void fadeIn(uint8 *palette); +}; + +} // End of Groovie namespace + +#endif // GROOVIE_VDX_H |