// MakeDoc
// version 9.2
//
// Compresses text files into a format that is ready to export to a Pilot
// and work with Rick Bram's PilotDOC reader.
//
// Copyright (C) Pat Beirne, 2000.
// Distributable under the GNU General Public License Version 2 or later.
//
// ver 0.6 enforce 31 char limit on database names
// ver 0.7 change header and record0 to structs
// ver 2.0 added category control on the command line
//		   changed extensions from .prc to .pdb
// ver 9.0
// ver 9.1 allowed .prc in Mobi format (which is just compressed html)
//         to be processed - mods by NR (May 4, 2003)
// ver 9.2 extract the image files used in .prc in sequential order
//		   but hyperlinks WILL be broken and must be manually inserted.

#pragma pack(1)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//template<class A> A max(const A& a, const A& b) {return (a<b) ? b : a;}
#define max(a,b) ((a>b) ? a : b)

typedef unsigned char byte;
typedef unsigned long DWORD;
typedef unsigned short WORD;
#define DISP_BITS 11
#define COUNT_BITS 3

char prefixNR[256];
int prefix=0, start=0;

// all numbers in these structs are big-endian, MAC format
struct tDocHeader {
	char sName[32];
	DWORD dwUnknown1;
	DWORD dwTime1;
	DWORD dwTime2;
	DWORD dwTime3;
	DWORD dwLastSync;
	DWORD ofsSort;
	DWORD ofsCatagories;
	DWORD dwCreator;
	DWORD dwType;
	DWORD dwUnknown2;
	DWORD dwUnknown3;
	WORD  wNumRecs;
};

struct tDocRecord0 {
	WORD wVersion;	// 1=plain text, 2=compressed
	WORD wSpare;
	DWORD dwStoryLen;   // in bytes, when decompressed
	WORD wNumRecs; 			// text records only; equals tDocHeader.wNumRecs-1
	WORD wRecSize;			// usually 0x1000
	DWORD dwSpare2;
};

////////////// utilities //////////////////////////////////////

WORD SwapWord(WORD r)
{
	return (r>>8) + (r<<8);
}
DWORD SwapLong(DWORD r)
{
	return  ((r>>24) & 0xFF) + (r<<24) + ((r>>8) & 0xFF00) + ((r<<8) & 0xFF0000);
}


// replacement for strstr() which deals with 0's in the data
byte* memfind(byte* t, int t_len, byte* m, int m_len)
{
	int i;
	byte* tw, mw;

	for (i = t_len - m_len + 1 ; i>0; i--, t++)
		if (t[0]==m[0] && memcmp(t,m,m_len)==0)
			return t;
	return 0;
}



/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
/////////////////////                                  //////////////////////
/////////////////////      tBuf class                  //////////////////////
/////////////////////                                  //////////////////////
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////


struct tBuf {
	byte* buf;
	unsigned len;

	tBuf() {buf = new byte[len = 6000];};
	~tBuf() {	if (buf) delete[] buf; }
	unsigned Len() const {	return len;	}

	unsigned RemoveBinary();
	unsigned Decompress();
	unsigned Compress();
	unsigned Issue(byte src, int& bSpace);
	unsigned DuplicateCR();
	void Clear() {delete[] buf; buf = new byte[len = 6000]; }
	void Dump() {printf("\nbuffer len=%d",len);}
};



//
// Issue()
//
// action: handle the details of writing a single
//		character to the compressed stream
//
unsigned
tBuf::Issue(byte src, int& bSpace)
{
	int iDest = len;
	byte* dest = buf;

#if 1
	// modified version of issue
	// just issue the char
	if (src>=0x80 || src<=8 )
		dest[iDest++] = 1;
	dest[iDest++] = src;

#else
	// if there is an outstanding space char, see if
	// we can squeeze it in with an ASCII char
	if (bSpace)
	{
		if (src>=0x40 && src<=0x7F)
			dest[iDest++] = src ^ 0x80;
		else
		{
			// couldn't squeeze it in, so issue the space char by itself
			// most chars go out simple, except the range 1...8,0x80...0xFF
			dest[iDest++] = ' ';
			if (src<0x80 && (src==0 || src>8) )
				dest[iDest++] = src;
			else
				dest[iDest++] = 1, dest[iDest++] = src;
		}
		// knock down the space flag
		bSpace = 0;
	}
	else
	{
		// check for a space char
		if (src==' ')
			bSpace = 1;
		else
		{
			if (src<0x80 && (src==0 || src>8))
				dest[iDest++] = src;
			else
				dest[iDest++] = 1, dest[iDest++] = src;

		}
	}
#endif
	len = iDest;
	return iDest;
}

