// MakeDoc
// version 0.8
//
// Compresses text files into a format that is ready to export to a Pilot
// and work with Rick Bram's PilotDOC reader.
//
// Freeware
//
// ver 0.6   enforce 31 char limit on database names
// ver 0.7   change header and record0 to structs
// ver 0.7a  minor mispellings and portability issues
/**********************************************************************
 * Rick,
 *
 * I made some more changes to fix another portability problem.  It seems
 * that SOME compilers will pad a structure to a DWORD boundary when you
 * use the sizeof operator.  In particular, for the Solaris compiler, the
 * 78 byte tDocHeader structure is reported as having 80 bytes.  This shifts
 * EVERYTHING by two bytes and wreaks havoc in the generated .prc file.
 * I fixed this (look at the comments in struct tDocHeader and the DOCHEADSZ
 * definition) in the two places it occurred.
 *
 * I also fixed a spelling error in an error message.
 *
 * I also changed the usage message to say this is version 0.7a (rather than
 * 0.6).
 *
 * I also changed the return type of main() to be int and added various
 * calls to exit() as needed.  Needed for portability and correctness.
 *
 * -- Harold Bamford
 **********************************************************************/
//
// Modifications by Kadar Tanuwidjaja (ktan8@dgs.monash.edu.au)
// ver 0.8   add -q for quiet mode
//           add -p for private flag
//           add -c for category
//           add -i for info (title, private flag, category)
//           add -m for changing title, category or private flag
//           default for out-filename is in-filename.pdb (encoding mode)
//           default for title is in-filename (encoding mode)
//           default for out-filename is stdout (decoding mode)
//           modify command syntax, but still maintain backward
//               compatibiliy (see makedoc -h for more details)

#define UNIX 1
#ifdef sparc
#       ifndef UNIX
#       define UNIX 1
#       endif
#endif

#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

// all numbers in these structs are big-endian, MAC format
struct tDocHeader {
	char sName[32];		// 32 bytes
	DWORD dwUnknown1;	// 36	2b file attribute & 2b version
	DWORD dwTime1;		// 40   Creation Date
	DWORD dwTime2;		// 44	Modification Date
	DWORD dwTime3;		// 48	Last Backup Date
	DWORD dwLastSync;	// 52	Modication Number
	DWORD ofsSort;		// 56	AppInfo Area
	DWORD ofsCatagories;	// 60	SortInfo Area
	DWORD dwCreator;	// 64	Database Type
	DWORD dwType;		// 68	Creator ID
	DWORD dwUnknown2;	// 72	Unique ID seed
	DWORD dwUnknown3;	// 76	Next Record List ID
	WORD  wNumRecs;		// 78	Number of Records
};

// Some compilers pad structures out to DWORD boundaries so using sizeof()
// doesn't give the intended result.
#define DOCHEADSZ 78

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 SwapWord21(WORD r)
{
	return (r>>8) + (r<<8);
}
WORD SwapWord12(WORD r)
{
	return r;  
}
DWORD SwapLong4321(DWORD r)
{
	return  ((r>>24) & 0xFF) + (r<<24) + ((r>>8) & 0xFF00) + ((r<<8) & 0xFF0000);
}
DWORD SwapLong1234(DWORD r)
{
	return r;
}

WORD (*SwapWord)(WORD r) = NULL;
DWORD (*SwapLong)(DWORD r) = NULL;

// copy bytes into a word and double word and see how they fall,
// then choose the appropriate swappers to make things come out
// in the right order.
int SwapChoose()
{
  union { char b[2]; WORD w; } w;
  union { char b[4]; DWORD d; } d;

  strncpy(w.b, "\1\2", 2);
  strncpy(d.b, "\1\2\3\4", 4);

  if (w.w == 0x0201)
    SwapWord = SwapWord21;
  else if (w.w == 0x0102)
    SwapWord = SwapWord12;
  else
    return 0;

  if (d.d == 0x04030201)
    SwapLong = SwapLong4321;
  else if (d.d == 0x01020304)
    SwapLong = SwapLong1234;
  else
    return 0;
  
  return 1;
}  

