[Scummvm-git-logs] scummvm master -> 16d564d826211d64390c146e4ca4c62e5ace4dc6
bluegr
noreply at scummvm.org
Sun Aug 25 11:57:11 UTC 2024
This automated email contains information about 6 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .
Summary:
78552492b1 ADL: Move DiskImage into COMMON
f31eb92d7a COMMON: Add DiskImage enhancements
5a7555dfcc COMMON: Add BitArray::size()
23a9f4ed54 COMMON: Add DiskImage lazy-decoding mode
2f2f3a59bd AGI: Apple II NIB and WOZ disk image support
16d564d826 AGI: Map engine error codes to Common codes
Commit: 78552492b1056c02c80eec0d815e1b7d53455079
https://github.com/scummvm/scummvm/commit/78552492b1056c02c80eec0d815e1b7d53455079
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2024-08-25T14:57:06+03:00
Commit Message:
ADL: Move DiskImage into COMMON
Changed paths:
A common/formats/disk_image.cpp
A common/formats/disk_image.h
common/formats/module.mk
engines/adl/adl.cpp
engines/adl/adl.h
engines/adl/adl_v2.cpp
engines/adl/adl_v2.h
engines/adl/adl_v4.cpp
engines/adl/console.cpp
engines/adl/disk.cpp
engines/adl/disk.h
engines/adl/hires1.cpp
engines/adl/hires2.cpp
engines/adl/hires4.cpp
engines/adl/hires5.cpp
engines/adl/hires6.cpp
diff --git a/common/formats/disk_image.cpp b/common/formats/disk_image.cpp
new file mode 100644
index 00000000000..f3991c7f5aa
--- /dev/null
+++ b/common/formats/disk_image.cpp
@@ -0,0 +1,784 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/stream.h"
+#include "common/substream.h"
+#include "common/memstream.h"
+#include "common/md5.h"
+#include "common/algorithm.h"
+#include "common/bitstream.h"
+
+#include "common/formats/disk_image.h"
+
+namespace Common {
+
+const uint kNibTrackLen = 256 * 26;
+
+static bool detectDOS33(Common::SeekableReadStream &f, uint size) {
+ uint count = 0;
+ uint dos32 = 0, dos33 = 0;
+ uint32 window = 0;
+
+ while (count++ < size) {
+ window &= 0xffff;
+ window <<= 8;
+ window |= f.readByte();
+
+ if (f.err() || f.eos())
+ return false;
+
+ if (window == 0xd5aa96)
+ ++dos33;
+ else if (window == 0xd5aab5)
+ ++dos32;
+ }
+
+ return dos33 > dos32;
+}
+
+static bool readSector_NIB(byte outBuf[], uint outBufSize, const byte inBuf[], uint inBufSize, uint &pos, const byte minNibble, const byte lookup[], const uint track, const uint sector) {
+ uint z = inBufSize - (pos % inBufSize);
+ if (z < outBufSize) {
+ memcpy(outBuf, inBuf + (pos % inBufSize), z);
+ memcpy(outBuf + z, inBuf, outBufSize - z);
+ } else
+ memcpy(outBuf, inBuf + (pos % inBufSize), outBufSize);
+ pos += outBufSize;
+
+ byte oldVal = 0;
+ for (uint n = 0; n < outBufSize; ++n) {
+ // expand
+ if (outBuf[n] == 0xd5) {
+ // Early end of block.
+ pos -= (outBufSize - n);
+ debug(2, "NIB: early end of block @ %x (%d, %d)", n, track, sector);
+ return false;
+ }
+
+ byte val = 0x40;
+
+ if (outBuf[n] >= minNibble)
+ val = lookup[outBuf[n] - minNibble];
+
+ if (val == 0x40) {
+ // Badly-encoded nibbles, stop trying to decode here.
+ pos -= (outBufSize - n);
+ debug(2, "NIB: bad nibble %02x @ %x (%d, %d)", outBuf[n], n, track, sector);
+ return false;
+ }
+
+ // undo checksum
+ oldVal = val ^ oldVal;
+ outBuf[n] = oldVal;
+ }
+
+ byte checksum = inBuf[pos++ % inBufSize];
+ if (checksum < minNibble || oldVal != lookup[checksum - minNibble]) {
+ debug(2, "NIB: checksum mismatch @ (%d, %d)", track, sector);
+ return false;
+ }
+
+ return true;
+}
+
+// 4-and-4 encoding (odd-even)
+static uint8 read44(byte *buffer, uint size, uint &pos) {
+ // 1s in the other fields, so we can just AND
+ uint8 ret = buffer[pos++ % size];
+ return ((ret << 1) | 1) & buffer[pos++ % size];
+}
+
+static bool decodeTrack(Common::SeekableReadStream &stream, uint trackLen, bool dos33, byte *const diskImage, uint tracks, Common::Array<bool> &goodSectors) {
+ // starting at 0xaa, 64 is invalid (see below)
+ const byte c_5and3_lookup[] = { 64, 0, 64, 1, 2, 3, 64, 64, 64, 64, 64, 4, 5, 6, 64, 64, 7, 8, 64, 9, 10, 11, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 12, 13, 64, 64, 14, 15, 64, 16, 17, 18, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 19, 20, 64, 21, 22, 23, 64, 64, 64, 64, 64, 24, 25, 26, 64, 64, 27, 28, 64, 29, 30, 31 };
+ // starting at 0x96, 64 is invalid (see below)
+ const byte c_6and2_lookup[] = { 0, 1, 64, 64, 2, 3, 64, 4, 5, 6, 64, 64, 64, 64, 64, 64, 7, 8, 64, 64, 64, 9, 10, 11, 12, 13, 64, 64, 14, 15, 16, 17, 18, 19, 64, 20, 21, 22, 23, 24, 25, 26, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 27, 64, 28, 29, 30, 64, 64, 64, 31, 64, 64, 32, 33, 64, 34, 35, 36, 37, 38, 39, 40, 64, 64, 64, 64, 64, 41, 42, 43, 64, 44, 45, 46, 47, 48, 49, 50, 64, 64, 51, 52, 53, 54, 55, 56, 64, 57, 58, 59, 60, 61, 62, 63 };
+
+ const uint sectorsPerTrack = (dos33 ? 16 : 13);
+ const uint bytesPerSector = 256;
+
+ bool sawAddress = false;
+ uint8 volNo = 0, track = 0, sector = 0;
+
+ byte *const buffer = (byte *)malloc(trackLen);
+ uint firstGoodTrackPos = 0;
+ uint pos = 0;
+
+ if (stream.read(buffer, trackLen) < trackLen) {
+ free(buffer);
+ return false;
+ }
+
+ while (true) {
+ if (pos >= trackLen + firstGoodTrackPos)
+ break;
+
+ // Read until we find two sync bytes.
+ if (buffer[pos++ % trackLen] != 0xd5 || buffer[pos++ % trackLen] != 0xaa)
+ continue;
+
+ byte prologue = buffer[pos++ % trackLen];
+
+ if (sawAddress && prologue == (dos33 ? 0x96 : 0xb5)) {
+ sawAddress = false;
+ }
+
+ if (!sawAddress) {
+ sawAddress = true;
+
+ // We should always find the address field first.
+ if (prologue != (dos33 ? 0x96 : 0xb5)) {
+ // Accept a DOS 3.3(?) header at the start.
+ if (prologue != (dos33 ? 0xb5 : 0x96) && prologue != 0xad && prologue != 0xfd)
+ debug(2, "NIB: unknown field prologue %02x", prologue);
+ sawAddress = false;
+ continue;
+ }
+
+ volNo = read44(buffer, trackLen, pos);
+ track = read44(buffer, trackLen, pos);
+ sector = read44(buffer, trackLen, pos);
+ uint8 checksum = read44(buffer, trackLen, pos);
+ if ((volNo ^ track ^ sector) != checksum) {
+ debug(2, "NIB: invalid checksum (volNo %d, track %d, sector %d)", volNo, track, sector);
+ sawAddress = false;
+ continue;
+ }
+
+ if (track >= tracks || sector >= sectorsPerTrack) {
+ debug(2, "NIB: sector out of bounds @ (%d, %d)", track, sector);
+ sawAddress = false;
+ continue;
+ }
+
+ if (!firstGoodTrackPos)
+ firstGoodTrackPos = pos;
+
+ // Epilogue is de/aa plus a gap, but we don't care.
+ continue;
+ }
+
+ sawAddress = false;
+
+ // We should always find the data field after an address field.
+ // TODO: we ignore volNo?
+ byte *output = diskImage + (track * sectorsPerTrack + sector) * bytesPerSector;
+
+ if (dos33) {
+ // We hardcode the DOS 3.3 mapping here. TODO: Do we also need raw/prodos?
+ int raw2dos[16] = { 0, 7, 14, 6, 13, 5, 12, 4, 11, 3, 10, 2, 9, 1, 8, 15 };
+ sector = raw2dos[sector];
+ output = diskImage + (track * sectorsPerTrack + sector) * bytesPerSector;
+
+ byte inbuffer[342];
+
+ if (!readSector_NIB(inbuffer, sizeof(inbuffer), buffer, trackLen, pos, 0x96, c_6and2_lookup, track, sector))
+ continue;
+
+ for (uint n = 0; n < 256; ++n) {
+ output[n] = inbuffer[86 + n] << 2;
+ if (n < 86) { // use first pair of bits
+ output[n] |= ((inbuffer[n] & 1) << 1);
+ output[n] |= ((inbuffer[n] & 2) >> 1);
+ } else if (n < 86*2) { // second pair
+ output[n] |= ((inbuffer[n-86] & 4) >> 1);
+ output[n] |= ((inbuffer[n-86] & 8) >> 3);
+ } else { // third pair
+ output[n] |= ((inbuffer[n-86*2] & 0x10) >> 3);
+ output[n] |= ((inbuffer[n-86*2] & 0x20) >> 5);
+ }
+ }
+ } else {
+ // 5-and-3 uses 410 on-disk bytes, decoding to just over 256 bytes
+ byte inbuffer[410];
+
+ if (!readSector_NIB(inbuffer, sizeof(inbuffer), buffer, trackLen, pos, 0xaa, c_5and3_lookup, track, sector))
+ continue;
+
+ // 8 bytes of nibbles expand to 5 bytes
+ // so we have 51 of these batches (255 bytes), plus 2 bytes of 'leftover' nibbles for byte 256
+ for (uint n = 0; n < 51; ++n) {
+ // e.g. figure 3.18 of Beneath Apple DOS
+ byte lowbits1 = inbuffer[51*3 - n];
+ byte lowbits2 = inbuffer[51*2 - n];
+ byte lowbits3 = inbuffer[51*1 - n];
+ byte lowbits4 = (lowbits1 & 2) << 1 | (lowbits2 & 2) | (lowbits3 & 2) >> 1;
+ byte lowbits5 = (lowbits1 & 1) << 2 | (lowbits2 & 1) << 1 | (lowbits3 & 1);
+ output[250 - 5*n] = (inbuffer[n + 51*3 + 1] << 3) | ((lowbits1 >> 2) & 0x7);
+ output[251 - 5*n] = (inbuffer[n + 51*4 + 1] << 3) | ((lowbits2 >> 2) & 0x7);
+ output[252 - 5*n] = (inbuffer[n + 51*5 + 1] << 3) | ((lowbits3 >> 2) & 0x7);
+ output[253 - 5*n] = (inbuffer[n + 51*6 + 1] << 3) | lowbits4;
+ output[254 - 5*n] = (inbuffer[n + 51*7 + 1] << 3) | lowbits5;
+ }
+ output[255] = (inbuffer[409] << 3) | (inbuffer[0] & 0x7);
+ }
+
+ goodSectors[track * sectorsPerTrack + sector] = true;
+ }
+
+ free(buffer);
+ return true;
+}
+
+static void printGoodSectors(Common::Array<bool> &goodSectors, uint sectorsPerTrack) {
+ if (Common::find(goodSectors.begin(), goodSectors.end(), false) != goodSectors.end()) {
+ debugN(1, "NIB: Bad/missing sectors:");
+
+ for (uint i = 0; i < goodSectors.size(); ++i) {
+ if (!goodSectors[i])
+ debugN(1, " (%d, %d)", i / sectorsPerTrack, i % sectorsPerTrack);
+ }
+
+ debugN(1, "\n");
+ }
+}
+
+static Common::SeekableReadStream *readImage_NIB(Common::File &f, bool dos33, uint tracks = 35) {
+ if (f.size() != 35 * kNibTrackLen) {
+ warning("NIB: image '%s' has invalid size of %d bytes", f.getName(), (int)f.size());
+ return nullptr;
+ }
+
+ const uint sectorsPerTrack = (dos33 ? 16 : 13);
+ const uint imageSize = tracks * sectorsPerTrack * 256;
+ byte *const diskImage = (byte *)calloc(imageSize, 1);
+ Common::Array<bool> goodSectors(tracks * sectorsPerTrack);
+
+ for (uint track = 0; track < tracks; ++track) {
+ if (!decodeTrack(f, kNibTrackLen, dos33, diskImage, tracks, goodSectors)) {
+ warning("NIB: error reading '%s'", f.getName());
+ free(diskImage);
+ return nullptr;
+ }
+ }
+
+ printGoodSectors(goodSectors, sectorsPerTrack);
+
+ return new Common::MemoryReadStream(diskImage, imageSize, DisposeAfterUse::YES);
+}
+
+static int getVersion_WOZ(Common::File &f) {
+ f.seek(0);
+ const uint32 fileId = f.readUint32BE();
+
+ if (f.eos() || f.err()) {
+ warning("WOZ: error reading '%s'", f.getName());
+ return 0;
+ }
+
+ if (fileId == MKTAG('W', 'O', 'Z', '1'))
+ return 1;
+ else if (fileId == MKTAG('W', 'O', 'Z', '2'))
+ return 2;
+
+ warning("WOZ: unsupported ID '%s' found in '%s'", tag2str(fileId), f.getName());
+ return 0;
+}
+
+static Common::SeekableReadStream *readTrack_WOZ(Common::File &f, uint track, bool woz2) {
+ f.seek(88 + track * 4);
+ const byte index = f.readByte();
+
+ if (index == 0xff) {
+ warning("WOZ: track %u not found in '%s', skipping", track, f.getName());
+ return nullptr;
+ }
+
+ uint32 offset, byteSize, bitSize;
+
+ if (woz2) {
+ f.seek(256 + index * 8);
+ offset = f.readUint16LE() << 9;
+ byteSize = f.readUint16LE() << 9;
+ bitSize = f.readUint32LE();
+ } else {
+ offset = 256 + index * 6656;
+ f.seek(offset + 6646);
+ byteSize = f.readUint16LE();
+ bitSize = f.readUint16LE();
+ }
+
+ f.seek(offset);
+
+ if (f.eos() || f.err() || byteSize == 0) {
+ warning("WOZ: failed to read track %u in '%s', aborting", track, f.getName());
+ return nullptr;
+ }
+
+ byte *inBuf = (byte *)malloc(byteSize);
+ byte *outBuf = (byte *)malloc(byteSize);
+ uint32 outSize = 0;
+ if (!inBuf || !outBuf) {
+ warning("WOZ: failed to create buffers of size %u for track %u in '%s'", byteSize, track, f.getName());
+ free(inBuf);
+ free(outBuf);
+ return nullptr;
+ }
+
+ if (f.read(inBuf, byteSize) < byteSize) {
+ warning("WOZ: error reading track %u in '%s'", track, f.getName());
+ free(inBuf);
+ free(outBuf);
+ return nullptr;
+ }
+
+ Common::BitStreamMemory8MSB bitStream(new Common::BitStreamMemoryStream(inBuf, byteSize, DisposeAfterUse::YES), DisposeAfterUse::YES);
+
+ byte nibble = 0;
+ bool stop = false;
+ for (;;) {
+ nibble = (nibble << 1) | bitStream.getBit();
+
+ if (nibble & 0x80) {
+ if (stop)
+ break;
+ nibble = 0;
+ }
+
+ if (bitStream.pos() == bitSize) {
+ bitStream.rewind();
+ if (stop) {
+ warning("WOZ: failed to find sync point for track %u in '%s'", track, f.getName());
+ break;
+ }
+ stop = true;
+ }
+ }
+
+ nibble = 0;
+ uint32 bitsRead = 0;
+ do {
+ nibble = (nibble << 1) | bitStream.getBit();
+ ++bitsRead;
+
+ if (nibble & 0x80) {
+ outBuf[outSize++] = nibble;
+ nibble = 0;
+ }
+
+ if (bitStream.pos() == bitSize)
+ bitStream.rewind();
+ } while (bitsRead < bitSize);
+
+ if (nibble != 0)
+ warning("WOZ: failed to sync track %u in '%s'", track, f.getName());
+
+ if (outSize == 0) {
+ warning("WOZ: track %u in '%s' is empty", track, f.getName());
+ free(outBuf);
+ return nullptr;
+ }
+
+ return new Common::MemoryReadStream(outBuf, outSize, DisposeAfterUse::YES);
+}
+
+static Common::SeekableReadStream *readImage_WOZ(Common::File &f, bool dos33, uint tracks = 35) {
+ int version = getVersion_WOZ(f);
+
+ if (version == 0)
+ return nullptr;
+
+ const uint sectorsPerTrack = (dos33 ? 16 : 13);
+ const uint imageSize = tracks * sectorsPerTrack * 256;
+ byte *const diskImage = (byte *)calloc(imageSize, 1);
+ Common::Array<bool> goodSectors(tracks * sectorsPerTrack);
+
+ for (uint track = 0; track < tracks; ++track) {
+ StreamPtr stream(readTrack_WOZ(f, track, version == 2));
+
+ if (stream) {
+ if (!decodeTrack(*stream, stream->size(), dos33, diskImage, tracks, goodSectors))
+ error("WOZ: error reading '%s'", f.getName());
+ }
+ }
+
+ printGoodSectors(goodSectors, sectorsPerTrack);
+
+ return new Common::MemoryReadStream(diskImage, imageSize, DisposeAfterUse::YES);
+}
+
+bool DiskImage::open(const Common::Path &filename) {
+ Common::File *f = new Common::File;
+
+ debug(1, "Opening '%s'", filename.toString(Common::Path::kNativeSeparator).c_str());
+
+ if (!f->open(filename)) {
+ warning("Failed to open '%s'", filename.toString(Common::Path::kNativeSeparator).c_str());
+ delete f;
+ return false;
+ }
+
+ Common::String lcName(filename.baseName());
+ lcName.toLowercase();
+
+ if (lcName.hasSuffix(".dsk")) {
+ _tracks = 35;
+ _sectorsPerTrack = 16;
+ _bytesPerSector = 256;
+ _stream = f;
+ } else if (lcName.hasSuffix(".d13")) {
+ _tracks = 35;
+ _sectorsPerTrack = 13;
+ _bytesPerSector = 256;
+ _stream = f;
+ } else if (lcName.hasSuffix(".nib")) {
+ _tracks = 35;
+
+ if (detectDOS33(*f, kNibTrackLen))
+ _sectorsPerTrack = 16;
+ else
+ _sectorsPerTrack = 13;
+
+ _bytesPerSector = 256;
+ f->seek(0);
+ _stream = readImage_NIB(*f, _sectorsPerTrack == 16);
+ delete f;
+ } else if (lcName.hasSuffix(".woz")) {
+ _tracks = 35;
+ _sectorsPerTrack = 13;
+ _bytesPerSector = 256;
+
+ int version = getVersion_WOZ(*f);
+
+ if (version > 0) {
+ StreamPtr bitStream(readTrack_WOZ(*f, 0, version == 2));
+ if (bitStream) {
+ if (detectDOS33(*bitStream, bitStream->size()))
+ _sectorsPerTrack = 16;
+ _stream = readImage_WOZ(*f, _sectorsPerTrack == 16);
+ } else {
+ warning("WOZ: failed to load bitstream for track 0 in '%s'", f->getName());
+ }
+ }
+
+ delete f;
+ } else if (lcName.hasSuffix(".xfd")) {
+ _tracks = 40;
+ _sectorsPerTrack = 18;
+ _bytesPerSector = 128;
+ _stream = f;
+ } else if (lcName.hasSuffix(".img")) {
+ _tracks = 40;
+ _sectorsPerTrack = 8;
+ _bytesPerSector = 512;
+ _firstSector = 1;
+ _stream = f;
+ }
+
+ int expectedSize = _tracks * _sectorsPerTrack * _bytesPerSector;
+
+ if (!_stream)
+ return false;
+
+ if (_stream->size() != expectedSize)
+ error("Unrecognized disk image '%s' of size %d bytes (expected %d bytes)", filename.toString(Common::Path::kNativeSeparator).c_str(), (int)_stream->size(), expectedSize);
+
+ return true;
+}
+
+const DataBlockPtr DiskImage::getDataBlock(uint track, uint sector, uint offset, uint size) const {
+ return DataBlockPtr(new DiskImage::DataBlock(this, track, sector, offset, size, _sectorLimit));
+}
+
+Common::SeekableReadStream *DiskImage::createReadStream(uint track, uint sector, uint offset, uint size, uint sectorLimit) const {
+ const uint bytesToRead = size * _bytesPerSector + _bytesPerSector - offset;
+ byte *const data = (byte *)malloc(bytesToRead);
+ uint dataOffset = 0;
+
+ if (sectorLimit == 0)
+ sectorLimit = _sectorsPerTrack;
+
+ if (sector < _firstSector || sector >= sectorLimit + _firstSector)
+ error("Sector %u is out of bounds for %u-sector %u-based reading", sector, sectorLimit, _firstSector);
+
+ sector -= _firstSector;
+
+ while (dataOffset < bytesToRead) {
+ uint bytesRemInTrack = (sectorLimit - 1 - sector) * _bytesPerSector + _bytesPerSector - offset;
+ _stream->seek((track * _sectorsPerTrack + sector) * _bytesPerSector + offset);
+
+ if (bytesToRead - dataOffset < bytesRemInTrack)
+ bytesRemInTrack = bytesToRead - dataOffset;
+
+ if (_stream->read(data + dataOffset, bytesRemInTrack) < bytesRemInTrack)
+ error("Error reading disk image at track %d; sector %d", track, sector);
+
+ ++track;
+
+ sector = 0;
+ offset = 0;
+
+ dataOffset += bytesRemInTrack;
+ }
+
+ return new Common::MemoryReadStream(data, bytesToRead, DisposeAfterUse::YES);
+}
+
+int32 computeMD5(const Common::FSNode &node, Common::String &md5, uint32 md5Bytes) {
+ Common::File f;
+
+ if (!f.open(node))
+ return -1;
+
+ const uint tracks = md5Bytes / (13 * 256) + 1;
+
+ if (node.getName().matchString("*.nib", true) && f.size() == 35 * kNibTrackLen) {
+ bool isDOS33 = detectDOS33(f, kNibTrackLen);
+
+ f.seek(0);
+ Common::SeekableReadStream *stream = readImage_NIB(f, isDOS33, tracks);
+ if (stream) {
+ md5 = Common::computeStreamMD5AsString(*stream, md5Bytes);
+ delete stream;
+ return 35 * (isDOS33 ? 16 : 13) * 256;
+ }
+
+ return -1;
+ } else if (node.getName().matchString("*.woz", true)) {
+ int version = getVersion_WOZ(f);
+
+ if (version > 0) {
+ StreamPtr nibbles(readTrack_WOZ(f, 0, version == 2));
+ if (nibbles) {
+ bool isDOS33 = detectDOS33(*nibbles, nibbles->size());
+ StreamPtr stream(readImage_WOZ(f, isDOS33, tracks));
+ if (stream) {
+ md5 = Common::computeStreamMD5AsString(*stream, md5Bytes);
+ return 35 * (isDOS33 ? 16 : 13) * 256;
+ }
+ }
+ }
+
+ return -1;
+ } else {
+ md5 = Common::computeStreamMD5AsString(f, md5Bytes);
+ return f.size();
+ }
+}
+
+const DataBlockPtr Files_Plain::getDataBlock(const Common::Path &filename, uint offset) const {
+ return Common::SharedPtr<Files::DataBlock>(new Files::DataBlock(this, filename, offset));
+}
+
+Common::SeekableReadStream *Files_Plain::createReadStream(const Common::Path &filename, uint offset) const {
+ Common::File *f(new Common::File());
+
+ if (!f->open(filename))
+ error("Failed to open '%s'", filename.toString(Common::Path::kNativeSeparator).c_str());
+
+ if (offset == 0)
+ return f;
+ else
+ return new Common::SeekableSubReadStream(f, offset, f->size(), DisposeAfterUse::YES);
+}
+
+Files_AppleDOS::~Files_AppleDOS() {
+ delete _disk;
+}
+
+Files_AppleDOS::Files_AppleDOS() :
+ _disk(nullptr) {
+}
+
+void Files_AppleDOS::readSectorList(TrackSector start, Common::Array<TrackSector> &list) {
+ TrackSector index = start;
+
+ while (index.track != 0) {
+ Common::ScopedPtr<Common::SeekableReadStream> stream(_disk->createReadStream(index.track, index.sector));
+
+ stream->readByte();
+ index.track = stream->readByte();
+ index.sector = stream->readByte();
+
+ stream->seek(9, SEEK_CUR);
+
+ // This only handles sequential files
+ TrackSector ts;
+ ts.track = stream->readByte();
+ ts.sector = stream->readByte();
+
+ while (ts.track != 0) {
+ list.push_back(ts);
+
+ ts.track = stream->readByte();
+ ts.sector = stream->readByte();
+
+ if (stream->err())
+ error("Error reading sector list");
+
+ if (stream->eos())
+ break;
+ }
+ }
+}
+
+void Files_AppleDOS::readVTOC() {
+ Common::ScopedPtr<Common::SeekableReadStream> stream(_disk->createReadStream(0x11, 0x00));
+ stream->readByte();
+ byte track = stream->readByte();
+
+ if (!track) {
+ // VTOC probably obfuscated, try track 0x10
+ stream.reset(_disk->createReadStream(0x10, 0x00));
+ stream->readByte();
+ track = stream->readByte();
+ }
+
+ if (!track)
+ error("VTOC not found");
+
+ byte sector = stream->readByte();
+
+ while (track != 0) {
+ char name[kFilenameLen + 1] = { };
+
+ stream.reset(_disk->createReadStream(track, sector));
+ stream->readByte();
+ track = stream->readByte();
+ sector = stream->readByte();
+ stream->seek(8, SEEK_CUR);
+
+ for (uint i = 0; i < 7; ++i) {
+ TOCEntry entry;
+ TrackSector sectorList;
+ sectorList.track = stream->readByte();
+ sectorList.sector = stream->readByte();
+ entry.type = stream->readByte();
+ stream->read(name, kFilenameLen);
+
+ // Convert to ASCII
+ for (uint j = 0; j < kFilenameLen; j++)
+ name[j] &= 0x7f;
+
+ // Strip trailing spaces
+ for (int j = kFilenameLen - 1; j >= 0; --j) {
+ if (name[j] == ' ')
+ name[j] = 0;
+ else
+ break;
+ }
+
+ entry.totalSectors = stream->readUint16BE();
+
+ // 0 is empty slot, 255 is deleted file
+ if (sectorList.track != 0 && sectorList.track != 255) {
+ readSectorList(sectorList, entry.sectors);
+ _toc[name] = entry;
+ }
+ }
+ }
+}
+
+const DataBlockPtr Files_AppleDOS::getDataBlock(const Common::Path &filename, uint offset) const {
+ return Common::SharedPtr<Files::DataBlock>(new Files::DataBlock(this, filename, offset));
+}
+
+Common::SeekableReadStream *Files_AppleDOS::createReadStreamText(const TOCEntry &entry) const {
+ byte *buf = (byte *)malloc(entry.sectors.size() * kSectorSize);
+ byte *p = buf;
+
+ for (uint i = 0; i < entry.sectors.size(); ++i) {
+ Common::ScopedPtr<Common::SeekableReadStream> stream(_disk->createReadStream(entry.sectors[i].track, entry.sectors[i].sector));
+
+ assert(stream->size() == kSectorSize);
+
+ while (true) {
+ byte textChar = stream->readByte();
+
+ if (stream->eos() || textChar == 0)
+ break;
+
+ if (stream->err())
+ error("Error reading text file");
+
+ *p++ = textChar;
+ }
+ }
+
+ return new Common::MemoryReadStream(buf, p - buf, DisposeAfterUse::YES);
+}
+
+Common::SeekableReadStream *Files_AppleDOS::createReadStreamBinary(const TOCEntry &entry) const {
+ byte *buf = (byte *)malloc(entry.sectors.size() * kSectorSize);
+
+ Common::ScopedPtr<Common::SeekableReadStream> stream(_disk->createReadStream(entry.sectors[0].track, entry.sectors[0].sector));
+
+ if (entry.type == kFileTypeBinary)
+ stream->readUint16LE(); // Skip start address
+
+ uint16 size = stream->readUint16LE();
+ uint16 offset = 0;
+ uint16 sectorIdx = 1;
+
+ while (true) {
+ offset += stream->read(buf + offset, size - offset);
+
+ if (offset == size)
+ break;
+
+ if (stream->err())
+ error("Error reading binary file");
+
+ assert(stream->eos());
+
+ if (sectorIdx == entry.sectors.size())
+ error("Not enough sectors for binary file size");
+
+ stream.reset(_disk->createReadStream(entry.sectors[sectorIdx].track, entry.sectors[sectorIdx].sector));
+ ++sectorIdx;
+ }
+
+ return new Common::MemoryReadStream(buf, size, DisposeAfterUse::YES);
+}
+
+Common::SeekableReadStream *Files_AppleDOS::createReadStream(const Common::Path &filename, uint offset) const {
+ if (!_toc.contains(filename))
+ error("Failed to locate '%s'", filename.toString().c_str());
+
+ const TOCEntry &entry = _toc[filename];
+
+ Common::SeekableReadStream *stream;
+
+ switch(entry.type) {
+ case kFileTypeText:
+ stream = createReadStreamText(entry);
+ break;
+ case kFileTypeAppleSoft:
+ case kFileTypeBinary:
+ stream = createReadStreamBinary(entry);
+ break;
+ default:
+ error("Unsupported file type %i", entry.type);
+ }
+
+ return new Common::SeekableSubReadStream(stream, offset, stream->size(), DisposeAfterUse::YES);
+}
+
+bool Files_AppleDOS::open(const Common::Path &filename) {
+ _disk = new DiskImage();
+ if (!_disk->open(filename))
+ return false;
+
+ readVTOC();
+ return true;
+}
+
+} // End of namespace Common
diff --git a/common/formats/disk_image.h b/common/formats/disk_image.h
new file mode 100644
index 00000000000..ee7ee6d8477
--- /dev/null
+++ b/common/formats/disk_image.h
@@ -0,0 +1,175 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/ptr.h"
+#include "common/file.h"
+#include "common/debug.h"
+
+#ifndef COMMON_FORMATS_DISK_IMAGE_H
+#define COMMON_FORMATS_DISK_IMAGE_H
+
+namespace Common {
+
+class SeekableReadStream;
+class String;
+
+// Used for disk image detection
+int32 computeMD5(const Common::FSNode &node, Common::String &md5, uint32 md5Bytes);
+
+class DataBlock {
+public:
+ virtual ~DataBlock() { }
+
+ virtual Common::SeekableReadStream *createReadStream() const = 0;
+};
+
+typedef Common::SharedPtr<DataBlock> DataBlockPtr;
+typedef Common::ScopedPtr<Common::SeekableReadStream> StreamPtr;
+
+class Files {
+public:
+ virtual ~Files() { }
+
+ virtual const DataBlockPtr getDataBlock(const Common::Path &filename, uint offset = 0) const = 0;
+ virtual Common::SeekableReadStream *createReadStream(const Common::Path &filename, uint offset = 0) const = 0;
+ virtual bool exists(const Common::Path &filename) const = 0;
+
+protected:
+ class DataBlock : public Common::DataBlock {
+ public:
+ DataBlock(const Files *files, const Common::Path &filename, uint offset) :
+ _files(files),
+ _filename(filename),
+ _offset(offset) { }
+
+ Common::SeekableReadStream *createReadStream() const override {
+ return _files->createReadStream(_filename, _offset);
+ }
+
+ private:
+ const Common::Path _filename;
+ uint _offset;
+ const Files *_files;
+ };
+};
+
+class DiskImage {
+public:
+ DiskImage() :
+ _stream(nullptr),
+ _tracks(0),
+ _sectorsPerTrack(0),
+ _bytesPerSector(0),
+ _sectorLimit(0),
+ _firstSector(0) { }
+
+ ~DiskImage() {
+ delete _stream;
+ }
+
+ bool open(const Common::Path &filename);
+ const DataBlockPtr getDataBlock(uint track, uint sector, uint offset = 0, uint size = 0) const;
+ Common::SeekableReadStream *createReadStream(uint track, uint sector, uint offset = 0, uint size = 0, uint sectorsUsed = 0) const;
+ void setSectorLimit(uint sectorLimit) { _sectorLimit = sectorLimit; } // Maximum number of sectors to read per track before stepping
+ uint getBytesPerSector() const { return _bytesPerSector; }
+ uint getSectorsPerTrack() const { return _sectorsPerTrack; }
+ uint getTracks() const { return _tracks; }
+
+protected:
+ class DataBlock : public Common::DataBlock {
+ public:
+ DataBlock(const DiskImage *disk, uint track, uint sector, uint offset, uint size, uint sectorLimit) :
+ _track(track),
+ _sector(sector),
+ _offset(offset),
+ _size(size),
+ _sectorLimit(sectorLimit),
+ _disk(disk) { }
+
+ Common::SeekableReadStream *createReadStream() const override {
+ return _disk->createReadStream(_track, _sector, _offset, _size, _sectorLimit);
+ }
+
+ private:
+ uint _track, _sector, _offset, _size;
+ uint _sectorLimit;
+ const DiskImage *_disk;
+ };
+
+ Common::SeekableReadStream *_stream;
+ uint _tracks, _sectorsPerTrack, _bytesPerSector, _firstSector;
+ uint _sectorLimit;
+};
+
+// Data in plain files
+class Files_Plain : public Files {
+public:
+ const DataBlockPtr getDataBlock(const Common::Path &filename, uint offset = 0) const override;
+ Common::SeekableReadStream *createReadStream(const Common::Path &filename, uint offset = 0) const override;
+ bool exists(const Common::Path &filename) const override { return Common::File::exists(filename); }
+};
+
+// Data in files contained in Apple DOS 3.3 disk image
+class Files_AppleDOS : public Files {
+public:
+ Files_AppleDOS();
+ ~Files_AppleDOS() override;
+
+ bool open(const Common::Path &filename);
+ const DataBlockPtr getDataBlock(const Common::Path &filename, uint offset = 0) const override;
+ Common::SeekableReadStream *createReadStream(const Common::Path &filename, uint offset = 0) const override;
+ bool exists(const Common::Path &filename) const override { return _toc.contains(filename); }
+
+private:
+ enum FileType {
+ kFileTypeText = 0,
+ kFileTypeAppleSoft = 2,
+ kFileTypeBinary = 4
+ };
+
+ enum {
+ kSectorSize = 256,
+ kFilenameLen = 30
+ };
+
+ struct TrackSector {
+ byte track;
+ byte sector;
+ };
+
+ struct TOCEntry {
+ byte type;
+ uint16 totalSectors;
+ Common::Array<TrackSector> sectors;
+ };
+
+ void readVTOC();
+ void readSectorList(TrackSector start, Common::Array<TrackSector> &list);
+ Common::SeekableReadStream *createReadStreamText(const TOCEntry &entry) const;
+ Common::SeekableReadStream *createReadStreamBinary(const TOCEntry &entry) const;
+
+ DiskImage *_disk;
+ Common::HashMap<Common::Path, TOCEntry, Common::Path::IgnoreCase_Hash, Common::Path::IgnoreCase_EqualTo> _toc;
+};
+
+} // End of namespace Common
+
+#endif
diff --git a/common/formats/module.mk b/common/formats/module.mk
index c0626152c1d..b15eeaa3c10 100644
--- a/common/formats/module.mk
+++ b/common/formats/module.mk
@@ -2,6 +2,7 @@ MODULE := common/formats
MODULE_OBJS := \
cue.o \
+ disk_image.o \
formatinfo.o \
iff_container.o \
ini-file.o \
diff --git a/engines/adl/adl.cpp b/engines/adl/adl.cpp
index dabfacc2798..a9e9895ae11 100644
--- a/engines/adl/adl.cpp
+++ b/engines/adl/adl.cpp
@@ -537,10 +537,10 @@ void AdlEngine::loadDroppedItemOffsets(Common::ReadStream &stream, byte count) {
void AdlEngine::drawPic(byte pic, Common::Point pos) const {
if (_roomData.pictures.contains(pic)) {
- StreamPtr stream(_roomData.pictures[pic]->createReadStream());
+ Common::StreamPtr stream(_roomData.pictures[pic]->createReadStream());
_graphics->drawPic(*stream, pos);
} else if (_pictures.contains(pic)) {
- StreamPtr stream(_pictures[pic]->createReadStream());
+ Common::StreamPtr stream(_pictures[pic]->createReadStream());
_graphics->drawPic(*stream, pos);
} else
error("Picture %d not found", pic);
diff --git a/engines/adl/adl.h b/engines/adl/adl.h
index 7d53ecd9f78..a749c65a690 100644
--- a/engines/adl/adl.h
+++ b/engines/adl/adl.h
@@ -107,13 +107,13 @@ struct Room {
byte description;
byte connections[IDI_DIR_TOTAL];
- DataBlockPtr data;
+ Common::DataBlockPtr data;
byte picture;
byte curPicture;
bool isFirstTime;
};
-typedef Common::HashMap<byte, DataBlockPtr> PictureMap;
+typedef Common::HashMap<byte, Common::DataBlockPtr> PictureMap;
typedef Common::Array<byte> Script;
@@ -400,7 +400,7 @@ protected:
// Opcodes
Common::Array<Opcode> _condOpcodes, _actOpcodes;
// Message strings in data file
- Common::Array<DataBlockPtr> _messages;
+ Common::Array<Common::DataBlockPtr> _messages;
// Picture data
PictureMap _pictures;
// Dropped item screen offsets
diff --git a/engines/adl/adl_v2.cpp b/engines/adl/adl_v2.cpp
index faf4d61a465..c8a76c3e46d 100644
--- a/engines/adl/adl_v2.cpp
+++ b/engines/adl/adl_v2.cpp
@@ -66,7 +66,7 @@ void AdlEngine_v2::mapExeStrings(const Common::StringArray &strings) {
void AdlEngine_v2::insertDisk(byte volume) {
delete _disk;
- _disk = new DiskImage();
+ _disk = new Common::DiskImage();
if (!_disk->open(getDiskImageName(volume)))
error("Failed to open disk volume %d", volume);
@@ -164,7 +164,7 @@ void AdlEngine_v2::handleTextOverflow() {
Common::String AdlEngine_v2::loadMessage(uint idx) const {
if (_messages[idx]) {
- StreamPtr strStream(_messages[idx]->createReadStream());
+ Common::StreamPtr strStream(_messages[idx]->createReadStream());
return readString(*strStream, 0xff);
}
@@ -216,7 +216,7 @@ void AdlEngine_v2::drawItem(Item &item, const Common::Point &pos) {
return;
}
- StreamPtr stream(_itemPics[item.picture - 1]->createReadStream());
+ Common::StreamPtr stream(_itemPics[item.picture - 1]->createReadStream());
stream->readByte(); // Skip clear opcode
_graphics->drawPic(*stream, pos);
}
@@ -231,7 +231,7 @@ void AdlEngine_v2::loadRoom(byte roomNr) {
}
Room &room = getRoom(roomNr);
- StreamPtr stream(room.data->createReadStream());
+ Common::StreamPtr stream(room.data->createReadStream());
uint16 descOffset = stream->readUint16LE();
uint16 commandOffset = stream->readUint16LE();
@@ -352,7 +352,7 @@ void AdlEngine_v2::drawItems() {
}
}
-DataBlockPtr AdlEngine_v2::readDataBlockPtr(Common::ReadStream &f) const {
+Common::DataBlockPtr AdlEngine_v2::readDataBlockPtr(Common::ReadStream &f) const {
byte track = f.readByte();
byte sector = f.readByte();
byte offset = f.readByte();
@@ -362,7 +362,7 @@ DataBlockPtr AdlEngine_v2::readDataBlockPtr(Common::ReadStream &f) const {
error("Error reading DataBlockPtr");
if (track == 0 && sector == 0 && offset == 0 && size == 0)
- return DataBlockPtr();
+ return Common::DataBlockPtr();
adjustDataBlockPtr(track, sector, offset, size);
diff --git a/engines/adl/adl_v2.h b/engines/adl/adl_v2.h
index 04fdf39787b..18300db8514 100644
--- a/engines/adl/adl_v2.h
+++ b/engines/adl/adl_v2.h
@@ -51,7 +51,7 @@ protected:
void mapExeStrings(const Common::StringArray &strings);
void insertDisk(byte volume);
- virtual DataBlockPtr readDataBlockPtr(Common::ReadStream &f) const;
+ virtual Common::DataBlockPtr readDataBlockPtr(Common::ReadStream &f) const;
virtual void adjustDataBlockPtr(byte &track, byte §or, byte &offset, byte &size) const { }
void loadItems(Common::ReadStream &stream);
void loadRooms(Common::ReadStream &stream, byte count);
@@ -87,9 +87,9 @@ protected:
} _strings_v2;
uint _maxLines;
- DiskImage *_disk;
+ Common::DiskImage *_disk;
byte _currentVolume;
- Common::Array<DataBlockPtr> _itemPics;
+ Common::Array<Common::DataBlockPtr> _itemPics;
bool _itemRemoved;
byte _roomOnScreen, _picOnScreen, _itemsOnScreen;
Common::Array<byte> _brokenRooms;
diff --git a/engines/adl/adl_v4.cpp b/engines/adl/adl_v4.cpp
index 7fdb1d4f6d1..c6c50d10041 100644
--- a/engines/adl/adl_v4.cpp
+++ b/engines/adl/adl_v4.cpp
@@ -176,7 +176,7 @@ void AdlEngine_v4::saveState(Common::WriteStream &stream) {
Common::String AdlEngine_v4::loadMessage(uint idx) const {
if (_messages[idx]) {
- StreamPtr strStream(_messages[idx]->createReadStream());
+ Common::StreamPtr strStream(_messages[idx]->createReadStream());
return readString(*strStream, 0xff, "AVISDURGAN");
}
@@ -293,7 +293,7 @@ void AdlEngine_v4::loadRegion(byte region) {
fixupDiskOffset(track, sector);
for (uint block = 0; block < 7; ++block) {
- StreamPtr stream(_disk->createReadStream(track, sector, offset, 1));
+ Common::StreamPtr stream(_disk->createReadStream(track, sector, offset, 1));
uint16 addr = stream->readUint16LE();
uint16 size = stream->readUint16LE();
diff --git a/engines/adl/console.cpp b/engines/adl/console.cpp
index 48d7a0c386d..35ea60a01e1 100644
--- a/engines/adl/console.cpp
+++ b/engines/adl/console.cpp
@@ -397,7 +397,7 @@ bool Console::Cmd_ConvertDisk(int argc, const char **argv) {
return true;
}
- DiskImage inDisk;
+ Common::DiskImage inDisk;
if (!inDisk.open(argv[1])) {
debugPrintf("Failed to open '%s' for reading\n", argv[1]);
return true;
@@ -414,7 +414,7 @@ bool Console::Cmd_ConvertDisk(int argc, const char **argv) {
byte *const buf = new byte[size];
- StreamPtr stream(inDisk.createReadStream(0, 0, 0, sectors - 1));
+ Common::StreamPtr stream(inDisk.createReadStream(0, 0, 0, sectors - 1));
if (stream->read(buf, size) < size) {
debugPrintf("Failed to read from stream");
delete[] buf;
diff --git a/engines/adl/disk.cpp b/engines/adl/disk.cpp
index 6c830113526..691a83e715e 100644
--- a/engines/adl/disk.cpp
+++ b/engines/adl/disk.cpp
@@ -22,9 +22,8 @@
#include "common/stream.h"
#include "common/substream.h"
#include "common/memstream.h"
-#include "common/md5.h"
-#include "common/algorithm.h"
-#include "common/bitstream.h"
+
+#include "common/formats/disk_image.h"
#include "adl/disk.h"
@@ -61,7 +60,7 @@ Common::SeekableReadStream *DataBlock_PC::createReadStream() const {
if (_offset == bps - 1)
sectors = 1;
- StreamPtr diskStream(_disk->createReadStream(_track, _sector, _offset, sectors));
+ Common::StreamPtr diskStream(_disk->createReadStream(_track, _sector, _offset, sectors));
byte sizeBuf[2];
read(*diskStream, sizeBuf, 2);
@@ -83,755 +82,4 @@ Common::SeekableReadStream *DataBlock_PC::createReadStream() const {
return new Common::MemoryReadStream(buf, blockSize, DisposeAfterUse::YES);
}
-const uint kNibTrackLen = 256 * 26;
-
-static bool detectDOS33(Common::SeekableReadStream &f, uint size) {
- uint count = 0;
- uint dos32 = 0, dos33 = 0;
- uint32 window = 0;
-
- while (count++ < size) {
- window &= 0xffff;
- window <<= 8;
- window |= f.readByte();
-
- if (f.err() || f.eos())
- return false;
-
- if (window == 0xd5aa96)
- ++dos33;
- else if (window == 0xd5aab5)
- ++dos32;
- }
-
- return dos33 > dos32;
-}
-
-static bool readSector_NIB(byte outBuf[], uint outBufSize, const byte inBuf[], uint inBufSize, uint &pos, const byte minNibble, const byte lookup[], const uint track, const uint sector) {
- uint z = inBufSize - (pos % inBufSize);
- if (z < outBufSize) {
- memcpy(outBuf, inBuf + (pos % inBufSize), z);
- memcpy(outBuf + z, inBuf, outBufSize - z);
- } else
- memcpy(outBuf, inBuf + (pos % inBufSize), outBufSize);
- pos += outBufSize;
-
- byte oldVal = 0;
- for (uint n = 0; n < outBufSize; ++n) {
- // expand
- if (outBuf[n] == 0xd5) {
- // Early end of block.
- pos -= (outBufSize - n);
- debug(2, "NIB: early end of block @ %x (%d, %d)", n, track, sector);
- return false;
- }
-
- byte val = 0x40;
-
- if (outBuf[n] >= minNibble)
- val = lookup[outBuf[n] - minNibble];
-
- if (val == 0x40) {
- // Badly-encoded nibbles, stop trying to decode here.
- pos -= (outBufSize - n);
- debug(2, "NIB: bad nibble %02x @ %x (%d, %d)", outBuf[n], n, track, sector);
- return false;
- }
-
- // undo checksum
- oldVal = val ^ oldVal;
- outBuf[n] = oldVal;
- }
-
- byte checksum = inBuf[pos++ % inBufSize];
- if (checksum < minNibble || oldVal != lookup[checksum - minNibble]) {
- debug(2, "NIB: checksum mismatch @ (%d, %d)", track, sector);
- return false;
- }
-
- return true;
-}
-
-// 4-and-4 encoding (odd-even)
-static uint8 read44(byte *buffer, uint size, uint &pos) {
- // 1s in the other fields, so we can just AND
- uint8 ret = buffer[pos++ % size];
- return ((ret << 1) | 1) & buffer[pos++ % size];
-}
-
-static bool decodeTrack(Common::SeekableReadStream &stream, uint trackLen, bool dos33, byte *const diskImage, uint tracks, Common::Array<bool> &goodSectors) {
- // starting at 0xaa, 64 is invalid (see below)
- const byte c_5and3_lookup[] = { 64, 0, 64, 1, 2, 3, 64, 64, 64, 64, 64, 4, 5, 6, 64, 64, 7, 8, 64, 9, 10, 11, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 12, 13, 64, 64, 14, 15, 64, 16, 17, 18, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 19, 20, 64, 21, 22, 23, 64, 64, 64, 64, 64, 24, 25, 26, 64, 64, 27, 28, 64, 29, 30, 31 };
- // starting at 0x96, 64 is invalid (see below)
- const byte c_6and2_lookup[] = { 0, 1, 64, 64, 2, 3, 64, 4, 5, 6, 64, 64, 64, 64, 64, 64, 7, 8, 64, 64, 64, 9, 10, 11, 12, 13, 64, 64, 14, 15, 16, 17, 18, 19, 64, 20, 21, 22, 23, 24, 25, 26, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 27, 64, 28, 29, 30, 64, 64, 64, 31, 64, 64, 32, 33, 64, 34, 35, 36, 37, 38, 39, 40, 64, 64, 64, 64, 64, 41, 42, 43, 64, 44, 45, 46, 47, 48, 49, 50, 64, 64, 51, 52, 53, 54, 55, 56, 64, 57, 58, 59, 60, 61, 62, 63 };
-
- const uint sectorsPerTrack = (dos33 ? 16 : 13);
- const uint bytesPerSector = 256;
-
- bool sawAddress = false;
- uint8 volNo = 0, track = 0, sector = 0;
-
- byte *const buffer = (byte *)malloc(trackLen);
- uint firstGoodTrackPos = 0;
- uint pos = 0;
-
- if (stream.read(buffer, trackLen) < trackLen) {
- free(buffer);
- return false;
- }
-
- while (true) {
- if (pos >= trackLen + firstGoodTrackPos)
- break;
-
- // Read until we find two sync bytes.
- if (buffer[pos++ % trackLen] != 0xd5 || buffer[pos++ % trackLen] != 0xaa)
- continue;
-
- byte prologue = buffer[pos++ % trackLen];
-
- if (sawAddress && prologue == (dos33 ? 0x96 : 0xb5)) {
- sawAddress = false;
- }
-
- if (!sawAddress) {
- sawAddress = true;
-
- // We should always find the address field first.
- if (prologue != (dos33 ? 0x96 : 0xb5)) {
- // Accept a DOS 3.3(?) header at the start.
- if (prologue != (dos33 ? 0xb5 : 0x96) && prologue != 0xad && prologue != 0xfd)
- debug(2, "NIB: unknown field prologue %02x", prologue);
- sawAddress = false;
- continue;
- }
-
- volNo = read44(buffer, trackLen, pos);
- track = read44(buffer, trackLen, pos);
- sector = read44(buffer, trackLen, pos);
- uint8 checksum = read44(buffer, trackLen, pos);
- if ((volNo ^ track ^ sector) != checksum) {
- debug(2, "NIB: invalid checksum (volNo %d, track %d, sector %d)", volNo, track, sector);
- sawAddress = false;
- continue;
- }
-
- if (track >= tracks || sector >= sectorsPerTrack) {
- debug(2, "NIB: sector out of bounds @ (%d, %d)", track, sector);
- sawAddress = false;
- continue;
- }
-
- if (!firstGoodTrackPos)
- firstGoodTrackPos = pos;
-
- // Epilogue is de/aa plus a gap, but we don't care.
- continue;
- }
-
- sawAddress = false;
-
- // We should always find the data field after an address field.
- // TODO: we ignore volNo?
- byte *output = diskImage + (track * sectorsPerTrack + sector) * bytesPerSector;
-
- if (dos33) {
- // We hardcode the DOS 3.3 mapping here. TODO: Do we also need raw/prodos?
- int raw2dos[16] = { 0, 7, 14, 6, 13, 5, 12, 4, 11, 3, 10, 2, 9, 1, 8, 15 };
- sector = raw2dos[sector];
- output = diskImage + (track * sectorsPerTrack + sector) * bytesPerSector;
-
- byte inbuffer[342];
-
- if (!readSector_NIB(inbuffer, sizeof(inbuffer), buffer, trackLen, pos, 0x96, c_6and2_lookup, track, sector))
- continue;
-
- for (uint n = 0; n < 256; ++n) {
- output[n] = inbuffer[86 + n] << 2;
- if (n < 86) { // use first pair of bits
- output[n] |= ((inbuffer[n] & 1) << 1);
- output[n] |= ((inbuffer[n] & 2) >> 1);
- } else if (n < 86*2) { // second pair
- output[n] |= ((inbuffer[n-86] & 4) >> 1);
- output[n] |= ((inbuffer[n-86] & 8) >> 3);
- } else { // third pair
- output[n] |= ((inbuffer[n-86*2] & 0x10) >> 3);
- output[n] |= ((inbuffer[n-86*2] & 0x20) >> 5);
- }
- }
- } else {
- // 5-and-3 uses 410 on-disk bytes, decoding to just over 256 bytes
- byte inbuffer[410];
-
- if (!readSector_NIB(inbuffer, sizeof(inbuffer), buffer, trackLen, pos, 0xaa, c_5and3_lookup, track, sector))
- continue;
-
- // 8 bytes of nibbles expand to 5 bytes
- // so we have 51 of these batches (255 bytes), plus 2 bytes of 'leftover' nibbles for byte 256
- for (uint n = 0; n < 51; ++n) {
- // e.g. figure 3.18 of Beneath Apple DOS
- byte lowbits1 = inbuffer[51*3 - n];
- byte lowbits2 = inbuffer[51*2 - n];
- byte lowbits3 = inbuffer[51*1 - n];
- byte lowbits4 = (lowbits1 & 2) << 1 | (lowbits2 & 2) | (lowbits3 & 2) >> 1;
- byte lowbits5 = (lowbits1 & 1) << 2 | (lowbits2 & 1) << 1 | (lowbits3 & 1);
- output[250 - 5*n] = (inbuffer[n + 51*3 + 1] << 3) | ((lowbits1 >> 2) & 0x7);
- output[251 - 5*n] = (inbuffer[n + 51*4 + 1] << 3) | ((lowbits2 >> 2) & 0x7);
- output[252 - 5*n] = (inbuffer[n + 51*5 + 1] << 3) | ((lowbits3 >> 2) & 0x7);
- output[253 - 5*n] = (inbuffer[n + 51*6 + 1] << 3) | lowbits4;
- output[254 - 5*n] = (inbuffer[n + 51*7 + 1] << 3) | lowbits5;
- }
- output[255] = (inbuffer[409] << 3) | (inbuffer[0] & 0x7);
- }
-
- goodSectors[track * sectorsPerTrack + sector] = true;
- }
-
- free(buffer);
- return true;
-}
-
-static void printGoodSectors(Common::Array<bool> &goodSectors, uint sectorsPerTrack) {
- if (Common::find(goodSectors.begin(), goodSectors.end(), false) != goodSectors.end()) {
- debugN(1, "NIB: Bad/missing sectors:");
-
- for (uint i = 0; i < goodSectors.size(); ++i) {
- if (!goodSectors[i])
- debugN(1, " (%d, %d)", i / sectorsPerTrack, i % sectorsPerTrack);
- }
-
- debugN(1, "\n");
- }
-}
-
-static Common::SeekableReadStream *readImage_NIB(Common::File &f, bool dos33, uint tracks = 35) {
- if (f.size() != 35 * kNibTrackLen) {
- warning("NIB: image '%s' has invalid size of %d bytes", f.getName(), (int)f.size());
- return nullptr;
- }
-
- const uint sectorsPerTrack = (dos33 ? 16 : 13);
- const uint imageSize = tracks * sectorsPerTrack * 256;
- byte *const diskImage = (byte *)calloc(imageSize, 1);
- Common::Array<bool> goodSectors(tracks * sectorsPerTrack);
-
- for (uint track = 0; track < tracks; ++track) {
- if (!decodeTrack(f, kNibTrackLen, dos33, diskImage, tracks, goodSectors)) {
- warning("NIB: error reading '%s'", f.getName());
- free(diskImage);
- return nullptr;
- }
- }
-
- printGoodSectors(goodSectors, sectorsPerTrack);
-
- return new Common::MemoryReadStream(diskImage, imageSize, DisposeAfterUse::YES);
-}
-
-static int getVersion_WOZ(Common::File &f) {
- f.seek(0);
- const uint32 fileId = f.readUint32BE();
-
- if (f.eos() || f.err()) {
- warning("WOZ: error reading '%s'", f.getName());
- return 0;
- }
-
- if (fileId == MKTAG('W', 'O', 'Z', '1'))
- return 1;
- else if (fileId == MKTAG('W', 'O', 'Z', '2'))
- return 2;
-
- warning("WOZ: unsupported ID '%s' found in '%s'", tag2str(fileId), f.getName());
- return 0;
-}
-
-static Common::SeekableReadStream *readTrack_WOZ(Common::File &f, uint track, bool woz2) {
- f.seek(88 + track * 4);
- const byte index = f.readByte();
-
- if (index == 0xff) {
- warning("WOZ: track %u not found in '%s', skipping", track, f.getName());
- return nullptr;
- }
-
- uint32 offset, byteSize, bitSize;
-
- if (woz2) {
- f.seek(256 + index * 8);
- offset = f.readUint16LE() << 9;
- byteSize = f.readUint16LE() << 9;
- bitSize = f.readUint32LE();
- } else {
- offset = 256 + index * 6656;
- f.seek(offset + 6646);
- byteSize = f.readUint16LE();
- bitSize = f.readUint16LE();
- }
-
- f.seek(offset);
-
- if (f.eos() || f.err() || byteSize == 0) {
- warning("WOZ: failed to read track %u in '%s', aborting", track, f.getName());
- return nullptr;
- }
-
- byte *inBuf = (byte *)malloc(byteSize);
- byte *outBuf = (byte *)malloc(byteSize);
- uint32 outSize = 0;
- if (!inBuf || !outBuf) {
- warning("WOZ: failed to create buffers of size %u for track %u in '%s'", byteSize, track, f.getName());
- free(inBuf);
- free(outBuf);
- return nullptr;
- }
-
- if (f.read(inBuf, byteSize) < byteSize) {
- warning("WOZ: error reading track %u in '%s'", track, f.getName());
- free(inBuf);
- free(outBuf);
- return nullptr;
- }
-
- Common::BitStreamMemory8MSB bitStream(new Common::BitStreamMemoryStream(inBuf, byteSize, DisposeAfterUse::YES), DisposeAfterUse::YES);
-
- byte nibble = 0;
- bool stop = false;
- for (;;) {
- nibble = (nibble << 1) | bitStream.getBit();
-
- if (nibble & 0x80) {
- if (stop)
- break;
- nibble = 0;
- }
-
- if (bitStream.pos() == bitSize) {
- bitStream.rewind();
- if (stop) {
- warning("WOZ: failed to find sync point for track %u in '%s'", track, f.getName());
- break;
- }
- stop = true;
- }
- }
-
- nibble = 0;
- uint32 bitsRead = 0;
- do {
- nibble = (nibble << 1) | bitStream.getBit();
- ++bitsRead;
-
- if (nibble & 0x80) {
- outBuf[outSize++] = nibble;
- nibble = 0;
- }
-
- if (bitStream.pos() == bitSize)
- bitStream.rewind();
- } while (bitsRead < bitSize);
-
- if (nibble != 0)
- warning("WOZ: failed to sync track %u in '%s'", track, f.getName());
-
- if (outSize == 0) {
- warning("WOZ: track %u in '%s' is empty", track, f.getName());
- free(outBuf);
- return nullptr;
- }
-
- return new Common::MemoryReadStream(outBuf, outSize, DisposeAfterUse::YES);
-}
-
-static Common::SeekableReadStream *readImage_WOZ(Common::File &f, bool dos33, uint tracks = 35) {
- int version = getVersion_WOZ(f);
-
- if (version == 0)
- return nullptr;
-
- const uint sectorsPerTrack = (dos33 ? 16 : 13);
- const uint imageSize = tracks * sectorsPerTrack * 256;
- byte *const diskImage = (byte *)calloc(imageSize, 1);
- Common::Array<bool> goodSectors(tracks * sectorsPerTrack);
-
- for (uint track = 0; track < tracks; ++track) {
- StreamPtr stream(readTrack_WOZ(f, track, version == 2));
-
- if (stream) {
- if (!decodeTrack(*stream, stream->size(), dos33, diskImage, tracks, goodSectors))
- error("WOZ: error reading '%s'", f.getName());
- }
- }
-
- printGoodSectors(goodSectors, sectorsPerTrack);
-
- return new Common::MemoryReadStream(diskImage, imageSize, DisposeAfterUse::YES);
-}
-
-bool DiskImage::open(const Common::Path &filename) {
- Common::File *f = new Common::File;
-
- debug(1, "Opening '%s'", filename.toString(Common::Path::kNativeSeparator).c_str());
-
- if (!f->open(filename)) {
- warning("Failed to open '%s'", filename.toString(Common::Path::kNativeSeparator).c_str());
- delete f;
- return false;
- }
-
- Common::String lcName(filename.baseName());
- lcName.toLowercase();
-
- if (lcName.hasSuffix(".dsk")) {
- _tracks = 35;
- _sectorsPerTrack = 16;
- _bytesPerSector = 256;
- _stream = f;
- } else if (lcName.hasSuffix(".d13")) {
- _tracks = 35;
- _sectorsPerTrack = 13;
- _bytesPerSector = 256;
- _stream = f;
- } else if (lcName.hasSuffix(".nib")) {
- _tracks = 35;
-
- if (detectDOS33(*f, kNibTrackLen))
- _sectorsPerTrack = 16;
- else
- _sectorsPerTrack = 13;
-
- _bytesPerSector = 256;
- f->seek(0);
- _stream = readImage_NIB(*f, _sectorsPerTrack == 16);
- delete f;
- } else if (lcName.hasSuffix(".woz")) {
- _tracks = 35;
- _sectorsPerTrack = 13;
- _bytesPerSector = 256;
-
- int version = getVersion_WOZ(*f);
-
- if (version > 0) {
- StreamPtr bitStream(readTrack_WOZ(*f, 0, version == 2));
- if (bitStream) {
- if (detectDOS33(*bitStream, bitStream->size()))
- _sectorsPerTrack = 16;
- _stream = readImage_WOZ(*f, _sectorsPerTrack == 16);
- } else {
- warning("WOZ: failed to load bitstream for track 0 in '%s'", f->getName());
- }
- }
-
- delete f;
- } else if (lcName.hasSuffix(".xfd")) {
- _tracks = 40;
- _sectorsPerTrack = 18;
- _bytesPerSector = 128;
- _stream = f;
- } else if (lcName.hasSuffix(".img")) {
- _tracks = 40;
- _sectorsPerTrack = 8;
- _bytesPerSector = 512;
- _firstSector = 1;
- _stream = f;
- }
-
- int expectedSize = _tracks * _sectorsPerTrack * _bytesPerSector;
-
- if (!_stream)
- return false;
-
- if (_stream->size() != expectedSize)
- error("Unrecognized disk image '%s' of size %d bytes (expected %d bytes)", filename.toString(Common::Path::kNativeSeparator).c_str(), (int)_stream->size(), expectedSize);
-
- return true;
-}
-
-const DataBlockPtr DiskImage::getDataBlock(uint track, uint sector, uint offset, uint size) const {
- return DataBlockPtr(new DiskImage::DataBlock(this, track, sector, offset, size, _sectorLimit));
-}
-
-Common::SeekableReadStream *DiskImage::createReadStream(uint track, uint sector, uint offset, uint size, uint sectorLimit) const {
- const uint bytesToRead = size * _bytesPerSector + _bytesPerSector - offset;
- byte *const data = (byte *)malloc(bytesToRead);
- uint dataOffset = 0;
-
- if (sectorLimit == 0)
- sectorLimit = _sectorsPerTrack;
-
- if (sector < _firstSector || sector >= sectorLimit + _firstSector)
- error("Sector %u is out of bounds for %u-sector %u-based reading", sector, sectorLimit, _firstSector);
-
- sector -= _firstSector;
-
- while (dataOffset < bytesToRead) {
- uint bytesRemInTrack = (sectorLimit - 1 - sector) * _bytesPerSector + _bytesPerSector - offset;
- _stream->seek((track * _sectorsPerTrack + sector) * _bytesPerSector + offset);
-
- if (bytesToRead - dataOffset < bytesRemInTrack)
- bytesRemInTrack = bytesToRead - dataOffset;
-
- if (_stream->read(data + dataOffset, bytesRemInTrack) < bytesRemInTrack)
- error("Error reading disk image at track %d; sector %d", track, sector);
-
- ++track;
-
- sector = 0;
- offset = 0;
-
- dataOffset += bytesRemInTrack;
- }
-
- return new Common::MemoryReadStream(data, bytesToRead, DisposeAfterUse::YES);
-}
-
-int32 computeMD5(const Common::FSNode &node, Common::String &md5, uint32 md5Bytes) {
- Common::File f;
-
- if (!f.open(node))
- return -1;
-
- const uint tracks = md5Bytes / (13 * 256) + 1;
-
- if (node.getName().matchString("*.nib", true) && f.size() == 35 * kNibTrackLen) {
- bool isDOS33 = detectDOS33(f, kNibTrackLen);
-
- f.seek(0);
- Common::SeekableReadStream *stream = readImage_NIB(f, isDOS33, tracks);
- if (stream) {
- md5 = Common::computeStreamMD5AsString(*stream, md5Bytes);
- delete stream;
- return 35 * (isDOS33 ? 16 : 13) * 256;
- }
-
- return -1;
- } else if (node.getName().matchString("*.woz", true)) {
- int version = getVersion_WOZ(f);
-
- if (version > 0) {
- StreamPtr nibbles(readTrack_WOZ(f, 0, version == 2));
- if (nibbles) {
- bool isDOS33 = detectDOS33(*nibbles, nibbles->size());
- StreamPtr stream(readImage_WOZ(f, isDOS33, tracks));
- if (stream) {
- md5 = Common::computeStreamMD5AsString(*stream, md5Bytes);
- return 35 * (isDOS33 ? 16 : 13) * 256;
- }
- }
- }
-
- return -1;
- } else {
- md5 = Common::computeStreamMD5AsString(f, md5Bytes);
- return f.size();
- }
-}
-
-const DataBlockPtr Files_Plain::getDataBlock(const Common::Path &filename, uint offset) const {
- return Common::SharedPtr<Files::DataBlock>(new Files::DataBlock(this, filename, offset));
-}
-
-Common::SeekableReadStream *Files_Plain::createReadStream(const Common::Path &filename, uint offset) const {
- Common::File *f(new Common::File());
-
- if (!f->open(filename))
- error("Failed to open '%s'", filename.toString(Common::Path::kNativeSeparator).c_str());
-
- if (offset == 0)
- return f;
- else
- return new Common::SeekableSubReadStream(f, offset, f->size(), DisposeAfterUse::YES);
-}
-
-Files_AppleDOS::~Files_AppleDOS() {
- delete _disk;
-}
-
-Files_AppleDOS::Files_AppleDOS() :
- _disk(nullptr) {
-}
-
-void Files_AppleDOS::readSectorList(TrackSector start, Common::Array<TrackSector> &list) {
- TrackSector index = start;
-
- while (index.track != 0) {
- Common::ScopedPtr<Common::SeekableReadStream> stream(_disk->createReadStream(index.track, index.sector));
-
- stream->readByte();
- index.track = stream->readByte();
- index.sector = stream->readByte();
-
- stream->seek(9, SEEK_CUR);
-
- // This only handles sequential files
- TrackSector ts;
- ts.track = stream->readByte();
- ts.sector = stream->readByte();
-
- while (ts.track != 0) {
- list.push_back(ts);
-
- ts.track = stream->readByte();
- ts.sector = stream->readByte();
-
- if (stream->err())
- error("Error reading sector list");
-
- if (stream->eos())
- break;
- }
- }
-}
-
-void Files_AppleDOS::readVTOC() {
- Common::ScopedPtr<Common::SeekableReadStream> stream(_disk->createReadStream(0x11, 0x00));
- stream->readByte();
- byte track = stream->readByte();
-
- if (!track) {
- // VTOC probably obfuscated, try track 0x10
- stream.reset(_disk->createReadStream(0x10, 0x00));
- stream->readByte();
- track = stream->readByte();
- }
-
- if (!track)
- error("VTOC not found");
-
- byte sector = stream->readByte();
-
- while (track != 0) {
- char name[kFilenameLen + 1] = { };
-
- stream.reset(_disk->createReadStream(track, sector));
- stream->readByte();
- track = stream->readByte();
- sector = stream->readByte();
- stream->seek(8, SEEK_CUR);
-
- for (uint i = 0; i < 7; ++i) {
- TOCEntry entry;
- TrackSector sectorList;
- sectorList.track = stream->readByte();
- sectorList.sector = stream->readByte();
- entry.type = stream->readByte();
- stream->read(name, kFilenameLen);
-
- // Convert to ASCII
- for (uint j = 0; j < kFilenameLen; j++)
- name[j] &= 0x7f;
-
- // Strip trailing spaces
- for (int j = kFilenameLen - 1; j >= 0; --j) {
- if (name[j] == ' ')
- name[j] = 0;
- else
- break;
- }
-
- entry.totalSectors = stream->readUint16BE();
-
- // 0 is empty slot, 255 is deleted file
- if (sectorList.track != 0 && sectorList.track != 255) {
- readSectorList(sectorList, entry.sectors);
- _toc[name] = entry;
- }
- }
- }
-}
-
-const DataBlockPtr Files_AppleDOS::getDataBlock(const Common::Path &filename, uint offset) const {
- return Common::SharedPtr<Files::DataBlock>(new Files::DataBlock(this, filename, offset));
-}
-
-Common::SeekableReadStream *Files_AppleDOS::createReadStreamText(const TOCEntry &entry) const {
- byte *buf = (byte *)malloc(entry.sectors.size() * kSectorSize);
- byte *p = buf;
-
- for (uint i = 0; i < entry.sectors.size(); ++i) {
- Common::ScopedPtr<Common::SeekableReadStream> stream(_disk->createReadStream(entry.sectors[i].track, entry.sectors[i].sector));
-
- assert(stream->size() == kSectorSize);
-
- while (true) {
- byte textChar = stream->readByte();
-
- if (stream->eos() || textChar == 0)
- break;
-
- if (stream->err())
- error("Error reading text file");
-
- *p++ = textChar;
- }
- }
-
- return new Common::MemoryReadStream(buf, p - buf, DisposeAfterUse::YES);
-}
-
-Common::SeekableReadStream *Files_AppleDOS::createReadStreamBinary(const TOCEntry &entry) const {
- byte *buf = (byte *)malloc(entry.sectors.size() * kSectorSize);
-
- Common::ScopedPtr<Common::SeekableReadStream> stream(_disk->createReadStream(entry.sectors[0].track, entry.sectors[0].sector));
-
- if (entry.type == kFileTypeBinary)
- stream->readUint16LE(); // Skip start address
-
- uint16 size = stream->readUint16LE();
- uint16 offset = 0;
- uint16 sectorIdx = 1;
-
- while (true) {
- offset += stream->read(buf + offset, size - offset);
-
- if (offset == size)
- break;
-
- if (stream->err())
- error("Error reading binary file");
-
- assert(stream->eos());
-
- if (sectorIdx == entry.sectors.size())
- error("Not enough sectors for binary file size");
-
- stream.reset(_disk->createReadStream(entry.sectors[sectorIdx].track, entry.sectors[sectorIdx].sector));
- ++sectorIdx;
- }
-
- return new Common::MemoryReadStream(buf, size, DisposeAfterUse::YES);
-}
-
-Common::SeekableReadStream *Files_AppleDOS::createReadStream(const Common::Path &filename, uint offset) const {
- if (!_toc.contains(filename))
- error("Failed to locate '%s'", filename.toString().c_str());
-
- const TOCEntry &entry = _toc[filename];
-
- Common::SeekableReadStream *stream;
-
- switch(entry.type) {
- case kFileTypeText:
- stream = createReadStreamText(entry);
- break;
- case kFileTypeAppleSoft:
- case kFileTypeBinary:
- stream = createReadStreamBinary(entry);
- break;
- default:
- error("Unsupported file type %i", entry.type);
- }
-
- return new Common::SeekableSubReadStream(stream, offset, stream->size(), DisposeAfterUse::YES);
-}
-
-bool Files_AppleDOS::open(const Common::Path &filename) {
- _disk = new DiskImage();
- if (!_disk->open(filename))
- return false;
-
- readVTOC();
- return true;
-}
-
-} // End of namespace Adl
+} // End of namespace Common
diff --git a/engines/adl/disk.h b/engines/adl/disk.h
index a3ddc64f4f4..1bf49cc7576 100644
--- a/engines/adl/disk.h
+++ b/engines/adl/disk.h
@@ -23,6 +23,8 @@
#include "common/file.h"
#include "common/debug.h"
+#include "common/formats/disk_image.h"
+
#ifndef ADL_DISK_H
#define ADL_DISK_H
@@ -33,153 +35,14 @@ class String;
namespace Adl {
-// Used for disk image detection
-int32 computeMD5(const Common::FSNode &node, Common::String &md5, uint32 md5Bytes);
-
-class DataBlock {
-public:
- virtual ~DataBlock() { }
-
- virtual Common::SeekableReadStream *createReadStream() const = 0;
-};
-
-typedef Common::SharedPtr<DataBlock> DataBlockPtr;
-typedef Common::ScopedPtr<Common::SeekableReadStream> StreamPtr;
-
-class Files {
-public:
- virtual ~Files() { }
-
- virtual const DataBlockPtr getDataBlock(const Common::Path &filename, uint offset = 0) const = 0;
- virtual Common::SeekableReadStream *createReadStream(const Common::Path &filename, uint offset = 0) const = 0;
- virtual bool exists(const Common::Path &filename) const = 0;
-
-protected:
- class DataBlock : public Adl::DataBlock {
- public:
- DataBlock(const Files *files, const Common::Path &filename, uint offset) :
- _files(files),
- _filename(filename),
- _offset(offset) { }
-
- Common::SeekableReadStream *createReadStream() const override {
- return _files->createReadStream(_filename, _offset);
- }
-
- private:
- const Common::Path _filename;
- uint _offset;
- const Files *_files;
- };
-};
-
-class DiskImage {
-public:
- DiskImage() :
- _stream(nullptr),
- _tracks(0),
- _sectorsPerTrack(0),
- _bytesPerSector(0),
- _sectorLimit(0),
- _firstSector(0) { }
-
- ~DiskImage() {
- delete _stream;
- }
-
- bool open(const Common::Path &filename);
- const DataBlockPtr getDataBlock(uint track, uint sector, uint offset = 0, uint size = 0) const;
- Common::SeekableReadStream *createReadStream(uint track, uint sector, uint offset = 0, uint size = 0, uint sectorsUsed = 0) const;
- void setSectorLimit(uint sectorLimit) { _sectorLimit = sectorLimit; } // Maximum number of sectors to read per track before stepping
- uint getBytesPerSector() const { return _bytesPerSector; }
- uint getSectorsPerTrack() const { return _sectorsPerTrack; }
- uint getTracks() const { return _tracks; }
-
-protected:
- class DataBlock : public Adl::DataBlock {
- public:
- DataBlock(const DiskImage *disk, uint track, uint sector, uint offset, uint size, uint sectorLimit) :
- _track(track),
- _sector(sector),
- _offset(offset),
- _size(size),
- _sectorLimit(sectorLimit),
- _disk(disk) { }
-
- Common::SeekableReadStream *createReadStream() const override {
- return _disk->createReadStream(_track, _sector, _offset, _size, _sectorLimit);
- }
-
- private:
- uint _track, _sector, _offset, _size;
- uint _sectorLimit;
- const DiskImage *_disk;
- };
-
- Common::SeekableReadStream *_stream;
- uint _tracks, _sectorsPerTrack, _bytesPerSector, _firstSector;
- uint _sectorLimit;
-};
-
-// Data in plain files
-class Files_Plain : public Files {
-public:
- const DataBlockPtr getDataBlock(const Common::Path &filename, uint offset = 0) const override;
- Common::SeekableReadStream *createReadStream(const Common::Path &filename, uint offset = 0) const override;
- bool exists(const Common::Path &filename) const override { return Common::File::exists(filename); }
-};
-
-// Data in files contained in Apple DOS 3.3 disk image
-class Files_AppleDOS : public Files {
-public:
- Files_AppleDOS();
- ~Files_AppleDOS() override;
-
- bool open(const Common::Path &filename);
- const DataBlockPtr getDataBlock(const Common::Path &filename, uint offset = 0) const override;
- Common::SeekableReadStream *createReadStream(const Common::Path &filename, uint offset = 0) const override;
- bool exists(const Common::Path &filename) const override { return _toc.contains(filename); }
-
-private:
- enum FileType {
- kFileTypeText = 0,
- kFileTypeAppleSoft = 2,
- kFileTypeBinary = 4
- };
-
- enum {
- kSectorSize = 256,
- kFilenameLen = 30
- };
-
- struct TrackSector {
- byte track;
- byte sector;
- };
-
- struct TOCEntry {
- byte type;
- uint16 totalSectors;
- Common::Array<TrackSector> sectors;
- };
-
- void readVTOC();
- void readSectorList(TrackSector start, Common::Array<TrackSector> &list);
- Common::SeekableReadStream *createReadStreamText(const TOCEntry &entry) const;
- Common::SeekableReadStream *createReadStreamBinary(const TOCEntry &entry) const;
-
- DiskImage *_disk;
- Common::HashMap<Common::Path, TOCEntry, Common::Path::IgnoreCase_Hash, Common::Path::IgnoreCase_EqualTo> _toc;
-};
-
// On the Apple II, sector headers contain a disk volume number. This number
// is used by ADL multi-disk games. The PC port has the disk volume number
// as the first data byte of every sector that contains game data. We need
// to skip this number as we read in the data. Additionally, the data is now
// prefixed with an uint16 containing the data size.
-class DataBlock_PC : public DataBlock {
+class DataBlock_PC : public Common::DataBlock {
public:
- DataBlock_PC(DiskImage *disk, byte track, byte sector, uint16 offset = 0) :
+ DataBlock_PC(Common::DiskImage *disk, byte track, byte sector, uint16 offset = 0) :
_disk(disk),
_track(track),
_sector(sector),
@@ -192,7 +55,7 @@ public:
private:
void read(Common::SeekableReadStream &stream, byte *const dataPtr, const uint32 size) const;
- DiskImage *_disk;
+ Common::DiskImage *_disk;
byte _track, _sector;
uint16 _offset;
};
diff --git a/engines/adl/hires1.cpp b/engines/adl/hires1.cpp
index d3b95b9a6d4..bce9b04b871 100644
--- a/engines/adl/hires1.cpp
+++ b/engines/adl/hires1.cpp
@@ -92,9 +92,9 @@ protected:
void showInstructions(Common::SeekableReadStream &stream);
void wordWrap(Common::String &str) const;
- Files *_files;
+ Common::Files *_files;
Common::File _exe;
- Common::Array<DataBlockPtr> _corners;
+ Common::Array<Common::DataBlockPtr> _corners;
Common::Array<byte> _roomDesc;
bool _messageDelay;
@@ -147,7 +147,7 @@ void HiRes1Engine::showInstructions(Common::SeekableReadStream &stream) {
}
void HiRes1Engine::runIntro() {
- StreamPtr stream(_files->createReadStream(IDS_HR1_EXE_0));
+ Common::StreamPtr stream(_files->createReadStream(IDS_HR1_EXE_0));
// Early version have no bitmap in 'AUTO LOAD OBJ'
if (getGameVersion() >= GAME_VER_HR1_COARSE) {
@@ -180,7 +180,7 @@ void HiRes1Engine::runIntro() {
if (!_files->exists(fileName))
fileName = "HELLO";
- StreamPtr basic(_files->createReadStream(fileName));
+ Common::StreamPtr basic(_files->createReadStream(fileName));
_display->setMode(Display::kModeText);
_display->home();
@@ -269,9 +269,9 @@ void HiRes1Engine::runIntro() {
void HiRes1Engine::init() {
if (Common::File::exists("ADVENTURE")) {
- _files = new Files_Plain();
+ _files = new Common::Files_Plain();
} else {
- Files_AppleDOS *files = new Files_AppleDOS();
+ Common::Files_AppleDOS *files = new Common::Files_AppleDOS();
if (!files->open(getDiskImageName(0)))
error("Failed to open '%s'", getDiskImageName(0).toString(Common::Path::kNativeSeparator).c_str());
_files = files;
@@ -280,7 +280,7 @@ void HiRes1Engine::init() {
_graphics = new GraphicsMan_v1<Display_A2>(*static_cast<Display_A2 *>(_display));
_display->moveCursorTo(Common::Point(0, 23)); // DOS 3.3
- StreamPtr stream(_files->createReadStream(IDS_HR1_EXE_1));
+ Common::StreamPtr stream(_files->createReadStream(IDS_HR1_EXE_1));
Common::StringArray exeStrings;
extractExeStrings(*stream, 0x1576, exeStrings);
@@ -363,7 +363,7 @@ void HiRes1Engine::init() {
void HiRes1Engine::initGameState() {
_state.vars.resize(IDI_HR1_NUM_VARS);
- StreamPtr stream(_files->createReadStream(IDS_HR1_EXE_1));
+ Common::StreamPtr stream(_files->createReadStream(IDS_HR1_EXE_1));
// Load room data from executable
_roomDesc.clear();
@@ -423,7 +423,7 @@ void HiRes1Engine::printString(const Common::String &str) {
Common::String HiRes1Engine::loadMessage(uint idx) const {
const char returnChar = _display->asciiToNative('\r');
- StreamPtr stream(_messages[idx]->createReadStream());
+ Common::StreamPtr stream(_messages[idx]->createReadStream());
return readString(*stream, returnChar) + returnChar;
}
@@ -493,7 +493,7 @@ void HiRes1Engine::drawItems() {
void HiRes1Engine::drawItem(Item &item, const Common::Point &pos) {
if (item.isShape) {
- StreamPtr stream(_corners[item.picture - 1]->createReadStream());
+ Common::StreamPtr stream(_corners[item.picture - 1]->createReadStream());
Common::Point p(pos);
_graphics->drawShape(*stream, p);
} else
@@ -604,7 +604,7 @@ void HiRes1Engine_VF::getInput(uint &verb, uint &noun) {
}
void HiRes1Engine_VF::runIntro() {
- StreamPtr stream(_files->createReadStream(IDS_HR1_EXE_0));
+ Common::StreamPtr stream(_files->createReadStream(IDS_HR1_EXE_0));
stream->seek(0x1000);
diff --git a/engines/adl/hires2.cpp b/engines/adl/hires2.cpp
index e87ff449f9f..c54826db470 100644
--- a/engines/adl/hires2.cpp
+++ b/engines/adl/hires2.cpp
@@ -60,13 +60,13 @@ HiResBaseEngine::HiResBaseEngine(OSystem *syst, const AdlGameDescription *gd, co
void HiResBaseEngine::init() {
_graphics = new GraphicsMan_v2<Display_A2>(*static_cast<Display_A2 *>(_display));
- _disk = new DiskImage();
+ _disk = new Common::DiskImage();
if (!_disk->open(getDiskImageName(0)))
error("Failed to open disk image '%s'", getDiskImageName(0).toString(Common::Path::kNativeSeparator).c_str());
_disk->setSectorLimit(13);
- StreamPtr stream(_disk->createReadStream(0x1f, 0x2, 0x00, 4));
+ Common::StreamPtr stream(_disk->createReadStream(0x1f, 0x2, 0x00, 4));
loadMessages(*stream, _numMsgs);
stream.reset(_disk->createReadStream(0x19, 0x0, 0x00, 25, 13));
@@ -114,7 +114,7 @@ void HiResBaseEngine::init() {
void HiResBaseEngine::initGameState() {
_state.vars.resize(40);
- StreamPtr stream(_disk->createReadStream(0x21, 0x5, 0x0e, 7));
+ Common::StreamPtr stream(_disk->createReadStream(0x21, 0x5, 0x0e, 7));
loadRooms(*stream, _numRooms);
stream.reset(_disk->createReadStream(0x21, 0x0, 0x00, 2));
@@ -145,7 +145,7 @@ void HiRes2Engine::runIntro() {
if (_disk->getSectorsPerTrack() != 16)
return;
- StreamPtr stream(_disk->createReadStream(0x00, 0xd, 0x17, 1));
+ Common::StreamPtr stream(_disk->createReadStream(0x00, 0xd, 0x17, 1));
_display->setMode(Display::kModeText);
diff --git a/engines/adl/hires4.cpp b/engines/adl/hires4.cpp
index a6147f7082e..9cdb0cccbc7 100644
--- a/engines/adl/hires4.cpp
+++ b/engines/adl/hires4.cpp
@@ -59,7 +59,7 @@ protected:
void init() override;
void initGameState() override;
- DiskImage *_boot;
+ Common::DiskImage *_boot;
};
HiRes4BaseEngine::HiRes4BaseEngine(OSystem *syst, const AdlGameDescription *gd) :
@@ -81,7 +81,7 @@ HiRes4BaseEngine::~HiRes4BaseEngine() {
void HiRes4BaseEngine::init() {
_graphics = new GraphicsMan_v2<Display_A2>(*static_cast<Display_A2 *>(_display));
- _boot = new DiskImage();
+ _boot = new Common::DiskImage();
if (!_boot->open(getDiskImageName(0)))
error("Failed to open disk image '%s'", getDiskImageName(0).toString(Common::Path::kNativeSeparator).c_str());
@@ -104,7 +104,7 @@ private:
};
void HiRes4Engine_v1_0::runIntro() {
- StreamPtr stream(_boot->createReadStream(0x06, 0x3, 0xb9, 1));
+ Common::StreamPtr stream(_boot->createReadStream(0x06, 0x3, 0xb9, 1));
_display->setMode(Display::kModeText);
@@ -121,7 +121,7 @@ void HiRes4Engine_v1_0::runIntro() {
void HiRes4Engine_v1_0::init() {
HiRes4BaseEngine::init();
- StreamPtr stream(_boot->createReadStream(0x9, 0x1, 0x00, 13));
+ Common::StreamPtr stream(_boot->createReadStream(0x9, 0x1, 0x00, 13));
Common::StringArray exeStrings;
extractExeStrings(*stream, 0x1566, exeStrings);
mapExeStrings(exeStrings);
@@ -157,7 +157,7 @@ void HiRes4Engine_v1_0::init() {
void HiRes4Engine_v1_0::initGameState() {
HiRes4BaseEngine::initGameState();
- StreamPtr stream(_boot->createReadStream(0x04, 0xa, 0x0e, 9, 13));
+ Common::StreamPtr stream(_boot->createReadStream(0x04, 0xa, 0x0e, 9, 13));
loadRooms(*stream, IDI_HR4_NUM_ROOMS);
stream.reset(_boot->createReadStream(0x04, 0x5, 0x00, 12, 13));
@@ -177,7 +177,7 @@ private:
// TODO: It might be worth replacing this with a more generic variant that
// can be used in both hires4 and hires6
-static Common::MemoryReadStream *readSkewedSectors(DiskImage *disk, byte track, byte sector, byte count) {
+static Common::MemoryReadStream *readSkewedSectors(Common::DiskImage *disk, byte track, byte sector, byte count) {
const uint bytesPerSector = disk->getBytesPerSector();
const uint sectorsPerTrack = disk->getSectorsPerTrack();
const uint bufSize = count * bytesPerSector;
@@ -185,7 +185,7 @@ static Common::MemoryReadStream *readSkewedSectors(DiskImage *disk, byte track,
byte *p = buf;
while (count-- != 0) {
- StreamPtr stream(disk->createReadStream(track, sector));
+ Common::StreamPtr stream(disk->createReadStream(track, sector));
stream->read(p, bytesPerSector);
if (stream->err() || stream->eos())
@@ -222,10 +222,10 @@ static Common::MemoryReadStream *decodeData(Common::SeekableReadStream &stream,
}
void HiRes4Engine_v1_1::runIntro() {
- Common::ScopedPtr<Files_AppleDOS> files(new Files_AppleDOS());
+ Common::ScopedPtr<Common::Files_AppleDOS> files(new Common::Files_AppleDOS());
files->open(getDiskImageName(0));
- StreamPtr menu(files->createReadStream("\b\b\b\b\b\b\bULYSSES\r(C) 1982"));
+ Common::StreamPtr menu(files->createReadStream("\b\b\b\b\b\b\bULYSSES\r(C) 1982"));
menu->seek(0x2eb);
for (uint i = 0; i < 4; ++i) {
@@ -241,7 +241,7 @@ void HiRes4Engine_v1_1::runIntro() {
void HiRes4Engine_v1_1::init() {
HiRes4BaseEngine::init();
- StreamPtr stream(readSkewedSectors(_boot, 0x05, 0x6, 1));
+ Common::StreamPtr stream(readSkewedSectors(_boot, 0x05, 0x6, 1));
_strings.verbError = readStringAt(*stream, 0x4f);
_strings.nounError = readStringAt(*stream, 0x8e);
_strings.enterCommand = readStringAt(*stream, 0xbc);
@@ -297,7 +297,7 @@ void HiRes4Engine_v1_1::init() {
void HiRes4Engine_v1_1::initGameState() {
HiRes4BaseEngine::initGameState();
- StreamPtr stream(readSkewedSectors(_boot, 0x0b, 0x9, 10));
+ Common::StreamPtr stream(readSkewedSectors(_boot, 0x0b, 0x9, 10));
stream->skip(0x0e);
loadRooms(*stream, IDI_HR4_NUM_ROOMS);
@@ -420,7 +420,7 @@ void HiRes4Engine_LNG::runIntroLogo(Common::SeekableReadStream &ms2) {
void HiRes4Engine_LNG::runIntroTitle(Common::SeekableReadStream &menu, Common::SeekableReadStream &ms2) {
ms2.seek(0x2290);
- StreamPtr shapeTable(ms2.readStream(0x450));
+ Common::StreamPtr shapeTable(ms2.readStream(0x450));
if (ms2.err() || ms2.eos())
error("Failed to read shape table");
@@ -543,17 +543,17 @@ void HiRes4Engine_LNG::runIntroLoading(Common::SeekableReadStream &adventure) {
}
void HiRes4Engine_LNG::runIntro() {
- Common::ScopedPtr<Files_AppleDOS> files(new Files_AppleDOS());
+ Common::ScopedPtr<Common::Files_AppleDOS> files(new Common::Files_AppleDOS());
files->open(getDiskImageName(0));
while (!shouldQuit()) {
- StreamPtr menu(files->createReadStream("MENU"));
+ Common::StreamPtr menu(files->createReadStream("MENU"));
const bool oldVersion = files->exists("MS2");
if (oldVersion) {
// Version 0.0
- StreamPtr ms2(files->createReadStream("MS2"));
+ Common::StreamPtr ms2(files->createReadStream("MS2"));
runIntroLogo(*ms2);
if (shouldQuit())
@@ -591,11 +591,11 @@ void HiRes4Engine_LNG::runIntro() {
return;
if (key == _display->asciiToNative('1')) {
- StreamPtr instructions(files->createReadStream("INSTRUCTIONS"));
+ Common::StreamPtr instructions(files->createReadStream("INSTRUCTIONS"));
runIntroInstructions(*instructions);
break;
} else if (key == _display->asciiToNative('2')) {
- StreamPtr adventure(files->createReadStream("THE ADVENTURE"));
+ Common::StreamPtr adventure(files->createReadStream("THE ADVENTURE"));
runIntroLoading(*adventure);
return;
}
@@ -622,12 +622,12 @@ private:
// AdlEngine_v2
void adjustDataBlockPtr(byte &track, byte §or, byte &offset, byte &size) const override;
- Common::SeekableReadStream *createReadStream(DiskImage *disk, byte track, byte sector, byte offset = 0, byte size = 0) const;
+ Common::SeekableReadStream *createReadStream(Common::DiskImage *disk, byte track, byte sector, byte offset = 0, byte size = 0) const;
void loadCommonData();
void insertDisk(byte diskNr);
void rebindDisk();
- DiskImage *_boot;
+ Common::DiskImage *_boot;
byte _curDisk;
};
@@ -640,14 +640,14 @@ HiRes4Engine_Atari::~HiRes4Engine_Atari() {
void HiRes4Engine_Atari::init() {
_graphics = new GraphicsMan_v2<Display_A2>(*static_cast<Display_A2 *>(_display));
- _boot = new DiskImage();
+ _boot = new Common::DiskImage();
if (!_boot->open(atariDisks[0]))
error("Failed to open disk image '%s'", atariDisks[0]);
insertDisk(1);
loadCommonData();
- StreamPtr stream(createReadStream(_boot, 0x06, 0x2));
+ Common::StreamPtr stream(createReadStream(_boot, 0x06, 0x2));
_strings.verbError = readStringAt(*stream, 0x4f);
_strings.nounError = readStringAt(*stream, 0x83);
_strings.enterCommand = readStringAt(*stream, 0xa6);
@@ -725,18 +725,18 @@ void HiRes4Engine_Atari::insertDisk(byte diskNr) {
_curDisk = diskNr;
delete _disk;
- _disk = new DiskImage();
+ _disk = new Common::DiskImage();
if (!_disk->open(atariDisks[diskNr]))
error("Failed to open disk image '%s'", atariDisks[diskNr]);
}
void HiRes4Engine_Atari::rebindDisk() {
- // As room.data is bound to the DiskImage, we need to rebind them here
+ // As room.data is bound to the Common::DiskImage, we need to rebind them here
// We cannot simply reload the rooms as that would reset their state
- // FIXME: Remove DataBlockPtr-DiskImage coupling?
+ // FIXME: Remove DataBlockPtr-Common::DiskImage coupling?
- StreamPtr stream(createReadStream(_boot, 0x03, 0x1, 0x0e, 9));
+ Common::StreamPtr stream(createReadStream(_boot, 0x03, 0x1, 0x0e, 9));
for (uint i = 0; i < IDI_HR4_NUM_ROOMS; ++i) {
stream->skip(7);
_state.rooms[i].data = readDataBlockPtr(*stream);
@@ -749,7 +749,7 @@ void HiRes4Engine_Atari::rebindDisk() {
void HiRes4Engine_Atari::loadCommonData() {
_messages.clear();
- StreamPtr stream(createReadStream(_boot, 0x0a, 0x4, 0x00, 3));
+ Common::StreamPtr stream(createReadStream(_boot, 0x0a, 0x4, 0x00, 3));
loadMessages(*stream, IDI_HR4_NUM_MESSAGES);
_pictures.clear();
@@ -764,7 +764,7 @@ void HiRes4Engine_Atari::loadCommonData() {
void HiRes4Engine_Atari::initGameState() {
_state.vars.resize(IDI_HR4_NUM_VARS);
- StreamPtr stream(createReadStream(_boot, 0x03, 0x1, 0x0e, 9));
+ Common::StreamPtr stream(createReadStream(_boot, 0x03, 0x1, 0x0e, 9));
loadRooms(*stream, IDI_HR4_NUM_ROOMS);
stream.reset(createReadStream(_boot, 0x02, 0xc, 0x00, 12));
@@ -774,7 +774,7 @@ void HiRes4Engine_Atari::initGameState() {
_display->moveCursorTo(Common::Point(0, 23));
}
-Common::SeekableReadStream *HiRes4Engine_Atari::createReadStream(DiskImage *disk, byte track, byte sector, byte offset, byte size) const {
+Common::SeekableReadStream *HiRes4Engine_Atari::createReadStream(Common::DiskImage *disk, byte track, byte sector, byte offset, byte size) const {
adjustDataBlockPtr(track, sector, offset, size);
return disk->createReadStream(track, sector, offset, size);
}
diff --git a/engines/adl/hires5.cpp b/engines/adl/hires5.cpp
index 6f60427ced2..8a14b0ab24f 100644
--- a/engines/adl/hires5.cpp
+++ b/engines/adl/hires5.cpp
@@ -243,7 +243,7 @@ void HiRes5Engine::runIntro() {
insertDisk(2);
- StreamPtr stream(_disk->createReadStream(0x10, 0x0, 0x00, 31));
+ Common::StreamPtr stream(_disk->createReadStream(0x10, 0x0, 0x00, 31));
display->setMode(Display::kModeGraphics);
display->loadFrameBuffer(*stream);
@@ -274,7 +274,7 @@ void HiRes5Engine::init() {
insertDisk(2);
- StreamPtr stream(_disk->createReadStream(0x5, 0x0, 0x02));
+ Common::StreamPtr stream(_disk->createReadStream(0x5, 0x0, 0x02));
loadRegionLocations(*stream, kRegions);
stream.reset(_disk->createReadStream(0xd, 0x2, 0x04));
@@ -331,7 +331,7 @@ void HiRes5Engine::initGameState() {
insertDisk(2);
- StreamPtr stream(_disk->createReadStream(0x5, 0x1, 0x00, 3));
+ Common::StreamPtr stream(_disk->createReadStream(0x5, 0x1, 0x00, 3));
loadItems(*stream);
// A combined total of 1213 rooms
diff --git a/engines/adl/hires6.cpp b/engines/adl/hires6.cpp
index 7c623e1e264..5fdb6ad8ed4 100644
--- a/engines/adl/hires6.cpp
+++ b/engines/adl/hires6.cpp
@@ -161,13 +161,13 @@ bool HiRes6Engine::canSaveGameStateCurrently(Common::U32String *msg) {
#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) {
+static Common::MemoryReadStream *loadSectors(Common::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));
+ Common::StreamPtr stream(disk->createReadStream(track, sector, 0, 0));
stream->read(p, BYTES_PER_SECTOR);
if (stream->err() || stream->eos())
@@ -195,7 +195,7 @@ void HiRes6Engine::runIntro() {
insertDisk(0);
- StreamPtr stream(loadSectors(_disk, 11, 1, 96));
+ Common::StreamPtr stream(loadSectors(_disk, 11, 1, 96));
display->setMode(Display::kModeGraphics);
display->loadFrameBuffer(*stream);
@@ -209,7 +209,7 @@ void HiRes6Engine::runIntro() {
display->loadFrameBuffer(*stream);
// Load copyright string from boot file
- Files_AppleDOS *files(new Files_AppleDOS());
+ Common::Files_AppleDOS *files(new Common::Files_AppleDOS());
if (!files->open(getDiskImageName(0)))
error("Failed to open disk volume 0");
@@ -232,7 +232,7 @@ void HiRes6Engine::init() {
insertDisk(0);
- StreamPtr stream(_disk->createReadStream(0x3, 0xf, 0x05));
+ Common::StreamPtr stream(_disk->createReadStream(0x3, 0xf, 0x05));
loadRegionLocations(*stream, kRegions);
stream.reset(_disk->createReadStream(0x5, 0xa, 0x07));
@@ -273,7 +273,7 @@ void HiRes6Engine::initGameState() {
insertDisk(0);
- StreamPtr stream(_disk->createReadStream(0x3, 0xe, 0x03));
+ Common::StreamPtr stream(_disk->createReadStream(0x3, 0xe, 0x03));
loadItems(*stream);
// A combined total of 91 rooms
Commit: f31eb92d7af8ed1a250a6bcfdbc4fcab6e293bc0
https://github.com/scummvm/scummvm/commit/f31eb92d7af8ed1a250a6bcfdbc4fcab6e293bc0
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2024-08-25T14:57:06+03:00
Commit Message:
COMMON: Add DiskImage enhancements
- open(FSNode)
- releaseStream()
- ".do" file extension recognized
- Parsing errors handled instead of calling error()
- Skip sector logging when it does nothing
Changed paths:
common/formats/disk_image.cpp
common/formats/disk_image.h
diff --git a/common/formats/disk_image.cpp b/common/formats/disk_image.cpp
index f3991c7f5aa..75e936a33f9 100644
--- a/common/formats/disk_image.cpp
+++ b/common/formats/disk_image.cpp
@@ -239,6 +239,10 @@ static bool decodeTrack(Common::SeekableReadStream &stream, uint trackLen, bool
}
static void printGoodSectors(Common::Array<bool> &goodSectors, uint sectorsPerTrack) {
+ if (gDebugLevel < 1) {
+ return;
+ }
+
if (Common::find(goodSectors.begin(), goodSectors.end(), false) != goodSectors.end()) {
debugN(1, "NIB: Bad/missing sectors:");
@@ -405,8 +409,11 @@ static Common::SeekableReadStream *readImage_WOZ(Common::File &f, bool dos33, ui
StreamPtr stream(readTrack_WOZ(f, track, version == 2));
if (stream) {
- if (!decodeTrack(*stream, stream->size(), dos33, diskImage, tracks, goodSectors))
- error("WOZ: error reading '%s'", f.getName());
+ if (!decodeTrack(*stream, stream->size(), dos33, diskImage, tracks, goodSectors)) {
+ warning("WOZ: error reading '%s'", f.getName());
+ free(diskImage);
+ return nullptr;
+ }
}
}
@@ -416,30 +423,46 @@ static Common::SeekableReadStream *readImage_WOZ(Common::File &f, bool dos33, ui
}
bool DiskImage::open(const Common::Path &filename) {
- Common::File *f = new Common::File;
-
+ Common::File *file = new Common::File();
debug(1, "Opening '%s'", filename.toString(Common::Path::kNativeSeparator).c_str());
-
- if (!f->open(filename)) {
+ if (file->open(filename)) {
+ if (open(filename.baseName(), file)) {
+ return true;
+ }
+ } else {
warning("Failed to open '%s'", filename.toString(Common::Path::kNativeSeparator).c_str());
- delete f;
- return false;
}
+ delete file;
+ return false;
+}
- Common::String lcName(filename.baseName());
- lcName.toLowercase();
+bool DiskImage::open(const Common::FSNode &node) {
+ Common::File *file = new Common::File();
+ debug(1, "Opening '%s'", node.getPath().toString(Common::Path::kNativeSeparator).c_str());
+ if (file->open(node)) {
+ if (open(node.getFileName(), file)) {
+ return true;
+ }
+ } else {
+ warning("Failed to open '%s'", node.getPath().toString(Common::Path::kNativeSeparator).c_str());
+ }
+ delete file;
+ return false;
+}
- if (lcName.hasSuffix(".dsk")) {
+bool DiskImage::open(const Common::String &name, Common::File *f) {
+ if (name.hasSuffixIgnoreCase(".dsk") ||
+ name.hasSuffixIgnoreCase(".do")) {
_tracks = 35;
_sectorsPerTrack = 16;
_bytesPerSector = 256;
_stream = f;
- } else if (lcName.hasSuffix(".d13")) {
+ } else if (name.hasSuffixIgnoreCase(".d13")) {
_tracks = 35;
_sectorsPerTrack = 13;
_bytesPerSector = 256;
_stream = f;
- } else if (lcName.hasSuffix(".nib")) {
+ } else if (name.hasSuffixIgnoreCase(".nib")) {
_tracks = 35;
if (detectDOS33(*f, kNibTrackLen))
@@ -450,8 +473,7 @@ bool DiskImage::open(const Common::Path &filename) {
_bytesPerSector = 256;
f->seek(0);
_stream = readImage_NIB(*f, _sectorsPerTrack == 16);
- delete f;
- } else if (lcName.hasSuffix(".woz")) {
+ } else if (name.hasSuffixIgnoreCase(".woz")) {
_tracks = 35;
_sectorsPerTrack = 13;
_bytesPerSector = 256;
@@ -468,14 +490,12 @@ bool DiskImage::open(const Common::Path &filename) {
warning("WOZ: failed to load bitstream for track 0 in '%s'", f->getName());
}
}
-
- delete f;
- } else if (lcName.hasSuffix(".xfd")) {
+ } else if (name.hasSuffixIgnoreCase(".xfd")) {
_tracks = 40;
_sectorsPerTrack = 18;
_bytesPerSector = 128;
_stream = f;
- } else if (lcName.hasSuffix(".img")) {
+ } else if (name.hasSuffixIgnoreCase(".img")) {
_tracks = 40;
_sectorsPerTrack = 8;
_bytesPerSector = 512;
@@ -485,12 +505,22 @@ bool DiskImage::open(const Common::Path &filename) {
int expectedSize = _tracks * _sectorsPerTrack * _bytesPerSector;
- if (!_stream)
+ if (_stream == nullptr) {
return false;
+ }
- if (_stream->size() != expectedSize)
- error("Unrecognized disk image '%s' of size %d bytes (expected %d bytes)", filename.toString(Common::Path::kNativeSeparator).c_str(), (int)_stream->size(), expectedSize);
+ if (_stream->size() != expectedSize) {
+ warning("Unrecognized disk image '%s' of size %d bytes (expected %d bytes)", f->getName(), (int)_stream->size(), expectedSize);
+ if (_stream != f) {
+ delete _stream;
+ _stream = nullptr;
+ }
+ return false;
+ };
+ if (_stream != f) {
+ delete f;
+ }
return true;
}
@@ -506,8 +536,11 @@ Common::SeekableReadStream *DiskImage::createReadStream(uint track, uint sector,
if (sectorLimit == 0)
sectorLimit = _sectorsPerTrack;
- if (sector < _firstSector || sector >= sectorLimit + _firstSector)
- error("Sector %u is out of bounds for %u-sector %u-based reading", sector, sectorLimit, _firstSector);
+ if (sector < _firstSector || sector >= sectorLimit + _firstSector) {
+ warning("Sector %u is out of bounds for %u-sector %u-based reading", sector, sectorLimit, _firstSector);
+ free(data);
+ return nullptr;
+ }
sector -= _firstSector;
@@ -518,8 +551,11 @@ Common::SeekableReadStream *DiskImage::createReadStream(uint track, uint sector,
if (bytesToRead - dataOffset < bytesRemInTrack)
bytesRemInTrack = bytesToRead - dataOffset;
- if (_stream->read(data + dataOffset, bytesRemInTrack) < bytesRemInTrack)
- error("Error reading disk image at track %d; sector %d", track, sector);
+ if (_stream->read(data + dataOffset, bytesRemInTrack) < bytesRemInTrack) {
+ warning("Error reading disk image at track %d; sector %d", track, sector);
+ free(data);
+ return nullptr;
+ }
++track;
@@ -532,6 +568,20 @@ Common::SeekableReadStream *DiskImage::createReadStream(uint track, uint sector,
return new Common::MemoryReadStream(data, bytesToRead, DisposeAfterUse::YES);
}
+Common::SeekableReadStream *DiskImage::releaseStream() {
+ Common::SeekableReadStream *stream = _stream;
+
+ // reset class
+ _stream = nullptr;
+ _tracks = 0;
+ _sectorsPerTrack = 0;
+ _bytesPerSector = 0;
+ _sectorLimit = 0;
+ _firstSector = 0;
+
+ return stream;
+}
+
int32 computeMD5(const Common::FSNode &node, Common::String &md5, uint32 md5Bytes) {
Common::File f;
diff --git a/common/formats/disk_image.h b/common/formats/disk_image.h
index ee7ee6d8477..d0a3805ab7a 100644
--- a/common/formats/disk_image.h
+++ b/common/formats/disk_image.h
@@ -86,8 +86,10 @@ public:
}
bool open(const Common::Path &filename);
+ bool open(const Common::FSNode &node);
const DataBlockPtr getDataBlock(uint track, uint sector, uint offset = 0, uint size = 0) const;
Common::SeekableReadStream *createReadStream(uint track, uint sector, uint offset = 0, uint size = 0, uint sectorsUsed = 0) const;
+ Common::SeekableReadStream *releaseStream();
void setSectorLimit(uint sectorLimit) { _sectorLimit = sectorLimit; } // Maximum number of sectors to read per track before stepping
uint getBytesPerSector() const { return _bytesPerSector; }
uint getSectorsPerTrack() const { return _sectorsPerTrack; }
@@ -117,6 +119,9 @@ protected:
Common::SeekableReadStream *_stream;
uint _tracks, _sectorsPerTrack, _bytesPerSector, _firstSector;
uint _sectorLimit;
+
+private:
+ bool open(const Common::String &name, Common::File *f);
};
// Data in plain files
Commit: 5a7555dfccff5b26ad0c0f8bc9508d426b36dff5
https://github.com/scummvm/scummvm/commit/5a7555dfccff5b26ad0c0f8bc9508d426b36dff5
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2024-08-25T14:57:06+03:00
Commit Message:
COMMON: Add BitArray::size()
Changed paths:
common/bitarray.h
diff --git a/common/bitarray.h b/common/bitarray.h
index bfb89823687..2e69322c78b 100644
--- a/common/bitarray.h
+++ b/common/bitarray.h
@@ -68,6 +68,9 @@ public:
return _bits[bit / 8] & (1 << (bit % 8));
}
+ uint size() const {
+ return _bitcount;
+ }
private:
uint _bitcount;
byte *_bits;
Commit: 23a9f4ed541e6e7db241c81f09dd636c7c8a5135
https://github.com/scummvm/scummvm/commit/23a9f4ed541e6e7db241c81f09dd636c7c8a5135
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2024-08-25T14:57:06+03:00
Commit Message:
COMMON: Add DiskImage lazy-decoding mode
Images can now be read without decoding all disk tracks.
This allows DiskImage to be used in detection code, where only
a little disk data is needed. Otherwise, formats such as WOZ
would cause noticeable delays due to decoding entire disks.
Changed paths:
common/formats/disk_image.cpp
common/formats/disk_image.h
diff --git a/common/formats/disk_image.cpp b/common/formats/disk_image.cpp
index 75e936a33f9..9d77213d633 100644
--- a/common/formats/disk_image.cpp
+++ b/common/formats/disk_image.cpp
@@ -106,7 +106,7 @@ static uint8 read44(byte *buffer, uint size, uint &pos) {
return ((ret << 1) | 1) & buffer[pos++ % size];
}
-static bool decodeTrack(Common::SeekableReadStream &stream, uint trackLen, bool dos33, byte *const diskImage, uint tracks, Common::Array<bool> &goodSectors) {
+static bool decodeTrack(Common::SeekableReadStream &stream, uint trackLen, bool dos33, byte *const diskImage, uint tracks, Common::BitArray &goodSectors) {
// starting at 0xaa, 64 is invalid (see below)
const byte c_5and3_lookup[] = { 64, 0, 64, 1, 2, 3, 64, 64, 64, 64, 64, 4, 5, 6, 64, 64, 7, 8, 64, 9, 10, 11, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 12, 13, 64, 64, 14, 15, 64, 16, 17, 18, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 19, 20, 64, 21, 22, 23, 64, 64, 64, 64, 64, 24, 25, 26, 64, 64, 27, 28, 64, 29, 30, 31 };
// starting at 0x96, 64 is invalid (see below)
@@ -231,60 +231,82 @@ static bool decodeTrack(Common::SeekableReadStream &stream, uint trackLen, bool
output[255] = (inbuffer[409] << 3) | (inbuffer[0] & 0x7);
}
- goodSectors[track * sectorsPerTrack + sector] = true;
+ goodSectors.set(track * sectorsPerTrack + sector);
}
free(buffer);
return true;
}
-static void printGoodSectors(Common::Array<bool> &goodSectors, uint sectorsPerTrack) {
+static void printGoodSectors(const Common::BitArray &goodSectors, uint sectorsPerTrack) {
if (gDebugLevel < 1) {
return;
}
- if (Common::find(goodSectors.begin(), goodSectors.end(), false) != goodSectors.end()) {
- debugN(1, "NIB: Bad/missing sectors:");
-
- for (uint i = 0; i < goodSectors.size(); ++i) {
- if (!goodSectors[i])
- debugN(1, " (%d, %d)", i / sectorsPerTrack, i % sectorsPerTrack);
+ bool foundBadSector = false;
+ for (uint i = 0; i < goodSectors.size(); ++i) {
+ if (!goodSectors.get(i)) {
+ if (!foundBadSector) {
+ debugN(1, "Bad/missing sectors:");
+ foundBadSector = true;
+ }
+ debugN(1, " (%d, %d)", i / sectorsPerTrack, i % sectorsPerTrack);
}
-
+ }
+ if (foundBadSector) {
debugN(1, "\n");
}
}
-static Common::SeekableReadStream *readImage_NIB(Common::File &f, bool dos33, uint tracks = 35) {
+static bool readImage_NIB(
+ const char *filename,
+ Common::SeekableReadStream &f,
+ byte *diskImage,
+ bool dos33,
+ uint startTrack = 0,
+ uint tracksToRead = 35) {
+
if (f.size() != 35 * kNibTrackLen) {
- warning("NIB: image '%s' has invalid size of %d bytes", f.getName(), (int)f.size());
- return nullptr;
+ warning("NIB: image '%s' has invalid size of %d bytes", filename, (int)f.size());
+ return false;
}
const uint sectorsPerTrack = (dos33 ? 16 : 13);
- const uint imageSize = tracks * sectorsPerTrack * 256;
- byte *const diskImage = (byte *)calloc(imageSize, 1);
- Common::Array<bool> goodSectors(tracks * sectorsPerTrack);
+ Common::BitArray goodSectors(35 * sectorsPerTrack);
- for (uint track = 0; track < tracks; ++track) {
- if (!decodeTrack(f, kNibTrackLen, dos33, diskImage, tracks, goodSectors)) {
- warning("NIB: error reading '%s'", f.getName());
- free(diskImage);
- return nullptr;
+ f.seek(startTrack * kNibTrackLen);
+ uint endTrack = startTrack + tracksToRead - 1;
+ for (uint track = startTrack; track <= endTrack; ++track) {
+ if (!decodeTrack(f, kNibTrackLen, dos33, diskImage, 35, goodSectors)) {
+ warning("NIB: error decoding track %d in '%s'", track, filename);
+ return false;
}
}
printGoodSectors(goodSectors, sectorsPerTrack);
+ return true;
+}
+
+static Common::SeekableReadStream *readImageToStream_NIB(Common::File &f, bool dos33, uint tracks = 35) {
+ const uint sectorsPerTrack = (dos33 ? 16 : 13);
+ const uint imageSize = tracks * sectorsPerTrack * 256;
+ byte *const diskImage = (byte *)calloc(imageSize, 1);
+
+ if (!readImage_NIB(f.getName(), f, diskImage, dos33, 0, tracks)) {
+ warning("NIB: error reading '%s'", f.getName());
+ free(diskImage);
+ return nullptr;
+ }
return new Common::MemoryReadStream(diskImage, imageSize, DisposeAfterUse::YES);
}
-static int getVersion_WOZ(Common::File &f) {
+static int getVersion_WOZ(const char *filename, Common::SeekableReadStream &f) {
f.seek(0);
const uint32 fileId = f.readUint32BE();
if (f.eos() || f.err()) {
- warning("WOZ: error reading '%s'", f.getName());
+ warning("WOZ: error reading '%s'", filename);
return 0;
}
@@ -293,16 +315,16 @@ static int getVersion_WOZ(Common::File &f) {
else if (fileId == MKTAG('W', 'O', 'Z', '2'))
return 2;
- warning("WOZ: unsupported ID '%s' found in '%s'", tag2str(fileId), f.getName());
+ warning("WOZ: unsupported ID '%s' found in '%s'", tag2str(fileId), filename);
return 0;
}
-static Common::SeekableReadStream *readTrack_WOZ(Common::File &f, uint track, bool woz2) {
+static Common::SeekableReadStream *readTrack_WOZ(const char *filename, Common::SeekableReadStream &f, uint track, bool woz2) {
f.seek(88 + track * 4);
const byte index = f.readByte();
if (index == 0xff) {
- warning("WOZ: track %u not found in '%s', skipping", track, f.getName());
+ warning("WOZ: track %u not found in '%s', skipping", track, filename);
return nullptr;
}
@@ -323,7 +345,7 @@ static Common::SeekableReadStream *readTrack_WOZ(Common::File &f, uint track, bo
f.seek(offset);
if (f.eos() || f.err() || byteSize == 0) {
- warning("WOZ: failed to read track %u in '%s', aborting", track, f.getName());
+ warning("WOZ: failed to read track %u in '%s', aborting", track, filename);
return nullptr;
}
@@ -331,14 +353,14 @@ static Common::SeekableReadStream *readTrack_WOZ(Common::File &f, uint track, bo
byte *outBuf = (byte *)malloc(byteSize);
uint32 outSize = 0;
if (!inBuf || !outBuf) {
- warning("WOZ: failed to create buffers of size %u for track %u in '%s'", byteSize, track, f.getName());
+ warning("WOZ: failed to create buffers of size %u for track %u in '%s'", byteSize, track, filename);
free(inBuf);
free(outBuf);
return nullptr;
}
if (f.read(inBuf, byteSize) < byteSize) {
- warning("WOZ: error reading track %u in '%s'", track, f.getName());
+ warning("WOZ: error reading track %u in '%s'", track, filename);
free(inBuf);
free(outBuf);
return nullptr;
@@ -360,7 +382,7 @@ static Common::SeekableReadStream *readTrack_WOZ(Common::File &f, uint track, bo
if (bitStream.pos() == bitSize) {
bitStream.rewind();
if (stop) {
- warning("WOZ: failed to find sync point for track %u in '%s'", track, f.getName());
+ warning("WOZ: failed to find sync point for track %u in '%s'", track, filename);
break;
}
stop = true;
@@ -383,10 +405,10 @@ static Common::SeekableReadStream *readTrack_WOZ(Common::File &f, uint track, bo
} while (bitsRead < bitSize);
if (nibble != 0)
- warning("WOZ: failed to sync track %u in '%s'", track, f.getName());
+ warning("WOZ: failed to sync track %u in '%s'", track, filename);
if (outSize == 0) {
- warning("WOZ: track %u in '%s' is empty", track, f.getName());
+ warning("WOZ: track %u in '%s' is empty", track, filename);
free(outBuf);
return nullptr;
}
@@ -394,30 +416,48 @@ static Common::SeekableReadStream *readTrack_WOZ(Common::File &f, uint track, bo
return new Common::MemoryReadStream(outBuf, outSize, DisposeAfterUse::YES);
}
-static Common::SeekableReadStream *readImage_WOZ(Common::File &f, bool dos33, uint tracks = 35) {
- int version = getVersion_WOZ(f);
+static bool readImage_WOZ(
+ const char *filename,
+ Common::SeekableReadStream &f,
+ byte *diskImage,
+ bool dos33,
+ uint startTrack = 0,
+ uint tracksToRead = 35) {
- if (version == 0)
- return nullptr;
+ int version = getVersion_WOZ(filename, f);
+ if (version == 0) {
+ return false;
+ }
const uint sectorsPerTrack = (dos33 ? 16 : 13);
- const uint imageSize = tracks * sectorsPerTrack * 256;
- byte *const diskImage = (byte *)calloc(imageSize, 1);
- Common::Array<bool> goodSectors(tracks * sectorsPerTrack);
+ Common::BitArray goodSectors(35 * sectorsPerTrack);
- for (uint track = 0; track < tracks; ++track) {
- StreamPtr stream(readTrack_WOZ(f, track, version == 2));
+ uint endTrack = startTrack + tracksToRead - 1;
+ for (uint track = startTrack; track <= endTrack; ++track) {
+ StreamPtr stream(readTrack_WOZ(filename, f, track, version == 2));
if (stream) {
- if (!decodeTrack(*stream, stream->size(), dos33, diskImage, tracks, goodSectors)) {
- warning("WOZ: error reading '%s'", f.getName());
- free(diskImage);
- return nullptr;
+ if (!decodeTrack(*stream, stream->size(), dos33, diskImage, 35, goodSectors)) {
+ warning("WOZ: error decoding track %d in '%s'", track, filename);
+ return false;
}
}
}
printGoodSectors(goodSectors, sectorsPerTrack);
+ return true;
+}
+
+static Common::SeekableReadStream *readImageToStream_WOZ(Common::File &f, bool dos33, uint tracks = 35) {
+ const uint sectorsPerTrack = (dos33 ? 16 : 13);
+ const uint imageSize = tracks * sectorsPerTrack * 256;
+ byte *const diskImage = (byte *)calloc(imageSize, 1);
+
+ if (!readImage_WOZ(f.getName(), f, diskImage, dos33, 0, tracks)) {
+ warning("WOZ: error reading '%s'", f.getName());
+ free(diskImage);
+ return nullptr;
+ }
return new Common::MemoryReadStream(diskImage, imageSize, DisposeAfterUse::YES);
}
@@ -456,12 +496,12 @@ bool DiskImage::open(const Common::String &name, Common::File *f) {
_tracks = 35;
_sectorsPerTrack = 16;
_bytesPerSector = 256;
- _stream = f;
+ _inputStream = f;
} else if (name.hasSuffixIgnoreCase(".d13")) {
_tracks = 35;
_sectorsPerTrack = 13;
_bytesPerSector = 256;
- _stream = f;
+ _inputStream = f;
} else if (name.hasSuffixIgnoreCase(".nib")) {
_tracks = 35;
@@ -471,56 +511,83 @@ bool DiskImage::open(const Common::String &name, Common::File *f) {
_sectorsPerTrack = 13;
_bytesPerSector = 256;
- f->seek(0);
- _stream = readImage_NIB(*f, _sectorsPerTrack == 16);
+ if (_lazyDecoding) {
+ // store the file stream and create an empty decode stream.
+ // tracks will be decoded into the decode stream as they're read.
+ uint32 imageSize = _tracks * _sectorsPerTrack * _bytesPerSector;
+ _inputStream = f;
+ _decodeBuffer = (byte *)calloc(imageSize, 1);
+ _decodeStream = new Common::MemoryReadStream(_decodeBuffer, imageSize, DisposeAfterUse::YES);
+ _decodedTracks.set_size(_tracks);
+ _encoding = DiskImageEncodingNib;
+ } else {
+ // decode the entire image
+ _inputStream = readImageToStream_NIB(*f, (_sectorsPerTrack == 16));
+ }
} else if (name.hasSuffixIgnoreCase(".woz")) {
_tracks = 35;
_sectorsPerTrack = 13;
_bytesPerSector = 256;
- int version = getVersion_WOZ(*f);
+ int version = getVersion_WOZ(name.c_str(), *f);
if (version > 0) {
- StreamPtr bitStream(readTrack_WOZ(*f, 0, version == 2));
+ StreamPtr bitStream(readTrack_WOZ(name.c_str(), *f, 0, version == 2));
if (bitStream) {
if (detectDOS33(*bitStream, bitStream->size()))
_sectorsPerTrack = 16;
- _stream = readImage_WOZ(*f, _sectorsPerTrack == 16);
+
+ if (_lazyDecoding) {
+ // store the file stream and create an empty decode stream.
+ // tracks will be decoded into the decode stream as they're read.
+ uint32 imageSize = _tracks * _sectorsPerTrack * _bytesPerSector;
+ _inputStream = f;
+ _decodeBuffer = (byte *)calloc(imageSize, 1);
+ _decodeStream = new Common::MemoryReadStream(_decodeBuffer, imageSize, DisposeAfterUse::YES);
+ _decodedTracks.set_size(_tracks);
+ _encoding = DiskImageEncodingWoz;
+ } else {
+ // decode the entire image
+ _inputStream = readImageToStream_WOZ(*f, (_sectorsPerTrack == 16), _tracks);
+ }
} else {
- warning("WOZ: failed to load bitstream for track 0 in '%s'", f->getName());
+ warning("WOZ: failed to load bitstream for track 0 in '%s'", name.c_str());
}
}
} else if (name.hasSuffixIgnoreCase(".xfd")) {
_tracks = 40;
_sectorsPerTrack = 18;
_bytesPerSector = 128;
- _stream = f;
+ _inputStream = f;
} else if (name.hasSuffixIgnoreCase(".img")) {
_tracks = 40;
_sectorsPerTrack = 8;
_bytesPerSector = 512;
_firstSector = 1;
- _stream = f;
+ _inputStream = f;
}
-
- int expectedSize = _tracks * _sectorsPerTrack * _bytesPerSector;
-
- if (_stream == nullptr) {
+
+ if (_inputStream == nullptr) {
return false;
}
- if (_stream->size() != expectedSize) {
- warning("Unrecognized disk image '%s' of size %d bytes (expected %d bytes)", f->getName(), (int)_stream->size(), expectedSize);
- if (_stream != f) {
- delete _stream;
- _stream = nullptr;
+ int expectedSize = _tracks * _sectorsPerTrack * _bytesPerSector;
+ if (getDiskStream()->size() != expectedSize) {
+ warning("Unrecognized disk image '%s' of size %d bytes (expected %d bytes)", name.c_str(), (int)getDiskStream()->size(), expectedSize);
+ if (_inputStream != f) {
+ delete _inputStream;
+ _inputStream = nullptr;
}
+ delete _decodeStream;
+ _decodeStream = nullptr;
return false;
};
- if (_stream != f) {
+ if (_inputStream != f) {
delete f;
}
+
+ _name = name;
return true;
}
@@ -542,16 +609,32 @@ Common::SeekableReadStream *DiskImage::createReadStream(uint track, uint sector,
return nullptr;
}
- sector -= _firstSector;
+ // lazy decoding not supported for createReadStream(), because decoding
+ // tracks here requires removing way too many existing const keywords.
+ // if it's ever needed, enable this code and remove all those consts.
+#if 0
+ if (_decodeBuffer != nullptr) {
+ // lazy decoding
+ uint32 bytesPerTrack = _sectorsPerTrack * _bytesPerSector;
+ uint32 endTrack = track + (((sector - _firstSector) * _bytesPerSector + offset + bytesToRead - 1) / bytesPerTrack);
+ for (uint32 t = track; t <= endTrack; t++) {
+ if (!_decodedTracks.get(t)) {
+ decodeTrack(t);
+ }
+ }
+ }
+#endif
+ sector -= _firstSector;
+ Common::SeekableReadStream *stream = getDiskStream();
while (dataOffset < bytesToRead) {
uint bytesRemInTrack = (sectorLimit - 1 - sector) * _bytesPerSector + _bytesPerSector - offset;
- _stream->seek((track * _sectorsPerTrack + sector) * _bytesPerSector + offset);
+ stream->seek((track * _sectorsPerTrack + sector) * _bytesPerSector + offset);
if (bytesToRead - dataOffset < bytesRemInTrack)
bytesRemInTrack = bytesToRead - dataOffset;
- if (_stream->read(data + dataOffset, bytesRemInTrack) < bytesRemInTrack) {
+ if (stream->read(data + dataOffset, bytesRemInTrack) < bytesRemInTrack) {
warning("Error reading disk image at track %d; sector %d", track, sector);
free(data);
return nullptr;
@@ -569,10 +652,19 @@ Common::SeekableReadStream *DiskImage::createReadStream(uint track, uint sector,
}
Common::SeekableReadStream *DiskImage::releaseStream() {
- Common::SeekableReadStream *stream = _stream;
+ Common::SeekableReadStream *stream = getDiskStream();
// reset class
- _stream = nullptr;
+ if (stream != _inputStream) {
+ delete _inputStream;
+ }
+ _inputStream = nullptr;
+ _decodeStream = nullptr;
+ _decodeBuffer = nullptr;
+ _decodedTracks.clear();
+ _encoding = DiskImageEncodingNone;
+ _lazyDecoding = false;
+
_tracks = 0;
_sectorsPerTrack = 0;
_bytesPerSector = 0;
@@ -582,6 +674,42 @@ Common::SeekableReadStream *DiskImage::releaseStream() {
return stream;
}
+uint32 DiskImage::read(void *dataPtr, uint32 diskPosition, uint32 dataSize) {
+ Common::SeekableReadStream *stream;
+ if (_decodeBuffer != nullptr) {
+ // lazy decoding
+ uint32 bytesPerTrack = _sectorsPerTrack * _bytesPerSector;
+ uint32 startTrack = diskPosition / bytesPerTrack;
+ uint32 endTrack = (diskPosition + dataSize - 1) / bytesPerTrack;
+ for (uint32 t = startTrack; t <= endTrack; t++) {
+ if (!_decodedTracks.get(t)) {
+ decodeTrack(t);
+ }
+ }
+ stream = _decodeStream;
+ }
+ else {
+ stream = _inputStream;
+ }
+ stream->seek(diskPosition);
+ return stream->read(dataPtr, dataSize);
+}
+
+void DiskImage::decodeTrack(uint track) {
+ switch (_encoding) {
+ case DiskImageEncodingNib:
+ readImage_NIB(_name.c_str(), *_inputStream, _decodeBuffer, (_sectorsPerTrack == 16), track, 1);
+ break;
+ case DiskImageEncodingWoz:
+ readImage_WOZ(_name.c_str(), *_inputStream, _decodeBuffer, (_sectorsPerTrack == 16), track, 1);
+ break;
+ default:
+ return;
+ }
+
+ _decodedTracks.set(track);
+}
+
int32 computeMD5(const Common::FSNode &node, Common::String &md5, uint32 md5Bytes) {
Common::File f;
@@ -593,8 +721,7 @@ int32 computeMD5(const Common::FSNode &node, Common::String &md5, uint32 md5Byte
if (node.getName().matchString("*.nib", true) && f.size() == 35 * kNibTrackLen) {
bool isDOS33 = detectDOS33(f, kNibTrackLen);
- f.seek(0);
- Common::SeekableReadStream *stream = readImage_NIB(f, isDOS33, tracks);
+ Common::SeekableReadStream *stream = readImageToStream_NIB(f, isDOS33, tracks);
if (stream) {
md5 = Common::computeStreamMD5AsString(*stream, md5Bytes);
delete stream;
@@ -603,13 +730,13 @@ int32 computeMD5(const Common::FSNode &node, Common::String &md5, uint32 md5Byte
return -1;
} else if (node.getName().matchString("*.woz", true)) {
- int version = getVersion_WOZ(f);
+ int version = getVersion_WOZ(f.getName(), f);
if (version > 0) {
- StreamPtr nibbles(readTrack_WOZ(f, 0, version == 2));
+ StreamPtr nibbles(readTrack_WOZ(f.getName(), f, 0, version == 2));
if (nibbles) {
bool isDOS33 = detectDOS33(*nibbles, nibbles->size());
- StreamPtr stream(readImage_WOZ(f, isDOS33, tracks));
+ StreamPtr stream(readImageToStream_WOZ(f, isDOS33, tracks));
if (stream) {
md5 = Common::computeStreamMD5AsString(*stream, md5Bytes);
return 35 * (isDOS33 ? 16 : 13) * 256;
diff --git a/common/formats/disk_image.h b/common/formats/disk_image.h
index d0a3805ab7a..5c590cccaba 100644
--- a/common/formats/disk_image.h
+++ b/common/formats/disk_image.h
@@ -19,6 +19,7 @@
*
*/
+#include "common/bitarray.h"
#include "common/ptr.h"
#include "common/file.h"
#include "common/debug.h"
@@ -28,9 +29,44 @@
namespace Common {
+// Disk image parsers / decoders
+//
+// These classes handle floppy disk image files. Multiple formats are supported.
+// An image's file name extension determines its format. DiskImage::open selects
+// the format and expected disk size. Data can be read by track/sector/offset
+// or by the calculated stream position. Several file systems can also be read.
+//
+// Supported image formats:
+// .do Apple II disk sectors. 35 tracks, 16 sectors, no encoding.
+// .dsk Same as .do. Note that alternative sector orders are not handled.
+// Currently, if clients want to support images with a different sector
+// order, then they will have to detect and handle it themselves.
+// .d13 Apple II disk sectors. 35 tracks, 13 sectors, no encoding.
+// .nib Apple II disk nibbles. 35 tracks, 13 or 16 sectors, nibble-encoded.
+// .woz Apple II comprehensive disk bitstream. 35 tracks, 13 or 16 sectors.
+// This encoding format takes a noticeable amount of time to decode.
+// .img PC disk sectors. 40 tracks, 8 sectors, no encoding.
+// .xfd Atari disk sectors. 40 tracks, 18 sectors, no encoding.
+//
+// For encoded formats, the default behavior is to decode every track when
+// opening an image. Lazy decoding can also be enabled, causing tracks to be
+// decoded when they are first accessed. This can significantly speed up access
+// if the format is expensive to decode but only a little data is needed.
+//
+// This code was originally part of the ADL engine.
+
class SeekableReadStream;
class String;
+/**
+ * Disk image formats that require decoding to read their sectors
+ */
+enum DiskImageEncoding {
+ DiskImageEncodingNone,
+ DiskImageEncodingNib,
+ DiskImageEncodingWoz
+};
+
// Used for disk image detection
int32 computeMD5(const Common::FSNode &node, Common::String &md5, uint32 md5Bytes);
@@ -74,7 +110,11 @@ protected:
class DiskImage {
public:
DiskImage() :
- _stream(nullptr),
+ _inputStream(nullptr),
+ _decodeStream(nullptr),
+ _decodeBuffer(nullptr),
+ _encoding(DiskImageEncodingNone),
+ _lazyDecoding(false),
_tracks(0),
_sectorsPerTrack(0),
_bytesPerSector(0),
@@ -82,7 +122,8 @@ public:
_firstSector(0) { }
~DiskImage() {
- delete _stream;
+ delete _inputStream;
+ delete _decodeStream; // frees _decodeBuffer
}
bool open(const Common::Path &filename);
@@ -90,6 +131,9 @@ public:
const DataBlockPtr getDataBlock(uint track, uint sector, uint offset = 0, uint size = 0) const;
Common::SeekableReadStream *createReadStream(uint track, uint sector, uint offset = 0, uint size = 0, uint sectorsUsed = 0) const;
Common::SeekableReadStream *releaseStream();
+ Common::SeekableReadStream *getDiskStream() const { return _decodeBuffer ? _decodeStream : _inputStream; }
+ uint32 read(void *dataPtr, uint32 diskPosition, uint32 dataSize);
+ void setLazyDecoding(bool lazyDecoding) { _lazyDecoding = lazyDecoding; }
void setSectorLimit(uint sectorLimit) { _sectorLimit = sectorLimit; } // Maximum number of sectors to read per track before stepping
uint getBytesPerSector() const { return _bytesPerSector; }
uint getSectorsPerTrack() const { return _sectorsPerTrack; }
@@ -116,12 +160,20 @@ protected:
const DiskImage *_disk;
};
- Common::SeekableReadStream *_stream;
+ Common::String _name;
+ Common::SeekableReadStream *_inputStream;
+ Common::SeekableReadStream *_decodeStream;
+ byte *_decodeBuffer;
+ Common::BitArray _decodedTracks;
+ DiskImageEncoding _encoding;
+ bool _lazyDecoding;
+
uint _tracks, _sectorsPerTrack, _bytesPerSector, _firstSector;
uint _sectorLimit;
-
+
private:
bool open(const Common::String &name, Common::File *f);
+ void decodeTrack(uint track);
};
// Data in plain files
Commit: 2f2f3a59bdcd1401c450dd864ac835aa30646401
https://github.com/scummvm/scummvm/commit/2f2f3a59bdcd1401c450dd864ac835aa30646401
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2024-08-25T14:57:06+03:00
Commit Message:
AGI: Apple II NIB and WOZ disk image support
Changed paths:
A engines/agi/disk_image.cpp
engines/agi/agi.h
engines/agi/detection.cpp
engines/agi/disk_image.h
engines/agi/loader_a2.cpp
engines/agi/loader_v1.cpp
engines/agi/module.mk
engines/agi/objects.cpp
diff --git a/engines/agi/agi.h b/engines/agi/agi.h
index a39d5708ddc..347f4df8a73 100644
--- a/engines/agi/agi.h
+++ b/engines/agi/agi.h
@@ -587,6 +587,7 @@ protected:
class AgiLoader_A2 : public AgiLoader {
public:
AgiLoader_A2(AgiEngine *vm) : AgiLoader(vm) {}
+ ~AgiLoader_A2() override;
void init() override;
int loadDirs() override;
@@ -595,7 +596,7 @@ public:
int loadWords() override;
private:
- Common::Array<Common::String> _imageFiles;
+ Common::Array<Common::SeekableReadStream *> _disks;
Common::Array<AgiDiskVolume> _volumes;
AgiDir _logDir;
AgiDir _picDir;
@@ -610,7 +611,7 @@ private:
static bool readVolumeMap(Common::SeekableReadStream &stream, uint32 position, uint32 bufferLength, Common::Array<uint32> &volumeMap);
A2DirVersion detectDirVersion(Common::SeekableReadStream &stream);
- bool loadDir(AgiDir *dir, Common::File &disk, uint32 dirOffset, uint32 dirLength, A2DirVersion dirVersion);
+ bool loadDir(AgiDir *dir, Common::SeekableReadStream &disk, uint32 dirOffset, uint32 dirLength, A2DirVersion dirVersion);
};
class AgiLoader_v1 : public AgiLoader {
@@ -933,7 +934,7 @@ public:
// Objects
public:
int loadObjects(const char *fname);
- int loadObjects(Common::File &fp, int flen);
+ int loadObjects(Common::SeekableReadStream &fp, int flen);
const char *objectName(uint16 objectNr);
int objectGetLocation(uint16 objectNr);
void objectSetLocation(uint16 objectNr, int location);
diff --git a/engines/agi/detection.cpp b/engines/agi/detection.cpp
index bc337ce3806..9d5c0ba9317 100644
--- a/engines/agi/detection.cpp
+++ b/engines/agi/detection.cpp
@@ -348,6 +348,7 @@ void AgiMetaEngineDetection::getPotentialDiskImages(
if (f->_key.baseName().hasSuffixIgnoreCase(imageExtensions[i])) {
debug(3, "potential disk image: %s", f->_key.baseName().c_str());
imageFiles.push_back(f->_key);
+ break;
}
}
}
@@ -368,16 +369,8 @@ ADDetectedGame AgiMetaEngineDetection::detectPcDiskImageGame(const FileMap &allF
// find disk one by reading potential images until a match is found
for (const Common::Path &imageFile : imageFiles) {
- Common::SeekableReadStream *stream = allFiles[imageFile].createReadStream();
+ Common::SeekableReadStream *stream = openPCDiskImage(imageFile, allFiles[imageFile]);
if (stream == nullptr) {
- warning("unable to open disk image: %s", imageFile.baseName().c_str());
- continue;
- }
-
- // image file size must be 360k
- int64 fileSize = stream->size();
- if (fileSize != PC_DISK_SIZE) {
- delete stream;
continue;
}
@@ -409,7 +402,7 @@ ADDetectedGame AgiMetaEngineDetection::detectPcDiskImageGame(const FileMap &allF
FileProperties fileProps;
fileProps.md5 = file->md5;
fileProps.md5prop = kMD5Archive;
- fileProps.size = fileSize;
+ fileProps.size = PC_DISK_SIZE;
detectedGame.matchedFiles[imageFile] = fileProps;
return detectedGame;
}
@@ -483,20 +476,16 @@ ADDetectedGame AgiMetaEngineDetection::detectA2DiskImageGame(const FileMap &allF
// find disk one by reading potential images until a match is found
for (const Common::Path &imageFile : imageFiles) {
- Common::SeekableReadStream *stream = allFiles[imageFile].createReadStream();
+ // lazily-load disk image tracks as they're accessed.
+ // prevents decoding entire disks just to read a few dynamic sectors.
+ // this would create a significant delay for images in the .woz format.
+ const bool loadAllTracks = false;
+ Common::SeekableReadStream *stream = openA2DiskImage(imageFile, allFiles[imageFile], loadAllTracks);
if (stream == nullptr) {
warning("unable to open disk image: %s", imageFile.baseName().c_str());
continue;
}
- // image file size must be 140k.
- // this simple check will be removed when more image formats are supported.
- int64 fileSize = stream->size();
- if (fileSize != A2_DISK_SIZE) {
- delete stream;
- continue;
- }
-
// attempt to locate and hash logdir by reading initdir,
// and also known logdir locations for games without initdir.
Common::String logdirHashInitdir = getLogDirHashFromA2DiskImage(*stream);
@@ -532,7 +521,7 @@ ADDetectedGame AgiMetaEngineDetection::detectA2DiskImageGame(const FileMap &allF
FileProperties fileProps;
fileProps.md5 = file->md5;
fileProps.md5prop = kMD5Archive;
- fileProps.size = fileSize;
+ fileProps.size = A2_DISK_SIZE;
detectedGame.matchedFiles[imageFile] = fileProps;
return detectedGame;
}
diff --git a/engines/agi/disk_image.cpp b/engines/agi/disk_image.cpp
new file mode 100644
index 00000000000..e2aad80ba38
--- /dev/null
+++ b/engines/agi/disk_image.cpp
@@ -0,0 +1,123 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/formats/disk_image.h"
+#include "common/memstream.h"
+#include "common/path.h"
+#include "common/textconsole.h"
+
+#include "agi/disk_image.h"
+
+namespace Agi {
+
+/**
+ * DiskImageStream is a stream wrapper around Common::DiskImage.
+ *
+ * This allows DiskImage to lazily decode tracks as a stream is used.
+ * This is important for detection, because the .woz format is noticeably
+ * expensive to decode all tracks at once, and detection has to read
+ * INITDIR to discover which track to read LOGDIR from.
+ */
+class DiskImageStream : virtual public Common::SeekableReadStream {
+public:
+ DiskImageStream(Common::DiskImage *diskImage) : _diskImage(diskImage), _stream(_diskImage->getDiskStream()) {}
+
+ ~DiskImageStream() {
+ delete _diskImage;
+ }
+
+ uint32 read(void *dataPtr, uint32 dataSize) override {
+ return _diskImage->read(dataPtr, pos(), dataSize);
+ }
+
+ bool eos() const { return _stream->eos(); }
+ void clearErr() { _stream->clearErr(); }
+
+ int64 pos() const { return _stream->pos(); }
+ int64 size() const { return _stream->size(); }
+
+ bool seek(int64 offs, int whence = SEEK_SET) { return _stream->seek(offs, whence); }
+
+private:
+ Common::DiskImage *_diskImage;
+ Common::SeekableReadStream *_stream;
+};
+
+Common::SeekableReadStream *openPCDiskImage(const Common::Path &path, const Common::FSNode &node) {
+ Common::SeekableReadStream *stream = node.createReadStream();
+ if (stream == nullptr) {
+ warning("unable to open disk image: %s", path.baseName().c_str());
+ return nullptr;
+ }
+
+ // validate disk size
+ if (stream->size() != PC_DISK_SIZE) {
+ delete stream;
+ return nullptr;
+ }
+
+ return stream;
+}
+
+Common::SeekableReadStream *openA2DiskImage(const Common::Path &path, const Common::FSNode &node, bool loadAllTracks) {
+ Common::String name = path.baseName();
+
+ // Open the image with Common::DiskImage, unless the file extension is ".img".
+ // DiskImage expects ".img" to be a PC disk image, but it also gets used as
+ // an Apple II raw sector disk image, so just open it and and read it.
+ Common::SeekableReadStream *stream = nullptr;
+ if (name.hasSuffixIgnoreCase(".img")) {
+ stream = node.createReadStream();
+ } else {
+ if (loadAllTracks) {
+ // when loading all tracks, open with DiskImage and take the stream.
+ Common::DiskImage diskImage;
+ if (diskImage.open(node)) {
+ stream = diskImage.releaseStream();
+ }
+ } else {
+ // when loading tracks as they're used, create a DiskImage with lazy
+ // decoding and wrap it in a stream.
+ Common::DiskImage *diskImage = new Common::DiskImage();
+ diskImage->setLazyDecoding(true);
+ if (diskImage->open(node)) {
+ stream = new DiskImageStream(diskImage);
+ } else {
+ delete diskImage;
+ }
+ }
+ }
+
+ if (stream == nullptr) {
+ warning("unable to open disk image: %s", path.baseName().c_str());
+ return nullptr;
+ }
+
+ // validate disk size
+ if (stream->size() != A2_DISK_SIZE) {
+ delete stream;
+ return nullptr;
+ }
+
+ return stream;
+}
+
+} // End of namespace Agi
diff --git a/engines/agi/disk_image.h b/engines/agi/disk_image.h
index c8340857433..941c081d5cf 100644
--- a/engines/agi/disk_image.h
+++ b/engines/agi/disk_image.h
@@ -22,6 +22,11 @@
#ifndef AGI_DISK_IMAGE_H
#define AGI_DISK_IMAGE_H
+namespace Common {
+class SeekableReadStream;
+class Path;
+}
+
namespace Agi {
// PC disk image values and helpers for AgiLoader_v1 and AgiMetaEngineDetection
@@ -57,7 +62,7 @@ static const char * const pcDiskImageExtensions[] = { ".ima", ".img" };
// A2 disk image values and helpers for AgiLoader_A2 and AgiMetaEngineDetection
// Disk image detection requires that image files have a known extension
-static const char * const a2DiskImageExtensions[] = { ".do", ".dsk" };
+static const char * const a2DiskImageExtensions[] = { ".do", ".dsk", ".img", ".nib", ".woz" };
#define A2_DISK_SIZE (35 * 16 * 256)
#define A2_DISK_POSITION(t, s, o) ((((t * 16) + s) * 256) + o)
@@ -93,6 +98,9 @@ static const char * const a2DiskImageExtensions[] = { ".do", ".dsk" };
#define A2_BC_DISK_COUNT 5
#define A2_BC_VOLUME_COUNT 9
+Common::SeekableReadStream *openPCDiskImage(const Common::Path &path, const Common::FSNode &node);
+Common::SeekableReadStream *openA2DiskImage(const Common::Path &path, const Common::FSNode &node, bool loadAllTracks = true);
+
} // End of namespace Agi
#endif /* AGI_DISK_IMAGE_H */
diff --git a/engines/agi/loader_a2.cpp b/engines/agi/loader_a2.cpp
index c1437dd929d..9fb0fe5daeb 100644
--- a/engines/agi/loader_a2.cpp
+++ b/engines/agi/loader_a2.cpp
@@ -24,7 +24,9 @@
#include "agi/words.h"
#include "common/config-manager.h"
+#include "common/formats/disk_image.h"
#include "common/fs.h"
+#include "common/memstream.h"
namespace Agi {
@@ -33,9 +35,8 @@ namespace Agi {
// Floppy disks have two sides; each side is a disk with its own image file.
// All disk sides are 140k with 35 tracks and 16 sectors per track.
//
-// Currently, the only supported image format is "raw", with sectors in logical
-// order. Each image file must be exactly 143,360 bytes.
-// TODO: Add support for other image formats with ADL's disk iamge code.
+// Multiple disk image formats are supported; see Common::DiskImage. The file
+// extension determines the format. For example: .do, .dsk, .nib, .woz.
//
// The disks do not use a standard file system. Instead, file locations are
// stored in an INITDIR structure at a fixed location. KQ2 and BC don't have
@@ -59,6 +60,12 @@ namespace Agi {
typedef Common::HashMap<Common::Path, Common::FSNode, Common::Path::IgnoreCase_Hash, Common::Path::IgnoreCase_EqualTo> FileMap;
+AgiLoader_A2::~AgiLoader_A2() {
+ for (uint d = 0; d < _disks.size(); d++) {
+ delete _disks[d];
+ }
+}
+
void AgiLoader_A2::init() {
// get all files in game directory
Common::FSList allFiles;
@@ -77,6 +84,7 @@ void AgiLoader_A2::init() {
Common::Path path = file.getPath();
imageFiles.push_back(path);
fileMap[path] = file;
+ break;
}
}
}
@@ -90,28 +98,21 @@ void AgiLoader_A2::init() {
uint diskOneIndex;
for (diskOneIndex = 0; diskOneIndex < imageFiles.size(); diskOneIndex++) {
const Common::Path &imageFile = imageFiles[diskOneIndex];
- Common::SeekableReadStream *stream = fileMap[imageFile].createReadStream();
+ Common::SeekableReadStream *stream = openA2DiskImage(imageFile, fileMap[imageFile]);
if (stream == nullptr) {
warning("AgiLoader_A2: unable to open disk image: %s", imageFile.baseName().c_str());
continue;
}
- // image file size must be 140k
- int64 fileSize = stream->size();
- if (fileSize != A2_DISK_SIZE) {
- delete stream;
- continue;
- }
-
// read image as disk one
diskCount = readDiskOne(*stream, volumeMap);
- delete stream;
-
if (diskCount > 0) {
debugC(3, "AgiLoader_A2: disk one found: %s", imageFile.baseName().c_str());
- _imageFiles.resize(diskCount);
- _imageFiles[0] = imageFile.baseName();
+ _disks.resize(diskCount);
+ _disks[0] = stream;
break;
+ } else {
+ delete stream;
}
}
@@ -132,23 +133,16 @@ void AgiLoader_A2::init() {
uint imageFileIndex = (diskOneIndex + i) % imageFiles.size();
Common::Path &imageFile = imageFiles[imageFileIndex];
- Common::SeekableReadStream *stream = fileMap[imageFile].createReadStream();
+ Common::SeekableReadStream *stream = openA2DiskImage(imageFile, fileMap[imageFile]);
if (stream == nullptr) {
- warning("AgiLoader_A2: unable to open disk image: %s", imageFile.baseName().c_str());
- continue;
- }
-
- // image file size must be 140k
- int32 fileSize = stream->size();
- if (fileSize != A2_DISK_SIZE) {
- delete stream;
continue;
}
// check each disk
+ bool diskFound = false;
for (int d = 1; d < diskCount; d++) {
// has disk already been found?
- if (!_imageFiles[d].empty()) {
+ if (_disks[d] != nullptr) {
continue;
}
@@ -173,13 +167,16 @@ void AgiLoader_A2::init() {
}
if (match) {
- _imageFiles[d] = imageFile.baseName();
+ _disks[d] = stream;
disksFound++;
+ diskFound = true;
break;
}
}
- delete stream;
+ if (!diskFound) {
+ delete stream;
+ }
}
// populate _volumes with the locations of the ones we will use.
@@ -332,21 +329,18 @@ bool AgiLoader_A2::readVolumeMap(
int AgiLoader_A2::loadDirs() {
// if init didn't find disks then fail
- if (_imageFiles.empty()) {
+ if (_disks.empty()) {
return errFilesNotFound;
}
- for (uint32 i = 0; i < _imageFiles.size(); i++) {
- if (_imageFiles.empty()) {
- warning("AgiLoader_A2: disk %d not found", i);
+ for (uint d = 0; d < _disks.size(); d++) {
+ if (_disks[d] == nullptr) {
+ warning("AgiLoader_A2: disk %d not found", d);
return errFilesNotFound;
}
}
- // open disk one
- Common::File disk;
- if (!disk.open(Common::Path(_imageFiles[0]))) {
- return errBadFileOpen;
- }
+ // all dirs are on disk one
+ Common::SeekableReadStream &disk = *_disks[0];
// detect dir format
A2DirVersion dirVersion = detectDirVersion(disk);
@@ -388,7 +382,7 @@ A2DirVersion AgiLoader_A2::detectDirVersion(Common::SeekableReadStream &stream)
return A2DirVersionOld;
}
-bool AgiLoader_A2::loadDir(AgiDir *dir, Common::File &disk, uint32 dirOffset, uint32 dirLength, A2DirVersion dirVersion) {
+bool AgiLoader_A2::loadDir(AgiDir *dir, Common::SeekableReadStream &disk, uint32 dirOffset, uint32 dirLength, A2DirVersion dirVersion) {
// seek to directory on disk
disk.seek(dirOffset);
@@ -440,12 +434,8 @@ uint8 *AgiLoader_A2::loadVolumeResource(AgiDir *agid) {
return nullptr;
}
- Common::File disk;
int diskIndex = _volumes[agid->volume].disk;
- if (!disk.open(Common::Path(_imageFiles[diskIndex]))) {
- warning("AgiLoader_A2: unable to open disk image: %s", _imageFiles[diskIndex].c_str());
- return nullptr;
- }
+ Common::SeekableReadStream &disk = *_disks[diskIndex];
// seek to resource and validate header
int offset = _volumes[agid->volume].offset + agid->offset;
@@ -469,22 +459,22 @@ uint8 *AgiLoader_A2::loadVolumeResource(AgiDir *agid) {
}
int AgiLoader_A2::loadObjects() {
- Common::File disk;
- if (!disk.open(Common::Path(_imageFiles[0]))) {
- return errBadFileOpen;
+ if (_disks.empty()) {
+ return errFilesNotFound;
}
+ Common::SeekableReadStream &disk = *_disks[0];
disk.seek(_objects.offset);
return _vm->loadObjects(disk, _objects.len);
}
int AgiLoader_A2::loadWords() {
- Common::File disk;
- if (!disk.open(Common::Path(_imageFiles[0]))) {
- return errBadFileOpen;
+ if (_disks.empty()) {
+ return errFilesNotFound;
}
// TODO: pass length and validate in parser
+ Common::SeekableReadStream &disk = *_disks[0];
disk.seek(_words.offset);
if (_vm->getVersion() < 0x2000) {
return _vm->_words->loadDictionary_v1(disk);
diff --git a/engines/agi/loader_v1.cpp b/engines/agi/loader_v1.cpp
index 66b563ad1c8..8291ac5ab8b 100644
--- a/engines/agi/loader_v1.cpp
+++ b/engines/agi/loader_v1.cpp
@@ -80,16 +80,8 @@ void AgiLoader_v1::init() {
uint diskOneIndex;
for (diskOneIndex = 0; diskOneIndex < imageFiles.size(); diskOneIndex++) {
const Common::Path &imageFile = imageFiles[diskOneIndex];
- Common::SeekableReadStream *stream = fileMap[imageFile].createReadStream();
+ Common::SeekableReadStream *stream = openPCDiskImage(imageFile, fileMap[imageFile]);
if (stream == nullptr) {
- warning("AgiLoader_v1: unable to open disk image: %s", imageFile.baseName().c_str());
- continue;
- }
-
- // image file size must be 360k
- int32 fileSize = stream->size();
- if (fileSize != PC_DISK_SIZE) {
- delete stream;
continue;
}
@@ -139,26 +131,17 @@ void AgiLoader_v1::init() {
uint diskTwoIndex = (diskOneIndex + i) % imageFiles.size();
Common::Path &imageFile = imageFiles[diskTwoIndex];
- Common::SeekableReadStream *stream = fileMap[imageFile].createReadStream();
+ Common::SeekableReadStream *stream = openPCDiskImage(imageFile, fileMap[imageFile]);
if (stream == nullptr) {
- warning("AgiLoader_v1: unable to open disk image: %s", imageFile.baseName().c_str());
- continue;
- }
-
- // image file size must be 360k
- int64 fileSize = stream->size();
- if (fileSize != PC_DISK_SIZE) {
- delete stream;
continue;
}
// read resource header
uint16 magic = stream->readUint16BE();
byte volume = stream->readByte();
- uint16 size = stream->readUint16LE();
delete stream;
- if (magic == 0x1234 && volume == 2 && 5 + size <= PC_DISK_SIZE) {
+ if (magic == 0x1234 && volume == 2) {
debugC(3, "AgiLoader_v1: disk two found: %s", imageFile.baseName().c_str());
_imageFiles.push_back(imageFile.baseName());
_volumes.push_back(AgiDiskVolume(_imageFiles.size() - 1, 0));
diff --git a/engines/agi/module.mk b/engines/agi/module.mk
index 8711a4a3f51..cf8269f2ed4 100644
--- a/engines/agi/module.mk
+++ b/engines/agi/module.mk
@@ -5,6 +5,7 @@ MODULE_OBJS := \
checks.o \
console.o \
cycle.o \
+ disk_image.o \
font.o \
global.o \
graphics.o \
@@ -57,3 +58,10 @@ DETECT_OBJS += $(MODULE)/detection.o
# This is unneeded by the engine module itself,
# so separate it completely.
DETECT_OBJS += $(MODULE)/wagparser.o
+
+# Skip building the following objects if a static
+# module is enabled, because it already has the contents.
+ifneq ($(ENABLE_AGI), STATIC_PLUGIN)
+# External dependencies for detection.
+DETECT_OBJS += $(MODULE)/disk_image.o
+endif
diff --git a/engines/agi/objects.cpp b/engines/agi/objects.cpp
index 5b1adb16d25..3ceea0627e0 100644
--- a/engines/agi/objects.cpp
+++ b/engines/agi/objects.cpp
@@ -95,16 +95,14 @@ int AgiEngine::loadObjects(const char *fname) {
* @param fp File pointer
* @param flen File length
*/
-int AgiEngine::loadObjects(Common::File &fp, int flen) {
+int AgiEngine::loadObjects(Common::SeekableReadStream &fp, int flen) {
uint8 *mem;
if ((mem = (uint8 *)calloc(1, flen + 32)) == nullptr) {
- fp.close();
return errNotEnoughMemory;
}
fp.read(mem, flen);
- fp.close();
decodeObjects(mem, flen);
free(mem);
Commit: 16d564d826211d64390c146e4ca4c62e5ace4dc6
https://github.com/scummvm/scummvm/commit/16d564d826211d64390c146e4ca4c62e5ace4dc6
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2024-08-25T14:57:06+03:00
Commit Message:
AGI: Map engine error codes to Common codes
Changed paths:
engines/agi/agi.cpp
diff --git a/engines/agi/agi.cpp b/engines/agi/agi.cpp
index d36e23b149c..5b3c0f5c05a 100644
--- a/engines/agi/agi.cpp
+++ b/engines/agi/agi.cpp
@@ -596,7 +596,12 @@ Common::Error AgiEngine::go() {
int ec = runGame();
- return (ec == errOK) ? Common::kNoError : Common::kUnknownError;
+ switch (ec) {
+ case errOK: return Common::kNoError;
+ case errFilesNotFound: return Common::kNoGameDataFoundError;
+ case errBadFileOpen: return Common::kReadingFailed;
+ default: return Common::kUnknownError;
+ }
}
void AgiEngine::syncSoundSettings() {
More information about the Scummvm-git-logs
mailing list