//
// Compress
//
// params: 	none
//
// action:	takes the given buffer,
//					and compresses
//					the original data down into a second buffer
//
// comment:	This version make heavy use of walking pointers.
//
unsigned tBuf::Compress()
{
	int i,j;

	// run through the input buffer
	byte* pBuffer;		// points to the input buffer
	byte* pHit;				// points to a walking test hit; works upwards on successive matches
	byte* pPrevHit;		// previous value of pHit; also, start of next test
	byte* pTestHead;	// current test string
	byte* pTestTail;	// current walking pointer; one past the current test buffer
	byte* pEnd;				// 1 past the end of the input buffer

	pHit = pPrevHit = pTestHead = pBuffer = buf;
	pTestTail = pTestHead+1;
	pEnd = buf + len;	// should point to a 0!

	// make a dest buffer and reassign the local buffer
	buf = new byte[6000];
	len = 0;		// used to walk through the output buffer

	// loop, absorbing one more char from the input buffer on each pass
	for (; pTestHead != pEnd; pTestTail++)
	{
		// if we already have 10 char match, don't bother scanning again for the 11th (wasted time)
		if (pTestTail-pTestHead != (1<<COUNT_BITS)+3) {
			// scan in the previous data for a match
			// terminate the test string (and the matcher string, as well!) in a 0
			byte tmp = *pTestTail;
			*pTestTail = 0;
			pHit = (byte*) strstr((const char*)pPrevHit, (const char*)pTestHead);
			*pTestTail = tmp;	// restore the char
		}

		// on a mismatch or end of buffer, issued codes
		if (pHit==pTestHead
			|| pTestTail-pTestHead>(1<<COUNT_BITS)+2
			|| pTestTail==pEnd)
		{
			// issue the codes
			// first, check for short runs
			if (pTestTail-pTestHead < 4)
			{
				if (pTestHead[0]>0x7F || pTestHead[0]<=8)
					buf[len++] = 1;
				buf[len++] = pTestHead[0];
				pTestHead++;
			}
			// for longer runs, issue a run-code
			else
			{
				unsigned int dist = pTestHead - pPrevHit;
				unsigned int compound = (dist << COUNT_BITS) + pTestTail-pTestHead - 4;

//if (dist>=(1<<DISP_BITS)) printf("\n!! error dist overflow");
//if (pTestTail-pTestHead-4>7) printf("\n!! error len overflow");

				buf[len++] = 0x80 + (compound>>8);
				buf[len++] = compound & 0xFF;
//printf("\nissuing code for sequence len %d <%c%c%c>",pTestTail-pTestHead-1,pTestHead[0],pTestHead[1],pTestHead[2]);
//printf("\n          <%x%x>",pOut[-2],pOut[-1]);
				// and start again
				pTestHead = pTestTail-1;
			}
			// start the search again
			pPrevHit = pBuffer;
			// within range
			if (pTestHead - pPrevHit > ((1<<DISP_BITS)-1))
				pPrevHit = pTestHead - ((1<<DISP_BITS)-1);
		}
		// got a match
		else
		{
			pPrevHit = pHit;
		}
		// when we get to the end of the buffer, don't inc past the end
		// this forces the residue chars out one at a time
		if (pTestTail==pEnd) pTestTail--;
	}


	// final scan to merge consecutive high chars together
	// and merge space chars
	int k;
	for (i=k=0; i<len; i++,k++)
	{
		buf[k] = buf[i];
		// skip the run-length codes
		if (buf[k]>=0x80 && buf[k]<0xC0)
			buf[++k] = buf[++i];
		// if we hit a high char marker, look ahead for another
		// and merge multiples together
		else if (buf[k]==1)
		{
			buf[k+1] = buf[i+1];
			while (i+2<len && buf[i+2]==1 && buf[k]<8)
			{
				buf[k]++;
				buf[k+buf[k]] = buf[i+3];
				i+=2;
			}
			k += buf[k]; i++;
		}
		else if (buf[k]==' ' && i<len-1 && buf[i+1]<=0x7F && buf[i+1]>=0x40)
			buf[k] = 0x80 | buf[++i];
	}

	// delete original buffer
	delete[] pBuffer;
	len = k;

	return k;
}
/*
	Decompress

	params:	none

	action: make a new buffer
					run through the source data
					check the 4 cases:
						0,9...7F represent self
						1...8		escape n chars
						80...bf reference earlier run
						c0...ff	space+ASCII

*/
unsigned
tBuf::Decompress()
{
	// we "know" that all decompresses fit within 4096, right?
	byte* pOut = new byte[6000];
	byte* pOutNR = new byte[6000];
	byte* in_buf = buf;
	byte* out_buf = pOut;
	byte* out_bufNR = pOutNR;

	int i,j, iii,lenNR;
	int jj;
	unsigned int c;

	for (j=i=0; j<len; )
	{

		// take a char from the input buffer
		c = in_buf[j++];

		// separate the char into zones: 0, 1...8, 9...0x7F, 0x80...0xBF, 0xC0...0xFF

		// codes 1...8 mean copy that many bytes; for accented chars & binary
		if (c>0 && c<9)
			while(c--) out_buf[i++] = in_buf[j++];

		// codes 0, 9...0x7F represent themselves
		else if (c<0x80)
			out_buf[i++] = c;

		// codes 0xC0...0xFF represent "space + ascii char"
		else if (c>=0xC0)
				out_buf[i++] = ' ', out_buf[i++] = c ^ 0x80;

		// codes 0x80...0xBf represent sequences
		else
		{
			int m,n;
			c <<= 8;
			c += in_buf[j++];
			m = (c & 0x3FFF) >> COUNT_BITS;
			n = c & ((1<<COUNT_BITS) - 1);
			n += 3;
			while (n--)
			{
				out_buf[i] = out_buf[i-m];
				i++;
			}
		}
	}

	lenNR = i;
	for (j=i=0; j<lenNR;)
	{
		// take a char from the input buffer
		c = out_bufNR[i++] = out_buf[j++];

		if(( c == 0x3C && (out_buf[j] == 0x69 || out_buf[j] == 0x49)
					  && (out_buf[j+1] == 0x6D || out_buf[j+1] == 0x4D)
					  && (out_buf[j+2] == 0x67 || out_buf[j+2] == 0x47)) || start)
		{
			while (out_buf[j] != 0x3E && j<lenNR)
			{

				out_bufNR[i] = out_buf[j];

				if ((out_buf[j] == 0x73 || out_buf[j] == 0x53)
				&& (out_buf[j+1] == 0x72 || out_buf[j+1] == 0x52)
				&& (out_buf[j+2] == 0x63 || out_buf[j+2] == 0x43))
				{
					i--;j--;
					while (out_buf[j++] != 0x22);
					while (out_buf[j]   != 0x22) j++;
				}

				if ((out_buf[j] == 0x72 || out_buf[j] == 0x52)
				&& (out_buf[j+1] == 0x65 || out_buf[j+1] == 0x45)
				&& (out_buf[j+2] == 0x63 || out_buf[j+2] == 0x43)
				&& (out_buf[j+3] == 0x69 || out_buf[j+3] == 0x49)
				&& (out_buf[j+4] == 0x6E || out_buf[j+4] == 0x4E)
				&& (out_buf[j+5] == 0x64 || out_buf[j+5] == 0x44)
				&& (out_buf[j+6] == 0x65 || out_buf[j+6] == 0x45)
				&& (out_buf[j+7] == 0x78 || out_buf[j+7] == 0x58))
				{
					out_bufNR[i++] = 's';
					out_bufNR[i++] = 'r';
					out_bufNR[i++] = 'c';
					j=j+8;

					while (out_buf[j] != 0x22)
						out_bufNR[i++] = out_buf[j++];

					out_bufNR[i++] = out_buf[j++];

					for (jj=0; jj<prefix; jj++)
						out_bufNR[i++] = prefixNR[jj];

					out_bufNR[i++] = out_buf[j++];
					out_bufNR[i++] = out_buf[j++];
					out_bufNR[i++] = out_buf[j++];
					out_bufNR[i++] = out_buf[j++];
					out_bufNR[i++] = out_buf[j++];
					out_bufNR[i]   = out_buf[j];
				}

				i++;
				j++;
			}
			if (j == lenNR) start =1;
			else start =0;
		}
	}

	delete[] buf;
	buf = pOutNR;
	len = i;

	return i;
}

