/*
 * Added 1.00 code - JPC
 * Dislite v1.15:  Modified Feb 1993 by Ray Van Tassle
 * "1.15" because that's what version of PKLITE it works on.
 * Also works for all currently known programs compressed with PKLITE 1.20
 * Added code for pklite version 1.15x
 * Misc cleanup
 * Tackle identification of PKLITE'ed exe's (& com's) that don't have the
 *   correct signature.  Removed "crippled" option--not needed anymore.
 *   Extended this a little... -- JPC
 * Added "5v" test, so I can prove to people that it was me (rvt) that
 *   diddled with this version of DISLITE.
 * Added simple encryption of some strings.  Just to annoy hackers!
 *   And anyway, I wanted by name to be hidden, not in the clear.
 * Added some time delays--just so it looks like we're working hard!!
 *    "-vvv" will NOT do the delays.
 *    Removed those again. Sorry Ray ;) -- JPC
 * Changed the fixup logic.  "-f" (only) will attempt to change the
 *   fixups to be relative to a (presumed) segment.  This will reverse
 *   (well, pretty much) what PKWARE's HDROPT does, and what "extra"
 *   compression does (it's the same).
 * Ask about overwriting existing output file.
 * Fix bug in reporting out of memory during fixup processing.
 * Added "-cNNN" option, and change relative sizes, for collecting
 *   fixup & segment info.
 * For "extra", stick in pre-code to insert the PK signature down
 *   in the psp (offset 0x5c).
 * Added '-k' Switch to defeat this.
 *
 * v.1.16:
 * Added PKLITE 1.00beta code (incomplete. 1.00beta not always recognised!)
 *
 * v.1.17:
 * Fixed bug in PK-kludge code when using RETF
 *
 *  Future options:
 *    allow wildcards on command line, multiple files with one command, etc?
 *    allow larger number of fixups (buffer > 64K!)
 *    use EMS etc
 *    optionally write fixups to a file to reduce memory usage
*/


#pragma hdrfile "dislite.sym"
#include <stdio.h>
#include <stdlib.h>
#include <io.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <alloc.h>
#include <dos.h>
#include <setjmp.h>
#include <sys/stat.h>
#include <conio.h>
#include <ctype.h>
#include "dislite.h"
#pragma hdrstop

#ifndef __COMPACT__
#error Requires COMPACT model
#endif

/** Include the hidden strings **/
#include "raysname.inc"
unsigned char x, *co, ch;

char far *pk_sign;	/* ptr to code that MAY exist, to put the
			 * special "extra  compression" signature
			 * at "psp:005c" before running the pgm */
unsigned isexe;		/* signature if file is .EXE, 0 if .COM */
unsigned origheaderpos; /* Position of original header, if any */
unsigned xheadersize;	/* Size of extra header data, if any */
unsigned minalloc;	/* Minimal memory to allocate, from compressed .EXE */
unsigned maxalloc;	/* Maximal memory, from ^^^ */
unsigned version;	/* Version of PKLITE that packed the file */
unsigned flags;		/* Flags nibble in the PKLITE header */
unsigned codestart;	/* Segment where the code starts */
long exesize;		/* Size of compressed .EXE data */
long newexesize;	/* Size of new .EXE data */
long filesize;		/* Size of compressed program */
long overlaystart;	/* Start of overlay data in compressed program */
long overlaysize;	/* Size of overlay data */
char* extradata;	/* Pointer to additional header data */
char* message = NULL;	/* If message becomes != NULL then something wants */
int exit_code = 0;	/* message printed. If exit_code != 0 then also exit */
int toomanysegments = 0; /* Nonzero if too many segments encountered. */
int progressind;	/* Nonzero if progress indicator is shown. */
char pix[] = " \xF9\xFE\xB0\xB2\xDB\xB2\xB0\xFE\xF9";
int pixpos = 0;		/* pix, pixpos for the progress indicator */

struct ftime filetime;	/* date/time info on compressed file */
struct Options options;	/* Option flags */
int options_verbose;	/* Count of "-v" flags */
struct Exe_State exestate; /* Executable state of program to uncompress */

unsigned* fixupseg;	/* Array of segments in program */
unsigned fixupsegmax;	/* Max of ^^^ */
unsigned* fixupbuf;	/* Array of fixups */
unsigned fixupbufmax;	/* Max of ^^^ */
unsigned fixupbufp;	/* Index to next empty slot of fixup buffer */
unsigned int max_segments = 0;	/* For allocating above (0 = 1/4 mem) */

jmp_buf myexestate;	/* State of DISLITE, for setjmp/longjmp */
char infilename[MAXFILELEN]; /* Name of input filename */
char outfilename[MAXFILELEN]; /* Name of output or temporary filename */

/* Progress indicator macro for speed */
#define PROGRESS if ( progressind ) {			\
		    putch(pix[pixpos++]);		\
		    putch(8);				\
		    if ( pix[pixpos] == 0 )		\
		       pixpos = 0;			\
		 }

/* Main. Does the following:
 * parse command line arguments
 * scan the file to be uncompressed
 * setup the internal debugger
 * load the file
 * execute (if applicable) the loop that decodes the compression stub
 * execute until the first RETF
 * execute until the expanded code is completely written
 * execute (if applicable) the code that does the fixups
 * write out the new executable
 * give messages
*/

