ciderpress/reformat/NiftyList.cpp
2007-03-27 17:47:10 +00:00

379 lines
8.8 KiB
C++

/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* NiftyList database functions.
*
* The NList.Data file is divided into several sections, separated by lines
* that start with a '*'. They are:
*
* ProDOS 8 MLI calls (prefaced with some comments)
* ProDOS 16 / GS/OS
* System tools (incl. GSBug goodies)
* User tools
* E1xxxx vectors
* E0xxxx vectors
* Softswitches and F8 ROM routines (plus Davex stuff at Bxxx)
* 01xxxx vectors
* Nifty List service calls
* Resource type names
* Error codes (GS/OS and toolbox)
* HyperCardIIgs callbacks
* Request codes (for Finder extensions?)
*
* All lines have the general form:
* NNNN Text ...
* where "NNNN" is a 4-digit hexadecimal value.
*
* All sections are in some sort of sort order, though it differs for toolbox
* calls which are byte-reversed. Generally speaking, do not expect the input
* to be sorted already. The original EOL char is 0x0d, but the file may be
* converted for convenience in Windows, so supporting arbitrary EOLs is
* useful.
*/
#include "StdAfx.h"
#include "Reformat.h"
/*static*/ NiftyList::DataSet NiftyList::fP8MLI = { 0 };
/*static*/ NiftyList::DataSet NiftyList::fGSOS = { 0 };
/*static*/ NiftyList::DataSet NiftyList::fSystemTools = { 0 };
/*static*/ NiftyList::DataSet NiftyList::fE1Vectors = { 0 };
/*static*/ NiftyList::DataSet NiftyList::fE0Vectors = { 0 };
/*static*/ NiftyList::DataSet NiftyList::f00Addrs = { 0 };
/*static*/ NiftyList::DataSet NiftyList::f01Vectors = { 0 };
/*static*/ char* NiftyList::fFileData = nil;
/*static*/ bool NiftyList::fDataReady = false;
/*
* Load the NiftyList data file.
*/
/*static*/ bool
NiftyList::AppInit(const char* fileName)
{
FILE* fp = nil;
long fileLen;
char* pData;
bool result = false;
/*
* Open the NList.Data file and load the contents into memory.
*/
fp = fopen(fileName, "rb");
if (fp == nil) {
WMSG1("NL Unable to open '%s'\n", fileName);
goto bail;
} else {
WMSG1("NL Reading '%s'\n", fileName);
}
if (fseek(fp, 0, SEEK_END) < 0) {
WMSG0("Seek failed\n");
goto bail;
}
fileLen = ftell(fp);
rewind(fp);
fFileData = new char[fileLen];
if (fFileData == nil) {
WMSG1("Failed allocating %d bytes\n", fileLen);
goto bail;
}
if (fread(fFileData, fileLen, 1, fp) != 1) {
WMSG1("Failed reading NList.Data (%d bytes)\n", fileLen);
goto bail;
}
/*
* Scan the data.
*/
pData = fFileData;
if (!ReadSection(&pData, &fileLen, &fP8MLI, kModeNormal))
goto bail;
if (!ReadSection(&pData, &fileLen, &fGSOS, kModeNormal))
goto bail;
if (!ReadSection(&pData, &fileLen, &fSystemTools, kModeNormal))
goto bail;
if (!ReadSection(&pData, &fileLen, nil, kModeSkip)) // user tools
goto bail;
if (!ReadSection(&pData, &fileLen, &fE1Vectors, kModeNormal))
goto bail;
if (!ReadSection(&pData, &fileLen, &fE0Vectors, kModeNormal))
goto bail;
if (!ReadSection(&pData, &fileLen, &f00Addrs, kModeNormal))
goto bail;
if (!ReadSection(&pData, &fileLen, &f01Vectors, kModeNormal))
goto bail;
//DumpSection(fP8MLI);
//DumpSection(fGSOS);
fDataReady = true;
result = true;
WMSG0("NL NiftyList data loaded\n");
bail:
if (fp != nil)
fclose(fp);
return result;
}
/*
* Discard all allocated storage.
*/
/*static*/ bool
NiftyList::AppCleanup(void)
{
delete[] fP8MLI.pEntries;
delete[] fGSOS.pEntries;
delete[] fSystemTools.pEntries;
delete[] fE1Vectors.pEntries;
delete[] fE0Vectors.pEntries;
delete[] f00Addrs.pEntries;
delete[] f01Vectors.pEntries;
delete[] fFileData;
fDataReady = false;
return true;
}
/*
* Slurp one section from the data file. Stomps EOL markers.
*
* Leaves "*ppData" pointing at the start of the next section. "*pRemLen"
* is updated appropriately.
*/
/*static*/ bool
NiftyList::ReadSection(char** ppData, long* pRemLen, DataSet* pSet,
LoadMode mode)
{
assert(ppData != nil);
assert(pRemLen != nil);
assert(*ppData != nil);
assert(*pRemLen > 0);
assert(pSet != nil || mode == kModeSkip);
assert(mode == kModeNormal || mode == kModeSkip);
char* pData = *ppData;
long remLen = *pRemLen;
int lineLen, numLines, entry;
/*
* Count up the #of entries in this section.
*/
numLines = 0;
while (1) {
lineLen = ScanLine(pData, remLen);
if (lineLen == 0) {
WMSG1("Failed scanning line, remLen=%ld\n", remLen);
return false;
}
if (*pData == '*') {
pData += lineLen;
remLen -= lineLen;
break; // end of section reached
}
pData += lineLen;
remLen -= lineLen;
numLines++;
}
if (mode == kModeSkip) {
WMSG1(" NL Skipping %d entries in section\n", numLines);
*ppData = pData;
*pRemLen = remLen;
return true;
} else {
WMSG1(" NL Found %d entries in section\n", numLines);
}
/*
* Allocate storage for the lookup array.
*/
assert(pSet->numEntries == 0 && pSet->pEntries == nil);
pSet->pEntries = new NameValue[numLines];
if (pSet->pEntries == nil) {
WMSG0("NameValue alloc failed\n");
return false;
}
pSet->numEntries = numLines;
/*
* Add the entries to the list.
*/
pData = *ppData;
remLen = *pRemLen;
entry = 0;
while (1) {
lineLen = ScanLine(pData, remLen);
if (lineLen == 0) {
WMSG1("Failed scanning line(2), remLen=%ld\n", remLen);
return false;
}
if (*pData == '*') {
pData += lineLen;
remLen -= lineLen;
break; // end of section reached
}
if (lineLen < 6 || pData[4] != ' ') {
WMSG1("Found garbled line '%.80s'\n", pData);
return false;
}
if (pData[lineLen-2] == '\r' || pData[lineLen-2] == '\n')
pData[lineLen-2] = '\0';
else if (pData[lineLen-1] == '\r' || pData[lineLen-1] == '\n')
pData[lineLen-1] = '\0';
else {
WMSG2("No EOL found on '%.80s' (%d)\n", pData, lineLen);
}
assert(entry < numLines);
pSet->pEntries[entry].name = pData +5;
pSet->pEntries[entry].value = ConvHexFour(pData);
entry++;
pData += lineLen;
remLen -= lineLen;
}
assert(entry == numLines);
*ppData = pData;
*pRemLen = remLen;
/*
* Sort the array. In most cases it will already be in sorted order,
* which is a worst-case for qsort, but the only really big section
* (toolbox calls) is byte-swapped and sorts just fine with qsort.
*/
qsort(pSet->pEntries, numLines, sizeof(NameValue), SortNameValue);
return true;
}
/*
* Scan a line of text. Return the #of chars, including the EOL bytes.
*
* Returns 0 if there's no data, which can only happen if "remLen" is zero
* (i.e. we hit EOF).
*/
/*static*/ int
NiftyList::ScanLine(const char* pData, long remLen)
{
bool atEOL = false;
int lineLen = 0;
while (remLen--) {
if (*pData == '\r' || *pData == '\n')
atEOL = true;
else if (atEOL)
break;
pData++;
lineLen++;
}
return lineLen;
}
/*
* qsort() sort function.
*/
/*static*/ int
NiftyList::SortNameValue(const void* v1, const void* v2)
{
const NameValue* pnv1 = (const NameValue*) v1;
const NameValue* pnv2 = (const NameValue*) v2;
if (pnv1->value > pnv2->value)
return 1;
else if (pnv1->value < pnv2->value)
return -1;
else {
DebugBreak(); // should never be equal in well-formed file
return 0;
}
}
/*
* Hex digit converter.
*/
static inline int
HexValue(char ch)
{
if (ch >= '0' && ch <= '9')
return ch - '0';
if (ch >= 'A' && ch <= 'F')
return ch - 'A' +10;
if (ch >= 'a' && ch <= 'f')
return ch - 'a' +10;
DebugBreak(); // shouldn't happen on well-formed file
return -1;
}
/*
* Convert a 4-char hexadecimal value to an unsigned short.
*/
/*static*/ unsigned short
NiftyList::ConvHexFour(const char* data)
{
return HexValue(data[0]) << 12 |
HexValue(data[1]) << 8 |
HexValue(data[2]) << 4 |
HexValue(data[3]);
}
/*
* Dump the contents of a section.
*/
/*static*/ void
NiftyList::DumpSection(const DataSet& dataSet)
{
long ent;
WMSG1("Dumping section (count=%ld)\n", dataSet.numEntries);
for (ent = 0; ent < dataSet.numEntries; ent++) {
WMSG3("%4d: %04x '%s'\n",
ent, dataSet.pEntries[ent].value, dataSet.pEntries[ent].name);
}
}
/*
* Look up a value in the specified table. Returns the name, or "nil" if
* not found.
*
* This uses a binary search, so the entries must be in sorted order.
*/
/*static*/ const char*
NiftyList::Lookup(const DataSet& dataSet, unsigned short key)
{
assert(dataSet.numEntries > 0);
int high = dataSet.numEntries-1;
int low = 0;
int mid;
while (low <= high) {
mid = (high + low)/2;
unsigned short midVal = dataSet.pEntries[mid].value;
if (key > midVal)
low = mid +1;
else if (key < midVal)
high = mid -1;
else
return dataSet.pEntries[mid].name;
}
return nil;
}