/** Projekt PATCHER - File Patcher **/

/* Cop. 2015, 2016 asdala.de. Alle Rechte vorbehalten. */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define PRGNAME "patcher"
#define PRGVER "1.1"
#define BUFSIZE 2048
#define MAXLN 160

static char fname[128], fbakname[128], buf[BUFSIZE], ln[MAXLN];
static FILE *f, *fbak, *fpatchinfo;

/** Drucke Programmhilfe **/
static int help(void)
  {
  puts(
  PRGNAME " " PRGVER " - Datei-Patcher.\n\n"
  "Syntax 1: " PRGNAME " [/?] <Datei> <Offset> <Patch>\n"
  "Syntax 2: " PRGNAME " [/?] <Datei> <Patchinfodatei>\n"
  "\n"
  "Bsp. 1: " PRGNAME " file.exe 18EF 735C0479B537D4C58487\n"
  "Bsp. 2: " PRGNAME " file.exe patchinfo.txt\n"
  "\n"
  "Bsp. Patchinfodatei mit Kommentaren und unterschiedlichen Notationen:\n"
  "# Update section headers to add space for code.\n"
  "00000118: 98\n"
  "00000119: 9B\n"
  "\n"
  "# Jump to additional code for adding SNI.\n"
  "0000EACC: E9 6A 68 01 00 90 90 90 90 90\n"
  "\n"
  "# Updated OpenSSL function names list.\n"
  "00028690:\n"
  "65 72 73 69-6F 6E 00 53-53 4C 5F 6C-69 62 72 61\n"
  "72 79 5F 69-6E 69 74 00-53 53 4C 5F-43 54 58 5F\n"
  );
  return 1;
  }

/** Programmabbruch **/
static int errout(const char *s, int lnno)
  {
  fprintf(stderr,PRGNAME "\a abgebrochen");
  if (lnno)
    fprintf(stderr," bei Zeile %d",lnno);
  fprintf(stderr,": %s!\n",s);
  if (f)
    fclose(f);
  if (fbak)
    fclose(fbak);
  if (fpatchinfo)
    fclose(fpatchinfo);
  return 2;
  }

/** Melde Dateigroesse **/
static unsigned long filesize(FILE *f)
  {
  unsigned long i, curpos = ftell(f);
  fseek(f,0L,SEEK_END);
  i = ftell(f);
  fseek(f,curpos,SEEK_SET);
  return i;
  }

/** Setze Dateierweiterung **/
static void setext(char *s, const char *ext)
  {
  int i;
  i = strlen(s);
  if (i > 4 && s[i-4] == '.')
    i -= 4;
  strcpy(s+i,ext);
  }

/** Wandle 1 Hexzeichen in Halbbyte -> -1 bei Fehler **/
__inline char x2h(char c)
  {
  if (c>='0' && c<='9')
    return (char)(c-'0');
  c &= 0xdf;
  if (c>='A' && c<='F')
    return (char)(c-'A'+10);
  return (char)-1;
  }

/** Wandle 2 Hexzeichen in Byte ("41" -> 'A') -> 0 bei Fehler **/
static int x2c(const char *s, char *c)
  {
  register char hb1, hb2;
  if ((hb1=x2h(*s)) < 0 || (hb2=x2h(*(s+1))) < 0)
    return 0;
  *c = (char)((hb1<<4)+hb2);
  return 1;
  }

/** Wandle Hexstring in Bytes ("4142" -> "AB") -> -1 bei Fehler, 0 falls leer, Bytezahl **/
static int x2s(const char *s1, char *s2)
  {
  char *s2start = s2;
  while (*s1) {
    while (*s1==' ' || *s1=='-' || *s1=='\n')
      ++s1;
    if (*s1 == 0)
      break;
    if (!x2c(s1,s2))
      return -1;
    s1 += 2;
    ++s2;
    }
  return s2 - s2start;
  }

/** Drucke sedezimales Speicherabbild **/
static void printh(const char a[], int alen)
  {
  register int i;
  if (alen) {
    for (i=0; i<alen; i++)
      printf("%02X%c", (unsigned char)a[i],i%16==15?'\n':' ');
    if (i%16)
      puts("");
    }
  }

/** Main **/
int main(int argc, char *argv[])
  {
  int i, lnno;
  unsigned long offset, fsize;
  char *p;

  /* Lies Programmargumente */
  if (argc < 3)
    return help();
  strcpy(fname,argv[1]);
  if ((f=fopen(fname, "r+b")) == NULL)
    return errout("Kann zu patchende Datei nicht lesen",0);
  fsize = filesize(f);
  strcpy(fbakname,fname); setext(fbakname,".bak");
  if ((fbak=fopen(fbakname, "wb")) == NULL)
    return errout("Kann zu patchende Datei nicht sichern",0);
  do {
    i = fread(buf,1,BUFSIZE,f);
    fwrite(buf,1,i,fbak);
    } while (i == BUFSIZE);
  fclose(fbak); fbak = NULL;

  /* Lese Patch von Kommandozeile oder Patchinfodatei? */
  offset = strtoul(argv[2],&p,16);
  if (*p == 0) {
    /* Lese Kommandozeile, da gueltiges Offset-Argument */
    if (argc < 4)
      return help();
    if (offset >= fsize)
      return errout("Offset groesser als Datei",0);
    fseek(f,offset,SEEK_SET);
    if ((i=x2s(argv[3],buf)) <= 0)
      return errout("Nonsedezimaler Patch",0);
    printf("# Patche bei %02lX: ",offset);
    printh(buf,i);
    fwrite(buf,1,i,f);
    }
  else {
    /* Lese Patchinfodatei, da ungueltiges Offset-Argument */
    if ((fpatchinfo=fopen(argv[2], "rt")) == NULL)
      return errout("Patchinfodatei nicht lesbar",0);
    lnno = 0;
    while ((p=fgets(ln,MAXLN,fpatchinfo)) != NULL) {
      ++lnno;
      if (strlen(ln) == MAXLN-1)
        return errout("Zeile zu lang",lnno);
      if (*p == '#' || *p == '\n')
        continue;
      /* Patch-Offset vorhanden? */
      if (strchr(p,':')) {
        offset = strtoul(p,&p,16);
        if (*p++ != ':')
          return errout("Ungueltige Offset-Syntax",lnno);
        if (offset >= fsize)
          return errout("Offset groesser als Datei",lnno);
        fseek(f,offset,SEEK_SET);
        printf("\n# Patche bei %02lX:\n",offset);
        }
      /* Patch-Inhalt vorhanden? */
      if ((i=x2s(p,buf)) < 0)
        return errout("Nonsedezimaler Patch",lnno);
      printh(buf,i);
      fwrite(buf,1,i,f);
      } /* while */
    fclose(fpatchinfo); fpatchinfo = NULL;
    }
  fclose(f); f = NULL;

  return 0;
  }