/*  binpatch.c  Simple non-interactive binary patch utility
    DWF  10/10/94  Third version -- bug fixes only

    Changes from version 2 (7/23/94):
      Fixed logic error that caused some matching strings to be missed.

    Changes from version 1 (7/19/94):
      Fixed logic error in check for line length overflow.
      Temp1 and temp2 may now be used as names for the output file.
      Null and garbled patch files are now flagged.
      #included <string.h>.

    Usage:  binpatch infile outfile < patch

    Although gcc will compile it just fine under Unix, this program is
    written so that it should work under Messy-DOS too; 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
#include <string.h>
#include <assert.h>

/* Names for temporary files */
#define temp1 "temp1"
#define temp2 "temp2"

/* Maximum length of text lines on input */
#define maxlen 80

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 {
    if ((t = (int) fgets (txtlin, maxlen, stdin)))
      if (strlen (txtlin) >= maxlen-1) {
        printf ("Line in patch file >= %d characters:  too long\n", maxlen-1);
        bail ();
      }
  } while ((txtlin[0] == '#') && t);
  if (!t)
    return 0;

  /* Check for garbled patch file */
  for (t=0;t<strlen(txtlin);t++)
    if (!(isxdigit(txtlin[t]) || isspace(txtlin[t]))) {
      puts ("Error -- illegal format in patch file");
      bail ();
    }

  t = 0;
  while (sscanf (txtlin+t, "%x", &tt) == 1) {
    line[(*len)++] = (unsigned char)tt;
    while (isxdigit (txtlin[t])) t++;
    while (isspace (txtlin[t])) t++;
  }

  if (*len == 0) {
    puts ("Error -- blank lines in patch file");
    bail ();
  }
  return *len;
}

int
backoff (unsigned char srch[maxlen], int fchar, int matchnum)
{
  int newmatchnum = matchnum;
  while (newmatchnum) {
    if (fchar == (int)srch[newmatchnum-1]) {
      if (newmatchnum == 1)
        return newmatchnum;
      if (!memcmp (srch, srch+matchnum-newmatchnum+1, newmatchnum-1))
        return newmatchnum;
    }
    newmatchnum--;
  }
  return 0;
}

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 {
      int newmatchnum = backoff (srch, fchar, matchnum);
      if (newmatchnum) {
        for (t=0;t<matchnum-newmatchnum+1;t++)
          fputc ((int)srch[t], otf);
      } else {
        for (t=0;t<matchnum;t++)
          fputc ((int)srch[t], otf);
        fputc (fchar, otf);
      }
      matchnum = newmatchnum;
    }
  }
  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;
  }

  if (!pnum) {
    puts ("Error -- null patch file");
    bail ();
  }

  /* Renaming a file to itself generates access denied under DOS */
  if (whichtemp) {
    unlink (temp1);
    if (strcmp (temp2, argv[2]))
      if (rename (temp2, argv[2])) {
        perror ("rename");
        bail ();
      }
  } else {
    unlink (temp2);
    if (strcmp (temp1, argv[2]))
      if (rename (temp1, argv[2])) {
        perror ("rename");
        bail ();
      }
  }

  exit (0);
}