void main(int argc, char** argv) {
   struct Exe_Header newx;
   char far* code;
   int i;
   char* retf_seq[9];
   char* eodec_seq[9];
   char* frst_xor_seq[9];
   long newfilesize;

   parseargs(argc, argv);
   if ( !options.silent ) {
      title();
      progressind = isatty(fileno(stdin));
   }
   else
      progressind = 0;
   if ( progressind )
      putch(' '); /* Work around bug in direct video routines */
   if ( !options.silent )
      putchar('\n');

   if (options_verbose >= 5) {	/* Put out my name */
      for (ch = x = 0xa5, co = ray; ch != 0; ++co) {
	 ch = *co ^ x;
	 putchar(ch);
      }
   }
   scanfile();
   if (isexe) {
      /* Fill the search tables for .EXE files. */

      /* For the format of these strings see comment at xmatch, multixmatch.
       * The (^) in the comment indicates the hot spot in the code.
      */
      retf_seq[0] = "s*\xF3\xA5sH\xCBs*"; /* REP MOVSW; (^) RETF */
      retf_seq[1] = "s*\xF3\xA5sH\xC3s*"; /* REP MOVSW; (^) RET */
      retf_seq[2] = "s*\xB8s?\x01\x50sH\xCBs*"; /* MOV AX,...;PUSH AX;RETF */
      retf_seq[3] = NULL;
      eodec_seq[0] = "s*\x5BsH\x8B\xEBs*"; /* POP BX; (^) MOV BP, BX */
      eodec_seq[1] = "s*sH\x8C\xD3\x06s*"; /* (^) MOVE BX, SS; PUSH ES */
      eodec_seq[2] = "s*sH\x2E\x8B\x1Es*"; /* (^) MOVE BX, CS:[...] */
      eodec_seq[3] = NULL;
      /* This is for the "pre-encryption" added to Version
       * 1.15 & 1.20 "extra"
       * near CS:0139
       * mov di,si; [push ds; pop es;] std;[nop]; dec cx; (^) jz 0148.
      */
      frst_xor_seq[0] = "s*\x8B\xFE\xFD\x90\x49sH\x74s*";
      frst_xor_seq[1] = "s*\x8B\xFE\xFD\x49sH\x74s*";
      frst_xor_seq[2] = "s*\x8B\xFE\x1E\x07\xFD\x90\x49sH\x74s*";
      frst_xor_seq[3] = "s*\x8B\xFE\x1E\x07\xFD\x49sH\x74s*";
      frst_xor_seq[4] = NULL;
   }
   else {
      /* Fill the search tables for .COM files. */
      retf_seq[0] = "s*\xDB\x53sH\xCBs*"; /* ... PUSH BX; (^) RETF */
      retf_seq[1] = "s*\x50\xFCsH\xCBs*"; /* PUSH AX; CLD; (^) RETF */
      retf_seq[2] = NULL;
      eodec_seq[0] = "s*\x75s?sH\xEB\x33s*"; /* JNZ ?; (^) JMP +33 */
      eodec_seq[1] = NULL;
   }
   allocbuf();
   if ( options_verbose ) {
      printf("Can handle %u fixups, and %u segments\n", fixupbufmax/2, fixupsegmax);
      printf("Loading compressed file...\n");
   }
   loadexe();
   codestart = FP_SEG(exestate.psp) + 0x10;
   if ( options_verbose >= 3 )
      printf("codestart segment: %04x\n", codestart);
   /* Search for the end of initialization.  If not found, it's
    * probably hidden (1.15 and above).
    * So find *THAT* decode loop.
    * Must walk thru it one loop cycle at a time, because he XORs up to the
    * next instr after the loop!!
    */
   if ( (code = multixmatch(exestate.rCSIP, 0x100, retf_seq)) == NULL ) {
      if ( (code = multixmatch(exestate.rCSIP, 0x100, frst_xor_seq)) == NULL )
	 doubleexit("Error: Cannot find end of either initialization.\n", 1);
      if ( options_verbose >= 3 )
	 printf("frst_xor_seq at (code): %08lx\n", code);

      if (options_verbose)
	 printf("Decoding decompression stub...\n");
      execuntil(code);	/* Get to there */
      if ( options_verbose >= 3 )
	 printf("   %d bytes to decode\n", exestate.rCX);
      while (exestate.rCX != 0) {
	 execuntil(NULL); /* must step to next instr */
	 execuntil(code); /* Now run thru loop again. */
	 if ( progressind && (exestate.rCX & 0x1F) == 0 ) {
	    PROGRESS;
	 }
      }
   }

   /* Search for the RET(F) in the initialization part */
   if ( (code = multixmatch(exestate.rCSIP, 0x100, retf_seq)) == NULL )
      doubleexit("Error: Cannot find end of initialization.\n", 1);
   if ( options_verbose >= 3 )
      printf("retf_seq at (code): %08lx\n", code);
   if ( options_verbose )
      printf("Copying decompression stub...\n");
   PROGRESS;
   execuntil(code);
   execuntil(NULL); /* means execute a single instruction (here RET(F)) */

   /* Find end of decode loop (resulting file length must be known there) */
   if ((code = multixmatch(exestate.rCSIP, 0x300, eodec_seq)) == NULL)
      doubleexit("Error: Cannot find end of decode loop.\n", 1);
   if ( options_verbose >= 3 )
      printf("eodec_seq at (code): %08lx\n", code);
   if (options_verbose)
      printf("Decompressing...\n");
   PROGRESS;
   execuntil(code);
   /* newexesize = ... it currently assumes ES:DI is pointing just after the
    * byte last decoded. */
   newexesize = ((long) exestate.rES - FP_SEG(exestate.psp)) * 16 +
	exestate.rDI - 0x100;
   /* If it was a COM file, we're ready */
   if (isexe) {
      /* Find the fixup instruction ADD ES:[DI], BX */
      if ((code = xmatch(exestate.rCSIP, 0x100, "s*sH\x26\x01\x1Ds*")) == NULL)
	 doubleexit("Error: Cannot find fixup instruction.\n", 1);
      if ( options_verbose >= 3 )
	 printf("fixup instruction at (code): %08lx\n", code);

      /* Replace this with an INT 0xF1, NOP */
      *code = ASM_INT; *(code + 1) = FIXUPINT;
      *(code + 2) = ASM_NOP;

      /* Find the end of the decode loop (LODSW; ADD AX, BX) */
      if ( (code = xmatch(code, 0x80, "s*sH\xAD\x03\xC3s*")) == NULL )
	 doubleexit("Error: Cannot find end of fixup loop.\n", 1);
      if ( options_verbose >= 3 )
	 printf("end of fixup loop at (code): %08lx\n", code);
      /* Look for the code to set the signature in the psp:
       * mov [word ptr 005C], 4B50h
       * Lame... extra compressed COM files don't have a PK signature
       * Extra lame... the signature isn't always 4B50, but might be 6B70.
       * let's accomodate for different values of 005C too
      */
      pk_sign = xmatch(code, 0x80, "s*sH\xC7\x06s?s0s*");
      if (options.nosignature)
	 pk_sign = NULL;
      if ( pk_sign && options_verbose )
	 printf("Adding PK signature code...\n");
      setvect(FIXUPINT, fixup);
      fixupseg[0] = ENDOFSEGLIST; /* Indicates the end of the segment list */
      fixupbufp = 0;
      if ( options_verbose )
	 printf("Processing fixups...\n");
      PROGRESS;
      execuntil(code);

      /* prepare a new exe header. This assumes DS:SI points to
       * a structure containing start SS:SP and CS:IP. */
      preparenew(&newx, (unsigned far*) MK_FP(exestate.rDS, exestate.rSI));
      addseg(newx.relCS); /* The initial CS is also always a segment */
      /* Print message possibly generated by addseg */
      if ( message ) {
	 fprintf(stderr, message);
	 if ( exit_code )
	    doubleexit("", exit_code);
      }

      if ( (flags & FLAGS_EXTRA) || options.dofixfixups )
	 fixfixups(0);
      else if ( options_verbose )
	 fixfixups(1);
   }
   if ( options_verbose )
      printf("Writing decompressed executable...\n");
   writeexe(&newx);
   if ( options_verbose ) {
      if ( flags & FLAGS_NOPK )
	 printf(" \nFile is probably compressed with PKLITE version %d.%02d or higher.\n\
Version info was removed or crippled, assuming extra compression.\n",
	 version >> 8, version & 0xFF);
      else
	 printf(" \nFile is compressed with PKLITE version %d.%02d,\n\
using %s model%s compression.\n", version >> 8, version & 0xFF,
	       ((flags & FLAGS_LARGE) ? "large" : "small"),
	       ((flags & FLAGS_EXTRA) ? " extra": ""));
      if (isexe) {
	 printf("Header size %d bytes, of which %d bytes is additional linker data,\n\
%d fixups, and %d filler bytes.\n", newx.headersize * 16, xheadersize,
	 fixupbufp / 2, newx.headersize * 16 - xheadersize -
	      fixupbufp * 2 - sizeof(struct Exe_Header));
	 if ( overlaysize )
	    printf("Overlay size %ld bytes.\n", overlaysize);
	 if ( fixupseg[0] != ENDOFSEGLIST && !toomanysegments ) {
	    printf("Inspection reveals these (probable) data and text segments:\n");
	    i = 0;
	    do
	       printf("%04X", fixupseg[i]);
	    while (fixupseg[++i] != ENDOFSEGLIST && printf(",   "));
	    printf("\n");
	 }
	 printf("\nCompressed text %ld, original text %ld, %d%% real compression\n",
	      exesize, newexesize, (newexesize - exesize) * 100 / newexesize);
      }
   }
   if ( !options.silent ) {
      if ( isexe )
	 newfilesize = newx.headersize * 16 + newexesize + overlaysize;
      else
	 newfilesize = newexesize;
      printf("Compressed file %ld, original file %ld, %d%% effective compression\n",
	   filesize, newfilesize, (newfilesize - filesize) * 100 / newfilesize);
   }
   doubleexit("", 0);
}