unsigned tBuf::DuplicateCR()
{
	byte* pBuf = new byte[2*len];

	int k,j;
	for (j=k=0; j<len; j++, k++)
	{
		pBuf[k] = buf[j];
		if (pBuf[k]==0x0A) pBuf[k++] = 0x0D, pBuf[k] = 0x0A;
	}
	delete[] buf;
	buf = pBuf;
	len = k;
	return k;
}


void Decomp(char* src, char* dest, int bBinary)
{
	FILE* fin;
	FILE* fout;
	FILE* foutNR;

	char destNR[256];
	int no=0, np=0, nq=0, nr=1;  //  ASCII '01'
	int n=0, img=0;
	int jj=0, kk=0;

	fin = fopen(src,"rb");

	if (fin==0)
	{
		printf("problem opening source file %s", src);
		exit(2);
	}
	// replace space char in destination filename to '_" to solve linking problems
	n = strlen(src);
	for (jj=0; jj<n-4; jj++)
			destNR[jj] = prefixNR[jj] = ((src[jj]==' ') ? '_' : src[jj]);
	prefix = jj;

	// just holds the first few bytes of the file
	byte buf[0x100];
	tDocHeader head;

	fread(&head, 1, sizeof(head), fin);
	if (head.dwType != 0x64414552 && head.dwType != 0x49424F4D           // 'dAER' or 'IBOM'
		|| head.dwCreator != 0x74584554 && head.dwCreator != 0x4B4F4F42) // 'tXET' or 'KOOB'
	{
    printf("file contains %s (%x) (%x); ", &head.dwCreator, head.dwCreator, head.dwType);
		printf(".prc file is not the correct format");
		exit(3);
	}
	if (head.dwType = 0x49424F4D && head.dwCreator != 0x4B4F4F42)          // 'IBOM' and 'KOOB'
		img=1;

	DWORD dwPos;
	// point to start of index
	fseek(fin, 0x4E, SEEK_SET);
	// read the location of the first record
	fread(&dwPos, 4, 1, fin);
	dwPos = SwapLong(dwPos);

	tDocRecord0 recNR;

	fseek(fin, dwPos, SEEK_SET);
	fread(&recNR, 1, sizeof(recNR), fin);

	WORD bCompressed;		// same as bVersion
	WORD bVersion;			// 1=plain text, 2=compressed
	DWORD bStoryLen;		// in bytes, when decompressed
	WORD bNumRecs; 			// text records only; equals tDocHeader.wNumRecs-1
	WORD bRecSize;			// usually 0x1000


	bCompressed = recNR.wVersion;
	bCompressed = SwapWord(bCompressed);

	bStoryLen = recNR.dwStoryLen;
	bStoryLen = SwapLong(bStoryLen);

	bNumRecs = recNR.wNumRecs;
	bNumRecs = SwapWord(bNumRecs);

	bRecSize = recNR.wRecSize;
	bRecSize = SwapWord(bRecSize);

//	printf("Record0:  bComp = %d bStoryLen = %d, bNumTextRec = %d, bRecSize = %d\n",bCompressed, bStoryLen, bNumRecs, bRecSize);

//	fread(&bCompressed, 1, 1, fin);

	if (bCompressed!=1 && bCompressed!=2)
		printf("\nWARNING: unknown file compression type:%d",bCompressed);
	bCompressed--;

	fout = fopen(dest,"wb");
	if (fout==0)
	{
		printf("problem openin output file %s",dest);
		exit(2);
	}

	DWORD dwLen;
	fseek(fin,0,SEEK_END);
	dwLen = ftell(fin);

	WORD nRecs;
	nRecs = SwapWord(head.wNumRecs) - 1;

	// this is the main record buffer
	// it knows how to stretch to accomodate the decompress
	tBuf t;

	DWORD dwRecLen;
	for (int i=0; i<nRecs; i++)
	{
		// read the record offset
		fseek(fin, 0x56 + 8*i, SEEK_SET);
		fread(&dwPos, 4, 1, fin);
		dwPos = SwapLong(dwPos);

		// read start of next record
		fseek(fin, 0x5E + 8*i, SEEK_SET);
		fread(&dwRecLen, 4, 1, fin);
		dwRecLen = SwapLong(dwRecLen);

		// for the last, use the file len
		if (i==nRecs-1) dwRecLen = dwLen;

		dwRecLen -= dwPos;

		fseek(fin,dwPos,SEEK_SET);

	//	printf("Reading %d of %d at %d for len = %d\n", i, nRecs, dwPos, dwRecLen);

		if (i<(long)bNumRecs) {

			n = fread(t.buf, 1, dwRecLen, fin);
			t.len = n;

	//		printf("Read %d characters\n", n);

			if(bCompressed)
				t.Decompress();

			// check for CR insert
			if (!bBinary)
				t.DuplicateCR();

			fwrite(t.buf, 1, t.Len(), fout);

		} else {

			if (nr > 9) {
				nq++;
				nr=0;
			}
			if (nq > 9) {
				np++;
				nq=0;
			}
			if (np > 9) {
				no++;
				np=0;
			}

			n = strlen(src);
			for (jj=0; jj<n-4; jj++)
				destNR[jj] = prefixNR[jj];
			destNR[jj++] = '0';
			destNR[jj++] = '0' + no;
			destNR[jj++] = '0' + np;
			destNR[jj++] = '0' + nq;
			destNR[jj++] = '0' + nr++;
			destNR[jj++] = 0;

			printf(" Image File (len=%6d): saved as %s\n", dwRecLen, destNR);

			foutNR = fopen(destNR,"wb");
			if (foutNR==0)
			{
				printf("problem opening output file %s",destNR);
				exit(2);
			}

			char bufNR[250000];
			int n = fread(bufNR, 1, dwRecLen, fin);
			fwrite(bufNR, 1, n, foutNR);
			fclose(foutNR);
		}

	}

	if (img && nq*10+nr-1) printf(" reconverted MOBI %s: with %d records with %d images.\n",head.sName,nRecs, nq*10 + nr - 1);
	else if(img) printf(" reconverted MOBI %s: with %d records.\n",head.sName,nRecs);

	else if (nq*10+nr-1) printf(" reconverted BOOK %s: with %d records with %d images.\n",head.sName,nRecs, nq*10 + nr - 1);
	else printf(" reconverted BOOK %s: with %d records.\n",head.sName,nRecs);
    fclose(fin);
	fclose(fout);

}