// 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;

	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 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;

		}
	}
	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;
	int bSpace = 0;

	// 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
	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;
//printf("pointers %x %x",pTestTail, pEnd);
//printf("\nstart compression buf len=%d",len);

	// 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++)
	{
//printf("\npointers pTestHead %x pTestTail %x pTestHead[]=%x %x",pTestHead, pTestTail, pTestHead[0], pTestHead[1]);
		// establish where the scan can begin
		if (pTestHead - pPrevHit > ((1<<DISP_BITS)-1))
			pPrevHit = pTestHead - ((1<<DISP_BITS)-1);

		// scan in the previous data for a match
		pHit = memfind(pPrevHit, pTestTail - pPrevHit, pTestHead, pTestTail - pTestHead);

		if (pHit==0)
			printf("!! bug source %x%x%x, dest %x%x%x, %d bytes",	pPrevHit[0],
				pPrevHit[1],pPrevHit[2],pTestHead[0],
	pTestHead[1],	pTestHead[2],	pTestTail-pTestHead);

		// on a mismatch or end of buffer, issued codes
		if (pHit==0
			|| pHit==pTestHead
			|| pTestTail-pTestHead>(1<<COUNT_BITS)+2
			|| pTestTail==pEnd)
		{
			// issued the codes
			// first, check for short runs
			if (pTestTail-pTestHead < 4)
			{
//printf("\nissue a char %x",pTestHead[0]);
				Issue(pTestHead[0], bSpace);
				pTestHead++;
			}
			// for longer runs, issue a run-code
			else
			{
				// issue space char if required
				if (bSpace) buf[len++] = ' ', bSpace = 0;

				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 dist 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;
		}
		// got a match
		else
		{
			pPrevHit = pHit;
		}
//printf("pointers %x %x %x",pTestHead, pTestTail, pPrevHit);
		// 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--;
	}

	// clean up any dangling spaces
	if (bSpace) buf[len++] = ' ';


	// final scan to merge consecutive high chars together
	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
		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++;
		}
	}

	// 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* in_buf = buf;
	byte* out_buf = pOut;

	int i,j;
	for (j=i=0; j<len; )
	{
		unsigned int c;

		// 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++;
			}
		}
	}
	delete[] buf;
	buf = pOut;
	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;
}

// 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 = SwapWord(w);
	fwrite(&m,2,1,fout);
}
void out_long(long d, FILE* fout)
{
	long d1 = SwapLong(d);
	fwrite(&d1,4,1,fout);
}



//////////////////////////////////////////////////
// Info Mode
//////////////////////////////////////////////////

void Info(char* src)
{
	FILE* fin;
	FILE* fout;
	fin = fopen(src,"rb");

	if (fin==0)
	{
		printf("problem opening source file %s", src);
#if UNIX
	        printf("\n");
#endif
		exit(2);
	}

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

	fread(&head, 1, DOCHEADSZ, fin);
	if (strncmp((char *)&head.dwType, "REAd", 4) != 0
	    || strncmp((char *)&head.dwCreator, "TEXt", 4) != 0)
	{
	  //printf("file contains %.4s, %.4s", (char *)&head.dwCreator, (char *)&head.dwType);
		printf("%s is not a DOC file", src);
#if UNIX
	        printf("\n");
#endif
		exit(3);
	}


	WORD wPos;
	int bPrivate = 0;
	int bCategory = 0;

	// point to start of index
	fseek(fin, 0x4E + 4, SEEK_SET);
	// read the location of the first record
	fread(&wPos, 1, 1, fin);
	bPrivate = (int) (wPos & 0x10) / 16;
	bCategory = (int) wPos & 0x0F;
	printf("%s p%d c%02d",head.sName, bPrivate, bCategory);

	fclose(fin);
}


//////////////////////////////////////////////////
// Modify Mode
//////////////////////////////////////////////////