/* getnamepart. Returns the start of the filename part of path */
static char* getnamepart(char* path) {
   char* p = path + strlen(path);

   while ( --p >= path )
      if ( *p == '\\' || *p == '/' || *p == ':' )
	 return p + 1;
   return path;
}


/* xmatch. extended verion of match. This finds str2 within a binary
 * block of length len starting at str1. str1 can contain anything
 * including null. str2 is an ordinary string but may contain the
 * following special combinations:
 *  s0 - matches 0
 *  ss - matches '\x73' ('s')
 *  s* - matches any string
 *  s? - matches any single byte
 *  sH - indicates the location of the "hot spot"; not used in matches
 *
 * NULL is returned if str2 doesn't match (str1, len).
 * If str2 does match (str1, len), then if 'sH' is part of the sequence
 * the position of sH in str1 is returned, else FFFF:FFFF is returned.
 *
 * Notice that to find a string "abc" inside a binary block you have to
 * set str2 to "s*sHabcs*", and this will return the position of "abc".
*/
static char far* xmatch(char far* str1, unsigned len, char* str2) {
   char far* result = MK_FP(0xFFFF, 0xFFFF);
   char far* res2;

   for (;;) {
      if ( *str2 == 0 )
	 return (len == 0) ? result : NULL;
      else if ( *str2 == 's' ) {
	 switch ( *++str2 ) {
	 case '0':
	    if ( len == 0 || *str1 != 0 )
	       return NULL;
	    break;
	 case 's':
	    if ( len == 0 || *str1 != 's' )
	       return NULL;
	    break;
	 case '*':
	    str2++;
	    // Shortcut s* wildcard at the end of str2 (common)
	    if ( *str2 == 0 )
	       return result;
	    do
	       if ( (res2 = xmatch(str1, len, str2)) != NULL )
		  return (res2 == MK_FP(0xFFFF, 0xFFFF)) ? result : res2;
	    while ( str1++, len-- );
	    return NULL;
	 case '?':
	    if ( len == 0 )
	       return NULL;
	    break;
	 case 'H':
	    result = str1;
	    str1--; /* undo coming increment of str1 and decrement of len */
	    len++;
	    break;
	 default:
	    /* Bad str2 string, 's' followed by illegal char */
	    return NULL;
	 }
      }
      else if ( len == 0 || *str2 != *str1 )
	 return NULL;
      str1++;
      len--;
      str2++;
   }
}


/* Returns result of any xmatch of an entry in str2arr
 * matching str1, len. Last entry of str2arr contains NULL.
 * Never returns FFFF:FFFF. If the matching entry of str2arr
 * doesn't contain 'sH', then str1 is returned.
*/
static char far* multixmatch(char far* str1, unsigned len, char** str2arr) {
   int seqnr;
   char far* result;

   for (seqnr = 0; str2arr[seqnr]; seqnr++)
      if ( (result = xmatch(str1, len, str2arr[seqnr])) != NULL )
	 return (result == MK_FP(0xFFFF, 0xFFFF)) ? str1 : result;

   return NULL;
}


/* Allocate buffers. Only does something usefull for .EXE, really.
 * Just wastes cycles when decoding .COM. Also checks for memory
 * usage.
*/
static void allocbuf(void) {
   void *playspace;
   long coreleftafterload;
   long tsize;

   /* playspace takes care that some heap is left free. As the program
    to decode loads immediately above DISLITE, the heap is effectively
    truncated. */
   if ( (playspace = malloc(PLAYSPACESIZE)) == NULL ) {
      fprintf(stderr, "Error: Not enough memory.\n");
      exit(1);
   }
   if (isexe) {
      if ( (coreleftafterload = coreleft() - exesize - 16 * minalloc -
	   SAFETYGUARD) < 0 ) {
	 fprintf(stderr, "Error: Not enough memory.\n");
	 exit(1);
      }
      if ( coreleftafterload / 2 > MAXALLOCSIZE )
	 tsize = MAXALLOCSIZE;
      else if ((tsize = coreleftafterload / 2) < MINALLOCSIZE ) {
	 fprintf(stderr, "Error: Not enough memory.\n");
	 exit(1);
      }

      /* ...buf needs more than ...seg----on the face of it, 2/3
       * Let's default to 1/4 of the available memory, but use what
       * the "-c" options specified, unless that's too big.
      */
      if ( max_segments != 0 ) {
	 if ( (fixupsegmax = max_segments * 2) > tsize) {
	    fprintf(stderr, "Error: Specified number of segments too big.\n");
	    exit(1);
	 }
      }
      else
	 fixupsegmax = (unsigned) tsize / 4;
      if ( coreleftafterload - fixupsegmax > MAXALLOCSIZE )
	 fixupbufmax = MAXALLOCSIZE;
      else
         fixupbufmax = (unsigned) (coreleftafterload - fixupsegmax);
      if ( !(fixupbuf = (unsigned*) malloc(fixupbufmax)) ||
	   !(fixupseg = (unsigned*) malloc(fixupsegmax)) ) {
	 fprintf(stderr, "Error: Memory allocation failure.\n");
	 exit(1);
      }
      fixupbufmax /= sizeof(unsigned);
      fixupsegmax /= sizeof(unsigned);
   }
   free(playspace);
}

/* Parse command line arguments.
 * Stores result into options structure and infilename/outfilename */
static void parseargs(int argc, char** argv) {
   int nomoreopts = 0;
   char* ch;

   *infilename = 0;
   *outfilename = 0;
   options.makebackup = 0;
   options.noheaderinf = 0;
   options.overwrite = 0;
   options.pageheader = 0;
   options.nooverlay = 0;
   options.silent = 0;
   options.settime = 0;
   options_verbose = 0;
   options.dofixfixups = 0;
   options.listonly = 0;

   while (++argv, --argc) {
      if (isoption(*argv) && ! nomoreopts) {
	 if ((*argv)[1] == 0)
	    nomoreopts = 1;
	 while (*++*argv) {
	    switch ( tolower(**argv) ) {
	    case 'b': options.makebackup = 1; break;
	    case 'c': /* Max segment count */
	       ch = *argv +1;
	       if ( *ch == 0 && argc > 1 ) {
		  argv++;
		  argc--;
		  max_segments = atoi(*argv);
	       }
	       else
		  max_segments = atoi(ch);
	       if ( max_segments < 10 )
		  max_segments = 10;
	       *(*argv + 1) = 0; // Force exit of loop
	       break;

	    case 'e': /* (ignore :-) */ break;
	    case 'f': options.dofixfixups = 1; break;
	    case 'h': options.noheaderinf = 1; break;
	    case 'k': options.nosignature = 1; break;
	    case 'l': options.listonly = 1; break;
	    case 'o': options.overwrite = 1; break;
	    case 'p': options.pageheader = 1; break;
	    case 'r': options.nooverlay = 1; break;
	    case 's': options.silent = 1; break;
	    case 'u': options.settime = 1; break;
	    case 'v': ++options_verbose; break;
	    case '?': goto userwantshelp;
	    default:
	       title();
	       fprintf(stderr, "Error: Illegal argument: %s\n", *argv);
	       exit(1);
	    }
	 }
      }
      else if ( *infilename ) {
	 if ( *outfilename ) {
	    title();
	    fprintf(stderr, "Error: Too many filenames: %s\n", *argv);
	    exit(1);
	 }
	 strcpy(outfilename, *argv);
      }
      else
	 strcpy(infilename, *argv);
   }
   if ( !*infilename ) {
      userwantshelp:
      title();
      usage();
   }
}


/* Checks if opt is an option.
 * Checks both '-' and current switchar. If / is used, also checks for a
 * possible filename, by testing for the option letters. */