// this nasty little beast removes really low ASCII and 0's
// and handles the CR problem
//
// if a cr appears before a lf, then remove the cr
// if a cr appears in isolation, change to a lf
unsigned tBuf::RemoveBinary()
{
	byte* in_buf = buf;
	byte* out_buf = new byte[len];

	int k,j;
	for (j=k=0; j<len; j++,k++)
	{
		// copy each byte
		out_buf[k] = in_buf[j];

		// throw away really low ASCII
		if ((out_buf[k]>=0 && out_buf[k]<9)) k--;

		// for CR
		if (out_buf[k]==0x0D)
		{
			// if next is LF, then drop it
			if (j<len-1 && in_buf[j+1]==0x0A)
				k--;
			else // turn it into a LF
				out_buf[k] = 0x0A;
		}
	}
	delete[] buf;
	buf = out_buf;
	len = k;
	return k;
}



void out_word(short w, FILE* fout)
{
	short m;
	m = ((w >> 8) & 0xFF) + ((w << 8) & 0xFF00);
	fwrite(&m,2,1,fout);
}
void out_long(long d, FILE* fout)
{
	long d1;
	d1 = ((d >> 24) & 0xFF)
		+ ((d >> 8) & 0xFF00)
		+ ((d << 8) & 0xFF0000)
		+ ((d << 24) & 0xFF000000);
	fwrite(&d1,4,1,fout);
}


