/*  modified code from fullsec.c written by ccexplore */
#include <stdio.h>
#include <stddef.h>
#include <string.h>
#include "pgchip.h"

#define ExeLen            128     /* includes null terminator */
char ExeIn[ExeLen + 4];  /* + 4 to allow for appending ".exe" */
char ExeOut[ExeLen + 4]; /* + 4 to allow for appending ".exe" */
/* hold the temp filename used during saving */
/* ExeLen + 4 for prepending rest of path (see getTmpFile) */
char ExeTemp[ExeLen + 4 + L_tmpnam];

/* hold user response to questions, typically a yes/no answer */
/* includes room for null terminator */
char YesNo[2];
#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;
  unsigned const char *whattowrite;

  printf("This is pgchip.exe, version 1\n\n");
  status = getInFile(ExeIn, &ExeInF);
  switch (status) {
    case FF_Original:
      whattowrite = patch1data;
      outStatus = getOutFile("patched", ExeIn, ExeOut, &ExeOutF);
      break;
    case FF_Patched1:
      whattowrite = originaldata;
      outStatus = getOutFile("unpatched", ExeIn, ExeOut, &ExeOutF);
      break;
    default:
      return status;
  }

  strcpy(ExeTemp, ExeOut);
  switch (outStatus) {
    case ExeOutF_REPLACE:
      printf ("\nCreating temporary file...");
      if (getTmpFile(ExeTemp, &ExeOutF) == 0) {
        printf ("success!\n"
                "Now writing to temporary file %s...", ExeTemp);
        if (patch(ExeInF, ExeOutF, whattowrite) == 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, whattowrite)) == 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 pieguy@ecst.csuchico.edu\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;
  }
}

void prependext(char *exefilename) {
  char *c;

  c = strrchr(exefilename, '\\');
  if (c == NULL) {
    c = strrchr(exefilename, ':');
    if (c == NULL)
      c = exefilename;
  }
  c = strrchr(c, '.');

  /* only append an extension if extension not already exists, including
     an empty extension */
  if (c == NULL) {
    strcat(exefilename, ".exe");
  }
}

