mirror of
https://github.com/fadden/ciderpress.git
synced 2024-11-26 17:49:21 +00:00
558 lines
15 KiB
C++
558 lines
15 KiB
C++
/*
|
|
* CiderPress
|
|
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
|
|
* See the file LICENSE for distribution terms.
|
|
*/
|
|
/*
|
|
* GCR nibble image support for 3.5" disks.
|
|
*
|
|
* Each track has between 8 and 12 512-byte sectors. The encoding is similar
|
|
* to but different from that used on 5.25" disks.
|
|
*
|
|
* THOUGHT: this is currently designed for unpacking all blocks from a track.
|
|
* We really ought to allow the user to view the track in nibble form, which
|
|
* means reworking the interface to be more like the 5.25" nibble stuff. We
|
|
* should present it as a block interface rather than track/sector; the code
|
|
* here can convert the block # to track/sector, and just provide a raw
|
|
* interface for the nibble track viewer.
|
|
*/
|
|
#include "StdAfx.h"
|
|
#include "DiskImgPriv.h"
|
|
|
|
/*
|
|
Physical sector layout:
|
|
|
|
+00 self-sync 0xff pattern (36 10-bit bytes, or 45 8-bit bytes)
|
|
+36 addr prolog (0xd5 0xaa 0x96)
|
|
+39 6&2enc track number (0-79 mod 63)
|
|
+40 6&2enc sector number (0-N)
|
|
+41 6&2enc side (0x00 or 0x20, ORed with 0x01 for tracks >= 64)
|
|
+42 6&2enc format (0x22, 0x24, others?)
|
|
+43 6&2enc checksum (track ^ sector ^ side ^ format)
|
|
+44 addr epilog (0xde 0xaa)
|
|
+46 self-sync 0xff (6 10-bit bytes)
|
|
+52 data prolog (0xd5 0xaa 0xad)
|
|
+55 6&2enc sector number (another copy)
|
|
+56 6&2enc nibblized data (699 bytes)
|
|
+755 checksum, 3 bytes 6&2 encoded as 4 bytes
|
|
+759 data epilog (0xde 0xaa)
|
|
+761 0xff (pad byte)
|
|
|
|
Some sources say it starts with 42 10-bit self-sync bytes instead of 36.
|
|
*/
|
|
|
|
/*
|
|
* Basic disk geometry.
|
|
*/
|
|
const int kCylindersPerDisk = 80;
|
|
const int kHeadsPerCylinder = 2;
|
|
const int kMaxSectorsPerTrack = 12;
|
|
const int kSectorSize35 = 524; // 512 data bytes + 12 tag bytes
|
|
const int kTagBytesLen = 12;
|
|
const int kDataChecksumLen = 3;
|
|
const int kChunkSize35 = 175; // ceil(524 / 3)
|
|
const int kOffsetToChecksum = 699;
|
|
const int kNibblizedOutputLen = (kOffsetToChecksum + 4);
|
|
const int kMaxDataReach = 48; // should only be 6 bytes */
|
|
|
|
enum {
|
|
kAddrProlog0 = 0xd5,
|
|
kAddrProlog1 = 0xaa,
|
|
kAddrProlog2 = 0x96,
|
|
kAddrEpilog0 = 0xde,
|
|
kAddrEpilog1 = 0xaa,
|
|
|
|
kDataProlog0 = 0xd5,
|
|
kDataProlog1 = 0xaa,
|
|
kDataProlog2 = 0xad,
|
|
kDataEpilog0 = 0xde,
|
|
kDataEpilog1 = 0xaa,
|
|
};
|
|
|
|
/*
|
|
* There are 12 sectors per track for the first 16 cylinders, 11 sectors
|
|
* per track for the next 16, and so on until we're down to 8 per track.
|
|
*/
|
|
/*static*/ int
|
|
DiskImg::SectorsPerTrack35(int cylinder)
|
|
{
|
|
return kMaxSectorsPerTrack - (cylinder / 16);
|
|
}
|
|
|
|
/*
|
|
* Convert cylinder/head/sector to a block number on a 3.5" disk.
|
|
*/
|
|
/*static*/ int
|
|
DiskImg::CylHeadSect35ToBlock(int cyl, int head, int sect)
|
|
{
|
|
int i, block;
|
|
|
|
assert(cyl >= 0 && cyl < kCylindersPerDisk);
|
|
assert(head >= 0 && head < kHeadsPerCylinder);
|
|
assert(sect >= 0 && sect < SectorsPerTrack35(cyl));
|
|
|
|
block = 0;
|
|
for (i = 0; i < cyl; i++)
|
|
block += SectorsPerTrack35(i) * kHeadsPerCylinder;
|
|
if (head)
|
|
block += SectorsPerTrack35(i);
|
|
block += sect;
|
|
|
|
//WMSG4("Nib35: c/h/s %d/%d/%d --> block %d\n", cyl, head, sect, block);
|
|
assert(block >= 0 && block < 1600);
|
|
return block;
|
|
}
|
|
|
|
/*
|
|
* Unpack a nibble track.
|
|
*
|
|
* "outputBuf" must be able to hold 512 * 12 sectors of decoded sector data.
|
|
*/
|
|
/*static*/ DIError
|
|
DiskImg::UnpackNibbleTrack35(const unsigned char* nibbleBuf,
|
|
long nibbleLen, unsigned char* outputBuf, int cyl, int head,
|
|
LinearBitmap* pBadBlockMap)
|
|
{
|
|
CircularBufferAccess buffer(nibbleBuf, nibbleLen);
|
|
bool foundSector[kMaxSectorsPerTrack];
|
|
unsigned char sectorBuf[kSectorSize35];
|
|
unsigned char readSum[kDataChecksumLen];
|
|
unsigned char calcSum[kDataChecksumLen];
|
|
int i;
|
|
|
|
memset(&foundSector, 0, sizeof(foundSector));
|
|
|
|
i = 0;
|
|
while (i < nibbleLen) {
|
|
int sector;
|
|
|
|
i = FindNextSector35(buffer, i, cyl, head, §or);
|
|
if (i < 0)
|
|
break;
|
|
|
|
assert(sector >= 0 && sector < SectorsPerTrack35(cyl));
|
|
if (foundSector[sector]) {
|
|
WMSG3("Nib35: WARNING: found two copies of sect %d on cyl=%d head=%d\n",
|
|
sector, cyl, head);
|
|
} else {
|
|
memset(sectorBuf, 0xa9, sizeof(sectorBuf));
|
|
if (DecodeNibbleSector35(buffer, i, sectorBuf, readSum, calcSum))
|
|
{
|
|
/* successfully decoded sector, copy data & verify checksum */
|
|
foundSector[sector] = true;
|
|
memcpy(outputBuf + kBlockSize * sector,
|
|
sectorBuf + kTagBytesLen, kBlockSize);
|
|
|
|
if (calcSum[0] != readSum[0] ||
|
|
calcSum[1] != readSum[1] ||
|
|
calcSum[2] != readSum[2])
|
|
{
|
|
WMSG2("Nib35: checksum mismatch: 0x%06x vs. 0x%06x\n",
|
|
calcSum[0] << 16 | calcSum[1] << 8 | calcSum[2],
|
|
readSum[0] << 16 | readSum[1] << 8 | readSum[2]);
|
|
WMSG4("Nib35: marking cyl=%d head=%d sect=%d (block=%d)\n",
|
|
cyl, head, sector,
|
|
CylHeadSect35ToBlock(cyl, head, sector));
|
|
pBadBlockMap->Set(CylHeadSect35ToBlock(cyl, head, sector));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check to see if we have all our parts. Anything missing sets
|
|
* a flag in the "bad block" map.
|
|
*/
|
|
for (i = SectorsPerTrack35(cyl)-1; i >= 0; i--) {
|
|
if (!foundSector[i]) {
|
|
WMSG4("Nib35: didn't find cyl=%d head=%d sect=%d (block=%d)\n",
|
|
cyl, head, i, CylHeadSect35ToBlock(cyl, head, i));
|
|
pBadBlockMap->Set(CylHeadSect35ToBlock(cyl, head, i));
|
|
}
|
|
|
|
/*
|
|
// DEBUG test
|
|
if ((cyl == 0 || cyl == 12 || cyl == 79) &&
|
|
(head == (cyl & 0x01)) &&
|
|
(i == 1 || i == 7))
|
|
{
|
|
WMSG4("DEBUG: setting bad %d/%d/%d (%d)\n",
|
|
cyl, head, i, CylHeadSect35ToBlock(cyl, head, i));
|
|
pBadBlockMap->Set(CylHeadSect35ToBlock(cyl, head, i));
|
|
}
|
|
*/
|
|
}
|
|
|
|
return kDIErrNone; // maybe return an error if nothing found?
|
|
}
|
|
|
|
/*
|
|
* Returns the offset of the next sector, or -1 if we went off the end.
|
|
*/
|
|
/*static*/ int
|
|
DiskImg::FindNextSector35(const CircularBufferAccess& buffer, int start,
|
|
int cyl, int head, int* pSector)
|
|
{
|
|
int end = buffer.GetSize();
|
|
int i;
|
|
|
|
for (i = start; i < end; i++) {
|
|
bool foundAddr = false;
|
|
|
|
if (buffer[i] == kAddrProlog0 &&
|
|
buffer[i+1] == kAddrProlog1 &&
|
|
buffer[i+2] == kAddrProlog2)
|
|
{
|
|
foundAddr = true;
|
|
}
|
|
|
|
if (foundAddr) {
|
|
/* decode the address field */
|
|
int trackNum, sectNum, side, format, checksum;
|
|
|
|
trackNum = kInvDiskBytes62[buffer[i+3]];
|
|
sectNum = kInvDiskBytes62[buffer[i+4]];
|
|
side = kInvDiskBytes62[buffer[i+5]];
|
|
format = kInvDiskBytes62[buffer[i+6]];
|
|
checksum = kInvDiskBytes62[buffer[i+7]];
|
|
if (trackNum == kInvInvalidValue ||
|
|
sectNum == kInvInvalidValue ||
|
|
side == kInvInvalidValue ||
|
|
format == kInvInvalidValue ||
|
|
checksum == kInvInvalidValue)
|
|
{
|
|
WMSG0("Nib35: garbled address header found\n");
|
|
continue;
|
|
}
|
|
//WMSG5(" Nib35: got addr: track=%2d sect=%2d side=%d format=%d sum=0x%02x\n",
|
|
// trackNum, sectNum, side, format, checksum);
|
|
if (side != ((head * 0x20) | (cyl >> 6))) {
|
|
WMSG3("Nib35: unexpected value for side: %d on cyl=%d head=%d\n",
|
|
side, cyl, head);
|
|
}
|
|
if (sectNum >= SectorsPerTrack35(cyl)) {
|
|
WMSG2("Nib35: invalid value for sector: %d (cyl=%d)\n",
|
|
sectNum, cyl);
|
|
continue;
|
|
}
|
|
/* format seems to be 0x22 or 0x24 */
|
|
if (checksum != (trackNum ^ sectNum ^ side ^ format)) {
|
|
WMSG2("Nib35: unexpected checksum: 0x%02x vs. 0x%02x\n",
|
|
checksum, trackNum ^ sectNum ^ side ^ format);
|
|
continue;
|
|
}
|
|
|
|
/* check the epilog bytes */
|
|
if (buffer[i+8] != kAddrEpilog0 ||
|
|
buffer[i+9] != kAddrEpilog1)
|
|
{
|
|
WMSG0("Nib35: invalid address epilog\n");
|
|
/* maybe we allow this anyway? */
|
|
}
|
|
|
|
*pSector = sectNum;
|
|
return i+10; // move past address field
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Unpack a 524-byte sector from a 3.5" disk. Start with "start" pointed
|
|
* in the general vicinity of the data prolog bytes.
|
|
*
|
|
* "sectorBuf" must hold at least kSectorSize35 bytes. It will be filled
|
|
* with the decoded data.
|
|
* "readChecksum" and "calcChecksum" must each hold at least kDataChecksumLen
|
|
* bytes. The former holds the checksum read from the sector, the latter
|
|
* holds the checksum computed from the data.
|
|
*
|
|
* The 4 to 3 conversion is pretty straightforward. The checksum is
|
|
* a little crazy.
|
|
*
|
|
* Returns "true" if all goes well, "false" if there is a problem. Does
|
|
* not return false on a checksum mismatch -- it's up to the caller to
|
|
* verify the checksum if desired.
|
|
*/
|
|
/*static*/ bool
|
|
DiskImg::DecodeNibbleSector35(const CircularBufferAccess& buffer, int start,
|
|
unsigned char* sectorBuf, unsigned char* readChecksum,
|
|
unsigned char* calcChecksum)
|
|
{
|
|
const int kMaxDataReach35 = 48; // fairly arbitrary
|
|
unsigned char* sectorBufStart = sectorBuf;
|
|
unsigned char part0[kChunkSize35], part1[kChunkSize35], part2[kChunkSize35];
|
|
unsigned int chk0, chk1, chk2;
|
|
unsigned char val, nib0, nib1, nib2, twos;
|
|
int i, off;
|
|
|
|
/*
|
|
* Find the start of the actual data. Adjust "start" to point at it.
|
|
*/
|
|
for (off = start; off < start + kMaxDataReach35; off++) {
|
|
if (buffer[off] == kDataProlog0 &&
|
|
buffer[off+1] == kDataProlog1 &&
|
|
buffer[off+2] == kDataProlog2)
|
|
{
|
|
start = off + 4; // 3 prolog bytes + sector number
|
|
break;
|
|
}
|
|
}
|
|
if (off == start + kMaxDataReach35) {
|
|
WMSG0("nib25: could not find start of data field\n");
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Assemble 8-bit bytes from 6&2 encoded values.
|
|
*/
|
|
off = start;
|
|
for (i = 0; i < kChunkSize35; i++) {
|
|
twos = kInvDiskBytes62[buffer[off++]];
|
|
nib0 = kInvDiskBytes62[buffer[off++]];
|
|
nib1 = kInvDiskBytes62[buffer[off++]];
|
|
if (i != kChunkSize35-1)
|
|
nib2 = kInvDiskBytes62[buffer[off++]];
|
|
else
|
|
nib2 = 0;
|
|
|
|
if (twos == kInvInvalidValue ||
|
|
nib0 == kInvInvalidValue ||
|
|
nib1 == kInvInvalidValue ||
|
|
nib2 == kInvInvalidValue)
|
|
{
|
|
// junk found
|
|
WMSG1("Nib25: found invalid disk byte in sector data at %d\n",
|
|
off - start);
|
|
WMSG4(" (one of 0x%02x 0x%02x 0x%02x 0x%02x)\n",
|
|
buffer[off-4], buffer[off-3], buffer[off-2], buffer[off-1]);
|
|
return false;
|
|
//if (twos == kInvInvalidValue)
|
|
// twos = 0;
|
|
//if (nib0 == kInvInvalidValue)
|
|
// nib0 = 0;
|
|
//if (nib1 == kInvInvalidValue)
|
|
// nib1 = 0;
|
|
//if (nib2 == kInvInvalidValue)
|
|
// nib2 = 0;
|
|
}
|
|
|
|
part0[i] = nib0 | ((twos << 2) & 0xc0);
|
|
part1[i] = nib1 | ((twos << 4) & 0xc0);
|
|
part2[i] = nib2 | ((twos << 6) & 0xc0);
|
|
}
|
|
assert(off == start + kOffsetToChecksum);
|
|
|
|
chk0 = chk1 = chk2 = 0;
|
|
i = 0;
|
|
while (true) {
|
|
chk0 = (chk0 & 0xff) << 1;
|
|
if (chk0 & 0x0100)
|
|
chk0++;
|
|
|
|
val = part0[i] ^ chk0;
|
|
chk2 += val;
|
|
if (chk0 & 0x0100) {
|
|
chk2++;
|
|
chk0 &= 0xff;
|
|
}
|
|
*sectorBuf++ = val;
|
|
|
|
val = part1[i] ^ chk2;
|
|
chk1 += val;
|
|
if (chk2 > 0xff) {
|
|
chk1++;
|
|
chk2 &= 0xff;
|
|
}
|
|
*sectorBuf++ = val;
|
|
|
|
if (sectorBuf - sectorBufStart == 524)
|
|
break;
|
|
|
|
val = part2[i] ^ chk1;
|
|
chk0 += val;
|
|
if (chk1 > 0xff) {
|
|
chk0++;
|
|
chk1 &= 0xff;
|
|
}
|
|
*sectorBuf++ = val;
|
|
|
|
i++;
|
|
assert(i < kChunkSize35);
|
|
//WMSG2("i = %d, diff=%d\n", i, sectorBuf - sectorBufStart);
|
|
}
|
|
|
|
calcChecksum[0] = chk0;
|
|
calcChecksum[1] = chk1;
|
|
calcChecksum[2] = chk2;
|
|
|
|
if (!UnpackChecksum35(buffer, off, readChecksum)) {
|
|
WMSG0("Nib35: failure reading checksum\n");
|
|
readChecksum[0] = calcChecksum[0] ^ 0xff; // force a failure
|
|
return false;
|
|
}
|
|
off += 4; // skip past checksum bytes
|
|
|
|
if (buffer[off] != kDataEpilog0 || buffer[off+1] != kDataEpilog1) {
|
|
WMSG0("nib25: WARNING: data epilog not found\n");
|
|
// allow it, if the checksum matches
|
|
}
|
|
|
|
//#define TEST_ENC_35
|
|
#ifdef TEST_ENC_35
|
|
{
|
|
unsigned char nibBuf[kNibblizedOutputLen];
|
|
memset(nibBuf, 0xcc, sizeof(nibBuf));
|
|
|
|
/* encode what we just decoded */
|
|
EncodeNibbleSector35(sectorBufStart, nibBuf);
|
|
/* compare it to the original */
|
|
for (i = 0; i < kNibblizedOutputLen; i++) {
|
|
if (buffer[start + i] != nibBuf[i]) {
|
|
/*
|
|
* The very last "twos" entry may have undefined bits when
|
|
* written by a real drive. Peel it apart and ignore the
|
|
* two flaky bits.
|
|
*/
|
|
if (i == 696) {
|
|
unsigned char val1, val2;
|
|
val1 = kInvDiskBytes62[buffer[start + i]];
|
|
val2 = kInvDiskBytes62[nibBuf[i]];
|
|
if ((val1 & 0xfc) != (val2 & 0xfc)) {
|
|
WMSG5("Nib35 DEBUG: output differs at byte %d"
|
|
" (0x%02x vs 0x%02x / 0x%02x vs 0x%02x)\n",
|
|
i, buffer[start+i], nibBuf[i], val1, val2);
|
|
}
|
|
} else {
|
|
// note: checksum is 699-702
|
|
WMSG3("Nib35 DEBUG: output differs at byte %d (0x%02x vs 0x%02x)\n",
|
|
i, buffer[start+i], nibBuf[i]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif /*TEST_ENC_35*/
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Unpack the 6&2 encoded 3-byte checksum at the end of a sector.
|
|
*
|
|
* "offset" should point to the first byte of the checksum.
|
|
*
|
|
* Returns "true" if all goes well, "false" otherwise.
|
|
*/
|
|
/*static*/ bool
|
|
DiskImg::UnpackChecksum35(const CircularBufferAccess& buffer, int offset,
|
|
unsigned char* checksumBuf)
|
|
{
|
|
unsigned char nib0, nib1, nib2, twos;
|
|
|
|
twos = kInvDiskBytes62[buffer[offset++]];
|
|
nib2 = kInvDiskBytes62[buffer[offset++]];
|
|
nib1 = kInvDiskBytes62[buffer[offset++]];
|
|
nib0 = kInvDiskBytes62[buffer[offset++]];
|
|
|
|
if (twos == kInvInvalidValue ||
|
|
nib0 == kInvInvalidValue ||
|
|
nib1 == kInvInvalidValue ||
|
|
nib2 == kInvInvalidValue)
|
|
{
|
|
WMSG0("nib25: found invalid disk byte in checksum\n");
|
|
return false;
|
|
}
|
|
|
|
checksumBuf[0] = nib0 | ((twos << 6) & 0xc0);
|
|
checksumBuf[1] = nib1 | ((twos << 4) & 0xc0);
|
|
checksumBuf[2] = nib2 | ((twos << 2) & 0xc0);
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Encode 524 bytes of sector data into 699 bytes of 6&2 nibblized data
|
|
* plus a 4-byte checksum.
|
|
*
|
|
* "outBuf" must be able to hold kNibblizedOutputLen bytes.
|
|
*/
|
|
/*static*/ void
|
|
DiskImg::EncodeNibbleSector35(const unsigned char* sectorData,
|
|
unsigned char* outBuf)
|
|
{
|
|
const unsigned char* sectorDataStart = sectorData;
|
|
unsigned char* outBufStart = outBuf;
|
|
unsigned char part0[kChunkSize35], part1[kChunkSize35], part2[kChunkSize35];
|
|
unsigned int chk0, chk1, chk2;
|
|
unsigned char val, twos;
|
|
int i;
|
|
|
|
/*
|
|
* Compute checksum and split the input into 3 pieces.
|
|
*/
|
|
i = 0;
|
|
chk0 = chk1 = chk2 = 0;
|
|
while (true) {
|
|
chk0 = (chk0 & 0xff) << 1;
|
|
if (chk0 & 0x0100)
|
|
chk0++;
|
|
|
|
val = *sectorData++;
|
|
chk2 += val;
|
|
if (chk0 & 0x0100) {
|
|
chk2++;
|
|
chk0 &= 0xff;
|
|
}
|
|
part0[i] = (val ^ chk0) & 0xff;
|
|
|
|
val = *sectorData++;
|
|
chk1 += val;
|
|
if (chk2 > 0xff) {
|
|
chk1++;
|
|
chk2 &= 0xff;
|
|
}
|
|
part1[i] = (val ^ chk2) & 0xff;
|
|
|
|
if (sectorData - sectorDataStart == 524)
|
|
break;
|
|
|
|
val = *sectorData++;
|
|
chk0 += val;
|
|
if (chk1 > 0xff) {
|
|
chk0++;
|
|
chk1 &= 0xff;
|
|
}
|
|
part2[i] = (val ^ chk1) & 0xff;
|
|
i++;
|
|
}
|
|
part2[kChunkSize35-1] = 0; // gets merged into the "twos"
|
|
|
|
assert(i == kChunkSize35-1);
|
|
|
|
/*
|
|
* Output the nibble data.
|
|
*/
|
|
for (i = 0; i < kChunkSize35; i++) {
|
|
twos = ((part0[i] & 0xc0) >> 2) |
|
|
((part1[i] & 0xc0) >> 4) |
|
|
((part2[i] & 0xc0) >> 6);
|
|
|
|
*outBuf++ = kDiskBytes62[twos];
|
|
*outBuf++ = kDiskBytes62[part0[i] & 0x3f];
|
|
*outBuf++ = kDiskBytes62[part1[i] & 0x3f];
|
|
if (i != kChunkSize35 -1)
|
|
*outBuf++ = kDiskBytes62[part2[i] & 0x3f];
|
|
}
|
|
|
|
/*
|
|
* Output the checksum.
|
|
*/
|
|
twos = ((chk0 & 0xc0) >> 6) | ((chk1 & 0xc0) >> 4) | ((chk2 & 0xc0) >> 2);
|
|
*outBuf++ = kDiskBytes62[twos];
|
|
*outBuf++ = kDiskBytes62[chk2 & 0x3f];
|
|
*outBuf++ = kDiskBytes62[chk1 & 0x3f];
|
|
*outBuf++ = kDiskBytes62[chk0 & 0x3f];
|
|
|
|
assert(outBuf - outBufStart == kNibblizedOutputLen);
|
|
}
|