/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "common/stream.h" #include "common/substream.h" #include "common/memstream.h" #include "adl/disk.h" namespace Adl { const DataBlockPtr DiskImage_DSK::getDataBlock(uint track, uint sector, uint offset, uint size) const { return Common::SharedPtr(new DiskImage::DataBlock(this, track, sector, offset, size)); } Common::SeekableReadStream *DiskImage_DSK::createReadStream(uint track, uint sector, uint offset, uint size) const { _f->seek((track * _sectorsPerTrack + sector) * _bytesPerSector + offset); Common::SeekableReadStream *stream = _f->readStream(size * _bytesPerSector + _bytesPerSector - offset); if (_f->eos() || _f->err()) error("Error reading disk image"); return stream; } bool DiskImage_DSK::open(const Common::String &filename) { assert(!_f->isOpen()); if (!_f->open(filename)) return false; uint filesize = _f->size(); switch (filesize) { case 143360: _tracks = 35; _sectorsPerTrack = 16; _bytesPerSector = 256; break; default: warning("Unrecognized disk image '%s' of size %d bytes", filename.c_str(), filesize); return false; } return true; } const DataBlockPtr DiskImage_NIB::getDataBlock(uint track, uint sector, uint offset, uint size) const { return Common::SharedPtr(new DiskImage::DataBlock(this, track, sector, offset, size)); } Common::SeekableReadStream *DiskImage_NIB::createReadStream(uint track, uint sector, uint offset, uint size) const { _memStream->seek((track * _sectorsPerTrack + sector) * _bytesPerSector + offset); Common::SeekableReadStream *stream = _memStream->readStream(size * _bytesPerSector + _bytesPerSector - offset); if (_memStream->eos() || _memStream->err()) error("Error reading NIB image"); return stream; } // 4-and-4 encoding (odd-even) static uint8 read44(Common::SeekableReadStream *f) { // 1s in the other fields, so we can just AND uint8 ret = f->readByte(); return ((ret << 1) | 1) & f->readByte(); } bool DiskImage_NIB::open(const Common::String &filename) { assert(!_f->isOpen()); if (!_f->open(filename)) return false; uint filesize = _f->size(); switch (filesize) { case 232960: _tracks = 35; _sectorsPerTrack = 13; _bytesPerSector = 256; break; default: error("Unrecognized NIB image '%s' of size %d bytes", filename.c_str(), filesize); } // starting at 0xaa, 32 is invalid (see below) const byte c_5and3_lookup[] = { 32, 0, 32, 1, 2, 3, 32, 32, 32, 32, 32, 4, 5, 6, 32, 32, 7, 8, 32, 9, 10, 11, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 12, 13, 32, 32, 14, 15, 32, 16, 17, 18, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 19, 20, 32, 21, 22, 23, 32, 32, 32, 32, 32, 24, 25, 26, 32, 32, 27, 28, 32, 29, 30, 31 }; uint32 diskSize = _tracks * _sectorsPerTrack * _bytesPerSector; byte *diskImage = (byte *)calloc(diskSize, 1); _memStream = new Common::MemoryReadStream(diskImage, diskSize, DisposeAfterUse::YES); bool sawAddress = false; uint8 volNo, track, sector; while (_f->pos() < _f->size()) { // Read until we find two sync bytes. if (_f->readByte() != 0xd5 || _f->readByte() != 0xaa) continue; byte prologue = _f->readByte(); if (sawAddress && prologue == 0xb5) { warning("NIB: data for %02x/%02x/%02x missing", volNo, track, sector); sawAddress = false; } if (!sawAddress) { sawAddress = true; // We should always find the address field first. if (prologue != 0xb5) { // Accept a DOS 3.3(?) header at the start. if (_f->pos() == 3 || prologue == 0x96) { // But skip it. _f->skip(20); sawAddress = false; continue; } else { error("unknown NIB field prologue %02x", prologue); } } volNo = read44(_f); track = read44(_f); sector = read44(_f); uint8 checksum = read44(_f); if ((volNo ^ track ^ sector) != checksum) error("invalid NIB checksum"); // FIXME: This is a hires0/hires2-specific hack. if (sector == 1) sector = 2; else if (sector == 2) sector = 1; // Epilogue is de/aa plus a gap, but we don't care. } else { sawAddress = false; // We should always find the data field after an address field. // TODO: we ignore volNo? byte *output = diskImage + (track * _sectorsPerTrack + sector) * _bytesPerSector; // 5-and-3 uses 410 on-disk bytes, decoding to just over 256 bytes byte inbuffer[410]; _f->read(inbuffer, 410); bool truncated = false; byte oldVal = 0; for (uint n = 0; n < 410; ++n) { // expand assert(inbuffer[n] >= 0xaa); // corrupt file (TODO: assert?) if (inbuffer[n] == 0xd5) { // Early end of block. truncated = true; _f->seek(-(410 - n), SEEK_CUR); warning("NIB: early end of block @ 0x%x (%x, %x)", _f->pos(), track, sector); break; } byte val = c_5and3_lookup[inbuffer[n] - 0xaa]; if (val == 0x20) { // Badly-encoded nibbles, stop trying to decode here. truncated = true; warning("NIB: bad nibble %02x @ 0x%x (%x, %x)", inbuffer[n], _f->pos(), track, sector); _f->seek(-(410 - n), SEEK_CUR); break; } // undo checksum oldVal = val ^ oldVal; inbuffer[n] = oldVal; } if (!truncated) { byte checksum = _f->readByte(); if (checksum < 0xaa || oldVal != c_5and3_lookup[checksum - 0xaa]) warning("NIB: checksum mismatch @ (%x, %x)", track, sector); } // 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); } } return true; } const DataBlockPtr Files_Plain::getDataBlock(const Common::String &filename, uint offset) const { return Common::SharedPtr(new Files::DataBlock(this, filename, offset)); } Common::SeekableReadStream *Files_Plain::createReadStream(const Common::String &filename, uint offset) const { Common::File *f(new Common::File()); if (!f->open(filename)) error("Failed to open '%s'", filename.c_str()); if (offset == 0) return f; else return new Common::SeekableSubReadStream(f, offset, f->size(), DisposeAfterUse::YES); } Files_DOS33::~Files_DOS33() { delete _disk; } Files_DOS33::Files_DOS33() : _disk(nullptr) { } void Files_DOS33::readSectorList(TrackSector start, Common::Array &list) { TrackSector index = start; while (index.track != 0) { Common::ScopedPtr 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_DOS33::readVTOC() { Common::ScopedPtr stream(_disk->createReadStream(0x11, 0x00)); stream->readByte(); byte track = stream->readByte(); 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(); if (sectorList.track != 0) { readSectorList(sectorList, entry.sectors); _toc[name] = entry; } } } } const DataBlockPtr Files_DOS33::getDataBlock(const Common::String &filename, uint offset) const { return Common::SharedPtr(new Files::DataBlock(this, filename, offset)); } Common::SeekableReadStream *Files_DOS33::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 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_DOS33::createReadStreamBinary(const TOCEntry &entry) const { byte *buf = (byte *)malloc(entry.sectors.size() * kSectorSize); Common::ScopedPtr 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_DOS33::createReadStream(const Common::String &filename, uint offset) const { if (!_toc.contains(filename)) error("Failed to locate '%s'", filename.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_DOS33::open(const Common::String &filename) { _disk = new DiskImage_DSK(); if (!_disk->open(filename)) return false; readVTOC(); return true; } } // End of namespace Adl