#include <stdio.h>
#include <string.h>

int getInFile(char* ExeIn, FILE **ExeInF);
int checkFileType(FILE *ExeInF);
int getOutFile(char* filetypeString, char* ExeIn, char* ExeOut, FILE **ExeOutF);
int patch(FILE *ExeInF, FILE *ExeOutF, char *patchdata);
char extractDriveLetter(char *pathname);
int getTmpFile(char *tmpFilename, FILE **ExeOutF, char driveLetter);

#define ChipFileSize  267776
#define BugPatch1Loc  0x00007C93L
#define BugPatch1Len  82

char Patch1Before[BugPatch1Len] =
        {0x8A, 0xD8, 0x2A, 0xFF, 0x8B, 0xC3, 0xD1, 0xE3,
         0x03, 0xD8, 0xD1, 0xE3, 0x8D, 0x7E, 0xF6, 0x8D,
         0xB7, 0x6C, 0x06, 0x8C, 0xD0, 0x8E, 0xC0, 0xA5,
         0xA5, 0xA5, 0x8A, 0x46, 0xF7, 0x2A, 0xE4, 0x8B,
         0x5E, 0x0E, 0x89, 0x07, 0x80, 0x7E, 0xF6, 0x01,
         0x75, 0x07, 0xB8, 0x01, 0x00, 0xE9, 0xD8, 0x01,
         0x90, 0x80, 0x7E, 0xF6, 0x02, 0x74, 0x03, 0xE9,
         0xC7, 0x01, 0x80, 0x7E, 0xFD, 0x40, 0x72, 0x09,
         0x80, 0x7E, 0xFD, 0x6F, 0x77, 0x03, 0xE9, 0x98,
         0x01, 0x80, 0x7E, 0xFD, 0x0A, 0x75, 0x03, 0xE9,
         0x8F, 0x01};

char Patch1After[BugPatch1Len] =
        {0xB3, 0x06, 0xF6, 0xE3, 0x89, 0xC3, 0x8D, 0x7E,
         0xF6, 0x8D, 0xB7, 0x6C, 0x06, 0x8C, 0xD0, 0x8E,
         0xC0, 0xA5, 0xA5, 0xA5, 0x8A, 0x46, 0xF7, 0x28,
         0xE4, 0x8B, 0x5E, 0x0E, 0x89, 0x07, 0x80, 0x7E,
         0xF6, 0x01, 0x75, 0x08, 0xB8, 0x01, 0x00, 0xE9,
         0xDE, 0x01, 0xEB, 0xF8, 0x80, 0x7E, 0xF6, 0x02,
         0x74, 0x03, 0xE9, 0xCC, 0x01, 0x80, 0x7E, 0xFD,
         0x40, 0x72, 0x0F, 0x80, 0x7E, 0xFD, 0x6F, 0x77,
         0x09, 0x83, 0x7E, 0x12, 0x00, 0x74, 0xDD, 0xE9,
         0x97, 0x01, 0x80, 0x7E, 0xFD, 0x0A, 0x75, 0x02,
         0xEB, 0xEF};

#define FF_Invalid        0
#define FF_Original       1
#define FF_Patched        2

/* while not necessarily relevant here, DOS imposes a 64-character limit on
   the "current directory" path; together with 3 letters for the drive letter,
   and 12 characters for the 8.3 filename, plus the null terminating character,
   and we get 80.*/
#define ExeLen            80
char ExeIn[ExeLen];
char ExeOut[ExeLen];
char ExeTemp[L_tmpnam+3];  /* hold the temporary filename used
                                                 during saving */
char YesNo[2] = {0,0};
#define AnswerYes        (YesNo[0] == 'Y' || YesNo[0] == 'y')
#define ExeOutF_CREATE    0
#define ExeOutF_REPLACE   1

unsigned int tmpCount = 0;