static int isoption(char* opt) {
   char switchar;

   _AX = GETSWITCHAR;
   geninterrupt(DOSINT);
   if ( _AL )
      switchar = '/';
   else
      switchar = _DL;
   if ( *opt == '-' || ( *opt == switchar && switchar != '/' ) )
      return 1;
   return ( *opt == switchar &&
	strspn(opt + 1, "bcefhkloprsuvBCEFHKLOPRSUV?0123456789") == strlen(opt + 1) );
}


/* Print title */
#include "title.inc"
static void title(void) {
   for (ch = x = 0xa5, co = titlestr; ch != 0; ++co) {
      ch = *co ^ x;
      putc(ch, stderr);
   }
}


/* Print help screen */
#include "z.inc"
void usage(void) {
   for (ch = x = 0xa5, co = strz; ch != 0; ++co) {
      ch = *co ^ x;
      putc(ch, stderr);
   }
   exit(1);
}


/* Convert numberofpages & bytesinlastpage to a total length. */
static long pages2long(struct Exe_Header* exeh) {
   return (long) (exeh->numberofpages - (exeh->bytesinlastpage ? 1 : 0))
   * 512 + exeh->bytesinlastpage;
}


/* Reverse of pages2long */
static void long2pages(long l, struct Exe_Header* exeh) {
   exeh->numberofpages = (unsigned) (l / 512) +
	( (exeh->bytesinlastpage = (unsigned) (l % 512)) != 0 ? 1 : 0 );
}


/* Scan input file. Find it (.EXE or .COM), analyse it, etc...
 * Sets isexe, minalloc, maxalloc, filesize, exesize, overlaystart,
 * overlaysize, version, flags, filetime.
 * Gives output of -l list option and exits.
 * Handles pathetic combination of -b and outfilename */
static void scanfile(void) {
   int fh;
   char* dotpos;
   char extcmnd[2*MAXFILELEN + 10];
   char* data;
   char* pklite;
   char* startofcode;
   struct Exe_Header* exeh;
   int gotwarning = 0;
   int unknownext = 0;
   int trycom;
   char c;
   char* codematch[9];
   char* pklitematch[9];

   /* See if there is a dot in the filename, if not, try both .EXE and .COM */
#pragma warn -pia
   if ( trycom = !(dotpos = strchr(getnamepart(infilename), '.')) ) {
#pragma warn +pia
      strcat(infilename, ".EXE");
      dotpos = strrchr(infilename, '.');
   }
   else if ( !options.silent &&
 	 stricmp(dotpos, ".COM") != 0 && stricmp(dotpos, ".EXE") != 0 ) {
      printf("Warning: File hasn't got a binary executable extension.\n");
      unknownext = gotwarning = 1;
   }
   if (
	(fh = open(infilename, O_RDONLY | O_BINARY)) == -1 && (
	errno != ENOFILE ||
	! trycom || (strcpy(dotpos, ".COM"),
	(fh = open(infilename, O_RDONLY | O_BINARY)) == -1))
	) {
      perror("Error: Cannot open executable");
      exit(1);
   }
   if ( options_verbose && !options.listonly )
      puts(infilename);

   if ( !options.settime && !options.listonly && getftime(fh, &filetime) ) {
      perror("Error: Cannot get file date/time");
      fprintf(stderr, "Assuming -u\n");
      options.settime = 1;
   }

   /* Read in the first INIBLOCK bytes of the file */
   if ( (data = (char*) malloc(INIBLOCK)) == NULL ) {
      fprintf(stderr, "Error: Not enough memory.\n");
      exit(1);
   }

   if ( read(fh, data, INIBLOCK) == -1 ) {
      perror("Error: Cannot read file");
      exit(1);
   }
   exeh = (struct Exe_Header*) data;

   if ((isexe = exeh->signature) != EXESIG1 && isexe != EXESIG2 )
      isexe = 0;
   if (!isexe == !stricmp(dotpos, ".EXE") && !options.silent && !unknownext) {
      gotwarning = 1;
      if (isexe)
	 printf("Warning: File is incorrectly called a .COM file, it has .EXE format\n");
      else
	 printf("Warning: File is called .EXE but hasn't got .EXE format\n");
   }
   if (unknownext)
      printf("File has .%s structure\n", isexe ? "EXE" : "COM" );

   /* Detect a PKLITEd executable. Check for certain instruction sequences
    * at the start of the code.
    * Also check for "PKLITE" string in header. If this is found, extract
    * version info and original executable header info, if present.
   */

   /* Try to match the initial instructions. the "hot spot" in the entries
    * of the codematch array indicate the estimated PKLITE version number
    * - 0x100. The lowest possible version number is returned, and not
    * every attempt is made to get the real version number as that is
    * pretty pointless I guess.
   */
   if ( isexe ) {
      if ( exeh->headersize * 16 < INIBLOCK ) {
	 startofcode = data + (exeh->headersize * 16);
	 /* 1.03 exe */
	 codematch[0] = "\xB8s?s?sH\xBAs?s?\x8C\xDB\x03\xD8\x3B\x1E\x02s0s\x73\x1Ds*";
	 /* 1.12 exe */
	 codematch[1] = "\xB8s?s?\xBAs?s?\x05s0s0\x3B\x06\x02sHs0s\x73\x1A\x2D\x20s0s*";
	 /* 1.14 exe */
	 codematch[2] = "\xB8s?s?\xBAs?s?\x05s0s0\x3B\x06\x02s0\x72sH\x1B\xB4\x09\xBA\x18\x01s*";
	 /* 1.20 exe */
	 codematch[3] = "\xB8s?s?\xBAs?s?\x05s0s0\x3B\x06\x02s0\x72s?\xB4\x09\xBAs?\x01sH\xCD\x21s*";
	 codematch[4] = NULL;
      }
      else {
	 // header too large, code not loaded. Very likely not a PKLITEd exe.
	 startofcode = data;
	 codematch[0] = NULL;
      }
   }
   else {
      startofcode = data;
      /* 1.03 com */
      codematch[0] = "\xB8s?s?sH\xBAs?s?\x3B\xC4s\x73\x67\x8B\xC4\x2D\x44\x03\x25\xF0\xFFs*";
      /* 1.15 com */
      codematch[1] = "\xB8s?s?\xBAs?s?\x3B\xC4s\x73\x69\x8B\xC4\x2D\x44\x03sH\x90\x25\xF0\xFFs*";
      /* 1.00 com */
      codematch[2] = "sH\xBAs?s?\xA1\x02s0\x2D\x20\x06\x8C\xCB\x81\xC3s0\x02\x3B\xC3s*";
      codematch[3] = NULL;
   }
   if ( (pklite = multixmatch(startofcode, 0x30, codematch)) != NULL ) {
      version = (unsigned) (pklite - startofcode) + 0x100;
      flags = FLAGS_EXTRA | FLAGS_NOPK;
   }
   else
      version = 0;

   /* Try to locate the PKLITE string */
   pklitematch[0] = "s*sHPKLITEs*"; /* all except 1.00 .COM */
   pklitematch[1] = "s*sHPK Copyr.s*"; /* 1.00 .COM */
   pklitematch[2] = NULL;
   if ( (pklite = multixmatch(data, 0x100, pklitematch)) != NULL ) {
      /* Before the "PKLITE" string there are two bytes encoded like this:
       * (in hex) vv FV. V.vv is the version number and F contains some
       * flag bits.
      */
      version = ((*(pklite - 1) & 0xF) << 8) | *(pklite - 2);
      flags = (*(pklite - 1) >> 4) & 0xF;
   }

   if ( options.listonly ) {
      printf("%-20s  ", infilename);
      if ( version ) {
	 printf("Compressed with PKLITE version %d.%02d", version >> 8,
	       version & 0xFF);
	 if ( flags & FLAGS_NOPK )
	    printf("?\n");
	 else
	    printf("%s%s\n", (flags & FLAGS_EXTRA) ? "X" : "",
		  (flags & FLAGS_LARGE) ? "L" : "");
      }
      else
	 printf("Not compressed by PKLITE\n");
      exit(0);
   }

   if ( version == 0 ) {
      fprintf(stderr, "Error: This is not a PKLITE'ed executable.\n");
      if ( options_verbose < 6 )
	 exit(1);
      gotwarning = 1;
   }

   /* Note that '114' is '1.20' (14hex = 20decimal) */
   if ((version > 0x114 || version < 0x100 ) && !options.silent) {
      printf("Warning: Unknown version of PKLITE %d.%02d\n",
	   version >> 8, version & 0xFF);
      gotwarning = 1;
   }
   if ((flags & 0xC) && !options.silent ) {
      printf("Warning: File has unknown flags set: 0x%X\n", flags);
      gotwarning = 1;
   }

   if ( (filesize = lseek(fh, 0, SEEK_END)) == -1 ) {
      perror("Error: Cannot get file size");
      exit(1);
   }
   if (isexe) {
      overlaystart = pages2long(exeh);
      exesize = overlaystart - exeh->headersize * 16;
      overlaysize = options.nooverlay ? 0 : filesize - overlaystart;
      minalloc = exeh->minalloc;
      maxalloc = exeh->maxalloc;
      if ( !(flags & FLAGS_EXTRA)) {
	 origheaderpos = exeh->reloctablep + 4 * exeh->relocentries - 2;
	 if ( origheaderpos + sizeof(struct Exe_Header) >
	      exeh->headersize * 16 )
	      origheaderpos = 0; /* Complain in preparenew(), not here */
      }
      if ( filesize < overlaystart && !options.silent ) {
	 fprintf(stderr, "Warning: File is damaged. EXE size exceeds real size.\n");
	 gotwarning = 1;
      }
   }
   else {
      minalloc = 0;
   }

   free(data);
   if (close(fh)) {
      perror("Error: Cannot close infile");
      exit(1);
   }

   if ( gotwarning ) {
      printf("Continue anyway ? ");
      while ( (c = tolower(getch())) != 'y' && c != 'n' )
	 ;
      printf("%c\n", c);
      if (c == 'n')
	 exit(1);
   }

   if ( *outfilename && options.makebackup ) {
      sprintf(extcmnd, "copy %s %*s.BAK", infilename, infilename,
	   (unsigned) (dotpos - infilename));
      if ( !system(extcmnd) ) {
	 perror("Warning: Cannot create backup file");
	 fprintf(stderr, "Ignored, since original file is not touched anyway.\n");
      }
   }
}


