ciderpress/diskimg/Nibble.cpp

1022 lines
32 KiB
C++

/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* DiskImg nibblized read/write functions.
*/
#include "StdAfx.h"
#include "DiskImgPriv.h"
/* define this for verbose output */
//#define NIB_VERBOSE_DEBUG
/*
* ===========================================================================
* Nibble encoding and decoding
* ===========================================================================
*/
/*static*/ uint8_t DiskImg::kDiskBytes53[32] = {
0xab, 0xad, 0xae, 0xaf, 0xb5, 0xb6, 0xb7, 0xba,
0xbb, 0xbd, 0xbe, 0xbf, 0xd6, 0xd7, 0xda, 0xdb,
0xdd, 0xde, 0xdf, 0xea, 0xeb, 0xed, 0xee, 0xef,
0xf5, 0xf6, 0xf7, 0xfa, 0xfb, 0xfd, 0xfe, 0xff
};
/*static*/ uint8_t DiskImg::kDiskBytes62[64] = {
0x96, 0x97, 0x9a, 0x9b, 0x9d, 0x9e, 0x9f, 0xa6,
0xa7, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb2, 0xb3,
0xb4, 0xb5, 0xb6, 0xb7, 0xb9, 0xba, 0xbb, 0xbc,
0xbd, 0xbe, 0xbf, 0xcb, 0xcd, 0xce, 0xcf, 0xd3,
0xd6, 0xd7, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde,
0xdf, 0xe5, 0xe6, 0xe7, 0xe9, 0xea, 0xeb, 0xec,
0xed, 0xee, 0xef, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6,
0xf7, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
};
/*static*/ uint8_t DiskImg::kInvDiskBytes53[256]; // all values are 0-31
/*static*/ uint8_t DiskImg::kInvDiskBytes62[256]; // all values are 0-63
/*
* Compute tables to convert disk bytes back to values.
*
* Should be called once, at DLL initialization time.
*/
/*static*/ void DiskImg::CalcNibbleInvTables(void)
{
unsigned int i;
memset(kInvDiskBytes53, kInvInvalidValue, sizeof(kInvDiskBytes53));
for (i = 0; i < sizeof(kDiskBytes53); i++) {
assert(kDiskBytes53[i] >= 0x96);
kInvDiskBytes53[kDiskBytes53[i]] = i;
}
memset(kInvDiskBytes62, kInvInvalidValue, sizeof(kInvDiskBytes62));
for (i = 0; i < sizeof(kDiskBytes62); i++) {
assert(kDiskBytes62[i] >= 0x96);
kInvDiskBytes62[kDiskBytes62[i]] = i;
}
}
/*
* Find the start of the data field of a sector in nibblized data.
*
* Returns the index start on success or -1 on failure.
*/
int DiskImg::FindNibbleSectorStart(const CircularBufferAccess& buffer, int track,
int sector, const NibbleDescr* pNibbleDescr, int* pVol)
{
const int kMaxDataReach = 48; // fairly arbitrary
//DIError dierr;
long trackLen = buffer.GetSize();
assert(sector >= 0 && sector < 16);
int i;
for (i = 0; i < trackLen; i++) {
bool foundAddr = false;
if (pNibbleDescr->special == kNibbleSpecialSkipFirstAddrByte) {
if (/*buffer[i] == pNibbleDescr->addrProlog[0] &&*/
buffer[i+1] == pNibbleDescr->addrProlog[1] &&
buffer[i+2] == pNibbleDescr->addrProlog[2])
{
foundAddr = true;
}
} else {
if (buffer[i] == pNibbleDescr->addrProlog[0] &&
buffer[i+1] == pNibbleDescr->addrProlog[1] &&
buffer[i+2] == pNibbleDescr->addrProlog[2])
{
foundAddr = true;
}
}
if (foundAddr) {
//i += 3;
/* found the address header, decode the address */
short hdrVol, hdrTrack, hdrSector, hdrChksum;
DecodeAddr(buffer, i+3, &hdrVol, &hdrTrack, &hdrSector,
&hdrChksum);
if (pNibbleDescr->addrVerifyTrack && track != hdrTrack) {
LOGI(" Track mismatch (T=%d) got T=%d,S=%d",
track, hdrTrack, hdrSector);
continue;
}
if (pNibbleDescr->addrVerifyChecksum) {
if ((pNibbleDescr->addrChecksumSeed ^
hdrVol ^ hdrTrack ^ hdrSector ^ hdrChksum) != 0)
{
LOGW(" Addr checksum mismatch (want T=%d,S=%d, got "
"T=%d,S=%d)",
track, sector, hdrTrack, hdrSector);
continue;
}
}
i += 3;
int j;
for (j = 0; j < pNibbleDescr->addrEpilogVerifyCount; j++) {
if (buffer[i+8+j] != pNibbleDescr->addrEpilog[j]) {
//LOGI(" Bad epilog byte %d (%02x vs %02x)",
// j, buffer[i+8+j], pNibbleDescr->addrEpilog[j]);
break;
}
}
if (j != pNibbleDescr->addrEpilogVerifyCount)
continue;
#ifdef NIB_VERBOSE_DEBUG
LOGI(" Good header, T=%d,S=%d (looking for T=%d,S=%d)",
hdrTrack, hdrSector, track, sector);
#endif
if (pNibbleDescr->special == kNibbleSpecialMuse) {
/* e.g. original Castle Wolfenstein */
if (track > 2) {
if ((hdrSector & 0x01) != 0)
continue;
hdrSector /= 2;
}
}
if (sector != hdrSector)
continue;
/*
* Scan forward and look for data prolog. We want to limit
* the reach of our search so we don't blunder into the data
* field of the next sector.
*/
for (j = 0; j < kMaxDataReach; j++) {
if (buffer[i + j] == pNibbleDescr->dataProlog[0] &&
buffer[i + j +1] == pNibbleDescr->dataProlog[1] &&
buffer[i + j +2] == pNibbleDescr->dataProlog[2])
{
*pVol = hdrVol;
return buffer.Normalize(i + j + 3);
}
}
}
}
#ifdef NIB_VERBOSE_DEBUG
LOGI(" Couldn't find T=%d,S=%d", track, sector);
#endif
return -1;
}
/*
* Decode the values in the address field.
*/
void DiskImg::DecodeAddr(const CircularBufferAccess& buffer, int offset,
short* pVol, short* pTrack, short* pSector, short* pChksum)
{
//unsigned int vol, track, sector, chksum;
*pVol = ConvFrom44(buffer[offset], buffer[offset+1]);
*pTrack = ConvFrom44(buffer[offset+2], buffer[offset+3]);
*pSector = ConvFrom44(buffer[offset+4], buffer[offset+5]);
*pChksum = ConvFrom44(buffer[offset+6], buffer[offset+7]);
}
/*
* Decode the sector pointed to by "pData" and described by "pNibbleDescr".
* This invokes the appropriate function (e.g. 5&3 or 6&2) to decode the
* data into a 256-byte sector.
*/
DIError DiskImg::DecodeNibbleData(const CircularBufferAccess& buffer, int idx,
uint8_t* sctBuf, const NibbleDescr* pNibbleDescr)
{
switch (pNibbleDescr->encoding) {
case kNibbleEnc62:
return DecodeNibble62(buffer, idx, sctBuf, pNibbleDescr);
case kNibbleEnc53:
return DecodeNibble53(buffer, idx, sctBuf, pNibbleDescr);
default:
assert(false);
return kDIErrInternal;
}
}
/*
* Encode the sector pointed to by "pData" and described by "pNibbleDescr".
* This invokes the appropriate function (e.g. 5&3 or 6&2) to encode the
* data from a 256-byte sector.
*/
void DiskImg::EncodeNibbleData(const CircularBufferAccess& buffer, int idx,
const uint8_t* sctBuf, const NibbleDescr* pNibbleDescr) const
{
switch (pNibbleDescr->encoding) {
case kNibbleEnc62:
EncodeNibble62(buffer, idx, sctBuf, pNibbleDescr);
break;
case kNibbleEnc53:
EncodeNibble53(buffer, idx, sctBuf, pNibbleDescr);
break;
default:
assert(false);
break;
}
}
/*
* Decode 6&2 encoding.
*/
DIError DiskImg::DecodeNibble62(const CircularBufferAccess& buffer, int idx,
uint8_t* sctBuf, const NibbleDescr* pNibbleDescr)
{
uint8_t twos[kChunkSize62 * 3]; // 258
int chksum = pNibbleDescr->dataChecksumSeed;
uint8_t decodedVal;
int i;
/*
* Pull the 342 bytes out, convert them from disk bytes to 6-bit
* values, and arrange them into a DOS-like pair of buffers.
*/
for (i = 0; i < kChunkSize62; i++) {
decodedVal = kInvDiskBytes62[buffer[idx++]];
if (decodedVal == kInvInvalidValue)
return kDIErrInvalidDiskByte;
assert(decodedVal < sizeof(kDiskBytes62));
chksum ^= decodedVal;
twos[i] =
((chksum & 0x01) << 1) | ((chksum & 0x02) >> 1);
twos[i + kChunkSize62] =
((chksum & 0x04) >> 1) | ((chksum & 0x08) >> 3);
twos[i + kChunkSize62*2] =
((chksum & 0x10) >> 3) | ((chksum & 0x20) >> 5);
}
for (i = 0; i < 256; i++) {
decodedVal = kInvDiskBytes62[buffer[idx++]];
if (decodedVal == kInvInvalidValue)
return kDIErrInvalidDiskByte;
assert(decodedVal < sizeof(kDiskBytes62));
chksum ^= decodedVal;
sctBuf[i] = (chksum << 2) | twos[i];
}
/*
* Grab the 343rd byte (the checksum byte) and see if we did this
* right.
*/
//printf("Dec checksum value is 0x%02x\n", chksum);
decodedVal = kInvDiskBytes62[buffer[idx++]];
if (decodedVal == kInvInvalidValue)
return kDIErrInvalidDiskByte;
assert(decodedVal < sizeof(kDiskBytes62));
chksum ^= decodedVal;
if (pNibbleDescr->dataVerifyChecksum && chksum != 0) {
LOGI(" NIB bad data checksum");
return kDIErrBadChecksum;
}
return kDIErrNone;
}
/*
* Encode 6&2 encoding.
*/
void DiskImg::EncodeNibble62(const CircularBufferAccess& buffer, int idx,
const uint8_t* sctBuf, const NibbleDescr* pNibbleDescr) const
{
uint8_t top[256];
uint8_t twos[kChunkSize62];
int twoPosn, twoShift;
int i;
memset(twos, 0, sizeof(twos));
twoShift = 0;
for (i = 0, twoPosn = kChunkSize62-1; i < 256; i++) {
unsigned int val = sctBuf[i];
top[i] = val >> 2;
twos[twoPosn] |= ((val & 0x01) << 1 | (val & 0x02) >> 1) << twoShift;
if (twoPosn == 0) {
twoPosn = kChunkSize62;
twoShift += 2;
}
twoPosn--;
}
int chksum = pNibbleDescr->dataChecksumSeed;
for (i = kChunkSize62-1; i >= 0; i--) {
assert(twos[i] < sizeof(kDiskBytes62));
buffer[idx++] = kDiskBytes62[twos[i] ^ chksum];
chksum = twos[i];
}
for (i = 0; i < 256; i++) {
assert(top[i] < sizeof(kDiskBytes62));
buffer[idx++] = kDiskBytes62[top[i] ^ chksum];
chksum = top[i];
}
//printf("Enc checksum value is 0x%02x\n", chksum);
buffer[idx++] = kDiskBytes62[chksum];
}
/*
* Decode 5&3 encoding.
*/
DIError DiskImg::DecodeNibble53(const CircularBufferAccess& buffer, int idx,
uint8_t* sctBuf, const NibbleDescr* pNibbleDescr)
{
uint8_t base[256];
uint8_t threes[kThreeSize];
int chksum = pNibbleDescr->dataChecksumSeed;
uint8_t decodedVal;
int i;
/*
* Pull the 410 bytes out, convert them from disk bytes to 5-bit
* values, and arrange them into a DOS-like pair of buffers.
*/
for (i = kThreeSize-1; i >= 0; i--) {
decodedVal = kInvDiskBytes53[buffer[idx++]];
if (decodedVal == kInvInvalidValue)
return kDIErrInvalidDiskByte;
assert(decodedVal < sizeof(kDiskBytes53));
chksum ^= decodedVal;
threes[i] = chksum;
}
for (i = 0; i < 256; i++) {
decodedVal = kInvDiskBytes53[buffer[idx++]];
if (decodedVal == kInvInvalidValue)
return kDIErrInvalidDiskByte;
assert(decodedVal < sizeof(kDiskBytes53));
chksum ^= decodedVal;
base[i] = (chksum << 3);
}
/*
* Grab the 411th byte (the checksum byte) and see if we did this
* right.
*/
//printf("Dec checksum value is 0x%02x\n", chksum);
decodedVal = kInvDiskBytes53[buffer[idx++]];
if (decodedVal == kInvInvalidValue)
return kDIErrInvalidDiskByte;
assert(decodedVal < sizeof(kDiskBytes53));
chksum ^= decodedVal;
if (pNibbleDescr->dataVerifyChecksum && chksum != 0) {
LOGI(" NIB bad data checksum (0x%02x)", chksum);
return kDIErrBadChecksum;
}
/*
* Convert this pile of stuff into 256 data bytes.
*/
uint8_t* bufPtr;
bufPtr = sctBuf;
for (i = kChunkSize53-1; i >= 0; i--) {
int three1, three2, three3, three4, three5;
three1 = threes[i];
three2 = threes[kChunkSize53 + i];
three3 = threes[kChunkSize53*2 + i];
three4 = (three1 & 0x02) << 1 | (three2 & 0x02) | (three3 & 0x02) >> 1;
three5 = (three1 & 0x01) << 2 | (three2 & 0x01) << 1 | (three3 & 0x01);
*bufPtr++ = base[i] | ((three1 >> 2) & 0x07);
*bufPtr++ = base[kChunkSize53 + i] | ((three2 >> 2) & 0x07);
*bufPtr++ = base[kChunkSize53*2 + i] | ((three3 >> 2) & 0x07);
*bufPtr++ = base[kChunkSize53*3 + i] | (three4 & 0x07);
*bufPtr++ = base[kChunkSize53*4 + i] | (three5 & 0x07);
}
assert(bufPtr == sctBuf + 255);
/*
* Convert the very last byte, which is handled specially.
*/
*bufPtr = base[255] | (threes[kThreeSize-1] & 0x07);
return kDIErrNone;
}
/*
* Encode 5&3 encoding.
*/
void DiskImg::EncodeNibble53(const CircularBufferAccess& buffer, int idx,
const uint8_t* sctBuf, const NibbleDescr* pNibbleDescr) const
{
uint8_t top[kChunkSize53 * 5 +1]; // (255 / 0xff) +1
uint8_t threes[kChunkSize53 * 3 +1]; // (153 / 0x99) +1
int i, chunk;
/*
* Split the bytes into sections.
*/
chunk = kChunkSize53-1;
for (i = 0; i < (int) sizeof(top)-1; i += 5) {
int three1, three2, three3, three4, three5;
three1 = *sctBuf++;
three2 = *sctBuf++;
three3 = *sctBuf++;
three4 = *sctBuf++;
three5 = *sctBuf++;
top[chunk] = three1 >> 3;
top[chunk + kChunkSize53*1] = three2 >> 3;
top[chunk + kChunkSize53*2] = three3 >> 3;
top[chunk + kChunkSize53*3] = three4 >> 3;
top[chunk + kChunkSize53*4] = three5 >> 3;
threes[chunk] =
(three1 & 0x07) << 2 | (three4 & 0x04) >> 1 | (three5 & 0x04) >> 2;
threes[chunk + kChunkSize53*1] =
(three2 & 0x07) << 2 | (three4 & 0x02) | (three5 & 0x02) >> 1;
threes[chunk + kChunkSize53*2] =
(three3 & 0x07) << 2 | (three4 & 0x01) << 1 | (three5 & 0x01);
chunk--;
}
assert(chunk == -1);
/*
* Handle the last byte.
*/
int val;
val = *sctBuf++;
top[255] = val >> 3;
threes[kThreeSize-1] = val & 0x07;
/*
* Write the bytes.
*/
int chksum = pNibbleDescr->dataChecksumSeed;
for (i = sizeof(threes)-1; i >= 0; i--) {
assert(threes[i] < sizeof(kDiskBytes53));
buffer[idx++] = kDiskBytes53[threes[i] ^ chksum];
chksum = threes[i];
}
for (i = 0; i < 256; i++) {
assert(top[i] < sizeof(kDiskBytes53));
buffer[idx++] = kDiskBytes53[top[i] ^ chksum];
chksum = top[i];
}
//printf("Enc checksum value is 0x%02x\n", chksum);
buffer[idx++] = kDiskBytes53[chksum];
}
/*
* ===========================================================================
* Higher-level functions
* ===========================================================================
*/
/*
* Dump some bytes as hex values into a string.
*
* "buf" must be able to hold (num * 3) characters.
*/
static void DumpBytes(const uint8_t* bytes, unsigned int num, char* buf)
{
sprintf(buf, "%02x", bytes[0]);
buf += 2;
for (int i = 1; i < (int) num; i++) {
sprintf(buf, " %02x", bytes[i]);
buf += 3;
}
*buf = '\0';
}
static inline const char* VerifyStr(bool val)
{
return val ? "verify" : "ignore";
}
/*
* Dump the contents of a NibbleDescr struct.
*/
void DiskImg::DumpNibbleDescr(const NibbleDescr* pNibDescr) const
{
char outBuf1[48];
char outBuf2[48];
const char* encodingStr;
switch (pNibDescr->encoding) {
case kNibbleEnc62: encodingStr = "6&2"; break;
case kNibbleEnc53: encodingStr = "5&3"; break;
case kNibbleEnc44: encodingStr = "4&4"; break;
default: encodingStr = "???"; break;
}
LOGI("NibbleDescr '%s':", pNibDescr->description);
LOGI(" Nibble encoding is %s", encodingStr);
DumpBytes(pNibDescr->addrProlog, sizeof(pNibDescr->addrProlog), outBuf1);
DumpBytes(pNibDescr->dataProlog, sizeof(pNibDescr->dataProlog), outBuf2);
LOGI(" Addr prolog: %s Data prolog: %s", outBuf1, outBuf2);
DumpBytes(pNibDescr->addrEpilog, sizeof(pNibDescr->addrEpilog), outBuf1);
DumpBytes(pNibDescr->dataEpilog, sizeof(pNibDescr->dataEpilog), outBuf2);
LOGI(" Addr epilog: %s (%d) Data epilog: %s (%d)",
outBuf1, pNibDescr->addrEpilogVerifyCount,
outBuf2, pNibDescr->dataEpilogVerifyCount);
LOGI(" Addr checksum: %s Data checksum: %s",
VerifyStr(pNibDescr->addrVerifyChecksum),
VerifyStr(pNibDescr->dataVerifyChecksum));
LOGI(" Addr checksum seed: 0x%02x Data checksum seed: 0x%02x",
pNibDescr->addrChecksumSeed, pNibDescr->dataChecksumSeed);
LOGI(" Addr check track: %s",
VerifyStr(pNibDescr->addrVerifyTrack));
}
/*
* Load a nibble track into our track buffer.
*/
DIError DiskImg::LoadNibbleTrack(long track, long* pTrackLen)
{
DIError dierr = kDIErrNone;
long offset;
assert(track >= 0 && track < kMaxNibbleTracks525);
*pTrackLen = GetNibbleTrackLength(track);
offset = GetNibbleTrackOffset(track);
assert(*pTrackLen > 0);
assert(offset >= 0);
if (track == fNibbleTrackLoaded) {
#ifdef NIB_VERBOSE_DEBUG
LOGI(" DI track %d already loaded", track);
#endif
return kDIErrNone;
} else {
LOGI(" DI loading track %ld", track);
}
/* invalidate in case we fail with partial read */
fNibbleTrackLoaded = -1;
/* alloc track buffer if needed */
if (fNibbleTrackBuf == NULL) {
fNibbleTrackBuf = new uint8_t[kTrackAllocSize];
if (fNibbleTrackBuf == NULL)
return kDIErrMalloc;
}
/*
* Read the entire track into memory.
*/
dierr = CopyBytesOut(fNibbleTrackBuf, offset, *pTrackLen);
if (dierr != kDIErrNone)
return dierr;
fNibbleTrackLoaded = track;
return dierr;
}
/*
* Save the track buffer back to disk.
*/
DIError DiskImg::SaveNibbleTrack(void)
{
if (fNibbleTrackLoaded < 0) {
LOGI("ERROR: tried to save track without loading it first");
return kDIErrInternal;
}
assert(fNibbleTrackBuf != NULL);
DIError dierr = kDIErrNone;
long trackLen = GetNibbleTrackLength(fNibbleTrackLoaded);
long offset = GetNibbleTrackOffset(fNibbleTrackLoaded);
/* write the track to fpDataGFD */
dierr = CopyBytesIn(fNibbleTrackBuf, offset, trackLen);
return dierr;
}
/*
* Count up the number of readable sectors found on this track, and
* return it. If "pVol" is non-NULL, return the volume number from
* one of the readable sectors.
*/
int DiskImg::TestNibbleTrack(int track, const NibbleDescr* pNibbleDescr,
int* pVol)
{
long trackLen;
int count = 0;
assert(track >= 0 && track < kTrackCount525);
assert(pNibbleDescr != NULL);
if (LoadNibbleTrack(track, &trackLen) != kDIErrNone) {
LOGI(" DI FindNibbleSectorStart: LoadNibbleTrack failed");
return 0;
}
CircularBufferAccess buffer(fNibbleTrackBuf, trackLen);
int i, sectorIdx;
for (i = 0; i < pNibbleDescr->numSectors; i++) {
int vol;
sectorIdx = FindNibbleSectorStart(buffer, track, i, pNibbleDescr, &vol);
if (sectorIdx >= 0) {
if (pVol != NULL)
*pVol = vol;
uint8_t sctBuf[256];
if (DecodeNibbleData(buffer, sectorIdx, sctBuf, pNibbleDescr) == kDIErrNone)
count++;
}
}
LOGI(" Tests on track=%d with '%s' returning count=%d",
track, pNibbleDescr->description, count);
return count;
}
/*
* Analyze the nibblized track data.
*
* On entry:
* fPhysical indicates the appropriate nibble format
*
* On exit:
* fpNibbleDescr points to the most-likely-to-succeed NibbleDescr
* fDOSVolumeNum holds a volume number from one of the tracks
* fNumTracks holds the number of tracks on the disk
*/
DIError DiskImg::AnalyzeNibbleData(void)
{
assert(IsNibbleFormat(fPhysical));
if (fPhysical == kPhysicalFormatNib525_Var) {
/* TrackStar can have up to 40 */
fNumTracks = fpImageWrapper->GetNibbleNumTracks();
assert(fNumTracks > 0);
} else {
/* fixed-length formats (.nib, .nb2) are always 35 tracks */
fNumTracks = kTrackCount525;
}
/*
* Try to read sectors from tracks 1, 16, 17, and 26. If we can get
* at least 13 out of 16 (or 10 out of 13) on three out of four tracks,
* we have a winner.
*/
int i, good, goodTracks;
int protoVol = kVolumeNumNotSet;
for (i = 0; i < fNumNibbleDescrEntries; i++) {
if (fpNibbleDescrTable[i].numSectors == 0) {
/* uninitialized "custom" entry */
LOGI(" Skipping '%s'", fpNibbleDescrTable[i].description);
continue;
}
LOGI(" Trying '%s'", fpNibbleDescrTable[i].description);
goodTracks = 0;
good = TestNibbleTrack(1, &fpNibbleDescrTable[i], NULL);
if (good > fpNibbleDescrTable[i].numSectors - 4)
goodTracks++;
good = TestNibbleTrack(16, &fpNibbleDescrTable[i], NULL);
if (good > fpNibbleDescrTable[i].numSectors - 4)
goodTracks++;
good = TestNibbleTrack(17, &fpNibbleDescrTable[i], &protoVol);
if (good > fpNibbleDescrTable[i].numSectors - 4)
goodTracks++;
good = TestNibbleTrack(26, &fpNibbleDescrTable[i], NULL);
if (good > fpNibbleDescrTable[i].numSectors - 4)
goodTracks++;
if (goodTracks >= 3) {
LOGI(" Looks like '%s' (%d-sector), vol=%d",
fpNibbleDescrTable[i].description,
fpNibbleDescrTable[i].numSectors, protoVol);
fpNibbleDescr = &fpNibbleDescrTable[i];
fDOSVolumeNum = protoVol;
break;
}
}
if (i == fNumNibbleDescrEntries) {
LOGI("AnalyzeNibbleData did not find matching NibbleDescr");
return kDIErrBadNibbleSectors;
}
return kDIErrNone;
}
/*
* Read a sector from a nibble image.
*
* While fNumTracks is valid, fNumSectPerTrack is a little flaky, because
* in theory each track could be formatted differently.
*/
DIError DiskImg::ReadNibbleSector(long track, int sector, void* buf,
const NibbleDescr* pNibbleDescr)
{
if (pNibbleDescr == NULL) {
/* disk has no recognizable sectors */
LOGI(" DI ReadNibbleSector: pNibbleDescr is NULL, returning failure");
return kDIErrBadNibbleSectors;
}
if (sector >= pNibbleDescr->numSectors) {
/* e.g. trying to read sector 14 on a 13-sector disk */
LOGI(" DI ReadNibbleSector: bad sector number request");
return kDIErrInvalidSector;
}
assert(pNibbleDescr != NULL);
assert(IsNibbleFormat(fPhysical));
assert(track >= 0 && track < GetNumTracks());
assert(sector >= 0 && sector < pNibbleDescr->numSectors);
DIError dierr = kDIErrNone;
long trackLen;
int sectorIdx, vol;
dierr = LoadNibbleTrack(track, &trackLen);
if (dierr != kDIErrNone) {
LOGI(" DI ReadNibbleSector: LoadNibbleTrack %ld failed", track);
return dierr;
}
CircularBufferAccess buffer(fNibbleTrackBuf, trackLen);
sectorIdx = FindNibbleSectorStart(buffer, track, sector, pNibbleDescr,
&vol);
if (sectorIdx < 0)
return kDIErrSectorUnreadable;
dierr = DecodeNibbleData(buffer, sectorIdx, (uint8_t*) buf,
pNibbleDescr);
return dierr;
}
/*
* Write a sector to a nibble image.
*/
DIError DiskImg::WriteNibbleSector(long track, int sector, const void* buf,
const NibbleDescr* pNibbleDescr)
{
assert(pNibbleDescr != NULL);
assert(IsNibbleFormat(fPhysical));
assert(track >= 0 && track < GetNumTracks());
assert(sector >= 0 && sector < pNibbleDescr->numSectors);
assert(!fReadOnly);
DIError dierr = kDIErrNone;
long trackLen;
int sectorIdx, vol;
dierr = LoadNibbleTrack(track, &trackLen);
if (dierr != kDIErrNone) {
LOGI(" DI ReadNibbleSector: LoadNibbleTrack %ld failed", track);
return dierr;
}
CircularBufferAccess buffer(fNibbleTrackBuf, trackLen);
sectorIdx = FindNibbleSectorStart(buffer, track, sector, pNibbleDescr,
&vol);
if (sectorIdx < 0)
return kDIErrSectorUnreadable;
EncodeNibbleData(buffer, sectorIdx, (uint8_t*) buf, pNibbleDescr);
dierr = SaveNibbleTrack();
if (dierr != kDIErrNone) {
LOGI(" DI ReadNibbleSector: SaveNibbleTrack %ld failed", track);
return dierr;
}
return dierr;
}
/*
* Get the contents of the nibble track.
*
* "buf" must be able to hold kTrackAllocSize bytes.
*/
DIError DiskImg::ReadNibbleTrack(long track, uint8_t* buf, long* pTrackLen)
{
DIError dierr;
dierr = LoadNibbleTrack(track, pTrackLen);
if (dierr != kDIErrNone) {
LOGI(" DI ReadNibbleTrack: LoadNibbleTrack %ld failed", track);
return dierr;
}
memcpy(buf, fNibbleTrackBuf, *pTrackLen);
return kDIErrNone;
}
/*
* Set the contents of a nibble track.
*
* NOTE: This currently does the wrong thing when converting from .nb2 to
* .nib. Fixed-length formats shouldn't be allowed to interact. Figure
* this out someday. For now, the higher-level code prevents it.
*/
DIError DiskImg::WriteNibbleTrack(long track, const uint8_t* buf, long trackLen)
{
DIError dierr;
long oldTrackLen;
/* load the track to set the "current track" stuff */
dierr = LoadNibbleTrack(track, &oldTrackLen);
if (dierr != kDIErrNone) {
LOGI(" DI WriteNibbleTrack: LoadNibbleTrack %ld failed", track);
return dierr;
}
if (trackLen > GetNibbleTrackAllocLength()) {
LOGI("ERROR: tried to write too-long track len (%ld vs %d)",
trackLen, GetNibbleTrackAllocLength());
return kDIErrInvalidArg;
}
if (trackLen < oldTrackLen) // pad out any extra space
memset(fNibbleTrackBuf, 0xff, oldTrackLen);
memcpy(fNibbleTrackBuf, buf, trackLen);
fpImageWrapper->SetNibbleTrackLength(track, trackLen);
dierr = SaveNibbleTrack();
if (dierr != kDIErrNone) {
LOGI(" DI ReadNibbleSector: SaveNibbleTrack %ld failed", track);
return dierr;
}
return kDIErrNone;
}
/*
* Create a blank nibble image, using fpNibbleDescr as the template.
* Sets "fLength".
*
* Tracks are written the same way regardless of actual track length (be
* it 6656, 6384, or variable-length). Anything longer than 6384 just has
* more padding at the end of the track.
*
* The format looks like this:
* Gap one (48 self-sync bytes)
* For each sector:
* Address field (14 bytes, e.g. d5aa96 vol track sect chksum deaaeb)
* Gap two (six self-sync bytes)
* Data field (6 header bytes, 1 checksum byte, and 342 or 410 data bytes)
* Gap three (27 self-sync bytes)
*
* 48 + (14 + 6 + (6 + 1 + 342) + 27) * 16 = 6384
* 48 + (14 + 6 + (6 + 1 + 410) + 27) * 13 = 6080
*/
DIError DiskImg::FormatNibbles(GenericFD* pGFD) const
{
assert(fHasNibbles);
assert(GetNumTracks() > 0);
DIError dierr = kDIErrNone;
uint8_t trackBuf[kTrackAllocSize];
/* these should be the same except for var-len images */
long trackAllocLen = GetNibbleTrackAllocLength();
long trackLen = GetNibbleTrackFormatLength();
int track;
assert(trackLen > 0);
pGFD->Rewind();
/*
* If we don't have sector access, take a shortcut and just fill the
* entire image with 0xff.
*/
if (!fHasSectors) {
memset(trackBuf, 0xff, trackLen);
for (track = 0; track < GetNumTracks(); track++) {
/* write the track to the GFD */
dierr = pGFD->Write(trackBuf, trackAllocLen);
if (dierr != kDIErrNone)
return dierr;
fpImageWrapper->SetNibbleTrackLength(track, trackAllocLen);
}
return kDIErrNone;
}
assert(fHasSectors);
assert(fpNibbleDescr != NULL);
assert(fpNibbleDescr->numSectors == GetNumSectPerTrack());
assert(fpNibbleDescr->encoding == kNibbleEnc53 ||
fpNibbleDescr->encoding == kNibbleEnc62);
assert(fDOSVolumeNum != kVolumeNumNotSet);
/*
* Create a prototype sector. The data for a sector full of zeroes
* is exactly the same; only the address header changes.
*/
uint8_t sampleSource[256];
uint8_t sampleBuf[512]; // must hold 5&3 and 6&2
CircularBufferAccess sample(sampleBuf, 512);
long dataLen;
if (fpNibbleDescr->encoding == kNibbleEnc53)
dataLen = 410 +1;
else
dataLen = 342 +1;
memset(sampleSource, 0, sizeof(sampleSource));
EncodeNibbleData(sample, 0, sampleSource, fpNibbleDescr);
/*
* For each track in the image, "format" the expected number of
* sectors, then write the data to the GFD.
*/
for (track = 0; track < GetNumTracks(); track++) {
//LOGI("Formatting track %d", track);
uint8_t* trackPtr = trackBuf;
/*
* Fill with "self-sync" bytes.
*/
memset(trackBuf, 0xff, trackAllocLen);
/* gap one */
trackPtr += 48;
for (int sector = 0; sector < fpNibbleDescr->numSectors; sector++) {
/*
* Write address field.
*/
uint16_t hdrTrack, hdrSector, hdrVol, hdrChksum;
hdrTrack = track;
hdrSector = sector;
hdrVol = fDOSVolumeNum;
*trackPtr++ = fpNibbleDescr->addrProlog[0];
*trackPtr++ = fpNibbleDescr->addrProlog[1];
*trackPtr++ = fpNibbleDescr->addrProlog[2];
*trackPtr++ = Conv44(hdrVol, true);
*trackPtr++ = Conv44(hdrVol, false);
*trackPtr++ = Conv44(hdrTrack, true);
*trackPtr++ = Conv44(hdrTrack, false);
*trackPtr++ = Conv44(hdrSector, true);
*trackPtr++ = Conv44(hdrSector, false);
hdrChksum = fpNibbleDescr->addrChecksumSeed ^
hdrVol ^ hdrTrack ^ hdrSector;
*trackPtr++ = Conv44(hdrChksum, true);
*trackPtr++ = Conv44(hdrChksum, false);
*trackPtr++ = fpNibbleDescr->addrEpilog[0];
*trackPtr++ = fpNibbleDescr->addrEpilog[1];
*trackPtr++ = fpNibbleDescr->addrEpilog[2];
/* gap two */
trackPtr += 6;
/*
* Write data field.
*/
*trackPtr++ = fpNibbleDescr->dataProlog[0];
*trackPtr++ = fpNibbleDescr->dataProlog[1];
*trackPtr++ = fpNibbleDescr->dataProlog[2];
memcpy(trackPtr, sampleBuf, dataLen);
trackPtr += dataLen;
*trackPtr++ = fpNibbleDescr->dataEpilog[0];
*trackPtr++ = fpNibbleDescr->dataEpilog[1];
*trackPtr++ = fpNibbleDescr->dataEpilog[2];
/* gap three */
trackPtr += 27;
}
assert(trackPtr - trackBuf == 6384 ||
trackPtr - trackBuf == 6080);
/*
* Write the track to the GFD.
*/
dierr = pGFD->Write(trackBuf, trackAllocLen);
if (dierr != kDIErrNone)
break;
/* on a variable-length image, reduce track len to match */
fpImageWrapper->SetNibbleTrackLength(track, trackLen);
}
return dierr;
}