void Modify(char* src, char* title, int bQuiet, int bPrivate, int bCategory )
{
	FILE* fin;
	fin = fopen(src,"r+b");

	if (fin==0)
	{
		printf("problem opening source file %s", src);
#if UNIX
	        printf("\n");
#endif
		exit(2);
	}

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

	fread(&head, 1, DOCHEADSZ, fin);
	if (strncmp((char *)&head.dwType, "REAd", 4) != 0
	    || strncmp((char *)&head.dwCreator, "TEXt", 4) != 0)
	{
	  //printf("file contains %.4s, %.4s", (char *)&head.dwCreator, (char *)&head.dwType);
		printf("%s is not a DOC file", src);
#if UNIX
	        printf("\n");
#endif
		exit(3);
	}

	if(!bQuiet) printf("Modifying %s",src);

        if ( title[0] != '\0' )
        {
          if ( strcmp(head.sName, title) == 0)
          {
            if(!bQuiet) printf("\n  current title is similar");
          }
          else
          {
            if(!bQuiet) printf("\n  changing title from %s to %s", head.sName, title);
	    sprintf(head.sName,"%.31s",title);
	    head.sName[31] = 0;
	  }
	}

	fseek(fin, 0, SEEK_SET);
	fwrite(&head,1,DOCHEADSZ,fin);


        WORD wNumRecs =  SwapWord(head.wNumRecs);  //  the number of records to follow


	WORD wPos;
	int bPrivateCurr;
	int bCategoryCurr;
	int bChange = 0;
	// point to start of index
	fseek(fin, 0x4E + 4, SEEK_SET);
	// read the location of the first record
	fread(&wPos, 1, 1, fin);
	bPrivateCurr = (int) (wPos & 0x10) / 16;
	bCategoryCurr = (int) wPos & 0x0F;

	if ( bPrivate < 2 )
	{
	  if ( bPrivate == bPrivateCurr) 
  	  {
            if(!bQuiet) printf("\n  current private flag is similar");
          }
          else
          {
            if(!bQuiet) printf("\n  changing private flag from %d to %d", bPrivateCurr, bPrivate);
            bChange = 1;
            wPos = wPos & 0xEF; // turn off the 5th bit
            wPos =  wPos | (bPrivate * 16); 
          }
        }

        if ( bCategory < 16 )
        {
	  if ( bCategory == bCategoryCurr) 
	  {
            if(!bQuiet) printf("\n  current category is similar");
          }
          else
          {
            if(!bQuiet) printf("\n  changing category from %d to %d", bCategoryCurr, bCategory);
            bChange = 1;
            wPos = wPos & 0xF0; // turn off the 1th to 4th bits
            wPos =  wPos | bCategory; 
          }
        }

	if(bChange) for ( int i = 0; i < wNumRecs; i++)
	{
	  fseek(fin, 0x4E + 4 + i*8,  SEEK_SET);
	  fwrite(&wPos, 1, 1, fin);
	}

	fclose(fin);
}


//////////////////////////////////////////////////
// Decode Mode
//////////////////////////////////////////////////

void Decode(char* src, char* dest, int bQuiet, int bBinary)
{

        if ( dest[0] == '\0' )
        {
          strcpy(dest, "/dev/stdout");
          bQuiet = 1;
        }

	FILE* fin;
	FILE* fout;
	fin = fopen(src,"rb");

	if (fin==0)
	{
		printf("problem opening source file %s", src);
#if UNIX
	        printf("\n");
#endif
		exit(2);
	}

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

	fread(&head, 1, DOCHEADSZ, fin);
	if (strncmp((char *)&head.dwType, "REAd", 4) != 0
	    || strncmp((char *)&head.dwCreator, "TEXt", 4) != 0)
	{
	  //printf("file contains %.4s, %.4s", (char *)&head.dwCreator, (char *)&head.dwType);
		printf("%s is not a DOC file", src);
#if UNIX
	        printf("\n");
#endif
		exit(3);
	}

	WORD bCompressed;
	DWORD dwPos;
	tDocRecord0 rec0;
	// 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);
	fseek(fin, dwPos, SEEK_SET);
	fread(&rec0, sizeof(rec0), 1, fin);
	bCompressed = SwapWord(rec0.wVersion);
	if (bCompressed!=1 && bCompressed!=2)
		printf("WARNING: unknown file compression type: %d", bCompressed);
	bCompressed--;

	fout = fopen(dest,"wb");
	if (fout==0)
	{
		printf("problem opening output file %s",dest);
#if UNIX
	        printf("\n");
#endif
		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);
		int n = fread(t.buf, 1, dwRecLen, fin);
		t.len = n;
		if(bCompressed)
			t.Decompress();

#ifndef UNIX
		// check for CR insert
		if (!bBinary)
			t.DuplicateCR();
#endif

		if(!bQuiet) printf("\rreconverting %s: record %d of %d",head.sName,i,nRecs);

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

	fclose(fin);
	fclose(fout);

}