/* Loads the file. Initializes exestate */
static void loadexe(void) {
   struct REGPACK r;
   long dummyFCB[4];
   struct Load_Exe ldx;

   dummyFCB[0] = dummyFCB[1] = dummyFCB[2] = dummyFCB[3] = 0;
   ldx.firstFCB = ldx.secondFCB = &dummyFCB;
   ldx.commandtail = "\0\r";
   ldx.envp = 0;		/* Use my environment */
   r.r_ax = LOADNOTEXEC;
   r.r_ds = FP_SEG(infilename);
   r.r_dx = FP_OFF(infilename);
   r.r_es = FP_SEG(&ldx);
   r.r_bx = FP_OFF(&ldx);
   intr(DOSINT, &r);
   if ( r.r_flags & 1 ) {
      errno = r.r_ax;
      perror("Error: Cannot load executable");
      exit(1);
   }
   exestate.rSSSP = ldx.SSSP;
   exestate.rCSIP = ldx.CSIP;
   exestate.rDS = exestate.rES = getpsp();
   exestate.psp = (struct Psp far*) MK_FP(exestate.rDS, 0);
   exestate.psp->exitaddr = unexit;
   exestate.rAX = exestate.rBX = exestate.rCX = exestate.rDX =
	exestate.rSI = exestate.rDI = exestate.rBP = 0;
   exestate.rFLAGS = _FLAGS; /* Who gives a damn how it's initialised */
}


/* Called on unexpected exit from decompressing program */
void interrupt unexit(void) {
   fprintf(stderr, "Error: Program exited unexpectedly.\n");
   exit(1);
}


/* Handles exit from both uncompressing program and DISLITE.
 * Displays the error message, and exits with the exit code
*/
static void doubleexit(char* message, int exit_code) {
   fprintf(stderr, message);
   if ( setjmp(myexestate) == 0 ) {
      exestate.psp->exitaddr = normalexit;
      terminate();
   }
   exit(exit_code);
}


/* Called on normal exit from running program */
void interrupt normalexit(void) {
   longjmp(myexestate, 1);
}


/* Main debugging routine. Either executes compressed program upto until,
 * or executes a single instruction if until == NULL
 * Disables interrupts 10h, 13h, 21h for decompressing program.
*/
static void execuntil(char far* until) {
   static void interrupt (* bpintsave)(void);
   static void interrupt (* biosintsave)(void);
   static void interrupt (* dosintsave)(void);
   static char oldbp;
   int jret, bpint;

   bpint = until ? BREAKPOINTINT : SINGLESTEPINT;
   bpintsave = getvect(bpint);
   biosintsave = getvect(BIOSINT);
   dosintsave = getvect(DOSINT);
   /* diskint is no longer redirected. Unsafe when using a delayed-write cache */
   setvect(bpint, resume);
   setvect(BIOSINT, unbiosint);
   *DOSINTVECT = undosint; /* Cannot set the vector of INT 21 using INT 21 */
   if ( until ) {
      oldbp = *until;
      *until = ASM_BREAKPOINT; /* Insert the breakpoint */
   }
   else
      exestate.rFLAGS |= SINGLESTEPFLAG; /* Set the single step flag bit */

   if ( (jret = setjmp(myexestate)) == 0 )
      switchto(); /* taskswitch to the program to be uncompressed */
   else {
      if ( until ) {
	 *until = oldbp; /* Put the original breakpoint code in place */
	 if ( jret == 1 )
	    ((char far*) exestate.rCSIP)--; /* And update the CS:IP */
      }
      else {
	 exestate.rFLAGS &= ~SINGLESTEPFLAG; /* reset the singlestep flagbit */
      }
      /* Reset all vectors */
      *DOSINTVECT = dosintsave;
      setvect(bpint, bpintsave);
      setvect(BIOSINT, biosintsave);
      if ( message ) {
	 fprintf(stderr, message);
	 message = NULL;
	 if ( exit_code )
	    doubleexit("", exit_code);
      }
      if ( jret != 1 ) {
	 /* Program returned unexpectedly via an interrupt ... */
	 fprintf(stderr, " \nError: Unexpected interrupt %02Xh from processed program\n\
AX=%04X BX=%04X CX=%04X DX=%04X SI=%04X DI=%04X BP=%04X DS=%04X ES=%04X\n\
relSSSP=%04X:%04X  relCSIP=%04X:%04X  Flags=%04X\n", jret,
	    exestate.rAX, exestate.rBX, exestate.rCX, exestate.rDX,
	    exestate.rSI, exestate.rDI, exestate.rBP,
	    exestate.rDS, exestate.rES,
	    FP_SEG(exestate.rSSSP) - codestart, FP_OFF(exestate.rSSSP),
	    FP_SEG(exestate.rCSIP) - codestart, FP_OFF(exestate.rCSIP) - 2,
	    exestate.rFLAGS);
	 /* If it was displaying a message via INT 21/AH = 9 ... */
	 /*** Warning---if this is in, the generated code seems to be
	  * slightly different, and you can't set breakpoints without
	  * KILLING the program.   Be warned!!!!
	 */
	 /* Surely not with BC++ 3.1??? */
	 if ( jret == DOSINT && (exestate.rAX & 0xFF00) == 0x900 ) {
	    *strchr(MK_FP(exestate.rDS, exestate.rDX), '$') = 0;
	    fprintf(stderr, "\nProgram was about to display this message:\n%Fs",
		 MK_FP(exestate.rDS, exestate.rDX));
	 }
	 doubleexit("\nSee DISLITE.DOC file for more information\n", 1);
      }
   }
}