int main() {

  int status, outStatus;
  FILE *ExeInF = NULL, *ExeOutF = NULL;
  char *newstuff;

  status = getInFile(ExeIn, &ExeInF);
  switch (status) {
    case FF_Original:
      newstuff = Patch1After;
      outStatus = getOutFile("patched", ExeIn, ExeOut, &ExeOutF);
      break;
    case FF_Patched:
      newstuff = Patch1Before;
      outStatus = getOutFile("unpatched", ExeIn, ExeOut, &ExeOutF);
      break;
    default:
      return status;
  }

  switch (outStatus) {
    case ExeOutF_REPLACE:
	   printf ("\nCreating temporary file...");
	   if (getTmpFile(ExeTemp, &ExeOutF, extractDriveLetter(ExeOut)) == 0) {
	     printf ("success!\n"
		          "Now writing to temporary file %s...", ExeTemp);
        if (patch(ExeInF, ExeOutF, newstuff) == 0) {
		    printf ("success!\n"
	               "Now deleting your old %s file...", ExeOut);
          fclose(ExeInF);
          fclose(ExeOutF);
          if (remove(ExeOut) == 0) {
		      printf ("success!\n");
		      printf ("And finally, renaming the new file %s to %s...",
                    ExeTemp, ExeOut);
            if (rename(ExeTemp, ExeOut) == 0) {
			     printf ("success!\n");
              status = 0;
              break;
            }
            else
			     printf ("FAILED!\n");
		    }
		    else {
		      printf ("\nError! Cannot delete file!\n");
		    }
		    printf ("\nAs a result, the new file has been written to %s instead of %s.\n"
                  "Sorry for the inconvenience!\n", ExeTemp, ExeOut);
		    return -1;
		  }
		  else
		    printf ("\nError while writing to temporary file %s!\n", ExeTemp);
	   }
	   else
	     printf ("\nError creating temporary file!\n");

	   printf ("Program must directly overwrite %s! Proceed (Y/N)? ", ExeOut);
	   scanf (" %1s", YesNo);
	   if (!AnswerYes) {
	     status = -1;
		  break;
	   }

	   /* purposely no BREAK here */

    case ExeOutF_CREATE:
      fclose(ExeOutF);
	   if ((ExeOutF = fopen(ExeOut, "wb")) != NULL) {
	     printf ("\nWriting to file %s...", ExeOut);
		  if ((status = patch(ExeInF, ExeOutF, newstuff)) == 0) {
		    printf ("success!\n");
		    break;
		  }
	   }
	   printf ("\nError writing to file %s! Program gives up!\n", ExeOut);
	   status = -1;
	   break;

    default:  /* defensive programming; code should never reached here */
      printf ("Internal error occurred in program during SAVE!\n"
              "Please e-mail a bug report to ccexplore@yahoo.com\n");
      return -1;
  }

  fclose (ExeInF);
  fclose (ExeOutF);

  if (status == 0) {
    printf ("%s has been successfully patched/unpatched.\n", ExeIn);
	return 0;
  }
  else {
    printf ("%s was NOT successfully patched/unpatched!\n", ExeIn);
	return -1;
  }
}

int getInFile(char* ExeIn, FILE **ExeInF) {

  int status;

  do {
    fclose(*ExeInF);
    printf ("\nEnter file to patch/unpatch: ");
    scanf (" %79s",ExeIn);
    if ((*ExeInF = fopen(ExeIn,"rb")) == NULL) {
      printf ("\nError opening %s for input!",ExeIn);
      status = -1;
    }
    else {
      printf ("\nFile successfully opened! Examining file...\n");
      status = checkFileType(*ExeInF);
      switch (status) {
        case FF_Invalid:
          printf ("%s does not appear to be a valid CHIPS.EXE file!", ExeIn);
          status = -1;
          break;
        case -FF_Original:
          printf ("%s looks similar to the original CHIPS.EXE file, but\n"
                  "the filesize is different!\n"
                  "Proceed to patch anyway (Y/N)? ", ExeIn);
          scanf (" %1s", YesNo);
          if (AnswerYes)
            return FF_Original;
          else {
            status = -1;
            break;
          }
        case FF_Original:
          printf ("%s has been recognized as an original CHIPS.EXE file.\n"
                  "Proceed to patch it so it won't crash anymore (Y/N)? ", ExeIn);
          scanf (" %1s", YesNo);
          if (AnswerYes)
            return FF_Original;
          else {
            status = 0;
            break;
          }
        case -FF_Patched:
          printf ("%s looks similar to the patched CHIPS.EXE file, but\n"
                  "the filesize is different!\n"
                  "Proceed to unpatch anyway (Y/N)? ", ExeIn);
          scanf (" %1s", YesNo);
          if (AnswerYes)
            return FF_Patched;
          else {
            status = -1;
            break;
          }
        case FF_Patched:
          printf ("%s has been recognized as a patched CHIPS.EXE file.\n"
                  "Proceed to unpatch (revert back to original CHIPS.EXE) "
                  "(Y/N)? ", ExeIn);
          scanf (" %1s", YesNo);
          if (AnswerYes)
            return FF_Patched;
          else {
            status = 0;
            break;
          }
        default:   /* defensive programming; code should never reached here */
          printf ("Internal error occurred in program during OPEN!\n"
                  "Please e-mail a bug report to ccexplore@yahoo.com\n");
          return -1;
      }
    }
    printf ("\nTry a different file (Y/N)? ");
    scanf (" %1s",YesNo);
    if (AnswerYes)
      continue;
    else
      return status;
  }
  while (1);
}