void
main(int argc, char** argv)
{
	printf(" MakeDoc ver 9.2 (mods by NR)\n");
	if (argc<4)
	{
		printf("\nsyntax makedoc [-n] [-b] [-c#] <text-file> <prc-file> <story-name>");
		printf("\n                 convert text files to .PDB format");
		printf("\n       makedoc -d [-b] <pdb-file> <text-file>");
		printf("\n                 decodes the .PDB back into the .txt file");
		printf("\n       makedoc -d [-b] <prc-file> <html-file>");
		printf("\n                 decodes the .PRC back into the .htm file");
 		printf("\n                 and extracts images in .prc into .gif!");
		printf("\n       -n builds the .pdb file without compression");
		printf("\n       -b option compresses/decompresses binary");
		printf("\n       -c# option marks the data for category # (0...15) default:0");
		exit(1);
	}

	int iArg = 1;
	int bDecomp = 0;
	int bBinary = 0;
	int bReport = 0;
	int bCompress = 1;
	int nCategory = 0;

	while (argv[iArg][0]=='-' || argv[iArg][0]=='/')
	{
		if (argv[iArg][1]=='d')
			bDecomp = 1;
		if (argv[iArg][1]=='b')
			bBinary = 1;
		if (argv[iArg][1]=='r')
			bReport = 1;
		if (argv[iArg][1]=='n')
			bCompress = 0;
		if (argv[iArg][1]=='c')
			nCategory = atoi(argv[iArg]+2);
		iArg++;
	}

	if (bDecomp)
		Decomp(argv[iArg], argv[iArg+1], bBinary);

	else
	{
		FILE* fin;
		FILE* fout;
		tDocHeader head1;

		fin = fopen(argv[iArg],"rb");
		fout = fopen(argv[iArg+1],"wb");
		if (fin==0 || fout==0)
		{
			printf("problem opening files");
			exit(2);
		}

		fseek(fin,0,SEEK_END);
		DWORD storySize = ftell(fin);
		fseek(fin,0,SEEK_SET);

		DWORD	x;
		WORD w;
		long	recSize = 4096;
		DWORD		z,numRecs;

		sprintf(head1.sName,"%.31s",argv[iArg+2]);
		head1.sName[31] = 0;
		printf("saving to %s as <%s>,%s%s compressed",argv[iArg+1],argv[iArg+2],
				bBinary ? " binary mode," : "",
				bCompress ? "" : " not");

			/*LocalWrite just writes to the new file the number of bytes starting at the passed pointer*/

		head1.dwUnknown1 = 0;
		head1.dwTime1 = 0xAE44D106;
		head1.dwTime2 = 0xAE44D106;
		head1.dwTime3 = 0;
		head1.dwLastSync = 0;
		head1.ofsSort = 0;
		head1.ofsCatagories = 0;
		head1.dwCreator = 0x74584554; //'TEXt';				// database type
		head1.dwType = 0x64414552;    //'REAd';	 			// database creator
		head1.dwUnknown2 = 0;
		head1.dwUnknown3 = 0;



		z = (int) (storySize/(long) recSize);
		if (((long) z * recSize) < storySize)
			z ++;

		numRecs = z;
		z ++;

		head1.wNumRecs = SwapWord(z);		//  the number of records to follow
		fwrite(&head1,1,sizeof(head1),fout);

		unsigned long index;
		index = 0x406F8000;		// the pattern for attributes=dirty + unique_id=0x6f8000
		index += (nCategory & 0x0f) << 24;
		x = 0x50L + (long) z * 8;

		out_long(x,fout);					// start writing the record offsets
		out_long(index,fout);
		x += 0x0010L;

		index++;
		z--;

		while(z--) {
			out_long(x,fout);			//more record offsets
			out_long(index++,fout);		// the attributes + ID's
			x += 0x1000L;
		}
		// one more word.....
		out_word(0,fout);


		tDocRecord0 rec0;
		rec0.wVersion = SwapWord(bCompress ? 2 : 1);
		rec0.wSpare = 0;
		rec0.dwStoryLen = SwapLong(storySize);
		rec0.wNumRecs = SwapWord(SwapWord(head1.wNumRecs) - 1);
		rec0.wRecSize = SwapWord(recSize);
		rec0.dwSpare2 = 0;

		fwrite(&rec0,1,sizeof(rec0),fout);

		int n = recSize;
		// dump the whole story into the new file
		int recNum = 0;
		printf("\n");

		tBuf buf;

		while(recNum < numRecs)
			{
			long pos;
			pos = ftell(fout);
			fseek(fout, 0x56 + 8*recNum, SEEK_SET);
			if (recNum!=numRecs) out_long(pos,fout);
			fseek(fout, pos, SEEK_SET);

			int nOrg;

			buf.Clear();
			nOrg = n = fread(buf.buf,1,4096,fin);
			buf.len = n;
			if (n==0) break;

			if (!bBinary)
				buf.RemoveBinary();
			if (bCompress)
				buf.Compress();
			n = fwrite(buf.buf,1,buf.Len(),fout);

			printf("\rconverting record %d of %d",recNum+1,numRecs);
			if (bReport && n && bCompress)
				printf("\noriginal %d bytes, compressed to %d bytes, ratio: %f5.1\n",
					nOrg, n, 100. * n / nOrg);
			recNum++;
			}

		fclose(fin);
		fclose(fout);

	}

}