//////////////////////////////////////////////////
// Encode Mode
//////////////////////////////////////////////////

void Encode(char* src, char* dest, char* title, int bQuiet, int bBinary,
            int bReport, int bCompress, int bPrivate, int bCategory)
{

	FILE* fin;
	FILE* fout;
	tDocHeader head1;

	if ( bPrivate > 1)
	{
	  bPrivate = 0;
	}

	if ( bCategory > 15)
	{
	  bCategory = 0;
	}

	if ( dest[0] == '\0' )
	{
	  if(strrchr(src, '/'))
	  {
	    strcpy(dest, strrchr(src, '/')+1);
	  }
	  else
	  {
	    strcpy(dest, src);
	  }
	  strcat(dest, ".pdb");
	}

	if ( title[0] == '\0' )
	{
	  if(strrchr(src, '/'))
	  {
	    strcpy(title, strrchr(src, '/')+1);
	  }
	  else
	  {
	    strcpy(title, src);
	  }
	}

	unsigned long index;
	if(bPrivate)
	{
	  index = 0x50000000; // private and dirty
	}
	else
	{
	  index = 0x40000000; // dirty
	}
	index = index + (bCategory * 0x1000000); // set category
	index = index + 0x6F8000; // unique_id=0x6f8000

	fin = fopen(src,"rb");
	fout = fopen(dest,"wb");
	if (fin==0 || fout==0)
	{
		printf("problem opening files");
#if UNIX
                printf("\n");
#endif
		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",title);
	head1.sName[31] = 0;
	if(!bQuiet) printf("Saving to %s as <%s>,%s%s compressed", dest, title,
			bBinary ? " binary mode," : "",
			bCompress ? "" : " not");

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

	head1.dwUnknown1 = 0;
	strncpy((char *)&head1.dwTime1, "\x06\xD1\x44\xAE", 4);
	strncpy((char *)&head1.dwTime2, "\x06\xD1\x44\xAE", 4);
	head1.dwTime3 = 0;
	head1.dwLastSync = 0;
	head1.ofsSort = 0;
	head1.ofsCatagories = 0;
	strncpy((char *)&head1.dwCreator, "TEXt", 4);	// database creator
        strncpy((char *)&head1.dwType, "REAd", 4);	// database type
	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,DOCHEADSZ,fout);
	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;
	if(!bQuiet) 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);

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

	fclose(fin);
	fclose(fout);
}



//////////////////////////////////////////////////
// Main Program
//////////////////////////////////////////////////

