ciderpress/diskimg/FDI.cpp

1523 lines
48 KiB
C++

/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* Support for Formatted Disk Image (FDI) format.
*
* Based on the v2.0 spec and "fdi2raw.c". The latter was released under
* version 2 of the GPL, so this code may be subject to it.
*
* (Note: I tend to abuse the term "nibble" here. Instead of 4 bits, I
* use it to refer to 8 bits of "nibblized" data. Sorry.)
*
* THOUGHT: we have access to the self-sync byte data. We could use this
* to pretty easily convert a track to 6656-byte format, which would allow
* conversion to .NIB instead of .APP. This would probably need to be
* specified as a global preference (how to open .FDI), though we could
* just drag the self-sync flags around in a parallel data structure and
* invent a format-conversion API. The former seems easier, and should
* be easy to explain in the UI.
*/
#include "StdAfx.h"
#include "DiskImgPriv.h"
/*
* ===========================================================================
* FDI compression functions
* ===========================================================================
*/
/*
* Pack a disk image with FDI.
*/
DIError WrapperFDI::PackDisk(GenericFD* pSrcGFD, GenericFD* pWrapperGFD)
{
DIError dierr = kDIErrGeneric; // not yet
return dierr;
}
/*
* ===========================================================================
* FDI expansion functions
* ===========================================================================
*/
/*
* Unpack an FDI-encoded disk image from "pGFD" to a new memory buffer
* created in "*ppNewGFD". The output is a collection of variable-length
* nibble tracks.
*
* "pNewGFD" will need to hold (kTrackAllocSize * numCyls * numHeads)
* bytes of data.
*
* Fills in "fNibbleTrackInfo".
*/
DIError WrapperFDI::UnpackDisk525(GenericFD* pGFD, GenericFD* pNewGFD,
int numCyls, int numHeads)
{
DIError dierr = kDIErrNone;
uint8_t nibbleBuf[kNibbleBufLen];
uint8_t* inputBuf = NULL;
bool goodTracks[kMaxNibbleTracks525];
int inputBufLen = -1;
int badTracks = 0;
int trk, type, length256;
long nibbleLen;
bool result;
assert(numHeads == 1);
memset(goodTracks, false, sizeof(goodTracks));
dierr = pGFD->Seek(kMinHeaderLen, kSeekSet);
if (dierr != kDIErrNone) {
LOGI("FDI: track seek failed (offset=%d)", kMinHeaderLen);
goto bail;
}
for (trk = 0; trk < numCyls * numHeads; trk++) {
GetTrackInfo(trk, &type, &length256);
LOGI("%2d.%d: t=0x%02x l=%d (%d)", trk / numHeads, trk % numHeads,
type, length256, length256 * 256);
/* if we have data to read, read it */
if (length256 > 0) {
if (length256 * 256 > inputBufLen) {
/* allocate or increase the size of the input buffer */
delete[] inputBuf;
inputBufLen = length256 * 256;
inputBuf = new uint8_t[inputBufLen];
if (inputBuf == NULL) {
dierr = kDIErrMalloc;
goto bail;
}
}
dierr = pGFD->Read(inputBuf, length256 * 256);
if (dierr != kDIErrNone)
goto bail;
} else {
assert(type == 0x00);
}
/* figure out what we want to do with this track */
switch (type) {
case 0x00:
/* blank track */
badTracks++;
memset(nibbleBuf, 0xff, sizeof(nibbleBuf));
nibbleLen = kTrackLenNb2525;
break;
case 0x80:
case 0x90:
case 0xa0:
case 0xb0:
/* low-level pulse-index */
nibbleLen = kNibbleBufLen;
result = DecodePulseTrack(inputBuf, length256*256, kBitRate525,
nibbleBuf, &nibbleLen);
if (!result) {
/* something failed in the decoder; fake it */
badTracks++;
memset(nibbleBuf, 0xff, sizeof(nibbleBuf));
nibbleLen = kTrackLenNb2525;
} else {
goodTracks[trk] = true;
}
if (nibbleLen > kTrackAllocSize) {
LOGI(" FDI: decoded %ld nibbles, buffer is only %d",
nibbleLen, kTrackAllocSize);
dierr = kDIErrBadRawData;
goto bail;
}
break;
default:
LOGI("FDI: unexpected track type 0x%04x", type);
dierr = kDIErrUnsupportedImageFeature;
goto bail;
}
fNibbleTrackInfo.offset[trk] = trk * kTrackAllocSize;
fNibbleTrackInfo.length[trk] = nibbleLen;
FixBadNibbles(nibbleBuf, nibbleLen);
dierr = pNewGFD->Seek(fNibbleTrackInfo.offset[trk], kSeekSet);
if (dierr != kDIErrNone)
goto bail;
dierr = pNewGFD->Write(nibbleBuf, nibbleLen);
if (dierr != kDIErrNone)
goto bail;
LOGI(" FDI: track %d: wrote %ld nibbles", trk, nibbleLen);
//offset += 256 * length256;
//break; // DEBUG DEBUG
}
LOGI(" FDI: %d of %d tracks bad or blank",
badTracks, numCyls * numHeads);
if (badTracks > (numCyls * numHeads) / 2) {
LOGI("FDI: too many bad tracks");
dierr = kDIErrBadRawData;
goto bail;
}
/*
* For convenience we want this to be 35 or 40 tracks. Start by
* reducing trk to 35 if there are no good tracks at 35+.
*/
bool want40;
int i;
want40 = false;
for (i = kTrackCount525; i < kMaxNibbleTracks525; i++) {
if (goodTracks[i]) {
want40 = true;
break;
}
}
if (!want40 && trk > kTrackCount525) {
LOGI(" FDI: no good tracks past %d, reducing from %d",
kTrackCount525, trk);
trk = kTrackCount525; // nothing good out there, roll back
}
/*
* Now pad us *up* to 35 if we have fewer than that.
*/
memset(nibbleBuf, 0xff, sizeof(nibbleBuf));
for ( ; trk < kMaxNibbleTracks525; trk++) {
if (trk == kTrackCount525)
break;
fNibbleTrackInfo.offset[trk] = trk * kTrackAllocSize;
fNibbleTrackInfo.length[trk] = kTrackLenNb2525;
fNibbleTrackInfo.numTracks++;
dierr = pNewGFD->Seek(fNibbleTrackInfo.offset[trk], kSeekSet);
if (dierr != kDIErrNone)
goto bail;
dierr = pNewGFD->Write(nibbleBuf, nibbleLen);
if (dierr != kDIErrNone)
goto bail;
}
assert(trk == kTrackCount525 || trk == kMaxNibbleTracks525);
fNibbleTrackInfo.numTracks = trk;
bail:
delete[] inputBuf;
return dierr;
}
/*
* Unpack an FDI-encoded disk image from "pGFD" to 800K of ProDOS-ordered
* 512-byte blocks in "pNewGFD".
*
* We could keep the 12-byte "tags" on each block, but they were never
* really used in the Apple II world.
*
* We also need to set up a "bad block" map to identify parts that we had
* trouble unpacking.
*/
DIError WrapperFDI::UnpackDisk35(GenericFD* pGFD, GenericFD* pNewGFD, int numCyls,
int numHeads, LinearBitmap* pBadBlockMap)
{
DIError dierr = kDIErrNone;
uint8_t nibbleBuf[kNibbleBufLen];
uint8_t* inputBuf = NULL;
uint8_t outputBuf[kMaxSectors35 * kBlockSize]; // 6KB
int inputBufLen = -1;
int badTracks = 0;
int trk, type, length256;
long nibbleLen;
bool result;
assert(numHeads == 2);
dierr = pGFD->Seek(kMinHeaderLen, kSeekSet);
if (dierr != kDIErrNone) {
LOGI("FDI: track seek failed (offset=%d)", kMinHeaderLen);
goto bail;
}
pNewGFD->Rewind();
for (trk = 0; trk < numCyls * numHeads; trk++) {
GetTrackInfo(trk, &type, &length256);
LOGI("%2d.%d: t=0x%02x l=%d (%d)", trk / numHeads, trk % numHeads,
type, length256, length256 * 256);
/* if we have data to read, read it */
if (length256 > 0) {
if (length256 * 256 > inputBufLen) {
/* allocate or increase the size of the input buffer */
delete[] inputBuf;
inputBufLen = length256 * 256;
inputBuf = new uint8_t[inputBufLen];
if (inputBuf == NULL) {
dierr = kDIErrMalloc;
goto bail;
}
}
dierr = pGFD->Read(inputBuf, length256 * 256);
if (dierr != kDIErrNone)
goto bail;
} else {
assert(type == 0x00);
}
/* figure out what we want to do with this track */
switch (type) {
case 0x00:
/* blank track */
badTracks++;
memset(nibbleBuf, 0xff, sizeof(nibbleBuf));
nibbleLen = kTrackLenNb2525;
break;
case 0x80:
case 0x90:
case 0xa0:
case 0xb0:
/* low-level pulse-index */
nibbleLen = kNibbleBufLen;
result = DecodePulseTrack(inputBuf, length256*256,
BitRate35(trk/numHeads), nibbleBuf, &nibbleLen);
if (!result) {
/* something failed in the decoder; fake it */
badTracks++;
memset(nibbleBuf, 0xff, sizeof(nibbleBuf));
nibbleLen = kTrackLenNb2525;
}
if (nibbleLen > kNibbleBufLen) {
LOGI(" FDI: decoded %ld nibbles, buffer is only %d",
nibbleLen, kTrackAllocSize);
dierr = kDIErrBadRawData;
goto bail;
}
break;
default:
LOGI("FDI: unexpected track type 0x%04x", type);
dierr = kDIErrUnsupportedImageFeature;
goto bail;
}
LOGI(" FDI: track %d got %ld nibbles", trk, nibbleLen);
/*
fNibbleTrackInfo.offset[trk] = trk * kTrackAllocSize;
fNibbleTrackInfo.length[trk] = nibbleLen;
dierr = pNewGFD->Seek(fNibbleTrackInfo.offset[trk], kSeekSet);
if (dierr != kDIErrNone)
goto bail;
dierr = pNewGFD->Write(nibbleBuf, nibbleLen);
if (dierr != kDIErrNone)
goto bail;
*/
dierr = DiskImg::UnpackNibbleTrack35(nibbleBuf, nibbleLen, outputBuf,
trk / numHeads, trk % numHeads, pBadBlockMap);
if (dierr != kDIErrNone)
goto bail;
dierr = pNewGFD->Write(outputBuf,
kBlockSize * DiskImg::SectorsPerTrack35(trk / numHeads));
if (dierr != kDIErrNone) {
LOGI("FDI: failed writing disk blocks (%d * %d)",
kBlockSize, DiskImg::SectorsPerTrack35(trk / numHeads));
goto bail;
}
}
//fNibbleTrackInfo.numTracks = numCyls * numHeads;
bail:
delete[] inputBuf;
return dierr;
}
/*
* Return the approximate bit rate for the specified cylinder, in bits/sec.
*/
int WrapperFDI::BitRate35(int cyl)
{
if (cyl >= 0 && cyl <= 15)
return 375000; // 394rpm
else if (cyl <= 31)
return 343750; // 429rpm
else if (cyl <= 47)
return 312500; // 472rpm
else if (cyl <= 63)
return 281250; // 525rpm
else if (cyl <= 79)
return 250000; // 590rpm
else {
LOGI(" FDI: invalid 3.5 cylinder %d", cyl);
return 250000;
}
}
/*
* Fix any obviously-bad nibble values.
*
* This should be unlikely, but if we find several zeroes in a row due to
* garbled data from the drive, it can happen. We clean it up here so that,
* when we convert to another format (e.g. TrackStar), we don't flunk a
* simple high-bit screening test.
*
* (We could be more rigorous and test against valid disk bytes, but that's
* probably excessive.)
*/
void WrapperFDI::FixBadNibbles(uint8_t* nibbleBuf, long nibbleLen)
{
int badCount = 0;
while (nibbleLen--) {
if ((*nibbleBuf & 0x80) == 0) {
badCount++;
*nibbleBuf = 0xff;
}
nibbleBuf++;
}
if (badCount != 0) {
LOGI(" FDI: fixed %d bad nibbles", badCount);
}
}
/*
* Get the info for the Nth track. The track number is used as an index
* into the track descriptor table.
*
* Returns the track type and amount of data (/256).
*/
void WrapperFDI::GetTrackInfo(int trk, int* pType, int* pLength256)
{
uint16_t trackDescr;
trackDescr = fHeaderBuf[kTrackDescrOffset + trk * 2] << 8 |
fHeaderBuf[kTrackDescrOffset + trk * 2 +1];
*pType = (trackDescr & 0xff00) >> 8;
*pLength256 = trackDescr & 0x00ff;
switch (trackDescr & 0xf000) {
case 0x0000:
/* high-level type */
switch (trackDescr & 0xff00) {
case 0x0000:
/* blank track */
break;
default:
/* miscellaneous high-level type */
break;
}
break;
case 0x8000:
case 0x9000:
case 0xa000:
case 0xb000:
/* low-level type, length is 14 bits */
*pType = (trackDescr & 0xc000) >> 8;
*pLength256 = trackDescr & 0x3fff;
break;
case 0xc000:
case 0xd000:
/* mid-level format, value in 0n00 holds a bit rate index */
break;
case 0xe000:
case 0xf000:
/* raw MFM; for 0xf000, the value in 0n00 holds a bit rate index */
break;
default:
LOGI("Unexpected trackDescr 0x%04x", trackDescr);
*pType = 0x7e; // return an invalid value
*pLength256 = 0;
break;
}
}
/*
* Convert a track encoded as one or more pulse streams to nibbles.
*
* This decompresses the pulse streams in "inputBuf", then converts them
* to nibble form in "nibbleBuf".
*
* "*pNibbleLen" should hold the maximum size of the buffer. On success,
* it will hold the actual number of bytes used.
*
* Returns "true" on success, "false" on failure.
*/
bool WrapperFDI::DecodePulseTrack(const uint8_t* inputBuf, long inputLen,
int bitRate, uint8_t* nibbleBuf, long* pNibbleLen)
{
const int kSizeValueMask = 0x003fffff;
const int kSizeCompressMask = 0x00c00000;
const int kSizeCompressShift = 22;
PulseIndexHeader hdr;
uint32_t val;
bool result = false;
memset(&hdr, 0, sizeof(hdr));
hdr.numPulses = GetLongBE(&inputBuf[0x00]);
val = Get24BE(&inputBuf[0x04]);
hdr.avgStreamLen = val & kSizeValueMask;
hdr.avgStreamCompression = (val & kSizeCompressMask) >> kSizeCompressShift;
val = Get24BE(&inputBuf[0x07]);
hdr.minStreamLen = val & kSizeValueMask;
hdr.minStreamCompression = (val & kSizeCompressMask) >> kSizeCompressShift;
val = Get24BE(&inputBuf[0x0a]);
hdr.maxStreamLen = val & kSizeValueMask;
hdr.maxStreamCompression = (val & kSizeCompressMask) >> kSizeCompressShift;
val = Get24BE(&inputBuf[0x0d]);
hdr.idxStreamLen = val & kSizeValueMask;
hdr.idxStreamCompression = (val & kSizeCompressMask) >> kSizeCompressShift;
if (hdr.numPulses < 64 || hdr.numPulses > 131072) {
/* should be about 40,000 */
LOGI(" FDI: bad pulse count %ld in track", hdr.numPulses);
return false;
}
/* advance past the 16 hdr bytes; now pointing at "average" stream */
inputBuf += kPulseStreamDataOffset;
LOGI(" pulses: %ld", hdr.numPulses);
//LOGI(" avg: len=%d comp=%d", hdr.avgStreamLen, hdr.avgStreamCompression);
//LOGI(" min: len=%d comp=%d", hdr.minStreamLen, hdr.minStreamCompression);
//LOGI(" max: len=%d comp=%d", hdr.maxStreamLen, hdr.maxStreamCompression);
//LOGI(" idx: len=%d comp=%d", hdr.idxStreamLen, hdr.idxStreamCompression);
/*
* Uncompress or endian-swap the pulse streams.
*/
hdr.avgStream = new uint32_t[hdr.numPulses];
if (hdr.avgStream == NULL)
goto bail;
if (!UncompressPulseStream(inputBuf, hdr.avgStreamLen, hdr.avgStream,
hdr.numPulses, hdr.avgStreamCompression, 4))
{
goto bail;
}
inputBuf += hdr.avgStreamLen;
if (hdr.minStreamLen > 0) {
hdr.minStream = new uint32_t[hdr.numPulses];
if (hdr.minStream == NULL)
goto bail;
if (!UncompressPulseStream(inputBuf, hdr.minStreamLen, hdr.minStream,
hdr.numPulses, hdr.minStreamCompression, 4))
{
goto bail;
}
inputBuf += hdr.minStreamLen;
}
if (hdr.maxStreamLen > 0) {
hdr.maxStream = new uint32_t[hdr.numPulses];
if (!UncompressPulseStream(inputBuf, hdr.maxStreamLen, hdr.maxStream,
hdr.numPulses, hdr.maxStreamCompression, 4))
{
goto bail;
}
inputBuf += hdr.maxStreamLen;
}
if (hdr.idxStreamLen > 0) {
hdr.idxStream = new uint32_t[hdr.numPulses];
if (!UncompressPulseStream(inputBuf, hdr.idxStreamLen, hdr.idxStream,
hdr.numPulses, hdr.idxStreamCompression, 2))
{
goto bail;
}
inputBuf += hdr.idxStreamLen;
}
/*
* Convert the pulse streams to a nibble stream.
*/
result = ConvertPulseStreamsToNibbles(&hdr, bitRate, nibbleBuf, pNibbleLen);
// fall through with result
bail:
/* clean up */
if (hdr.avgStream != NULL)
delete[] hdr.avgStream;
if (hdr.minStream != NULL)
delete[] hdr.minStream;
if (hdr.maxStream != NULL)
delete[] hdr.maxStream;
if (hdr.idxStream != NULL)
delete[] hdr.idxStream;
return result;
}
/*
* Uncompress, or at least endian-swap, the input data.
*
* "inputLen" is the length in bytes of the input stream. For an uncompressed
* stream this should be equal to numPulses*bytesPerPulse, for a compressed
* stream it's the length of the compressed data.
*
* "bytesPerPulse" indicates the width of the input data. This will usually
* be 4, but is 2 for the index stream. The output is always 4 bytes/pulse.
* For Huffman-compressed data, it appears that the input is always 4 bytes.
*
* Returns "true" if all went well, "false" if we hit something that we
* couldn't handle.
*/
bool WrapperFDI::UncompressPulseStream(const uint8_t* inputBuf, long inputLen,
uint32_t* outputBuf, long numPulses, int format, int bytesPerPulse)
{
assert(bytesPerPulse == 2 || bytesPerPulse == 4);
/*
* Sample code has a snippet that says: if the format is "uncompressed"
* but inputLen < (numPulses*2), treat it as compressed. This may be
* for handling some badly-formed images. Not currently doing it here.
*/
if (format == kCompUncompressed) {
int i;
LOGE("NOT TESTED"); // remove this when we've tested it
if (inputLen != numPulses * bytesPerPulse) {
LOGI(" FDI: got unc inputLen=%ld, outputLen=%ld",
inputLen, numPulses * bytesPerPulse);
return false;
}
if (bytesPerPulse == 2) {
for (i = 0; i < numPulses; i++) {
*outputBuf++ = GetShortBE(inputBuf);
inputBuf += 2;
}
} else {
for (i = 0; i < numPulses; i++) {
*outputBuf++ = GetLongBE(inputBuf);
inputBuf += 4;
}
}
} else if (format == kCompHuffman) {
if (!ExpandHuffman(inputBuf, inputLen, outputBuf, numPulses))
return false;
//LOGI(" FDI: Huffman expansion succeeded");
} else {
LOGI(" FDI: got weird compression format %d", format);
return false;
}
return true;
}
/*
* Expand a Huffman-compressed stream.
*
* The code takes bit-slices across the entire input and compresses them
* separately with a static Huffman variant.
*
* "outputBuf" is expected to hold "numPulses" entries.
*
* This implementation is based on the fdi2raw code.
*/
bool WrapperFDI::ExpandHuffman(const uint8_t* inputBuf, long inputLen,
uint32_t* outputBuf, long numPulses)
{
HuffNode root;
const uint8_t* origInputBuf = inputBuf;
bool signExtend, sixteenBits;
int i, subStreamShift;
uint8_t bits;
uint8_t bitMask;
memset(outputBuf, 0, numPulses * sizeof(uint32_t));
subStreamShift = 1;
while (subStreamShift != 0) {
if (inputBuf - origInputBuf >= inputLen) {
LOGI(" FDI: overran input(1)");
return false;
}
/* decode the sub-stream header */
bits = *inputBuf++;
subStreamShift = bits & 0x7f; // low-order bit number
signExtend = (bits & 0x80) != 0;
bits = *inputBuf++;
sixteenBits = (bits & 0x80) != 0; // ignore redundant high-order
//LOGI(" FDI: shift=%d ext=%d sixt=%d",
// subStreamShift, signExtend, sixteenBits);
/* decode the Huffman tree structure */
root.left = NULL;
root.right = NULL;
bitMask = 0;
inputBuf = HuffExtractTree(inputBuf, &root, &bits, &bitMask);
//LOGI(" after tree: off=%d", inputBuf - origInputBuf);
/* extract the Huffman node values */
if (sixteenBits)
inputBuf = HuffExtractValues16(inputBuf, &root);
else
inputBuf = HuffExtractValues8(inputBuf, &root);
if (inputBuf - origInputBuf >= inputLen) {
LOGI(" FDI: overran input(2)");
return false;
}
//LOGI(" after values: off=%d", inputBuf - origInputBuf);
/* decode the data over all pulses */
bitMask = 0;
for (i = 0; i < numPulses; i++) {
uint32_t outVal;
const HuffNode* pCurrent = &root;
/* chase down the tree until we hit a leaf */
/* (note: nodes have two kids or none) */
while (true) {
if (pCurrent->left == NULL) {
break;
} else {
bitMask >>= 1;
if (bitMask == 0) {
bitMask = 0x80;
bits = *inputBuf++;
}
if (bits & bitMask)
pCurrent = pCurrent->right;
else
pCurrent = pCurrent->left;
}
}
outVal = outputBuf[i];
if (signExtend) {
if (sixteenBits)
outVal |= HuffSignExtend16(pCurrent->val) << subStreamShift;
else
outVal |= HuffSignExtend8(pCurrent->val) << subStreamShift;
} else {
outVal |= pCurrent->val << subStreamShift;
}
outputBuf[i] = outVal;
}
HuffFreeNodes(root.left);
HuffFreeNodes(root.right);
}
if (inputBuf - origInputBuf != inputLen) {
LOGI(" FDI: warning: Huffman input %ld vs. %ld",
inputBuf - origInputBuf, inputLen);
return false;
}
return true;
}
/*
* Recursively extract the Huffman tree structure for this sub-stream.
*/
const uint8_t* WrapperFDI::HuffExtractTree(const uint8_t* inputBuf,
HuffNode* pNode, uint8_t* pBits, uint8_t* pBitMask)
{
uint8_t val;
if (*pBitMask == 0) {
*pBits = *inputBuf++;
*pBitMask = 0x80;
}
val = *pBits & *pBitMask;
(*pBitMask) >>= 1;
//LOGI(" val=%d", val);
if (val != 0) {
assert(pNode->left == NULL);
assert(pNode->right == NULL);
return inputBuf;
} else {
pNode->left = new HuffNode;
memset(pNode->left, 0, sizeof(HuffNode));
inputBuf = HuffExtractTree(inputBuf, pNode->left, pBits, pBitMask);
pNode->right = new HuffNode;
memset(pNode->right, 0, sizeof(HuffNode));
return HuffExtractTree(inputBuf, pNode->right, pBits, pBitMask);
}
}
/*
* Recursively get the 16-bit values for our Huffman tree from the stream.
*/
const uint8_t* WrapperFDI::HuffExtractValues16(const uint8_t* inputBuf,
HuffNode* pNode)
{
if (pNode->left == NULL) {
pNode->val = (*inputBuf++) << 8;
pNode->val |= *inputBuf++;
return inputBuf;
} else {
inputBuf = HuffExtractValues16(inputBuf, pNode->left);
return HuffExtractValues16(inputBuf, pNode->right);
}
}
/*
* Recursively get the 8-bit values for our Huffman tree from the stream.
*/
const uint8_t* WrapperFDI::HuffExtractValues8(const uint8_t* inputBuf,
HuffNode* pNode)
{
if (pNode->left == NULL) {
pNode->val = *inputBuf++;
return inputBuf;
} else {
inputBuf = HuffExtractValues8(inputBuf, pNode->left);
return HuffExtractValues8(inputBuf, pNode->right);
}
}
/*
* Recursively free up the current node and all nodes beneath it.
*/
void WrapperFDI::HuffFreeNodes(HuffNode* pNode)
{
if (pNode != NULL) {
HuffFreeNodes(pNode->left);
HuffFreeNodes(pNode->right);
delete pNode;
}
}
/*
* Sign-extend a 16-bit value to 32 bits.
*/
uint32_t WrapperFDI::HuffSignExtend16(uint32_t val)
{
if (val & 0x8000)
val |= 0xffff0000;
return val;
}
/*
* Sign-extend an 8-bit value to 32 bits.
*/
uint32_t WrapperFDI::HuffSignExtend8(uint32_t val)
{
if (val & 0x80)
val |= 0xffffff00;
return val;
}
/* use these to extract values from the index stream */
#define ZeroStateCount(_val) (((_val) >> 8) & 0xff)
#define OneStateCount(_val) ((_val) & 0xff)
/*
* Convert our collection of pulse streams into (what we hope will be)
* Apple II nibble form.
*
* This modifies the contents of the minStream, maxStream, and idxStream
* arrays.
*
* "*pNibbleLen" should hold the maximum size of the buffer. On success,
* it will hold the actual number of bytes used.
*/
bool WrapperFDI::ConvertPulseStreamsToNibbles(PulseIndexHeader* pHdr, int bitRate,
uint8_t* nibbleBuf, long* pNibbleLen)
{
uint32_t* fakeIdxStream = NULL;
bool result = false;
int i;
/*
* Stream pointers. If we don't have a stream, fake it.
*/
uint32_t* avgStream;
uint32_t* minStream;
uint32_t* maxStream;
uint32_t* idxStream;
avgStream = pHdr->avgStream;
if (pHdr->minStream != NULL && pHdr->maxStream != NULL) {
minStream = pHdr->minStream;
maxStream = pHdr->maxStream;
/* adjust the values in the min/max streams */
for (i = 0; i < pHdr->numPulses; i++) {
maxStream[i] = avgStream[i] + minStream[i] - maxStream[i];
minStream[i] = avgStream[i] - minStream[i];
}
} else {
minStream = pHdr->avgStream;
maxStream = pHdr->avgStream;
}
if (pHdr->idxStream != NULL)
idxStream = pHdr->idxStream;
else {
/*
* The UAE sample code has some stuff to fake it. The code there
* is broken, so I'm guessing it has never been used, but I'm going
* to replicate it here (and probably never test it either). This
* assumes that the original was written for a big-endian machine.
*/
LOGI(" FDI: HEY: using fake index stream");
DebugBreak();
fakeIdxStream = new uint32_t[pHdr->numPulses];
if (fakeIdxStream == NULL) {
LOGI(" FDI: unable to alloc fake idx stream");
goto bail;
}
for (i = 1; i < pHdr->numPulses; i++)
fakeIdxStream[i] = 0x0200; // '1' for two, '0' for zero
fakeIdxStream[0] = 0x0101; // '1' for one, '0' for one
idxStream = fakeIdxStream;
}
/*
* Compute a value for maxIndex.
*/
uint32_t maxIndex;
maxIndex = 0;
for (i = 0; i < pHdr->numPulses; i++) {
uint32_t sum;
/* add up the two single-byte values in the index stream */
sum = ZeroStateCount(idxStream[i]) + OneStateCount(idxStream[i]);
if (sum > maxIndex)
maxIndex = sum;
}
/*
* Compute a value for indexOffset.
*/
int indexOffset;
indexOffset = 0;
for (i = 0; i < pHdr->numPulses && OneStateCount(idxStream[i]) != 0; i++) {
/* "falling edge, replace with ZeroStateCount for rising edge" */
}
if (i < pHdr->numPulses) {
int start = i;
do {
i++;
if (i >= pHdr->numPulses)
i = 0; // wrapped around
} while (i != start && ZeroStateCount(idxStream[i]) == 0);
if (i != start) {
/* index pulse detected */
while (i != start &&
ZeroStateCount(idxStream[i]) > OneStateCount(idxStream[i]))
{
i++;
if (i >= pHdr->numPulses)
i = 0;
}
if (i != start)
indexOffset = i; /* index position detected */
}
}
/*
* Compute totalAvg and weakBits, and rewrite idxStream.
* (We don't actually use weakBits.)
*/
uint32_t totalAvg;
int weakBits;
totalAvg = weakBits = 0;
for (i = 0; i < pHdr->numPulses; i++) {
unsigned int sum;
sum = ZeroStateCount(idxStream[i]) + OneStateCount(idxStream[i]);
if (sum >= maxIndex)
totalAvg += avgStream[i]; // could this overflow...?
else
weakBits++;
idxStream[i] = sum;
}
LOGI(" FDI: maxIndex=%u indexOffset=%d totalAvg=%d weakBits=%d",
maxIndex, indexOffset, totalAvg, weakBits);
/*
* Take our altered stream values and the stuff we've calculated,
* and convert the pulse values into bits.
*/
uint8_t bitBuffer[kBitBufferSize];
int bitCount;
bitCount = kBitBufferSize;
if (!ConvertPulsesToBits(avgStream, minStream, maxStream, idxStream,
pHdr->numPulses, maxIndex, indexOffset, totalAvg, bitRate,
bitBuffer, &bitCount))
{
LOGI(" FDI: ConvertPulsesToBits() failed");
goto bail;
}
//LOGI(" Got %d bits", bitCount);
if (bitCount < 0) {
LOGI(" FDI: overran output bit buffer");
goto bail;
}
/*
* We have a bit stream with the GCR bits as they appear coming out of
* the IWM. Convert it to 8-bit nibble form.
*
* We currently discard self-sync byte information.
*/
if (!ConvertBitsToNibbles(bitBuffer, bitCount, nibbleBuf, pNibbleLen))
{
LOGI(" FDI: ConvertBitsToNibbles() failed");
goto bail;
}
result = true;
bail:
delete[] fakeIdxStream;
return result;
}
/*
* Local data structures. Not worth putting in the header file.
*/
const int kPulseLimitVal = 15; /* "tolerance of 15%" */
typedef struct PulseSamples {
uint32_t size;
int numBits;
} PulseSamples;
class PulseSampleCollection {
public:
PulseSampleCollection(void) {
fArrayIndex = fTotalDiv = -1;
fTotal = 0;
}
~PulseSampleCollection(void) {}
void Create(int stdMFM2BitCellSize, int numBits) {
int i;
fArrayIndex = 0;
fTotal = 0;
fTotalDiv = 0;
for (i = 0; i < kSampleArrayMax; i++) {
// "That is (total track length / 50000) for Amiga double density"
fArray[i].size = stdMFM2BitCellSize;
fTotal += fArray[i].size;
fArray[i].numBits = numBits;
fTotalDiv += fArray[i].numBits;
}
assert(fTotalDiv != 0);
}
uint32_t GetTotal(void) const { return fTotal; }
int GetTotalDiv(void) const { return fTotalDiv; }
void AdjustTotal(long val) { fTotal += val; }
void AdjustTotalDiv(int val) { fTotalDiv += val; }
void IncrIndex(void) {
fArrayIndex++;
if (fArrayIndex >= kSampleArrayMax)
fArrayIndex = 0;
}
PulseSamples* GetCurrentArrayEntry(void) {
return &fArray[fArrayIndex];
}
enum {
kSampleArrayMax = 10,
};
private:
PulseSamples fArray[kSampleArrayMax];
int fArrayIndex;
uint32_t fTotal;
int fTotalDiv;
};
#define MY_RANDOM
#ifdef MY_RANDOM
/* replace rand() with my function */
#define rand() MyRand()
/*
* My psuedo-random number generator, which is even less random than
* rand(). It is, however, consistent across all platforms, and the
* value for RAND_MAX is small enough to avoid some integer overflow
* problems that the code has with (2^31-1) implementations.
*/
#undef RAND_MAX
#define RAND_MAX 32767
int WrapperFDI::MyRand(void)
{
const int kNumStates = 31;
const int kQuantum = RAND_MAX / (kNumStates+1);
static int state = 0;
int retVal;
state++;
if (state == kNumStates)
state = 0;
retVal = (kQuantum * state) + (kQuantum / 2);
assert(retVal >= 0 && retVal <= RAND_MAX);
return retVal;
}
#endif
/*
* Convert the pulses we've read to a bit stream. This is a tad complex
* because the FDI scanner was reading a GCR disk with an MFM drive.
*
* Pass the output buffer size in bytes in "*pOutputLen". The actual number
* of *bits* output is returned in it.
*
* This is a fairly direct conversion from the sample code. There's a lot
* here that I haven't taken the time to figure out.
*/
bool WrapperFDI::ConvertPulsesToBits(const uint32_t* avgStream,
const uint32_t* minStream, const uint32_t* maxStream,
const uint32_t* idxStream, int numPulses, int maxIndex,
int indexOffset, uint32_t totalAvg, int bitRate,
uint8_t* outputBuf, int* pOutputLen)
{
PulseSampleCollection samples;
BitOutputBuffer bitOutput(outputBuf, *pOutputLen);
/* magic numbers, from somewhere */
const uint32_t kStdMFM2BitCellSize = (totalAvg * 5) / bitRate;
const uint32_t kStdMFM8BitCellSize = (totalAvg * 20) / bitRate;
int mfmMagic = 0; // if set to 1, decode as MFM rather than GCR
bool result = false;
int i;
//int debugCounter = 0;
/* sample code doesn't do this, but I want consistent results */
srand(0);
/*
* "detects a long-enough stable pulse coming just after another
* stable pulse"
*/
i = 1;
while (i < numPulses &&
(idxStream[i] < (uint32_t) maxIndex ||
idxStream[i-1] < (uint32_t) maxIndex ||
minStream[i] < (kStdMFM2BitCellSize - (kStdMFM2BitCellSize / 4))
))
{
i++;
}
if (i == numPulses) {
LOGW(" FDI: no stable and long-enough pulse in track");
goto bail;
}
/*
* Set up some variables.
*/
int nextI, endOfData, adjust, /*bitOffset,*/ step;
uint32_t refPulse;
long jitter;
samples.Create(kStdMFM2BitCellSize, 1 + mfmMagic);
nextI = i;
endOfData = i;
i--;
adjust = 0;
//bitOffset = 0;
refPulse = 0;
jitter = 0;
step = -1;
/*
* Run through the data three times:
* (-1) do stuff
* (0) do more stuff
* (1) output bits
*/
while (step < 2) {
/*
* Calculates the current average bit rate from previously
* decoded data.
*/
uint32_t avgSize;
int kCell8Limit = (kPulseLimitVal * kStdMFM8BitCellSize) / 100;
/* this is the new average size for one MFM bit */
avgSize = (samples.GetTotal() << (2 + mfmMagic)) / samples.GetTotalDiv();
/*
* Prevent avgSize from getting too far out of whack.
*
* "you can try tighter ranges than 25%, or wider ranges. I would
* probably go for tighter..."
*/
if ((avgSize < kStdMFM8BitCellSize - kCell8Limit) ||
(avgSize > kStdMFM8BitCellSize + kCell8Limit))
{
avgSize = kStdMFM8BitCellSize;
}
/*
* Get the next long-enough pulse (may require more than one pulse).
*/
uint32_t pulse;
pulse = 0;
while (pulse < ((avgSize / 4) - (avgSize / 16))) {
uint32_t avgPulse, minPulse, maxPulse;
/* advance i */
i++;
if (i >= numPulses)
i = 0; // wrapped around
/* advance nextI */
if (i == nextI) {
do {
nextI++;
if (nextI >= numPulses)
nextI = 0;
} while (idxStream[nextI] < (uint32_t) maxIndex);
}
if (idxStream[i] >= (uint32_t) maxIndex) {
/* stable pulse */
avgPulse = avgStream[i] - jitter;
minPulse = minStream[i];
maxPulse = maxStream[i];
if (jitter >= 0)
maxPulse -= jitter;
else
minPulse -= jitter;
if (maxStream[nextI] - avgStream[nextI] < avgPulse - minPulse)
minPulse = avgPulse - (maxStream[nextI] - avgStream[nextI]);
if (avgStream[nextI] - minStream[nextI] < maxPulse - avgPulse)
maxPulse = avgPulse + (avgStream[nextI] - minStream[nextI]);
if (minPulse < refPulse)
minPulse = refPulse;
/*
* This appears to use a pseudo-random number generator
* to dither the signal. This strikes me as highly
* questionable, but I'm trying to recreate what the sample
* code does, and I don't fully understand this stuff.
*/
int randVal;
randVal = rand();
if (randVal < (RAND_MAX / 2)) {
if (randVal > (RAND_MAX / 4)) {
if (randVal <= (3 * (RAND_MAX / 8)))
randVal = (2 * randVal) - (RAND_MAX / 4);
else
randVal = (4 * randVal) - RAND_MAX;
}
jitter = 0 - (randVal * (avgPulse - minPulse)) / RAND_MAX;
} else {
randVal -= RAND_MAX / 2;
if (randVal > (RAND_MAX / 4)) {
if (randVal <= (3 * (RAND_MAX / 8)))
randVal = (2 * randVal) - (RAND_MAX / 4);
else
randVal = (4 * randVal) - RAND_MAX;
}
jitter = (randVal * (maxPulse - avgPulse)) / RAND_MAX;
}
avgPulse += jitter;
if (avgPulse < minPulse || avgPulse > maxPulse) {
/* this is bad -- we're out of bounds */
LOGI(" FDI: avgPulse out of bounds: avg=%u min=%u max=%u",
avgPulse, minPulse, maxPulse);
}
if (avgPulse < refPulse) {
/* I guess this is also bad */
LOGI(" FDI: avgPulse < refPulse (%u %u)",
avgPulse, refPulse);
}
pulse += avgPulse - refPulse;
refPulse = 0;
/*
* If we've reached the end, advance to the next step.
*/
if (i == endOfData)
step++;
} else if ((uint32_t) rand() <= (idxStream[i] * RAND_MAX) / maxIndex) {
/* futz with it */
int randVal;
avgPulse = avgStream[i];
minPulse = minStream[i];
maxPulse = maxStream[i];
randVal = rand();
if (randVal < (RAND_MAX / 2)) {
if (randVal > (RAND_MAX / 4)) {
if (randVal <= (3 * (RAND_MAX / 8)))
randVal = (2 * randVal) - (RAND_MAX / 4);
else
randVal = (4 * randVal) - RAND_MAX;
}
avgPulse -= (randVal * (avgPulse - minPulse)) / RAND_MAX;
} else {
randVal -= RAND_MAX / 2;
if (randVal > (RAND_MAX / 4)) {
if (randVal <= (3 * (RAND_MAX / 8)))
randVal = (2 * randVal) - (RAND_MAX / 4);
else
randVal = (4 * randVal) - RAND_MAX;
}
avgPulse += (randVal * (maxPulse - avgPulse)) / RAND_MAX;
}
if (avgPulse > refPulse &&
avgPulse < (avgStream[nextI] - jitter))
{
pulse += avgPulse - refPulse;
refPulse = avgPulse;
}
} else {
// do nothing
}
}
/*
* "gets the size in bits from the pulse width, considering the current
* average bitrate"
*
* "realSize" will end up holding the number of bits we're going
* to output for this pulse.
*/
uint32_t adjustedPulse;
int realSize;
adjustedPulse = pulse;
realSize = 0;
if (mfmMagic != 0) {
while (adjustedPulse >= avgSize) {
realSize += 4;
adjustedPulse -= avgSize / 2;
}
adjustedPulse <<= 3;
while (adjustedPulse >= ((avgSize * 4) + (avgSize / 4))) {
realSize += 2;
adjustedPulse -= avgSize * 2;
}
if (adjustedPulse >= ((avgSize * 3) + (avgSize / 4))) {
if (adjustedPulse <= ((avgSize * 4) - (avgSize / 4))) {
if ((2* ((adjustedPulse >> 2) - adjust)) <=
((2 * avgSize) - (avgSize / 4)))
{
realSize += 3;
} else {
realSize += 4;
}
} else {
realSize += 4;
}
} else {
if (adjustedPulse > ((avgSize * 3) - (avgSize / 4))) {
realSize += 3;
} else {
if (adjustedPulse >= ((avgSize * 2) + (avgSize / 4))) {
if ((2 * ((adjustedPulse >> 2) - adjust)) <
(avgSize + (avgSize / 4)))
{
realSize += 2;
} else {
realSize += 3;
}
} else {
realSize += 2;
}
}
}
} else {
/* mfmMagic == 0, whatever that means */
while (adjustedPulse >= (2 * avgSize)) {
realSize += 4;
adjustedPulse -= avgSize;
}
adjustedPulse <<= 2;
while (adjustedPulse >= ((avgSize * 3) + (avgSize / 4))) {
realSize += 2;
adjustedPulse -= avgSize * 2;
}
if (adjustedPulse >= ((avgSize * 2) + (avgSize / 4))) {
if (adjustedPulse <= ((avgSize * 3) - (avgSize / 4))) {
if (((adjustedPulse >> 1) - adjust) <
(avgSize + (avgSize / 4)))
{
realSize += 2;
} else {
realSize += 3;
}
} else {
realSize += 3;
}
} else {
if (adjustedPulse > ((avgSize * 2) - (avgSize / 4)))
realSize += 2;
else {
if (adjustedPulse >= (avgSize + (avgSize / 4))) {
if (((adjustedPulse >> 1) - adjust) <=
(avgSize - (avgSize / 4)))
{
realSize++;
} else {
realSize += 2;
}
} else {
realSize++;
}
}
}
}
/*
* "after one pass to correctly initialize the average bitrate,
* outputs the bits"
*/
if (step == 1) {
int j;
for (j = realSize; j > 1; j--)
bitOutput.WriteBit(0);
bitOutput.WriteBit(1);
}
/*
* Prepare for next pulse.
*/
adjust = ((realSize * avgSize) / (4 << mfmMagic)) - pulse;
PulseSamples* pSamples;
pSamples = samples.GetCurrentArrayEntry();
samples.AdjustTotal(-(long)pSamples->size);
samples.AdjustTotalDiv(-pSamples->numBits);
pSamples->size = pulse;
pSamples->numBits = realSize;
samples.AdjustTotal(pulse);
samples.AdjustTotalDiv(realSize);
samples.IncrIndex();
}
*pOutputLen = bitOutput.Finish();
LOGI(" FDI: converted pulses to %d bits", *pOutputLen);
result = true;
bail:
return result;
}
/*
* Convert a stream of GCR bits into nibbles.
*
* The stream includes 9-bit and 10-bit self-sync bytes. We need to process
* the bits as if we were an Apple II, shifting bits into a register until
* we get a 1 in the msb.
*
* There is a (roughly) 7 in 8 chance that we will not start out reading
* the stream on a byte boundary. We have to read for a bit to let the
* self-sync bytes do their job.
*
* "*pNibbleLen" should hold the maximum size of the buffer. On success,
* it will hold the actual number of bytes used.
*/
bool WrapperFDI::ConvertBitsToNibbles(const uint8_t* bitBuffer, int bitCount,
uint8_t* nibbleBuf, long* pNibbleLen)
{
BitInputBuffer inputBuffer(bitBuffer, bitCount);
const uint8_t* nibbleBufStart = nibbleBuf;
long outputBufSize = *pNibbleLen;
bool result = false;
uint8_t val;
bool wrap;
/*
* Start 3/4 of the way through the buffer. That should give us a
* couple of self-sync zones before we hit the end of the buffer.
*/
inputBuffer.SetStartPosition(3 * (bitCount / 4));
/*
* Run until we wrap. We should be in sync by that point.
*/
wrap = false;
while (!wrap) {
val = inputBuffer.GetByte(&wrap);
if ((val & 0x80) == 0)
val = (val << 1) | inputBuffer.GetBit(&wrap);
if ((val & 0x80) == 0)
val = (val << 1) | inputBuffer.GetBit(&wrap);
if ((val & 0x80) == 0) {
// not allowed by GCR encoding, probably garbage between sectors
LOGI(" FDI: WARNING: more than 2 consecutive zeroes (sync)");
}
}
/*
* Extract the nibbles.
*/
inputBuffer.ResetBitsConsumed();
wrap = false;
while (true) {
val = inputBuffer.GetByte(&wrap);
if ((val & 0x80) == 0)
val = (val << 1) | inputBuffer.GetBit(&wrap);
if ((val & 0x80) == 0)
val = (val << 1) | inputBuffer.GetBit(&wrap);
if ((val & 0x80) == 0) {
LOGW(" FDI: WARNING: more than 2 consecutive zeroes (read)");
}
if (nibbleBuf - nibbleBufStart >= outputBufSize) {
LOGW(" FDI: bits overflowed nibble buffer");
goto bail;
}
*nibbleBuf++ = val;
/* if we wrapped around on this one, we've reached the start point */
if (wrap)
break;
}
if (inputBuffer.GetBitsConsumed() != bitCount) {
/* we dropped some or double-counted some */
LOGW(" FDI: WARNING: consumed %d of %d bits",
inputBuffer.GetBitsConsumed(), bitCount);
}
LOGI(" FDI: consumed %d of %d (first=0x%02x last=0x%02x)",
inputBuffer.GetBitsConsumed(), bitCount,
*nibbleBufStart, *(nibbleBuf-1));
*pNibbleLen = nibbleBuf - nibbleBufStart;
result = true;
bail:
return result;
}