/* Called instead of a real fixup. Saves fixup in buffer & adds the
 *  [presumed] segment (in the executable code) to the segment list */
void interrupt fixup(void) {
   unsigned fixupofs = _BX;
   unsigned far* fixuploc = (unsigned far*) MK_FP(_ES, _DI);

   if ( exit_code )
      return; /* Do nothing if error has been detected */
   addseg(*fixuploc);
   fixupbuf[fixupbufp++] = FP_OFF(fixuploc);
   fixupbuf[fixupbufp++] = FP_SEG(fixuploc) - fixupofs;
   if (fixupbufp >= fixupbufmax) {
      if ( fixupbufmax * 2 == MAXALLOCSIZE )
	 message = "Error: Too many fixups! Sorry!\n\
Ask the author of DISLITE to make a version that can handle more fixups!\n";
      else
	 message = "Error: Too many fixups. Give me more memory.\n";
      exit_code = 1;
   }
}


/* Add the segment, if it's not already in the table.
 * Insert in ascending order. Because negative relative segments
 * upto 0xF000 are possible (practically segments of 0xFF00 would be
 * encountered most), the comparison adds 0x1000 and compares unsigned
 * so the sort order is (lowest) 0xF000...0...0x8000...0xEFFF (highest)
*/
void addseg(unsigned fupseg) {
   unsigned temp, temp2;
   int i = 0;

   if ( toomanysegments )
      return;
   while (fixupseg[i] + COMPAREOFFSET < fupseg + COMPAREOFFSET)
      i++;
   if (fixupseg[i] != fupseg) {
      temp2 = fixupseg[i];
      fixupseg[i] = fupseg;
      do {
	 temp = temp2;
	 temp2 = fixupseg[++i];
	 if ( i >= fixupsegmax ) {
	    message = "Harmless warning: Too many segments! Use -c switch\n";
	    toomanysegments = 1;
	 }
      }
      while ((fixupseg[i] = temp) != ENDOFSEGLIST);
   }
}


/* Fix the fixup table, or only check it if checkonly != 0.
 * "fix" means to make them relative to what appears to be the
 * segments that exist in the executable.
 * This will un-do what HDROPT does.
*/
void fixfixups(int checkonly) {
   unsigned fseg;
   unsigned* seglist;
   int i, firstwarn = 1;

   if ( toomanysegments ) {
      printf("Harmless warning: Cannot fix fixups\n");
      return;
   }
   for (i = 0; i < fixupbufp; i+=2) {
      fseg = fixupbuf[i+1] + (fixupbuf[i] >> 4);
      seglist = fixupseg;
      while (*seglist + COMPAREOFFSET <= fseg + COMPAREOFFSET)
	 seglist++;
      seglist--;
      if (!checkonly) {
	 if ( fseg - *seglist > 0xFFF || (
	       fseg - *seglist == 0xFFF && (fixupbuf[i] & 0xF) == 0xF ) ) {
	    if ( firstwarn ) {
	       fprintf(stderr, "Harmless warning: static huge array of far pointers encountered.\n");
	       firstwarn = 0;
	    }
	 }
	 else {
	    fixupbuf[i+1] = *seglist;
	    fixupbuf[i] = (fixupbuf[i] & 0xF) | ((fseg - *seglist) << 4);
	 }
      }
      else if ( fixupbuf[i+1] != *seglist ) {
	 if ( firstwarn ) {
	    fprintf(stderr, "Harmless warning: Fixup segments don't match observed segments.\n\
   You might use '-f' to correct this.\n");
	    firstwarn = 0;
	 }
	 if ( options_verbose >= 3 )
	    printf("   Fixup %04X:%04X out of segment (should be in %04X)\n",
		  fixupbuf[i+1], fixupbuf[i], *seglist);
	 else
	    return;
      }
   }
}


/* Prepare new executable header. At this point the wrap code for
 * the PK signature get inserted, if necessary.
*/
static void preparenew(struct Exe_Header* exeh, unsigned far* inidata) {
   long newfilesize1;
   struct Exe_Header origheader;

   exeh->signature = isexe;
   exeh->relSS = *inidata++;
   exeh->SP = *inidata++;
   exeh->relCS = *inidata++;
   exeh->IP = *inidata;

   if (pk_sign)
      addpksign(exeh);

   exeh->relocentries = fixupbufp / 2;

   /* If not extra-compressed, test the stored EXE header for correctness.
    If it seems to be incorrect, assume extra-compression */
   if (!(flags & FLAGS_EXTRA) &&
	(newfilesize1 = teststoredheader(exeh, &origheader)) > 0 ) {
      exeh->minalloc = origheader.minalloc;
      exeh->maxalloc = origheader.maxalloc;
      exeh->checksum = origheader.checksum;
      exeh->overlaynum = 0;
      /* Allow the no-additional-linker-data flag */
      if ( options.noheaderinf ) {
	 xheadersize = 0;
	 exeh->reloctablep = sizeof(struct Exe_Header);
	 exeh->headersize = (sizeof(struct Exe_Header) +
	      4 * origheader.relocentries + 0xF) >> 4;
	 newfilesize1 = newexesize + exeh->headersize * 16;
	 long2pages(newfilesize1, exeh);
      }
   }
   else {
      xheadersize = 0;
      exeh->headersize = (exeh->relocentries * 4 +
	   sizeof(struct Exe_Header) + 0xF) >> 4;
      newfilesize1 = newexesize + exeh->headersize * 16;
      long2pages(newfilesize1, exeh);
      exeh->maxalloc = maxalloc;
      exeh->minalloc = minalloc - (unsigned) ((newexesize - exesize) >> 4);
      exeh->checksum = 0; /* Does this work for DOS 2.x too?? */
      exeh->reloctablep = sizeof(struct Exe_Header);
      exeh->overlaynum = 0;
   }
   if ( options.pageheader ) {
      exeh->headersize = (exeh->headersize + 0x1F) & ~0x1F;
      newfilesize1 = newexesize + exeh->headersize * 16;
      long2pages(newfilesize1, exeh);
   }
}