int checkFileType(FILE *ExeInF) {
  int returnCode;
  long int i;
  int b;
  char *signature;

  for (i=0; i < BugPatch1Loc; i++)
    if ((b = getc(ExeInF)) == EOF) return FF_Invalid; /* file size too small */

  b = getc(ExeInF);
  if ((char)b == Patch1Before[0]) {
    signature = Patch1Before;
    returnCode = FF_Original;
  }
  else if ((char)b == Patch1After[0]) {
    signature = Patch1After;
    returnCode = FF_Patched;
  }
  else
    return FF_Invalid;  /* file size too small, or bad signature */

  for (i=1; i < BugPatch1Len; i++) {
    b = getc(ExeInF);
    if (b == EOF || (char)b != signature[i])
      return FF_Invalid;  /* file size too small, or signature not recognized */
  }

  /* valid signature at patching location, but file size smaller than expected */
  for (i = BugPatch1Loc + BugPatch1Len; i < ChipFileSize; i++)
    if ((b = getc(ExeInF)) == EOF) return -returnCode;

  /* valid signature at patching location, but file size too large */
  if ((b = getc(ExeInF)) != EOF)
    return -returnCode;
  else
    return returnCode;
}

int getOutFile(char* filetypeString, char* ExeIn, char* ExeOut, FILE **ExeOutF){

  int fileExist;

  do {
    printf("\nEnter pathname to save this %s CHIPS.EXE to: ",
           filetypeString, ExeIn);
    scanf(" %79s", ExeOut);

    fileExist = 0;
/*	 if (ExeOut[0] == 0) {
	   strncpy(ExeOut, ExeIn, ExeLen);
	   fileExist = 1;
	 }*/
    fclose(*ExeOutF);
    *ExeOutF = fopen(ExeOut,"rb");

    if (!fileExist)
	   fileExist = (*ExeOutF != NULL);
    else
	   if (*ExeOutF != NULL)
        return 1;       /* skips over the confirmation to overwrite */
	   else
        fileExist = 0;

    if (fileExist) {
      printf("\nFile already exists! Overwrite (Y/N)? ");
	   scanf(" %1s", YesNo);
	   if (!AnswerYes)
        continue;
    }
    else {
      if ((*ExeOutF = fopen(ExeOut,"ab")) == NULL) {
        printf("\nTrouble creating new file %s. "
               "You must try a different pathname.\n", ExeOut);
        continue;
	   }
	 }

	 return fileExist;
  }
  while (1);
}


int patch(FILE *ExeInF, FILE *ExeOutF, char *patchdata) {
  long int i;
  int b;

  if (fseek(ExeInF, 0L, SEEK_SET) != 0)
    return -1;
  if (fseek(ExeOutF, 0L, SEEK_SET) != 0)
    return -1;

  for(i=0; i<BugPatch1Loc; i++) {
    b = getc(ExeInF);
    if (b == EOF) return -1;
    b = putc((char)b, ExeOutF);
	 if (ferror(ExeOutF)) return -1;
  }

  for(i=0; i<BugPatch1Len; i++) {
    b = getc(ExeInF);
    if (b == EOF) return -1;
    b = putc(patchdata[i], ExeOutF);
	 if (ferror(ExeOutF)) return -1;
  }

  while ((b = getc(ExeInF)) != EOF) {
    if (ferror(ExeInF)) return -1;
    b = putc((char)b, ExeOutF);
    if (ferror(ExeOutF)) return -1;
  }

  return 0;
}

char extractDriveLetter(char *pathname) {
  return (pathname[1] == ':') ? pathname[0] : 0;
}

int getTmpFile(char *tmpFilename, FILE **ExeOutF, char driveLetter) {

  FILE *tmpF = NULL;

  while (tmpCount < TMP_MAX) {
   tmpCount++;

	if (driveLetter) {
	  tmpnam(&tmpFilename[2]);
	  tmpFilename[0] = driveLetter; tmpFilename[1] = ':';
	}
	else
	  tmpnam(tmpFilename);

   fclose(tmpF);
	if ((tmpF = fopen(tmpFilename, "rb")) != NULL) {
     continue;
   }
	else {
	  if ((tmpF = fopen(tmpFilename, "wb")) == NULL)
	    continue;
	  else {
       *ExeOutF = tmpF;
	    return 0;
     }
	}
  }
  return -1;
}