2007-03-27 17:47:10 +00:00
|
|
|
/*
|
|
|
|
* 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:
|
|
|
|
*
|
2014-11-04 00:26:53 +00:00
|
|
|
* 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?)
|
2007-03-27 17:47:10 +00:00
|
|
|
*
|
|
|
|
* All lines have the general form:
|
2014-11-04 00:26:53 +00:00
|
|
|
* NNNN Text ...
|
2007-03-27 17:47:10 +00:00
|
|
|
* 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 };
|
|
|
|
|
2014-11-18 05:13:13 +00:00
|
|
|
/*static*/ char* NiftyList::fFileData = NULL;
|
2007-03-27 17:47:10 +00:00
|
|
|
/*static*/ bool NiftyList::fDataReady = false;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Load the NiftyList data file.
|
|
|
|
*/
|
2014-11-25 22:34:14 +00:00
|
|
|
/*static*/ bool NiftyList::AppInit(const WCHAR* fileName)
|
2007-03-27 17:47:10 +00:00
|
|
|
{
|
2014-11-18 05:13:13 +00:00
|
|
|
FILE* fp = NULL;
|
2014-11-04 00:26:53 +00:00
|
|
|
long fileLen;
|
|
|
|
char* pData;
|
|
|
|
bool result = false;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Open the NList.Data file and load the contents into memory.
|
|
|
|
*/
|
Large set of changes to restore CiderPress build.
CiderPress and MDC now compile, and execute far enough to open
their respective "about" boxes, but I doubt they'll do much
more than that.
* Switch from MBCS to UNICODE APIs
Microsoft switched to UTF-16 (by way of UCS-2) a long time ago,
and the support for MBCS seems to be getting phased out. So it's
time to switch to wide strings.
This is a bit awkward for CiderPress because it works with disk
and file archives with 8-bit filenames, and I want NufxLib and
DiskImgLib to continue to work on Linux (which has largely taken
the UTF-8 approach to Unicode). The libraries will continue to
work with 8-bit filenames, with CiderPress/MDC doing the
conversion at the appropriate point.
There were a couple of places where strings from a structure
handed back by one of the libraries were used directly in the UI,
or vice-versa, which is a problem because we have nowhere to
store the result of the conversion. These currently have fixed
place-holder "xyzzy" strings.
All UI strings are now wide.
Various format strings now use "%ls" and "%hs" to explicitly
specify wide and narrow. This doesn't play well with gcc, so
only the Windows-specific parts use those.
* Various updates to vcxproj files
The project-file conversion had some cruft that is now largely
gone. The build now has a common output directory for the EXEs
and libraries, avoiding the old post-build copy steps.
* Added zlib 1.2.8 and nufxlib 2.2.2 source snapshots
The old "prebuilts" directory is now gone. The libraries are now
built as part of building the apps.
I added a minimal set of files for zlib, and a full set for nufxlib.
The Linux-specific nufxlib goodies are included for the benefit of
the Linux utilities, which are currently broken (don't build).
* Replace symbols used for include guards
Symbols with a leading "__" are reserved.
2014-11-10 23:32:55 +00:00
|
|
|
fp = _wfopen(fileName, L"rb");
|
2014-11-18 05:13:13 +00:00
|
|
|
if (fp == NULL) {
|
2014-11-18 21:05:15 +00:00
|
|
|
LOGI("NL Unable to open '%ls'", fileName);
|
2014-11-04 00:26:53 +00:00
|
|
|
goto bail;
|
|
|
|
} else {
|
2014-11-18 21:05:15 +00:00
|
|
|
LOGI("NL Reading '%ls'", fileName);
|
2014-11-04 00:26:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (fseek(fp, 0, SEEK_END) < 0) {
|
2014-11-18 21:05:15 +00:00
|
|
|
LOGI("Seek failed");
|
2014-11-04 00:26:53 +00:00
|
|
|
goto bail;
|
|
|
|
}
|
|
|
|
fileLen = ftell(fp);
|
|
|
|
rewind(fp);
|
|
|
|
|
|
|
|
fFileData = new char[fileLen];
|
2014-11-18 05:13:13 +00:00
|
|
|
if (fFileData == NULL) {
|
2014-11-18 21:05:15 +00:00
|
|
|
LOGI("Failed allocating %d bytes", fileLen);
|
2014-11-04 00:26:53 +00:00
|
|
|
goto bail;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fread(fFileData, fileLen, 1, fp) != 1) {
|
2014-11-18 21:05:15 +00:00
|
|
|
LOGI("Failed reading NList.Data (%d bytes)", fileLen);
|
2014-11-04 00:26:53 +00:00
|
|
|
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;
|
2014-11-18 05:13:13 +00:00
|
|
|
if (!ReadSection(&pData, &fileLen, NULL, kModeSkip)) // user tools
|
2014-11-04 00:26:53 +00:00
|
|
|
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;
|
2014-11-18 21:05:15 +00:00
|
|
|
LOGI("NL NiftyList data loaded");
|
2007-03-27 17:47:10 +00:00
|
|
|
|
|
|
|
bail:
|
2014-11-18 05:13:13 +00:00
|
|
|
if (fp != NULL)
|
2014-11-04 00:26:53 +00:00
|
|
|
fclose(fp);
|
|
|
|
return result;
|
2007-03-27 17:47:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Discard all allocated storage.
|
|
|
|
*/
|
2014-11-25 22:34:14 +00:00
|
|
|
/*static*/ bool NiftyList::AppCleanup(void)
|
2007-03-27 17:47:10 +00:00
|
|
|
{
|
2014-11-04 00:26:53 +00:00
|
|
|
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;
|
2007-03-27 17:47:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Slurp one section from the data file. Stomps EOL markers.
|
|
|
|
*
|
|
|
|
* Leaves "*ppData" pointing at the start of the next section. "*pRemLen"
|
|
|
|
* is updated appropriately.
|
|
|
|
*/
|
2014-11-25 22:34:14 +00:00
|
|
|
/*static*/ bool NiftyList::ReadSection(char** ppData, long* pRemLen,
|
|
|
|
DataSet* pSet, LoadMode mode)
|
2007-03-27 17:47:10 +00:00
|
|
|
{
|
2014-11-18 05:13:13 +00:00
|
|
|
assert(ppData != NULL);
|
|
|
|
assert(pRemLen != NULL);
|
|
|
|
assert(*ppData != NULL);
|
2014-11-04 00:26:53 +00:00
|
|
|
assert(*pRemLen > 0);
|
2014-11-18 05:13:13 +00:00
|
|
|
assert(pSet != NULL || mode == kModeSkip);
|
2014-11-04 00:26:53 +00:00
|
|
|
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) {
|
2014-11-18 21:05:15 +00:00
|
|
|
LOGI("Failed scanning line, remLen=%ld", remLen);
|
2014-11-04 00:26:53 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (*pData == '*') {
|
|
|
|
pData += lineLen;
|
|
|
|
remLen -= lineLen;
|
|
|
|
break; // end of section reached
|
|
|
|
}
|
|
|
|
|
|
|
|
pData += lineLen;
|
|
|
|
remLen -= lineLen;
|
|
|
|
numLines++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mode == kModeSkip) {
|
2014-11-18 21:05:15 +00:00
|
|
|
LOGI(" NL Skipping %d entries in section", numLines);
|
2014-11-04 00:26:53 +00:00
|
|
|
*ppData = pData;
|
|
|
|
*pRemLen = remLen;
|
|
|
|
return true;
|
|
|
|
} else {
|
2014-11-18 21:05:15 +00:00
|
|
|
LOGI(" NL Found %d entries in section", numLines);
|
2014-11-04 00:26:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Allocate storage for the lookup array.
|
|
|
|
*/
|
2014-11-18 05:13:13 +00:00
|
|
|
assert(pSet->numEntries == 0 && pSet->pEntries == NULL);
|
2014-11-04 00:26:53 +00:00
|
|
|
|
|
|
|
pSet->pEntries = new NameValue[numLines];
|
2014-11-18 05:13:13 +00:00
|
|
|
if (pSet->pEntries == NULL) {
|
2014-11-18 21:05:15 +00:00
|
|
|
LOGI("NameValue alloc failed");
|
2014-11-04 00:26:53 +00:00
|
|
|
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) {
|
2014-11-18 21:05:15 +00:00
|
|
|
LOGI("Failed scanning line(2), remLen=%ld", remLen);
|
2014-11-04 00:26:53 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (*pData == '*') {
|
|
|
|
pData += lineLen;
|
|
|
|
remLen -= lineLen;
|
|
|
|
break; // end of section reached
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lineLen < 6 || pData[4] != ' ') {
|
2014-11-18 21:05:15 +00:00
|
|
|
LOGI("Found garbled line '%.80hs'", pData);
|
2014-11-04 00:26:53 +00:00
|
|
|
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 {
|
2014-11-18 21:05:15 +00:00
|
|
|
LOGI("No EOL found on '%.80hs' (%d)", pData, lineLen);
|
2014-11-04 00:26:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2007-03-27 17:47:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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).
|
|
|
|
*/
|
2014-11-25 22:34:14 +00:00
|
|
|
/*static*/ int NiftyList::ScanLine(const char* pData, long remLen)
|
2007-03-27 17:47:10 +00:00
|
|
|
{
|
2014-11-04 00:26:53 +00:00
|
|
|
bool atEOL = false;
|
|
|
|
int lineLen = 0;
|
|
|
|
|
|
|
|
while (remLen--) {
|
|
|
|
if (*pData == '\r' || *pData == '\n')
|
|
|
|
atEOL = true;
|
|
|
|
else if (atEOL)
|
|
|
|
break;
|
|
|
|
pData++;
|
|
|
|
lineLen++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return lineLen;
|
2007-03-27 17:47:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* qsort() sort function.
|
|
|
|
*/
|
2014-11-25 22:34:14 +00:00
|
|
|
/*static*/ int NiftyList::SortNameValue(const void* v1, const void* v2)
|
2007-03-27 17:47:10 +00:00
|
|
|
{
|
2014-11-04 00:26:53 +00:00
|
|
|
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;
|
|
|
|
}
|
2007-03-27 17:47:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Hex digit converter.
|
|
|
|
*/
|
2014-11-25 22:34:14 +00:00
|
|
|
static inline int HexValue(char ch)
|
2007-03-27 17:47:10 +00:00
|
|
|
{
|
2014-11-04 00:26:53 +00:00
|
|
|
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;
|
2007-03-27 17:47:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2014-11-21 02:10:18 +00:00
|
|
|
* Convert a 4-char hexadecimal value to an unsigned 16-bit value.
|
2007-03-27 17:47:10 +00:00
|
|
|
*/
|
2014-11-25 22:34:14 +00:00
|
|
|
/*static*/ uint16_t NiftyList::ConvHexFour(const char* data)
|
2007-03-27 17:47:10 +00:00
|
|
|
{
|
2014-11-04 00:26:53 +00:00
|
|
|
return HexValue(data[0]) << 12 |
|
|
|
|
HexValue(data[1]) << 8 |
|
|
|
|
HexValue(data[2]) << 4 |
|
|
|
|
HexValue(data[3]);
|
2007-03-27 17:47:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Dump the contents of a section.
|
|
|
|
*/
|
2014-11-25 22:34:14 +00:00
|
|
|
/*static*/ void NiftyList::DumpSection(const DataSet& dataSet)
|
2007-03-27 17:47:10 +00:00
|
|
|
{
|
2014-11-04 00:26:53 +00:00
|
|
|
long ent;
|
2007-03-27 17:47:10 +00:00
|
|
|
|
2014-11-19 01:10:23 +00:00
|
|
|
LOGD("Dumping section (count=%ld)", dataSet.numEntries);
|
2007-03-27 17:47:10 +00:00
|
|
|
|
2014-11-04 00:26:53 +00:00
|
|
|
for (ent = 0; ent < dataSet.numEntries; ent++) {
|
2014-11-19 01:10:23 +00:00
|
|
|
LOGD("%4d: %04x '%hs'",
|
2014-11-04 00:26:53 +00:00
|
|
|
ent, dataSet.pEntries[ent].value, dataSet.pEntries[ent].name);
|
|
|
|
}
|
2007-03-27 17:47:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2014-11-19 01:10:23 +00:00
|
|
|
* Look up a value in the specified table. Returns the name, or NULL if
|
2007-03-27 17:47:10 +00:00
|
|
|
* not found.
|
|
|
|
*
|
|
|
|
* This uses a binary search, so the entries must be in sorted order.
|
|
|
|
*/
|
2014-11-25 22:34:14 +00:00
|
|
|
/*static*/ const char* NiftyList::Lookup(const DataSet& dataSet, uint16_t key)
|
2007-03-27 17:47:10 +00:00
|
|
|
{
|
2014-11-19 01:10:23 +00:00
|
|
|
if (!fDataReady) {
|
|
|
|
return NULL;
|
|
|
|
}
|
2014-11-04 00:26:53 +00:00
|
|
|
assert(dataSet.numEntries > 0);
|
2007-03-27 17:47:10 +00:00
|
|
|
|
2014-11-04 00:26:53 +00:00
|
|
|
int high = dataSet.numEntries-1;
|
|
|
|
int low = 0;
|
|
|
|
int mid;
|
2007-03-27 17:47:10 +00:00
|
|
|
|
2014-11-04 00:26:53 +00:00
|
|
|
while (low <= high) {
|
|
|
|
mid = (high + low)/2;
|
2014-11-21 02:10:18 +00:00
|
|
|
uint16_t midVal = dataSet.pEntries[mid].value;
|
2007-03-27 17:47:10 +00:00
|
|
|
|
2014-11-04 00:26:53 +00:00
|
|
|
if (key > midVal)
|
|
|
|
low = mid +1;
|
|
|
|
else if (key < midVal)
|
|
|
|
high = mid -1;
|
|
|
|
else
|
|
|
|
return dataSet.pEntries[mid].name;
|
|
|
|
}
|
2007-03-27 17:47:10 +00:00
|
|
|
|
2014-11-18 05:13:13 +00:00
|
|
|
return NULL;
|
2007-03-27 17:47:10 +00:00
|
|
|
}
|