int
main(int argc, char** argv)
{
	int iArg = 1;
	int bEncode = 1;
	int bDecode = 0;
	int bInfo   = 0;
	int bModify = 0;
	int bBinary = 0;
	int bReport = 0;
	int bCompress = 1;
	int bPrivate = 2;
	int bCategory = 16;
	int bQuiet = 0;
	char filein[250]="";
	char fileout[250]="";
	char title[250]="";

	if ( ! SwapChoose()) {
	  printf("\nfailed to select proper byte swapping algorithm");
#if UNIX
	  printf("\n");
#endif
	  exit(1);
	}

	while (iArg < argc)
	{
	  if (argv[iArg][0] == '-')
	  {
	    switch(argv[iArg][1])
	    {
	      case 'e' :
	        bEncode = 1;
	        bDecode = 0;
	        bInfo   = 0;
	        bModify = 0;
		break;
	      case 'd' :
	        bEncode = 0;
	        bDecode = 1;
	        bInfo   = 0;
	        bModify = 0;
		break;
	      case 'i':
	        bEncode = 0;
	        bDecode = 0;
	        bInfo   = 1;
	        bModify = 0;
		break;
	      case 'm':
	        bEncode = 0;
	        bDecode = 0;
	        bInfo   = 0;
		bModify = 1;
		break;
	      case 'b' :
		bBinary = 1;
		break;
	      case 'r' :
		bReport = 1;
		break;
	      case 'n' :
		bCompress = 0;
	      case 'p' :
                bPrivate = 1;
		if (argv[iArg][2]=='0')
		   bPrivate = 0;
		break;
	      case 'c' :
		char nb[3];
		nb[0] = argv[iArg][2];
		nb[1] = argv[iArg][3];
		nb[2] = argv[iArg][4];
		bCategory = atoi(nb);
		break;
	      case 'o' :
	        iArg++;
	        strcpy(fileout, argv[iArg]);
	        break;
	      case 't' :
	        iArg++;
	        strcpy(title, argv[iArg]);
	        break;
	      case 'q' :
		bQuiet = 1;
		break;
	    }
	  }
	  else if (filein[0] == '\0')
	  {
	    strcpy(filein, argv[iArg]);
	  }
	  else if (fileout[0] == '\0')
	  {
	    strcpy(fileout, argv[iArg]);
	  }
	  else if (title[0] == '\0')
	  {
	    strcpy(title, argv[iArg]);
	  }
	  iArg++;
	}

	if (bInfo && filein[0] != '\0')
	{
		Info(filein);
	}
	else if (bModify && filein[0] != '\0')
	{
		Modify(filein, title, bQuiet, bPrivate, bCategory);
	}
	else if (bDecode && filein[0] != '\0')
	{
		Decode(filein, fileout, bQuiet, bBinary);
        }
	else if (bEncode && filein[0] != '\0')
	{
		Encode(filein, fileout, title, bQuiet, bBinary, bReport, bCompress, bPrivate, bCategory);
	}
	else
	{
	   printf("MakeDoc ver 0.8\n");
	   printf("\nmakedoc [-q] [-n] [-b] [-p#] [-c#] [-o <pdb-file>] [-t <pdb-title>] <text-file>");
	   printf("\nmakedoc [-q] [-n] [-b] [-p#] [-c#] <text-file> <pdb-file> <pdb-title>");
	   printf("\n   encode text file to DOC format");
	   printf("\n");
	   printf("\nmakedoc -d [-q] [-b] [-o <txt-file>] <pdb-file>");
	   printf("\nmakedoc -d [-q] [-b] <pdb-file> <txt-file>");
	   printf("\n   decode DOC file back to text file");
	   printf("\n");
	   printf("\nmakedoc -i <pdb-file>");
	   printf("\n   print the DOC info (title, private flag, category)");
	   printf("\n");
	   printf("\nmakedoc -m [-q] [-p#] [-c#] [-t <pdb-title>] <pdb-file>");
	   printf("\n   modify title, private flag, category");
	   printf("\n");
	   printf("\n   -q quiet mode");
	   printf("\n   -n builds the .pdb file without compression");
	   printf("\n   -b option compresses/decompresses binary");
	   printf("\n   -p private, #=0 private off (default=off)");
	   printf("\n   -c category, #=[0-15] category number (default=0)");
	   printf("\n   -o output filename (encoding mode default=<text-file>.pdb)");
	   printf("\n                      (decoding mode default=stdout)");
	   printf("\n   -t title (max=31, default=<text-file>)");
#if UNIX
	   printf("\n");
#endif
	   exit(1);
	}
#if UNIX
	if(!bQuiet) printf("\n");
#endif
	exit(0);
}