/* Add code to insert the "PK" signature in the psp.
 * If the loaded image of the decompressed executable contains the stack,
 * then only 4 bytes of code are added to the program (and 6 or 8 bytes
 * are preloaded on stack. This is safe since there should be stack space
 * for interrupts anyway, using 8 bytes isn't too much). If the stack
 * isn't loaded, then a simple mov, jmp sequence is added requiring 8, 9
 * or 11 bytes of code.
 * If the added code can be placed in the initial code segment, there is
 * no need for an extra fixup that is otherwise needed, and a near jump
 * (or short jump) or near return is used.
 * Notice that the code that uses the stack clobbers DI, but DI is
 * undefined on program startup anyway (PKLITE assumes it's zero, but
 * that's incorrect).
 * Counting the fixup, the worst case maximum increase in program size
 * compared to the original uncompressed executable is 27 bytes.
*/
static void addpksign(struct Exe_Header* exeh)
{
   unsigned nearofs;
   char far* end_of_code;
   char huge* hp1;
   char huge* hp2;
   char huge* hp3;
   int stackonboard;

   // Get pointer to the end of the decompressed code
   end_of_code = (char far*) MK_FP(
	 (unsigned) ((newexesize >> 4)) + codestart,
	 (unsigned) (newexesize & 0xF));
   hp1 = (char huge*) end_of_code;
   hp2 = (char huge*) MK_FP(exeh->relSS + codestart, exeh->SP);
   hp3 = (char huge*) MK_FP(codestart, 0);
   // Determine if stack is in decompressed code
#pragma warn -pia
   if ( stackonboard = (hp2 <= hp1 && hp2 > hp3) ) {
#pragma warn +pia
      hp1 += 4; // length of pksign code when using the stack
      // Insert the first two instructions when using the stack
      memcpy(end_of_code, "\x5F\x8F\x05", 3); // POP DI; POP [DI];
      // The size of the code that uses the stack is constant, so adjust here
      newexesize += 4;
   }
   else {
      hp1 += 9; // length of pksign code when using mov and near jmp
      // Insert the first instruction when not using the stack (mov)
      memcpy(end_of_code, pk_sign, 6); // mov [WORD PTR PKSIGNOFFSET],PKSIGN
   }
   hp3 = (char huge*) MK_FP(exeh->relCS + codestart, 0);
   if ( hp1 - hp3 < 0xFFFF ) {
      // kludge code can be in the same segment as initial CS -> near jump/ret
      if ( stackonboard ) {
	 // Insert code that uses the stack and a near return...
	 *(unsigned*) (hp2 - 2) = exeh->IP;
	 *(unsigned*) (hp2 - 4) = *(unsigned*) (pk_sign+4);
	 *(unsigned*) (hp2 - 6) = *(unsigned*) (pk_sign+2);
	 exeh->SP -= 6; // adjust the stack pointer accordingly
	 *(end_of_code+3) = ASM_RET; // return
      }
      else {
	 // Calculate offset to real starting IP
	 hp2 = (char huge*) MK_FP(codestart + exeh->relCS, exeh->IP);
	 nearofs = (unsigned) (hp1 - hp2);
	 if ( nearofs < 0x7F ) {
	    // Short jump (this is unlikely, I agree...)
	    // Insert short jump and offset - 1, because hp1 is end_of_code+9
	    // while the relative start address of the short jump is at
	    // end_of_code+8
	    *(end_of_code+6) = ASM_JMPSHORT;
	    *(end_of_code+7) = (unsigned char) 1-nearofs;
	    newexesize += 8;
	 }
	 else {
	    // Near jump
	    *(end_of_code+6) = ASM_JMPNEAR;
	    *((unsigned*) (end_of_code+7)) = -nearofs;
	    newexesize += 9;
	 }
      }
      hp1 = (char huge*) end_of_code;
      exeh->IP = (unsigned) (hp1 - hp3); // adjust IP to point to pksign code
   }
   else {
      // Not the same segment... use long jump or far return. This
      // requires one extra fixup.
      if ( stackonboard ) {
	 // Insert code that uses the stack and a far return...
	 *(unsigned*) (hp2 - 2) = exeh->relCS;
	 fixupbuf[fixupbufp++] = FP_OFF(hp2 - 2); // Add fixup for relCS on stack
	 fixupbuf[fixupbufp++] = FP_SEG(hp2 - 2) - codestart;
	 *(unsigned*) (hp2 - 4) = exeh->IP;
	 *(unsigned*) (hp2 - 6) = *(unsigned*) (pk_sign+4);
	 *(unsigned*) (hp2 - 8) = *(unsigned*) (pk_sign+2);
	 exeh->SP -= 8; // adjust the stack pointer accordingly
	 *(end_of_code+3) = ASM_RETF; // far return
      }
      else {
	 // Insert code that uses mov and jmp far...
	 *(end_of_code+6) = ASM_JMPFAR;
	 *((unsigned*) (end_of_code+7)) = exeh->IP;
	 *((unsigned*) (end_of_code+9)) = exeh->relCS;
	 fixupbuf[fixupbufp++] = FP_OFF(end_of_code+9); // Add fixup for relCS
	 fixupbuf[fixupbufp++] = FP_SEG(end_of_code+9) - codestart;
	 newexesize += 11;
      }
      addseg(exeh->relCS); // Add relCS as segment before overwriting it...
      // Adjust CS,IP to point to the pksign code
      exeh->relCS = FP_SEG(end_of_code) - codestart;
      exeh->IP = FP_OFF(end_of_code);
   }
}


/* Test original stored header.
 * Reads original header and additional header data, sets bytesinlastpage,
 * numberofpages, headersize. Returns length of header+EXE image
 * (filelen - overlay),	or 0 if stored header is incorrect */
long teststoredheader(struct Exe_Header* exeh, struct Exe_Header* origh) {
   long tmpfileofs, newfilesize1;
   int fh;

   if ( origheaderpos ) {
      /* If an original header exists, read it in */
      if ( (fh = open(infilename, O_RDONLY | O_BINARY)) == -1 ) {
	 perror("Error: Cannot open backup file");
	 doubleexit("", 1);
      }
      if ( lseek(fh, origheaderpos, SEEK_SET) == -1 ) {
	 perror("Error: Cannot seek to original header info");
	 doubleexit("", 1);
      }
      if ( read(fh, origh, sizeof(struct Exe_Header)) == -1 ) {
	 perror("Error: Cannot read backup file");
	 doubleexit("", 1);
      }
      exeh->reloctablep = origh->reloctablep;
#pragma warn -pia
      if ( xheadersize = exeh->reloctablep - sizeof(struct Exe_Header) ) {
#pragma warn +pia
	 if ( (extradata = (char*) malloc(xheadersize)) == NULL )
	    doubleexit("Error: Underestimated memory requirements. Sorry\n", 1);
	 if ( read(fh, extradata, xheadersize) == -1 ) {
	    perror("Error: Cannot read backup file");
	    doubleexit("", 1);
	 }
      }
      if ( close(fh) ) {
	 perror("Error: Cannot close backup file.\n");
	 doubleexit("", 1);
      }
      /* Copy the information from that original header into the new exehdr */
      exeh->bytesinlastpage = origh->bytesinlastpage;
      exeh->numberofpages = origh->numberofpages;
      exeh->headersize = origh->headersize;
      newfilesize1 = pages2long(exeh);

      /* Now check the stored original exeheader information against
       what we calculated */
      if ( newfilesize1 - exeh->headersize * 16 == newexesize &&
	    origh->relSS == exeh->relSS &&
	    origh->SP == exeh->SP &&
	    origh->relCS == exeh->relCS &&
	    origh->IP == exeh->IP &&
	    origh->relocentries == exeh->relocentries &&
	    origh->overlaynum == 0 )
	 return newfilesize1;
   }

   /* Something is wrong here... do some sanity checks on the information
    we calculated. */
   if ( ! options.silent )
      fprintf(stderr, "Harmless warning: Stored header information is incorrect.\n   Assuming extra compression.\n");
   /* Check the new .EXE size. This allows for 16x original compression. Maybe
    we should instead check for 640K limit or something? */
   if ( newexesize < exesize || newexesize / 16 > exesize ) {
      fprintf(stderr, "Error: The calculated new executable size is absurd: %ld\n",
	   newexesize);
      doubleexit("Aborting.\n", 1);
   }
   /* Check if SS:SP and CS:IP have sane values */
   tmpfileofs = (long) exeh->relSS * 16 + exeh->SP;
   if ( tmpfileofs < 0 || tmpfileofs > newexesize + minalloc * 16 ) {
      fprintf(stderr, "Error: The calculated initial relSS:SP is absurd: %04X:%04X\n",
	   exeh->relSS, exeh->SP);
      doubleexit("Aborting.\n", 1);
   }
   tmpfileofs = (long) exeh->relCS * 16 + exeh->IP;
   if ( tmpfileofs < 0 || tmpfileofs > newexesize ) {
      fprintf(stderr, "Error: The calculated initial relCS:IP is absurd: %04X:%04X\n",
	   exeh->relCS, exeh->IP);
      doubleexit("Aborting.\n", 1);
   }
   if ( options_verbose ) {
      /* Print out what seems wrong here... */
      if ( ! origheaderpos )
	 fprintf(stderr, "The compressed executable header is too short to contain an original header.\n");
      else {
	 if ( newfilesize1 - exeh->headersize * 16 != newexesize )
	    fprintf(stderr, "Stored executable image size is incorrect: %ld should be %ld\n",
	      newfilesize1 - exeh->headersize * 16, newexesize);
	 if ( origh->relSS != exeh->relSS || origh->SP != exeh->SP )
	    fprintf(stderr, "Stored initial relSS:SP is incorrect: %04X:%04X should be %04X:%04X\n",
	      origh->relSS, origh->SP, exeh->relSS, exeh->SP);
	 if ( origh->relCS != exeh->relCS || origh->IP != exeh->IP )
	    fprintf(stderr, "Stored initial relCS:IP is incorrect: %04X:%04X should be %04X:%04X\n",
	      origh->relCS, origh->IP, exeh->relCS, exeh->IP);
	 if ( origh->relocentries != exeh->relocentries )
	    fprintf(stderr, "Stored number of fixups is incorrect: %d should be %d\n",
	      origh->relocentries, exeh->relocentries);
	 if ( origh->overlaynum )
	    fprintf(stderr, "Stored header has an overlay number: %d should be 0\n",
	      origh->overlaynum);
      }
   }
   if ( ! options.silent )
      fprintf(stderr, "Assuming an extra-compressed file.\n");
   return 0;
}