int getInFile(char* ExeIn, FILE **ExeInF) {

  int status;

  do {
    if (*ExeInF != NULL)
      fclose(*ExeInF);

    printf ("\nEnter file to patch/unpatch: ");
    scanf ("%127s",ExeIn);
    prependext(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!\n"
                  "Proceed anyway (Y/N)? ", ExeIn);
          scanf ("%1s", YesNo);
          if (AnswerYes) {
            do {
              printf("\npatch or unpatch (P/U)? ");
              scanf ("%1s", YesNo);
              if (YesNo[0] == 'p' || YesNo[0] == 'P')
                return FF_Original;
              else if (YesNo[0] == 'u' || YesNo[0] == 'U')
                return FF_Patched1;
              else
                printf("please enter either p or u.\n");
            } while (1);
          }
          else {
            status = -1;
            break;
          }

        case -FF_Original:
          printf ("%s looks similar to the original CHIPS.EXE file, but\n"
                  "the filesize is larger!\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 (Y/N)? ",
                  ExeIn);
          scanf ("%1s", YesNo);
          if (AnswerYes)
            return FF_Original;
          else {
            status = 0;
            break;
          }

        case -FF_Patched1:
          printf ("%s looks similar to the pgchip-patched "
                  "CHIPS.EXE file, but\n"
                  "the filesize is larger!\n"
                  "Proceed to unpatch anyway (Y/N)? ", ExeIn);
          scanf ("%1s", YesNo);
          if (AnswerYes)
            return FF_Patched1;
          else {
            status = -1;
            break;
          }

        case FF_Patched1:
          printf ("%s has been recognized as a "
                  "pgchip-patched CHIPS.EXE file.\n"
                  "Proceed to unpatch (revert back to original CHIPS.EXE) "
                  "(Y/N)? ", ExeIn);
          scanf ("%1s", YesNo);
          if (AnswerYes)
            return FF_Patched1;
          else {
            status = 0;
            break;
          }

        default:   /* defensive programming; code should never reached here */
          printf ("unrecognized status code %d in function getInFile!\n"
                  "Please e-mail a bug report to pieguy@ecst.csuchico.edu\n",status);
          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, i;
  long int len;
  int b, beforeaft;

  fseek(ExeInF, 0, SEEK_END);
  len = ftell(ExeInF);

  if (len < CHIPFILESIZE)
    return FF_Invalid;


  returnCode=
    (fileMatches(ExeInF, originaldata)&FF_Original)|
    (fileMatches(ExeInF, patch1data)&FF_Patched1);

  if (len > CHIPFILESIZE)
    return -returnCode;
  else
    return returnCode;
}

int fileMatches(FILE *ExeInF, unsigned const char *pattern){
  //returns -1 if match, 0 otherwise
  long int pos=0;
  int b;
  unsigned char c;
  unsigned short int s;
  unsigned long int l;

  fseek(ExeInF, 0, SEEK_SET);

  while(1){
    c=pattern[pos++];
    if(c!=0xff){
      b=fgetc(ExeInF);
      if(b==EOF || (unsigned char)b!=c) return 0;
    }else{
      c=pattern[pos++];
      if(c==0){
        b=fgetc(ExeInF);
        if(b==EOF || (unsigned char)b!=0xff) return 0;
      }else if(c!=0xff){
        if(fseek(ExeInF, c, SEEK_CUR)!=0) return 0;
      }else{
        s=(pattern[pos]<<8)+pattern[pos+1];
        pos+=2;
        if(s!=0xffff){

          if(fseek(ExeInF, s, SEEK_CUR)!=0) return 0;
        }else{
          l=(((((pattern[pos]<<8)+pattern[pos+1])<<8)
             +pattern[pos+2])<<8)+pattern[pos+3];
          pos+=4;
          if(l!=0xffffffff){
            if(fseek(ExeInF, l, SEEK_CUR)!=0) return 0;
          }else{
            return -1;
          }
        }
      }
    }
  }
}

int getOutFile(char* filetypeString, char* ExeIn, char* ExeOut, FILE **ExeOutF){

  do {
    int fileexist;

    printf("\nEnter pathname to save this %s CHIPS.EXE to: ",
           filetypeString, ExeIn);
    scanf("%127s", ExeOut);
    prependext(ExeOut);

    if (*ExeOutF != NULL)
      fclose(*ExeOutF);
    *ExeOutF = fopen(ExeOut,"rb");
    fclose(*ExeOutF);
    fileexist = (*ExeOutF != NULL);

    if (fileexist) {
      printf("\nFile already exists! Overwrite (Y/N)? ");
      scanf("%1s", YesNo);
      if (!AnswerYes) {
        *ExeOutF = NULL;
        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, unsigned const char *pattern) {
  int i, b;
  long int pos=0;
  unsigned char c;
  unsigned short int s;
  unsigned long int l;

  if (fseek(ExeInF, 0L, SEEK_SET) != 0)
    return -1;
  if (fseek(ExeOutF, 0L, SEEK_SET) != 0)
    return -1;

  /* first just copy the whole thing */
  while ((b = fgetc(ExeInF)) != EOF) {
    b = fputc((char)b, ExeOutF);
    if (b == EOF) return -1;  /* EOF on fputc means error */
  }

  if (fseek(ExeOutF, 0L, SEEK_SET) != 0)
    return -1;

  /* now do the patches */
  while(1){
    c=pattern[pos++];
    if(c!=0xff){
      b=fputc(c, ExeOutF);
      if(b==EOF) return -1;
    }else{
      c=pattern[pos++];
      if(c==0){
        b=fputc(0xff, ExeOutF);
        if(b==EOF) return -1;
      }else if(c!=0xff){
        if(fseek(ExeOutF, c, SEEK_CUR)!=0) return -1;
      }else{
        s=(pattern[pos]<<8)+pattern[pos+1];
        pos+=2;
        if(s!=0xffff){
          if(fseek(ExeOutF, s, SEEK_CUR)!=0) return -1;
        }else{
          l=(((((pattern[pos]<<8)+pattern[pos+1])<<8)
             +pattern[pos+2])<<8)+pattern[pos+3];
          pos+=4;
          if(l!=0xffffffff){
            if(fseek(ExeOutF, l, SEEK_CUR)!=0) return -1;
          }else{
            break;
          }
        }
      }
    }
  }

  return 0;
}

int getTmpFile(char *tmpFilename, FILE **ExeOutF) {

  FILE *tmpF = NULL;
  char *filenamepart;

  if ((filenamepart = strrchr(tmpFilename, '\\')) == NULL &&
      (filenamepart = strrchr(tmpFilename, ':')) == NULL)
    filenamepart = tmpFilename;
  else
    filenamepart++;

  while (tmpCount < TMP_MAX) {
   tmpCount++;

   tmpnam(filenamepart);

   fclose(tmpF);
   if ((tmpF = fopen(tmpFilename, "rb")) != NULL) {
     continue;
   }
   else {
     if ((tmpF = fopen(tmpFilename, "wb")) == NULL)
       continue;
     else {
       *ExeOutF = tmpF;
       return 0;
     }
   }
  }
  return -1;
}
