scummvm/engines/glk/scott/disk_image.cpp

811 lines
19 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/*
* https://paradroid.automac.se/diskimage/
* Copyright (c) 2003-2006, Per Olofsson
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer. Redistributions in
* binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or
* other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "common/scummsys.h"
#include "common/str.h"
#include "common/textconsole.h"
#include "glk/scott/disk_image.h"
namespace Glk {
namespace Scott {
/* dos errors as used by the DOS internally (and as saves in the error info) */
struct DosError {
signed int _number; /* dos error number */
signed int _errnum; /* reported error number */
const char *_string; /* description */
};
DosError g_dosError[] = {
/* non-errors */
{0x01, 0, "ok"},
/* errors */
{0x02, 20, "Header descriptor byte not found (Seek)"},
/* { 0x02, 20, "Header block not found (Seek)" }, */
{0x03, 21, "No SYNC sequence found (Seek)"},
{0x04, 22, "Data descriptor byte not found (Read)"},
{0x05, 23, "Checksum error in data block (Read)"},
{0x06, 24, "Write verify (Write)"},
{0x07, 25, "Write verify error (Write)"},
{0x08, 26, "Write protect on (Write)"},
{0x09, 27, "Checksum error in header block (Seek)"},
{0x0A, 28, "Write error (Write)"},
{0x0B, 29, "Disk sector ID mismatch (Seek)"},
{0x0F, 74, "Drive Not Ready (Read)"},
{-1, -1, nullptr}
};
int setStatus(DiskImage *di, int status, int track, int sector) {
di->_status = status;
di->_statusts._track = track;
di->_statusts._sector = sector;
return status;
}
/* check if given track/sector is within valid range */
int diTsIsValid(ImageType type, TrackSector ts) {
if ((ts._track < 1) || (ts._track > diTracks(type))) {
return 0; /* track out of range */
}
if (ts._sector > (diSectorsPerTrack(type, ts._track) - 1)) {
return 0; /* sector out of range */
}
return 1;
}
int matchPattern(byte *rawPattern, byte *rawname) {
int i;
for (i = 0; i < 16; ++i) {
if (rawPattern[i] == '*') {
return 1;
}
if (rawname[i] == 0xa0) {
if (rawPattern[i] == 0xa0) {
return 1;
} else {
return 0;
}
} else {
if (rawPattern[i] == '?' || rawPattern[i] == rawname[i]) {
} else {
return 0;
}
}
}
return 1;
}
/* return a pointer to the next block in the chain */
TrackSector nextTsInChain(DiskImage *di, TrackSector ts) {
byte *p;
TrackSector newTs;
p = diGetTsAddr(di, ts);
newTs._track = p[0];
newTs._sector = p[1];
return newTs;
}
RawDirEntry *findFileEntry(DiskImage *di, byte *rawPattern, int type) {
byte *buffer;
TrackSector ts;
RawDirEntry *rde;
int offset;
ts = diGetDirTs(di);
while (ts._track) {
buffer = diGetTsAddr(di, ts);
for (offset = 0; offset < 256; offset += 32) {
rde = (RawDirEntry *)(buffer + offset);
if (matchPattern(rawPattern, rde->_rawname)) {
return rde;
}
}
/* todo: add sanity checking */
ts = nextTsInChain(di, ts);
}
return nullptr;
}
/* allocate next available directory block */
TrackSector allocNextDirTs(DiskImage *di) {
byte *p;
int spt;
TrackSector ts, lastTs;
if (diTrackBlocksFree(di, di->_bam._track)) {
lastTs._track = di->_bam._track;
lastTs._sector = 0;
if ((di->_type == D64) || (di->_type == D71)) {
ts._track = 18;
ts._sector = 1;
} else {
ts = nextTsInChain(di, lastTs);
}
while (ts._track) {
lastTs = ts;
ts = nextTsInChain(di, ts);
}
ts._track = lastTs._track;
ts._sector = lastTs._sector + 3;
spt = diSectorsPerTrack(di->_type, ts._track);
for (;; ts._sector = (ts._sector + 1) % spt) {
if (diIsTsFree(di, ts)) {
diAllocTs(di, ts);
p = diGetTsAddr(di, lastTs);
p[0] = ts._track;
p[1] = ts._sector;
p = diGetTsAddr(di, ts);
memset(p, 0, 256);
p[1] = 0xff;
di->_modified = 1;
return ts;
}
}
} else {
ts._track = 0;
ts._sector = 0;
return ts;
}
}
RawDirEntry *allocFileEntry(DiskImage *di, byte *rawName, int type) {
byte *buffer;
TrackSector ts;
RawDirEntry *rde;
int offset;
/* check if file already exists */
ts = nextTsInChain(di, di->_bam);
while (ts._track) {
buffer = diGetTsAddr(di, ts);
for (offset = 0; offset < 256; offset += 32) {
rde = (RawDirEntry *)(buffer + offset);
if (rde->_type) {
if (scumm_strnicmp((char *)rawName, (char *)rde->_rawname, 16) == 0) {
setStatus(di, 63, 0, 0);
return nullptr;
}
}
}
ts = nextTsInChain(di, ts);
}
/* allocate empty slot */
ts = nextTsInChain(di, di->_bam);
while (ts._track) {
buffer = diGetTsAddr(di, ts);
for (offset = 0; offset < 256; offset += 32) {
rde = (RawDirEntry *)(buffer + offset);
if (rde->_type == 0) {
memset((byte *)rde + 2, 0, 30);
memcpy(rde->_rawname, rawName, 16);
rde->_type = type;
di->_modified = 1;
return rde;
}
}
ts = nextTsInChain(di, ts);
}
/* allocate new dir block */
ts = allocNextDirTs(di);
if (ts._track) {
rde = (RawDirEntry *)diGetTsAddr(di, ts);
memset((byte *)rde + 2, 0, 30);
memcpy(rde->_rawname, rawName, 16);
rde->_type = type;
di->_modified = 1;
return rde;
} else {
setStatus(di, 72, 0, 0);
return nullptr;
}
}
ImageFile *diOpen(DiskImage *di, byte *rawname, byte type, const char *mode) {
ImageFile *imgFile;
RawDirEntry *rde;
byte *p;
setStatus(di, 255, 0, 0);
if (scumm_stricmp("rb", mode) == 0) {
if ((imgFile = new ImageFile) == nullptr) {
return nullptr;
}
memset(imgFile->_visited, 0, sizeof(imgFile->_visited));
if (scumm_stricmp("$", (char *)rawname) == 0) {
imgFile->_mode = 'r';
imgFile->_ts = di->_dir;
p = diGetTsAddr(di, di->_dir);
imgFile->_buffer = p + 2;
imgFile->_nextts = diGetDirTs(di);
imgFile->_buflen = 254;
if (!diTsIsValid(di->_type, imgFile->_nextts)) {
setStatus(di, 66, imgFile->_nextts._track, imgFile->_nextts._sector);
delete imgFile;
return nullptr;
}
rde = nullptr;
} else {
if ((rde = findFileEntry(di, rawname, type)) == nullptr) {
setStatus(di, 62, 0, 0);
delete imgFile;
return nullptr;
}
imgFile->_mode = 'r';
imgFile->_ts = rde->_startts;
if (!diTsIsValid(di->_type, imgFile->_ts)) {
setStatus(di, 66, imgFile->_ts._track, imgFile->_ts._sector);
delete imgFile;
return nullptr;
}
p = diGetTsAddr(di, rde->_startts);
imgFile->_buffer = p + 2;
imgFile->_nextts._track = p[0];
imgFile->_nextts._sector = p[1];
if (imgFile->_nextts._track == 0) {
if (imgFile->_nextts._sector != 0) {
imgFile->_buflen = imgFile->_nextts._sector - 1;
} else {
imgFile->_buflen = 254;
}
} else {
if (!diTsIsValid(di->_type, imgFile->_nextts)) {
setStatus(di, 66, imgFile->_nextts._track, imgFile->_nextts._sector);
delete imgFile;
return nullptr;
}
imgFile->_buflen = 254;
}
}
} else if (strcmp("wb", mode) == 0) {
if ((rde = allocFileEntry(di, rawname, type)) == nullptr) {
return nullptr;
}
if ((imgFile = new ImageFile) == nullptr) {
return nullptr;
}
imgFile->_mode = 'w';
imgFile->_ts._track = 0;
imgFile->_ts._sector = 0;
if ((imgFile->_buffer = new byte[254]) == nullptr) {
delete imgFile;
return nullptr;
}
imgFile->_buflen = 254;
di->_modified = 1;
} else {
return nullptr;
}
imgFile->_diskimage = di;
imgFile->_rawdirentry = rde;
imgFile->_position = 0;
imgFile->_bufptr = 0;
++(di->_openfiles);
setStatus(di, 0, 0, 0);
return imgFile;
}
int diRead(ImageFile *imgFile, byte *buffer, int len) {
byte *p;
int bytesLeft;
int counter = 0;
int err;
while (len) {
bytesLeft = imgFile->_buflen - imgFile->_bufptr;
err = diGetTsErr(imgFile->_diskimage, imgFile->_ts);
if (err) {
setStatus(imgFile->_diskimage, err, imgFile->_ts._track, imgFile->_ts._sector);
return counter;
}
if (bytesLeft == 0) {
if (imgFile->_nextts._track == 0) {
return counter;
}
if (((imgFile->_diskimage->_type == D64) || (imgFile->_diskimage->_type == D71)) && imgFile->_ts._track == 18 && imgFile->_ts._sector == 0) {
imgFile->_ts._track = 18;
imgFile->_ts._sector = 1;
} else {
imgFile->_ts = nextTsInChain(imgFile->_diskimage, imgFile->_ts);
}
if (imgFile->_ts._track == 0) {
return counter;
}
/* check for cyclic files */
if (imgFile->_visited[imgFile->_ts._track][imgFile->_ts._sector]) {
/* return 52, file too long error */
setStatus(imgFile->_diskimage, 52, imgFile->_ts._track, imgFile->_ts._sector);
} else {
imgFile->_visited[imgFile->_ts._track][imgFile->_ts._sector] = 1;
}
err = diGetTsErr(imgFile->_diskimage, imgFile->_ts);
if (err) {
setStatus(imgFile->_diskimage, err, imgFile->_ts._track, imgFile->_ts._sector);
return counter;
}
p = diGetTsAddr(imgFile->_diskimage, imgFile->_ts);
imgFile->_buffer = p + 2;
imgFile->_nextts._track = p[0];
imgFile->_nextts._sector = p[1];
if (imgFile->_nextts._track == 0) {
if (imgFile->_nextts._sector == 0) {
/* fixme, something is wrong if this happens, should be a proper error */
imgFile->_buflen = 0;
setStatus(imgFile->_diskimage, -1, imgFile->_ts._track, imgFile->_ts._sector);
} else {
imgFile->_buflen = imgFile->_nextts._sector - 1;
}
} else {
if (!diTsIsValid(imgFile->_diskimage->_type, imgFile->_nextts)) {
setStatus(imgFile->_diskimage, 66, imgFile->_nextts._track, imgFile->_nextts._sector);
return counter;
}
imgFile->_buflen = 254;
}
imgFile->_bufptr = 0;
} else {
if (len >= bytesLeft) {
while (bytesLeft) {
*buffer++ = imgFile->_buffer[imgFile->_bufptr++];
--len;
--bytesLeft;
++counter;
++(imgFile->_position);
}
} else {
while (len) {
*buffer++ = imgFile->_buffer[imgFile->_bufptr++];
--len;
--bytesLeft;
++counter;
++(imgFile->_position);
}
}
}
}
return counter;
}
// get a pointer to block data
byte *diGetTsAddr(DiskImage *di, TrackSector ts) {
return di->_image + diGetBlockNum(di->_type, ts) * 256;
}
/* get error info for a sector */
int getTsDosErr(DiskImage *di, TrackSector ts) {
// return 1;
if (di->_errinfo == nullptr) {
return 1; /* return OK if image has no error info */
}
return di->_errinfo[diGetBlockNum(di->_type, ts)];
}
int diGetTsErr(DiskImage *di, TrackSector ts) {
int errnum;
DosError *err = g_dosError;
errnum = getTsDosErr(di, ts);
while (err->_number >= 0) {
if (errnum == err->_number) {
return err->_errnum;
}
++err;
}
return -1; /* unknown error */
}
/* return disk geometry for track */
int diSectorsPerTrack(ImageType type, int track) {
switch (type) {
case D71:
if (track > 35) {
track -= 35;
}
/* fall through */
case D64:
if (track < 18) {
return 21;
} else if (track < 25) {
return 19;
} else if (track < 31) {
return 18;
} else {
return 17;
}
break;
case D81:
return 40;
break;
}
return 0;
}
/* return number of tracks for image type */
int diTracks(ImageType type) {
switch (type) {
case D64:
return 35;
break;
case D71:
return 70;
break;
case D81:
return 80;
break;
}
return 0;
}
int diGetBlockNum(ImageType type, TrackSector ts) {
int block;
/* assertion, should never happen (indicates bad error handling elsewhere) */
if (!diTsIsValid(type, ts)) {
error("diGetBlockNum: internal error, track/sector out of range");
}
switch (type) {
case D64:
if (ts._track < 18) {
block = (ts._track - 1) * 21;
} else if (ts._track < 25) {
block = (ts._track - 18) * 19 + 17 * 21;
} else if (ts._track < 31) {
block = (ts._track - 25) * 18 + 17 * 21 + 7 * 19;
} else {
block = (ts._track - 31) * 17 + 17 * 21 + 7 * 19 + 6 * 18;
}
return block + ts._sector;
break;
case D71:
if (ts._track > 35) {
block = 683;
ts._track -= 35;
} else {
block = 0;
}
if (ts._track < 18) {
block += (ts._track - 1) * 21;
} else if (ts._track < 25) {
block += (ts._track - 18) * 19 + 17 * 21;
} else if (ts._track < 31) {
block += (ts._track - 25) * 18 + 17 * 21 + 7 * 19;
} else {
block += (ts._track - 31) * 17 + 17 * 21 + 7 * 19 + 6 * 18;
}
return block + ts._sector;
break;
case D81:
return (ts._track - 1) * 40 + ts._sector;
break;
}
return 0;
}
/* return t/s of first directory sector */
TrackSector diGetDirTs(DiskImage *di) {
TrackSector newTs;
byte *p;
p = diGetTsAddr(di, di->_dir);
if ((di->_type == D64) || (di->_type == D71)) {
newTs._track = 18; /* 1541/71 ignores bam t/s link */
newTs._sector = 1;
} else {
newTs._track = p[0];
newTs._sector = p[1];
}
return newTs;
}
/* return number of free blocks in track */
int diTrackBlocksFree(DiskImage *di, int track) {
byte *bam;
switch (di->_type) {
default:
case D64:
bam = diGetTsAddr(di, di->_bam);
break;
case D71:
bam = diGetTsAddr(di, di->_bam);
if (track >= 36) {
return bam[track + 185];
}
break;
case D81:
if (track <= 40) {
bam = diGetTsAddr(di, di->_bam);
} else {
bam = diGetTsAddr(di, di->_bam2);
track -= 40;
}
return bam[track * 6 + 10];
break;
}
return bam[track * 4];
}
/* check if track, sector is free in BAM */
int diIsTsFree(DiskImage *di, TrackSector ts) {
byte mask;
byte *bam;
switch (di->_type) {
case D64:
bam = diGetTsAddr(di, di->_bam);
if (bam[ts._track * 4]) {
mask = 1 << (ts._sector & 7);
return bam[ts._track * 4 + ts._sector / 8 + 1] & mask ? 1 : 0;
} else {
return 0;
}
break;
case D71:
mask = 1 << (ts._sector & 7);
if (ts._track < 36) {
bam = diGetTsAddr(di, di->_bam);
return bam[ts._track * 4 + ts._sector / 8 + 1] & mask ? 1 : 0;
} else {
bam = diGetTsAddr(di, di->_bam2);
return bam[(ts._track - 35) * 3 + ts._sector / 8 - 3] & mask ? 1 : 0;
}
break;
case D81:
mask = 1 << (ts._sector & 7);
if (ts._track < 41) {
bam = diGetTsAddr(di, di->_bam);
} else {
bam = diGetTsAddr(di, di->_bam2);
ts._track -= 40;
}
return bam[ts._track * 6 + ts._sector / 8 + 11] & mask ? 1 : 0;
break;
}
return 0;
}
/* allocate track, sector in BAM */
void diAllocTs(DiskImage *di, TrackSector ts) {
byte mask;
byte *bam;
di->_modified = 1;
switch (di->_type) {
case D64:
bam = diGetTsAddr(di, di->_bam);
bam[ts._track * 4] -= 1;
mask = 1 << (ts._sector & 7);
bam[ts._track * 4 + ts._sector / 8 + 1] &= ~mask;
break;
case D71:
mask = 1 << (ts._sector & 7);
if (ts._track < 36) {
bam = diGetTsAddr(di, di->_bam);
bam[ts._track * 4] -= 1;
bam[ts._track * 4 + ts._sector / 8 + 1] &= ~mask;
} else {
bam = diGetTsAddr(di, di->_bam);
bam[ts._track + 185] -= 1;
bam = diGetTsAddr(di, di->_bam2);
bam[(ts._track - 35) * 3 + ts._sector / 8 - 3] &= ~mask;
}
break;
case D81:
if (ts._track < 41) {
bam = diGetTsAddr(di, di->_bam);
} else {
bam = diGetTsAddr(di, di->_bam2);
ts._track -= 40;
}
bam[ts._track * 6 + 10] -= 1;
mask = 1 << (ts._sector & 7);
bam[ts._track * 6 + ts._sector / 8 + 11] &= ~mask;
break;
}
}
/* convert to rawname */
int diRawnameFromName(byte *rawname, const char *name) {
int i;
memset(rawname, 0xa0, 16);
for (i = 0; i < 16 && name[i]; ++i)
rawname[i] = name[i];
return i;
}
/* count number of free blocks */
int blocksFree(DiskImage *di) {
int blocks = 0;
for (int track = 1; track <= diTracks(di->_type); ++track) {
if (track != di->_dir._track) {
blocks += diTrackBlocksFree(di, track);
}
}
return blocks;
}
/* return write interleave */
int interleave(ImageType type) {
switch (type) {
case D64:
return 10;
break;
case D71:
return 6;
break;
default:
return 1;
break;
}
}
RawDirEntry *findLargestFileEntry(DiskImage *di) {
byte *buffer;
TrackSector ts;
RawDirEntry *rde, *largest = nullptr;
int offset, largestSize = 0;
ts = diGetDirTs(di);
while (ts._track) {
buffer = diGetTsAddr(di, ts);
for (offset = 0; offset < 256; offset += 32) {
rde = (RawDirEntry *)(buffer + offset);
int size = rde->_sizelo + rde->_sizehi * 0x100;
if (size > largestSize) {
largest = rde;
largestSize = size;
}
}
/* todo: add sanity checking */
ts = nextTsInChain(di, ts);
}
return largest;
}
DiskImage *diCreateFromData(uint8_t *data, int length) {
DiskImage *di;
if (data == nullptr)
return nullptr;
if ((di = new DiskImage) == nullptr) {
return nullptr;
}
di->_size = length;
di->_image = data;
di->_errinfo = nullptr;
/* check image type */
switch (length) {
case D64ERRSIZE: /* D64 with error info */
// di->_errinfo = &(di->_error_);
// fallthrough
case D64SIZE: /* standard D64 */
di->_type = D64;
di->_bam._track = 18;
di->_bam._sector = 0;
di->_dir = di->_bam;
break;
case D71ERRSIZE: /* D71 with error info */
di->_errinfo = &(di->_image[D71SIZE]);
// fallthrough
case D71SIZE:
di->_type = D71;
di->_bam._track = 18;
di->_bam._sector = 0;
di->_bam2._track = 53;
di->_bam2._sector = 0;
di->_dir = di->_bam;
break;
case D81ERRSIZE: /* D81 with error info */
di->_errinfo = &(di->_image[D81SIZE]);
// fallthrough
case D81SIZE:
di->_type = D81;
di->_bam._track = 40;
di->_bam._sector = 1;
di->_bam2._track = 40;
di->_bam2._sector = 2;
di->_dir._track = 40;
di->_dir._sector = 0;
break;
default:
delete di;
return nullptr;
}
di->_filename = nullptr;
di->_openfiles = 0;
di->_blocksfree = blocksFree(di);
di->_modified = 0;
di->_interleave = interleave(di->_type);
setStatus(di, 254, 0, 0);
return di;
}
} // End of namespace Scott
} // End of namespace Glk