aboutsummaryrefslogtreecommitdiff
path: root/engines/adl/hires6.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/adl/hires6.cpp')
-rw-r--r--engines/adl/hires6.cpp444
1 files changed, 444 insertions, 0 deletions
diff --git a/engines/adl/hires6.cpp b/engines/adl/hires6.cpp
new file mode 100644
index 0000000000..c42b4165a6
--- /dev/null
+++ b/engines/adl/hires6.cpp
@@ -0,0 +1,444 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/system.h"
+#include "common/debug.h"
+#include "common/error.h"
+#include "common/file.h"
+#include "common/stream.h"
+#include "common/memstream.h"
+
+#include "adl/hires6.h"
+#include "adl/display.h"
+#include "adl/graphics.h"
+#include "adl/disk.h"
+
+namespace Adl {
+
+static const char *disks[] = { "DARK1A.DSK", "DARK1B.NIB", "DARK2A.NIB", "DARK2B.NIB" };
+
+#define SECTORS_PER_TRACK 16
+#define BYTES_PER_SECTOR 256
+
+static Common::MemoryReadStream *loadSectors(DiskImage *disk, byte track, byte sector = SECTORS_PER_TRACK - 1, byte count = SECTORS_PER_TRACK) {
+ const int bufSize = count * BYTES_PER_SECTOR;
+ byte *const buf = (byte *)malloc(bufSize);
+ byte *p = buf;
+
+ while (count-- > 0) {
+ StreamPtr stream(disk->createReadStream(track, sector, 0, 0));
+ stream->read(p, BYTES_PER_SECTOR);
+
+ if (stream->err() || stream->eos())
+ error("Error loading from disk image");
+
+ p += BYTES_PER_SECTOR;
+ if (sector > 0)
+ --sector;
+ else {
+ ++track;
+
+ // Skip VTOC track
+ if (track == 17)
+ ++track;
+
+ sector = SECTORS_PER_TRACK - 1;
+ }
+ }
+
+ return new Common::MemoryReadStream(buf, bufSize, DisposeAfterUse::YES);
+}
+
+void HiRes6Engine::runIntro() const {
+ DiskImage_DSK *boot(new DiskImage_DSK());
+
+ if (!boot->open(disks[0]))
+ error("Failed to open disk image '%s'", disks[0]);
+
+ StreamPtr stream(loadSectors(boot, 11, 1, 96));
+
+ _display->setMode(DISPLAY_MODE_HIRES);
+ _display->loadFrameBuffer(*stream);
+ _display->updateHiResScreen();
+ delay(256 * 8609 / 1000);
+
+ _display->loadFrameBuffer(*stream);
+ _display->updateHiResScreen();
+ delay(256 * 8609 / 1000);
+
+ _display->loadFrameBuffer(*stream);
+
+ delete boot;
+
+ // Load copyright string from boot file
+ Files_DOS33 *files(new Files_DOS33());
+
+ if (!files->open(disks[0]))
+ error("Failed to open disk image '%s'", disks[0]);
+
+ stream.reset(files->createReadStream("\010\010\010\010\010\010"));
+ Common::String copyright(readStringAt(*stream, 0x103, APPLECHAR('\r')));
+
+ delete files;
+
+ _display->updateHiResScreen();
+ _display->home();
+ _display->setMode(DISPLAY_MODE_MIXED);
+ _display->moveCursorTo(Common::Point(0, 21));
+ _display->printString(copyright);
+ delay(256 * 8609 / 1000);
+}
+
+void HiRes6Engine::init() {
+ _boot = new DiskImage_DSK();
+ _graphics = new Graphics_v2(*_display);
+
+ if (!_boot->open(disks[0]))
+ error("Failed to open disk image '%s'", disks[0]);
+
+ StreamPtr stream(loadSectors(_boot, 0x7));
+
+ // Read parser messages
+ _strings.verbError = readStringAt(*stream, 0x666);
+ _strings.nounError = readStringAt(*stream, 0x6bd);
+ _strings.enterCommand = readStringAt(*stream, 0x6e9);
+
+ // Read line feeds
+ _strings.lineFeeds = readStringAt(*stream, 0x408);
+
+ // Read opcode strings (TODO)
+ _strings_v2.saveInsert = readStringAt(*stream, 0xad8);
+ readStringAt(*stream, 0xb95); // Confirm save
+ // _strings_v2.saveReplace
+ _strings_v2.restoreInsert = readStringAt(*stream, 0xc07);
+ // _strings_v2.restoreReplace
+ _strings.playAgain = readStringAt(*stream, 0xcdf, 0xff);
+
+ _messageIds.cantGoThere = IDI_HR6_MSG_CANT_GO_THERE;
+ _messageIds.dontUnderstand = IDI_HR6_MSG_DONT_UNDERSTAND;
+ _messageIds.itemDoesntMove = IDI_HR6_MSG_ITEM_DOESNT_MOVE;
+ _messageIds.itemNotHere = IDI_HR6_MSG_ITEM_NOT_HERE;
+ _messageIds.thanksForPlaying = IDI_HR6_MSG_THANKS_FOR_PLAYING;
+
+ // Item descriptions
+ stream.reset(loadSectors(_boot, 0x6, 0xb, 2));
+ stream->seek(0x34);
+ for (uint i = 0; i < IDI_HR6_NUM_ITEM_DESCS; ++i)
+ _itemDesc.push_back(readString(*stream, 0xff));
+
+ // Load dropped item offsets
+ stream.reset(_boot->createReadStream(0x8, 0x9, 0x16));
+ for (uint i = 0; i < IDI_HR6_NUM_ITEM_OFFSETS; ++i) {
+ Common::Point p;
+ p.x = stream->readByte();
+ p.y = stream->readByte();
+ _itemOffsets.push_back(p);
+ }
+
+ // Location of game data for each disc
+ stream.reset(_boot->createReadStream(0x5, 0xa, 0x03));
+ for (uint i = 0; i < sizeof(disks); ++i) {
+ DiskDataDesc desc;
+ desc.track = stream->readByte();
+ desc.sector = stream->readByte();
+ desc.offset = stream->readByte();
+ desc.volume = stream->readByte();
+ _diskDataDesc.push_back(desc);
+ }
+
+ // DataBlockPtr offsets for each disk
+ stream.reset(_boot->createReadStream(0x3, 0xf, 0x03));
+ for (uint i = 0; i < sizeof(disks); ++i) {
+ DiskOffset offset;
+ offset.track = stream->readByte();
+ offset.sector = stream->readByte();
+ _diskOffsets.push_back(offset);
+ }
+}
+
+void HiRes6Engine::loadDisk(byte disk) {
+ delete _disk;
+ _disk = new DiskImage_NIB();
+
+ if (!_disk->open(disks[disk]))
+ error("Failed to open disk image '%s'", disks[disk]);
+
+ _curDisk = 0;
+
+ // Load item picture data (indexed on boot disk)
+ StreamPtr stream(_boot->createReadStream(0xb, 0xd, 0x08));
+ _itemPics.clear();
+ for (uint i = 0; i < IDI_HR6_NUM_ITEM_PICS; ++i) {
+ stream->readByte();
+ _itemPics.push_back(readDataBlockPtr(*stream));
+ }
+
+ _curDisk = disk;
+
+ byte track = _diskDataDesc[disk].track;
+ byte sector = _diskDataDesc[disk].sector;
+ uint offset = _diskDataDesc[disk].offset;
+
+ applyDiskOffset(track, sector);
+
+ for (uint block = 0; block < 7; ++block) {
+ stream.reset(_disk->createReadStream(track, sector, offset, 1));
+
+ uint16 addr = stream->readUint16LE();
+ uint16 size = stream->readUint16LE();
+
+ stream.reset(_disk->createReadStream(track, sector, offset, size / 256 + 1));
+ stream->skip(4);
+
+ switch (addr) {
+ case 0x9000: {
+ // Messages
+ _messages.clear();
+ uint count = size / 4;
+ for (uint i = 0; i < count; ++i)
+ _messages.push_back(readDataBlockPtr(*stream));
+ break;
+ }
+ case 0x4a80: {
+ // Global pics
+ _pictures.clear();
+ byte picNr;
+ while ((picNr = stream->readByte()) != 0xff) {
+ if (stream->eos() || stream->err())
+ error("Error reading global pic list");
+ _pictures[picNr] = readDataBlockPtr(*stream);
+ }
+ break;
+ }
+ case 0x4000:
+ // Verbs
+ loadWords(*stream, _verbs, _priVerbs);
+ break;
+ case 0x1800:
+ // Nouns
+ loadWords(*stream, _nouns, _priNouns);
+ break;
+ case 0x0e00: {
+ // Rooms
+ uint count = size / 14 - 1;
+ stream->skip(14); // Skip invalid room 0
+
+ _state.rooms.clear();
+ for (uint i = 0; i < count; ++i) {
+ Room room;
+ stream->readByte(); // number
+ for (uint j = 0; j < 6; ++j)
+ room.connections[j] = stream->readByte();
+ room.data = readDataBlockPtr(*stream);
+ room.picture = stream->readByte();
+ room.curPicture = stream->readByte();
+ room.isFirstTime = stream->readByte();
+ _state.rooms.push_back(room);
+ }
+ break;
+ }
+ case 0x7b00:
+ // Global commands
+ readCommands(*stream, _globalCommands);
+ break;
+ case 0x9500:
+ // Room commands
+ readCommands(*stream, _roomCommands);
+ break;
+ default:
+ error("Unknown data block found (addr %04x; size %04x)", addr, size);
+ }
+
+ offset += 4 + size;
+ while (offset >= 256) {
+ offset -= 256;
+ ++sector;
+ if (sector >= 16) {
+ sector = 0;
+ ++track;
+ }
+ }
+ }
+}
+
+void HiRes6Engine::initGameState() {
+ _state.vars.resize(IDI_HR6_NUM_VARS);
+
+ loadDisk(1);
+
+ StreamPtr stream(_boot->createReadStream(0x3, 0xe, 0x03));
+
+ byte id;
+ while ((id = stream->readByte()) != 0xff) {
+ Item item = Item();
+ item.id = id;
+ item.noun = stream->readByte();
+ item.room = stream->readByte();
+ item.picture = stream->readByte();
+ item.isLineArt = stream->readByte(); // Now seems to be disk number
+ item.position.x = stream->readByte();
+ item.position.y = stream->readByte();
+ item.state = stream->readByte();
+ item.description = stream->readByte();
+
+ stream->readByte(); // Struct size
+
+ byte picListSize = stream->readByte();
+
+ // Flag to keep track of what has been drawn on the screen
+ stream->readByte();
+
+ for (uint i = 0; i < picListSize; ++i)
+ item.roomPictures.push_back(stream->readByte());
+
+ _state.items.push_back(item);
+ }
+
+ _currVerb = _currNoun = 0;
+}
+
+void HiRes6Engine::showRoom() {
+ bool redrawPic = false;
+
+ if (getVar(26) == 0xfe)
+ setVar(26, 0);
+ else if (getVar(26) != 0xff)
+ setVar(26, _state.room);
+
+ if (_state.room != _roomOnScreen) {
+ loadRoom(_state.room);
+
+ if (getVar(26) < 0x80 && getCurRoom().isFirstTime)
+ setVar(26, 0);
+
+ clearScreen();
+
+ if (!_state.isDark)
+ redrawPic = true;
+ } else {
+ if (getCurRoom().curPicture != _picOnScreen || _itemRemoved)
+ redrawPic = true;
+ }
+
+ if (redrawPic) {
+ _roomOnScreen = _state.room;
+ _picOnScreen = getCurRoom().curPicture;
+
+ drawPic(getCurRoom().curPicture);
+ _itemRemoved = false;
+ _itemsOnScreen = 0;
+
+ Common::List<Item>::iterator item;
+ for (item = _state.items.begin(); item != _state.items.end(); ++item)
+ item->isOnScreen = false;
+ }
+
+ if (!_state.isDark)
+ drawItems();
+
+ _display->updateHiResScreen();
+ setVar(2, 0xff);
+ printString(_roomData.description);
+
+ // FIXME: move to main loop?
+ _linesPrinted = 0;
+}
+
+Common::String HiRes6Engine::formatVerbError(const Common::String &verb) const {
+ Common::String err = _strings.verbError;
+
+ for (uint i = 0; i < verb.size(); ++i)
+ err.setChar(verb[i], i + 24);
+
+ err.setChar(APPLECHAR(' '), 32);
+
+ uint i = 24;
+ while (err[i] != APPLECHAR(' '))
+ ++i;
+
+ err.setChar(APPLECHAR('.'), i);
+
+ return err;
+}
+
+Common::String HiRes6Engine::formatNounError(const Common::String &verb, const Common::String &noun) const {
+ Common::String err = _strings.nounError;
+
+ for (uint i = 0; i < noun.size(); ++i)
+ err.setChar(noun[i], i + 24);
+
+ for (uint i = 35; i > 31; --i)
+ err.setChar(APPLECHAR(' '), i);
+
+ uint i = 24;
+ while (err[i] != APPLECHAR(' '))
+ ++i;
+
+ err.setChar(APPLECHAR('I'), i + 1);
+ err.setChar(APPLECHAR('S'), i + 2);
+ err.setChar(APPLECHAR('.'), i + 3);
+
+ return err;
+}
+
+void HiRes6Engine::printString(const Common::String &str) {
+ Common::String s;
+ uint found = 0;
+
+ // This does not emulate the corner cases of the original, hence this check
+ if (getVar(27) > 1)
+ error("Invalid value %i encountered for variable 27", getVar(27));
+
+ for (uint i = 0; i < str.size(); ++i) {
+ if (str[i] == '%') {
+ ++found;
+ if (found == 3)
+ found = 0;
+ } else {
+ if (found == 0 || found - 1 == getVar(27))
+ s += str[i];
+ }
+ }
+
+ if (getVar(2) != 0xff) {
+ AdlEngine_v2::printString(s);
+ } else {
+ if (getVar(26) == 0) {
+ if (str.size() != 1 || APPLECHAR(str[0]) != APPLECHAR(' '))
+ return AdlEngine_v2::printString(s);
+ setVar(2, APPLECHAR(' '));
+ } else if (getVar(26) != 0xff) {
+ setVar(2, 'P');
+ } else {
+ setVar(26, _state.room);
+ setVar(2, 1);
+ }
+
+ doAllCommands(_globalCommands, _currVerb, _currNoun);
+ }
+}
+
+Engine *HiRes6Engine_create(OSystem *syst, const AdlGameDescription *gd) {
+ return new HiRes6Engine(syst, gd);
+}
+
+} // End of namespace Adl