/*  binpatch.c  Simple non-interactive binary patch utility
 *  DWF  7/19/94  First version and hopefully the last.  Just say NO to
 *  creeping features!
 *
 *  Usage:  binpatch infile outfile < patch
 *
 *  This program is written so that it should work under Messy-DOS, i.e.,
 *  it uses temporary files instead of reading the entire binary into memory.
 *  I have successfully compiled it with an old version of Turbo C, but I had
 *  to add an #ifdef to the includes.
 *
 *  Here is an example patch file.  Comments, with # in the first column,
 *  are ignored; the other lines alternate between search-string and
 *  replace-string in hexadecimal.
 *
 *    #  Patches for shareware doom.exe 1.5 beta posted to alt.games.doom by
 *    #  ep104@cus.cam.ac.uk (Elias 'CaveMan' Papavassilopoulos)
 *    #
 *    #  Fix crash & burn on idkfa + shotgun
 *    B9 09 00 00 00 C7 40 B0 02 00 00 00
 *    B9 08 00 00 00 C7 40 B0 02 00 00 00
 *    #
 *    #  Enable cheat codes in Nightmare mode
 *    0F 85 29 03 00 00 83 3D 70 82 02 00 04
 *    0F 85 29 03 00 00 83 3D 70 82 02 00 45
 */

#include <stdio.h>
#include <ctype.h>
#ifndef __TURBOC__
#include <unistd.h>
#endif

/* Names for temporary files */
#define temp1 "temp1"
#define temp2 "temp2"

/* Maximum length of text lines on input */
/*#define maxlen 80*/
#define maxlen 256

void
usage ()
{
  puts ("Usage:  binpatch infile outfile < patch");
  exit (0);
}

void
bail ()
{
  unlink (temp1);
  unlink (temp2);
  exit (-1);
}

void
dont_clobber (char *fname)
{
  FILE *inf;
  if ((inf = fopen (fname, "r"))) {
    printf ("Will not overwrite existing file %s -- rename or delete it\n",
      fname);
    exit (-1);
  }
}

int
get_line (unsigned char line[maxlen], int *len)
{
  char txtlin[maxlen];
  int t, tt;
  *len = 0;
  do t = (int) fgets (txtlin, maxlen, stdin); while ((txtlin[0] == '#') && t);
  if (!t)
    return 0;
  if (strlen (txtlin) >= maxlen-1) {
    printf ("Line in patch file >= %d characters:  too long\n", maxlen-1);
    bail ();
  }
  t = 0;
  while (sscanf (txtlin+t, "%x", &tt) == 1) {
    line[(*len)++] = (unsigned char)tt;
    while (isalnum (txtlin[t])) t++;
    while (isspace (txtlin[t])) t++;
  }
  return *len;
}

void
search_and_destroy (FILE *inf, FILE *otf, unsigned char srch[maxlen],
int srchlen, unsigned char repl[maxlen], int *matches)
{
  int matchnum=0, fchar, t;
  while ((fchar = fgetc (inf)) != EOF) {
    if (fchar == (int)srch[matchnum]) {
      matchnum++;
      if (matchnum == srchlen) {
        for (t=0;t<matchnum;t++)
          fputc ((int)repl[t], otf);
        matchnum = 0;
        (*matches)++;
      }
    } else {
      for (t=0;t<matchnum;t++)
        fputc ((int)srch[t], otf);
      matchnum = 0;
      fputc (fchar, otf);
    }
  }
  for (t=0;t<matchnum;t++)
    fputc ((int)srch[t], otf);
}

int
main (int argc, char **argv)
{
  FILE *inf=NULL, *otf=NULL;
  int matches, whichtemp=0, srchlen, repllen, pnum=0;
  unsigned char srch[maxlen], repl[maxlen];
  if (argc != 3)
    usage ();

  dont_clobber (temp1);
  dont_clobber (temp2);
  dont_clobber (argv[2]);

  /* Open initial input file */
  if (!(inf = fopen (argv[1], "rb"))) {
    perror ("fopen");
    usage ();
  }

  while (get_line (srch, &srchlen)) {
    pnum++;
    if (!(get_line (repl, &repllen))) {
      printf ("Unexpected EOF in patch file\n");
      bail ();
    }
    if (srchlen != repllen) {
      printf ("Search-string length != replace-string length in patch file\n");
      bail ();
    }

    /* Open files */
    if (whichtemp) {
      if (!(otf = fopen (temp1, "wb"))) {
        perror ("fopen");
        bail ();
      }
      if (!inf)
        if (!(inf = fopen (temp2, "rb"))) {
          perror ("fopen");
          bail ();
        }
    } else {
      if (!(otf = fopen (temp2, "wb"))) {
        perror ("fopen");
        bail ();
      }
      if (!inf)
        if (!(inf = fopen (temp1, "rb"))) {
          perror ("fopen");
          bail ();
        }
    }
    whichtemp = 1 - whichtemp;

    matches = 0;
    search_and_destroy (inf, otf, srch, srchlen, repl, &matches);
    printf ("Chunk #%d:  ", pnum);
    switch (matches) {
    case 0:
      puts ("Error -- no strings matched.");
      break;
    case 1:
      puts ("Replaced 1 string.");
      break;
    default:
      printf ("Replaced %d strings.\n", matches);
    }

    fclose (inf);
    fclose (otf);
    inf = otf = NULL;
  }

  unlink (argv[2]);
  if (whichtemp) {
    unlink (temp1);
    if (rename (temp2, argv[2])) {
      perror ("rename");
      bail ();
    }
  } else {
    unlink (temp2);
    if (rename (temp1, argv[2])) {
      perror ("rename");
      bail ();
    }
  }

  exit (0);
}