/* Write out the new executable */
static void writeexe(struct Exe_Header* exeh) {
   unsigned char far *xtext;
   unsigned chunklen;
   long codesize;
   int fh, fh2;
   char c;
   int dollartemp;
   char backname[MAXFILELEN];
   char zeros[ZEROLEN];

   /* Create a temporary filename is no outfile is given */
#pragma warn -pia
   if ( dollartemp = ! *outfilename ) {
#pragma warn +pia
      strcpy(outfilename, infilename);
      strcpy(strrchr(outfilename, '.'), ".$$$");
   }
   else if ( ! strchr(getnamepart(outfilename), '.' ) )
      strcat(outfilename, strrchr(infilename, '.'));

   /* Create the output file.
    * If it already exists, ask about deleting it. */
   if (access(outfilename, 0) == 0 &&
	!(options.overwrite || dollartemp)) {
      printf(" \nOutput file already exists. Overwrite it? ");
      while ( (c = tolower(getch())) != 'y' && c != 'n' )
	 ;
      printf("%c\n", c);
      if (c == 'y')
	 options.overwrite = 1;
   }

   if ( (fh = open(outfilename, O_BINARY | O_CREAT | O_WRONLY |
	(options.overwrite || dollartemp ? O_TRUNC : O_EXCL),
	S_IFREG | S_IREAD | S_IWRITE | S_IEXEC)) == -1 ) {
      perror("Error: Cannot create new executable");
      doubleexit("", 1);
   }
   xtext = MK_FP(codestart, 0);
   PROGRESS;
   if (isexe) {
      /* Write the .EXE header, any extra linker data, and the fixups */
      if ( write(fh, exeh, sizeof(struct Exe_Header)) == -1 ||
	   ( xheadersize &&
	   write(fh, extradata, xheadersize) == -1 ) ||
	   write(fh, fixupbuf, fixupbufp * 2) == -1 ) {
	 perror("Error: Cannot write new executable header");
	 doubleexit("", 1);
      }
      free(fixupbuf);
      if (extradata)
	 free(extradata);
      /* Write any header filler 00 bytes */
      if ( (codesize = ((long) exeh->headersize * 16 - tell(fh))) > 4096 )
	 doubleexit("Error: Ridiculous header filler size.\n", 1);
      memset(zeros, 0, ZEROLEN);
      while ( codesize ) {
	 PROGRESS;
	 chunklen = (unsigned) min(codesize, ZEROLEN);
	 if ( write(fh, zeros, chunklen) == -1 ) {
	    perror("Error: Cannot write new executable header");
	    doubleexit("", 1);
	 }
	 codesize -= chunklen;
      }
   }
   /* Write out the actual decompressed code */
   xtext = MK_FP(codestart, 0);
   codesize = newexesize;

   if ( codesize > MAXCHUNK )
      chunklen = MAXCHUNK;
   else
      chunklen = (unsigned) codesize;
   while ( codesize ) {
      PROGRESS;
      if ( write(fh, xtext, chunklen) == -1 ) {
	 perror("Error: Cannot write new executable");
	 doubleexit("", 1);
      }
      xtext = MK_FP(FP_SEG(xtext)+(chunklen>>4), 0);
      if ( (codesize -= chunklen) < MAXCHUNK )
	 chunklen = (unsigned) codesize;
   }

   /* Write out (copy) any EXE overlays */
   if ( isexe && overlaysize ) {
      if ( (fh2 = open(infilename, O_BINARY | O_RDONLY)) == -1 ) {
	 perror("Error: Cannot reopen backup file");
	 doubleexit("", 1);
      }
      if ( lseek(fh2, overlaystart, SEEK_SET) == -1 ) {
	 perror("Error: Cannot seek to overlay area");
	 doubleexit("", 1);
      }

      /* Allocate a memory buffer to do the copying */
      if ( (codesize = overlaysize) > MAXCHUNK )
	 chunklen = MAXCHUNK;
      else
	 chunklen = (unsigned) codesize;
      if ( coreleft() < chunklen &&
	   (chunklen = (unsigned) coreleft()) < MINCHUNK )
	   doubleexit("Error: Out of memory copying overlays.\n", 1);

      if ( (xtext = malloc(chunklen)) == NULL )
	 doubleexit("Error: Memory allocation failure.\n", 1);
      while ( codesize ) {
	 PROGRESS;
	 if ( read(fh2, xtext, chunklen) == -1 ) {
	    perror("Error: Cannot read overlays");
	    doubleexit("", 1);
	 }
	 if ( write(fh, xtext, chunklen) == -1 ) {
	    perror("Error: Cannot write new executable");
	    doubleexit("", 1);
	 }
	 if ( (codesize -= chunklen) < chunklen )
	    chunklen = (unsigned) codesize;
      }
      if ( close(fh2) ) {
	 perror("Error: Cannot close backup file");
	 doubleexit("", 1);
      }
   }
   /* Set file date/time info */
   if ( !options.settime && setftime(fh, &filetime) ) {
      perror("Error: Cannot set new file date/time");
      fprintf(stderr, "Ignored.\n");
   }
   if ( close(fh) ) {
      perror("Error: Cannot close executable");
      doubleexit("", 1);
   }

   /* Now rename and delete files, if applicable */
   if (dollartemp) {
      if (options.makebackup) {
	 strcpy(backname, infilename);
	 strcpy(strrchr(backname, '.'), ".BAK");
	 /* Remove a possibly existing .BAK files */
	 if ( unlink(backname) && errno != ENOFILE ) {
	    perror("Error: Cannot remove old backup file");
	    doubleexit("", 1);
	 }
	 /* Rename the original file */
	 if ( rename(infilename, backname) ) {
	    perror("Error: Cannot rename original file");
	    doubleexit("", 1);
	 }
      }
      else if ( unlink(infilename) ) {
	 perror("Error: Cannot remove original file");
	 doubleexit("", 1);
      }
      if ( rename(outfilename, infilename) ) {
	 perror("Error: Cannot rename temporary file");
	 doubleexit("", 1);
      }
   }
}

