mirror of
https://github.com/spiffcode/hostile-takeover.git
synced 2026-03-29 16:29:39 -06:00
515 lines
12 KiB
C++
515 lines
12 KiB
C++
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <memory.h>
|
|
#include <string.h>
|
|
#include "ini.h"
|
|
|
|
#ifndef WINDOWS
|
|
#include <limits.h>
|
|
#define _MAX_PATH PATH_MAX
|
|
#define _MAX_FNAME NAME_MAX
|
|
#define _MAX_DRIVE NAME_MAX
|
|
#define _MAX_DIR (PATH_MAX-NAME_MAX)
|
|
#define _MAX_EXT NAME_MAX
|
|
#define _unlink remove
|
|
#define _fullpath(resolved_path, path, length) realpath(path, resolved_path)
|
|
|
|
void _splitpath(const char* path, char* drv, char* dir, char* name, char* ext)
|
|
{
|
|
const char* end; /* end of processed string */
|
|
const char* p; /* search pointer */
|
|
const char* s; /* copy pointer */
|
|
|
|
/* extract drive name */
|
|
if (path[0] && path[1]==':') {
|
|
if (drv) {
|
|
*drv++ = *path++;
|
|
*drv++ = *path++;
|
|
*drv = '\0';
|
|
}
|
|
} else if (drv)
|
|
*drv = '\0';
|
|
|
|
/* search for end of string or stream separator */
|
|
for(end=path; *end && *end!=':'; )
|
|
end++;
|
|
|
|
/* search for begin of file extension */
|
|
for(p=end; p>path && *--p!='\\' && *p!='/'; )
|
|
if (*p == '.') {
|
|
end = p;
|
|
break;
|
|
}
|
|
|
|
if (ext)
|
|
for(s=end; (*ext=*s++); )
|
|
ext++;
|
|
|
|
/* search for end of directory name */
|
|
for(p=end; p>path; )
|
|
if (*--p=='\\' || *p=='/') {
|
|
p++;
|
|
break;
|
|
}
|
|
|
|
if (name) {
|
|
for(s=p; s<end; )
|
|
*name++ = *s++;
|
|
|
|
*name = '\0';
|
|
}
|
|
|
|
if (dir) {
|
|
for(s=path; s<p; )
|
|
*dir++ = *s++;
|
|
|
|
*dir = '\0';
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
#define kcbRecordMax 0x8000
|
|
#define SwapWord(x) ((((x)&0xFF)<<8) | (((x)&0xFF00)>>8))
|
|
#define SwapDword(x) ((((x)&0xFF)<<24) | (((x)&0xFF00)<<8) | (((x)&0xFF0000)>>8) | (((x)&0xFF000000)>>24))
|
|
|
|
struct RecordChunk // rck
|
|
{
|
|
byte *pb;
|
|
byte *pbMax;
|
|
};
|
|
|
|
struct SecChunk // sck
|
|
{
|
|
word offSecNext;
|
|
short cprop;
|
|
// char szSecName[];
|
|
};
|
|
|
|
struct PropChunk // pck
|
|
{
|
|
// char szProp[];
|
|
// char szPropValue[];
|
|
};
|
|
|
|
bool WriteDatabase(char *pszFn, RecordChunk *arck, int cChunks, long *pcbFile);
|
|
bool WriteBinary(char *pszFn, RecordChunk *arck, int cChunks, long *pcbFile);
|
|
int CalcSectionSize(IniSection *psec);
|
|
bool ReadString(FILE *pf, int nch, int cch, byte *pb);
|
|
|
|
void Usage()
|
|
{
|
|
printf("\n");
|
|
printf("Usage: inicrunch infile outfile\n");
|
|
printf("\n");
|
|
printf("This will read the infile and\n");
|
|
printf("output outfile which is a straight image binary\n");
|
|
printf("\n");
|
|
printf("Usage: inicrunch -pdb infile outfile\n");
|
|
printf("\n");
|
|
printf("This will read the infile and output outfile\n");
|
|
printf("in .pdb format.\n");
|
|
printf("\n");
|
|
exit(1);
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
bool fPdbOnly = false;
|
|
char *pszFnIn;
|
|
char *pszFnOut;
|
|
if (argc == 4 && strcmp(argv[1], "-pdb") == 0) {
|
|
fPdbOnly = true;
|
|
pszFnIn = argv[2];
|
|
pszFnOut = argv[3];
|
|
} else if (argc == 3) {
|
|
pszFnIn = argv[1];
|
|
pszFnOut = argv[2];
|
|
} else {
|
|
Usage();
|
|
}
|
|
|
|
// File exists?
|
|
|
|
FILE *pf = fopen(pszFnIn, "rb");
|
|
if (pf == NULL) {
|
|
printf("Error opening %s for reading.\n", pszFnIn);
|
|
exit(1);
|
|
}
|
|
|
|
// Parse .ini file
|
|
|
|
int cSections;
|
|
IniSection *psec = LoadIniFile(pszFnIn, &cSections);
|
|
if (psec == NULL) {
|
|
printf("Error parsing .ini file %s.\n", pszFnIn);
|
|
fclose(pf);
|
|
exit(1);
|
|
}
|
|
|
|
// Build in memory format
|
|
|
|
RecordChunk arck[256];
|
|
memset(arck, 0, sizeof(arck));
|
|
int cChunks = 0;
|
|
|
|
IniSection *psecT = psec;
|
|
IniSection *psecMax = &psec[cSections];
|
|
|
|
bool fDone = false;
|
|
while (!fDone) {
|
|
RecordChunk *prck = &arck[cChunks++];
|
|
prck->pb = new byte[kcbRecordMax];
|
|
byte *pbT = prck->pb;
|
|
|
|
while (!fDone) {
|
|
// If this section does not fit in this chunk, start a new chunk
|
|
|
|
if (&pbT[CalcSectionSize(psecT)] > &prck->pb[kcbRecordMax])
|
|
break;
|
|
|
|
// Add this section to this chunk
|
|
|
|
SecChunk *psck = (SecChunk *)pbT;
|
|
psck->cprop = SwapWord(psecT->cprop);
|
|
|
|
// Copy in the section name and zero extend
|
|
|
|
pbT = (byte *)(psck + 1);
|
|
if (!ReadString(pf, psecT->nchSec, psecT->cchSec, pbT)) {
|
|
printf("Error reading %s.\n", pszFnIn);
|
|
fclose(pf);
|
|
exit(1);
|
|
}
|
|
pbT += psecT->cchSec + 1;
|
|
|
|
// Copy in the property / value pairs
|
|
|
|
IniProperty *ppropT = psecT->pprop;
|
|
for (int n = 0; n < psecT->cprop; n++) {
|
|
if (!ReadString(pf, ppropT->nchProp, ppropT->cchProp, pbT)) {
|
|
printf("Error reading %s.\n", pszFnIn);
|
|
fclose(pf);
|
|
exit(1);
|
|
}
|
|
pbT += ppropT->cchProp + 1;
|
|
if (!ReadString(pf, ppropT->nchValue, ppropT->cchValue, pbT)) {
|
|
printf("Error reading %s.\n", pszFnIn);
|
|
fclose(pf);
|
|
exit(1);
|
|
}
|
|
pbT += ppropT->cchValue + 1;
|
|
ppropT++;
|
|
}
|
|
|
|
// Even align
|
|
|
|
if ((pbT - prck->pb) & 1)
|
|
*pbT++ = 0;
|
|
|
|
// Backfill the "next section" offset
|
|
|
|
psck->offSecNext = SwapWord(pbT - (byte *)psck);
|
|
|
|
// Next section...
|
|
|
|
psecT++;
|
|
if (psecT >= psecMax) {
|
|
psck->offSecNext = 0;
|
|
fDone = true;
|
|
}
|
|
}
|
|
|
|
// Remember how much of this chunk we wrote to
|
|
|
|
prck->pbMax = pbT;
|
|
}
|
|
fclose(pf);
|
|
|
|
// Write the chunks out to the database
|
|
|
|
long cbFile;
|
|
bool fSuccess;
|
|
if (fPdbOnly) {
|
|
fSuccess = WriteDatabase(pszFnOut, arck, cChunks, &cbFile);
|
|
} else {
|
|
fSuccess = WriteBinary(pszFnOut, arck, cChunks, &cbFile);
|
|
}
|
|
|
|
// Free chunks
|
|
|
|
int n;
|
|
for (n = 0; n < cChunks; n++)
|
|
delete arck[n].pb;
|
|
|
|
// Free property sections
|
|
|
|
for (n = 0; n < cSections; n++)
|
|
delete psec[n].pprop;
|
|
delete psec;
|
|
|
|
return fSuccess ? 0 : 1;
|
|
}
|
|
|
|
int CalcSectionSize(IniSection *psec)
|
|
{
|
|
int cb = sizeof(SecChunk);
|
|
cb += psec->cchSec + 1;
|
|
for (int n = 0; n < psec->cprop; n++) {
|
|
cb += psec->pprop[n].cchProp + 1;
|
|
cb += psec->pprop[n].cchValue + 1;
|
|
}
|
|
cb = (cb + 1) & ~1;
|
|
return cb;
|
|
}
|
|
|
|
bool ReadString(FILE *pf, int nch, int cch, byte *pb)
|
|
{
|
|
*pb = 0;
|
|
if (cch == 0)
|
|
return true;
|
|
fseek(pf, nch, SEEK_SET);
|
|
if (fread(pb, cch, 1, pf) != 1)
|
|
return false;
|
|
pb[cch] = 0;
|
|
return true;
|
|
}
|
|
|
|
bool WriteBinary(char *pszFn, RecordChunk *arck, int cChunks, long *pcbFile)
|
|
{
|
|
// Create bin file
|
|
|
|
FILE *pf = fopen(pszFn, "wb");
|
|
if (pf == NULL) {
|
|
printf("Unable to create \"%s\".\n", pszFn);
|
|
return false;
|
|
}
|
|
|
|
// Write out the chunks
|
|
|
|
for (int n = 0; n < cChunks; n++) {
|
|
if (fwrite(arck[n].pb, arck[n].pbMax - arck[n].pb, 1, pf) != 1) {
|
|
printf("Error writing record data.\n");
|
|
fclose(pf);
|
|
_unlink(pszFn);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Close & success
|
|
|
|
*pcbFile = ftell(pf);
|
|
fclose(pf);
|
|
return true;
|
|
}
|
|
|
|
#define dmDBNameLength 32
|
|
|
|
typedef unsigned char UInt8;
|
|
typedef unsigned short UInt16;
|
|
typedef unsigned long UInt32;
|
|
typedef unsigned long LocalID;
|
|
typedef char Char;
|
|
|
|
struct RecordEntryType {
|
|
LocalID localChunkID; // local chunkID of a record
|
|
UInt8 attributes; // record attributes;
|
|
UInt8 uniqueID[3]; // unique ID of record; should
|
|
// not be 0 for a legal record.
|
|
};
|
|
|
|
struct RecordListType {
|
|
LocalID nextRecordListID; // local chunkID of next list
|
|
UInt16 numRecords; // number of records in this list
|
|
UInt16 firstEntry; // array of Record/Rsrc entries
|
|
// starts here
|
|
};
|
|
|
|
struct DatabaseHdrType {
|
|
char name[dmDBNameLength]; // name of database
|
|
UInt16 attributes; // database attributes
|
|
UInt16 version; // version of database
|
|
UInt32 creationDate; // creation date of database
|
|
UInt32 modificationDate; // latest modification date
|
|
UInt32 lastBackupDate; // latest backup date
|
|
UInt32 modificationNumber; // modification number of database
|
|
LocalID appInfoID; // application specific info
|
|
LocalID sortInfoID; // app specific sorting info
|
|
UInt32 type; // database type
|
|
UInt32 creator; // database creator
|
|
UInt32 uniqueIDSeed; // used to generate unique IDs.
|
|
// Note that only the low order
|
|
// 3 bytes of this is used (in
|
|
// RecordEntryType.uniqueID).
|
|
// We are keeping 4 bytes for
|
|
// alignment purposes.
|
|
RecordListType recordList; // first record list
|
|
};
|
|
|
|
#ifdef WINDOWS
|
|
#include <windows.h>
|
|
dword GetCurrentTimePalmUnits()
|
|
{
|
|
// Palm keeps time as a count of seconds from Jan 1, 1904.
|
|
|
|
FILETIME timCurrent;
|
|
GetSystemTimeAsFileTime(&timCurrent);
|
|
|
|
SYSTEMTIME timsys1904;
|
|
timsys1904.wYear = 1904;
|
|
timsys1904.wMonth = 1;
|
|
timsys1904.wDayOfWeek = 6;
|
|
timsys1904.wDay = 1;
|
|
timsys1904.wHour = 0;
|
|
timsys1904.wMinute = 0;
|
|
timsys1904.wSecond = 0;
|
|
timsys1904.wMilliseconds = 0;
|
|
|
|
FILETIME tim1904;
|
|
SystemTimeToFileTime(&timsys1904, &tim1904);
|
|
|
|
// Now we have 2 64 bit unsigned ints that specifies time in terms of 100 nanosecond
|
|
// units. Subtract the two and we have the time between now and Jan 1 1904 in 100 nanosecond
|
|
// units. 1 s == 1e9 ns, so there are 1e7 filetime units in 1 second.
|
|
|
|
unsigned __int64 tCurrent;
|
|
tCurrent = ((unsigned __int64)timCurrent.dwHighDateTime << 32) | timCurrent.dwLowDateTime;
|
|
unsigned __int64 t1904;
|
|
t1904 = ((unsigned __int64)tim1904.dwHighDateTime << 32) | tim1904.dwLowDateTime;
|
|
unsigned __int64 tDiff = tCurrent - t1904;
|
|
|
|
// Divide by 1024*1024 so it fits in a dword
|
|
|
|
dword dw = (dword)(tDiff >> 20);
|
|
|
|
// Divide by 1e9 / (1024*1024)
|
|
|
|
return (dword)((double)dw / (10000000.0 / (double)(1024 * 1024)));
|
|
|
|
#else // TODO(darrinm): maybe this is fine on Windows too?
|
|
#include <ctime>
|
|
dword GetCurrentTimePalmUnits()
|
|
{
|
|
// Palm keeps time as a count of seconds from Jan 1, 1904.
|
|
struct tm tm1904;
|
|
tm1904.tm_year = 1904 - 1900; // tm_year 0=1900
|
|
tm1904.tm_mon = 1;
|
|
tm1904.tm_wday = 6;
|
|
tm1904.tm_mday = 1;
|
|
tm1904.tm_yday = 0;
|
|
tm1904.tm_hour = 0;
|
|
tm1904.tm_min = 0;
|
|
tm1904.tm_sec = 0;
|
|
tm1904.tm_isdst = -1;
|
|
time_t time1904 = mktime(&tm1904);
|
|
time_t timeCurrent = time(NULL);
|
|
return (dword)difftime(timeCurrent, time1904);
|
|
}
|
|
#endif
|
|
|
|
bool WriteDatabase(char *pszFnPdb, RecordChunk *arck, int cChunks, long *pcbFile)
|
|
{
|
|
// Construct the new filename, which is same as the current with an extension of ".pdb"
|
|
|
|
char szFnT[_MAX_PATH];
|
|
_fullpath(szFnT, pszFnPdb, _MAX_PATH);
|
|
char szDrive[_MAX_DRIVE];
|
|
char szDir[_MAX_DIR];
|
|
char szFname[_MAX_FNAME];
|
|
char szExt[_MAX_EXT];
|
|
_splitpath(szFnT, szDrive, szDir, szFname, szExt);
|
|
|
|
// Create pdb file
|
|
|
|
FILE *pf = fopen(pszFnPdb, "wb");
|
|
if (pf == NULL) {
|
|
printf("Unable to create \"%s\".\n", pszFnPdb);
|
|
return false;
|
|
}
|
|
|
|
// Figure out the creator
|
|
|
|
//dword dwCreator = (pszCreator[0] << 24) | (pszCreator[1] << 16) | (pszCreator[2] << 8) | pszCreator[3];
|
|
// TODO(darrinm): huh? Is this right for WI?
|
|
dword dwCreator = 'Vexd';
|
|
|
|
// Init the .pdb header
|
|
|
|
DatabaseHdrType hdr;
|
|
memset(&hdr, 0, sizeof(hdr));
|
|
|
|
// Use the filename as the database name
|
|
|
|
strcpy(hdr.name, szFname);
|
|
if (strlen(hdr.name) > dmDBNameLength - 1) {
|
|
printf("Pdb filename too long.\n");
|
|
Error:
|
|
fclose(pf);
|
|
_unlink(pszFnPdb);
|
|
return false;
|
|
}
|
|
|
|
// Hardwire a bunch of stuff about this .pdb.
|
|
|
|
hdr.attributes = SwapWord(0);
|
|
hdr.version = SwapWord(1);
|
|
dword dwDate = GetCurrentTimePalmUnits();
|
|
hdr.creationDate = SwapDword(dwDate);
|
|
hdr.modificationDate = SwapDword(dwDate);
|
|
hdr.lastBackupDate = SwapDword(dwDate);
|
|
hdr.modificationNumber = SwapDword(1);
|
|
hdr.appInfoID = SwapDword(0);
|
|
hdr.sortInfoID = SwapDword(0);
|
|
hdr.type = SwapDword('DATA');
|
|
hdr.creator = SwapDword(dwCreator);
|
|
hdr.uniqueIDSeed = SwapDword(cChunks + 1);
|
|
hdr.recordList.nextRecordListID = SwapDword(0);
|
|
hdr.recordList.numRecords = SwapWord((word)cChunks);
|
|
|
|
// Write the header out
|
|
|
|
int cbHdr = sizeof(hdr) - sizeof(hdr.recordList.firstEntry);
|
|
if (fwrite(&hdr, cbHdr, 1, pf) != 1) {
|
|
printf("Error writing database header.\n");
|
|
goto Error;
|
|
}
|
|
|
|
// Calculate where the actual records begin, which is after the record headers
|
|
|
|
int offNext = cbHdr + cChunks * sizeof(RecordEntryType);
|
|
|
|
// Write out the record headers.
|
|
|
|
int n;
|
|
for (n = 0; n < cChunks; n++) {
|
|
RecordEntryType entry;
|
|
memset(&entry, 0, sizeof(entry));
|
|
entry.localChunkID = SwapDword(offNext);
|
|
entry.attributes = 0;
|
|
dword id = n + 1;
|
|
entry.uniqueID[2] = (byte)(id & 0xff);
|
|
entry.uniqueID[1] = (byte)((id >> 8) & 0xff);
|
|
entry.uniqueID[0] = (byte)((id >> 16) & 0xff);
|
|
id++;
|
|
offNext += arck[n].pbMax - arck[n].pb;
|
|
if (fwrite(&entry, sizeof(entry), 1, pf) != 1) {
|
|
printf("Error writing database record header.\n");
|
|
goto Error;
|
|
}
|
|
}
|
|
|
|
// Write out the chunks
|
|
|
|
for (n = 0; n < cChunks; n++) {
|
|
if (fwrite(arck[n].pb, arck[n].pbMax - arck[n].pb, 1, pf) != 1) {
|
|
printf("Error writing record data.\n");
|
|
goto Error;
|
|
}
|
|
}
|
|
|
|
*pcbFile = ftell(pf);
|
|
fclose(pf);
|
|
|
|
return true;
|
|
}
|