Subversion Repositories nw_plus

Rev

Blame | Last modification | View Log | RSS feed

/*
** Universal IPS patch create/apply utility
** Written by Neill Corlett - Copyright 1999
** See UIPS.TXT for terms of use and disclaimer.
**
** To compile this, if you have gcc installed:
**
**    gcc uips.c -o uips
**
** (Add optimization options to taste.)
**
** If you don't have gcc, figure something else out.
*/


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

/* Define truncate(2) for systems that don't have it */

#if defined(__MSDOS__) && defined(__TURBOC__)

#include <dos.h>
#include <io.h>
#include <fcntl.h>
static void truncate(const char *filename, long size) {
  int handle;
  unsigned nwritten;
  if(_dos_open(filename, O_WRONLY, &handle)) return;
  if(lseek(handle, size, SEEK_SET) != -1L) {
    _dos_write(handle, (void far*)(&handle), 0, &nwritten);
  }
  _dos_close(handle);
}

#elif defined(__WIN32__)

#include <windows.h>
static void truncate(const char *filename, long size) {
  HANDLE f = CreateFile(
    filename,
    GENERIC_WRITE,
    0,
    NULL,
    OPEN_EXISTING,
    FILE_ATTRIBUTE_NORMAL,
    NULL
  );
  if(f == INVALID_HANDLE_VALUE) return;
  SetFilePointer(f, size, NULL, FILE_BEGIN);
  if(GetLastError() == NO_ERROR) SetEndOfFile(f);
  CloseHandle(f);
}

#else

#include <unistd.h>

#endif

#define IPS_EOF   (0x00454F46l)
#define IPS_LIMIT (0x01000000l)

/* Show program banner */
static void banner(void) {
  fprintf(stderr,
    "Universal IPS create/apply utility\n"
    "Written by Neill Corlett - Copyright 1999\n"
  );
}

/* Show usage info */
static void usage(const char *prgname) {
  fprintf(stderr,
    "Usage:\n"
    "To create an IPS patch:\n"
    "  %s c patch_file source_file(s) target_file\n"
    "To apply an IPS patch:\n"
    "  %s a patch_file target_file\n",
    prgname, prgname
  );
}

/* Wrapper for fopen that does various things */
static FILE *my_fopen(const char *filename, const char *mode, long *size) {
  FILE *f = fopen(filename, mode);
  if(!f) {
    perror(filename);
    return NULL;
  }
  if(size) {
    fseek(f, 0, SEEK_END);
    *size = ftell(f);
    fseek(f, 0, SEEK_SET);
  }
  return f;
}

/* Read a number from a file, MSB first */
static long readvalue(FILE *f, unsigned nbytes) {
  long v = 0;
  while(nbytes--) {
    int c = fgetc(f);
    if(c == EOF) return -1;
    v = (v << 8) | (c & 0xFF);
  }
  return v;
}

/* Write a number to a file, MSB first */
static void writevalue(long value, FILE *f, unsigned nbytes) {
  unsigned i = nbytes << 3;
  while(nbytes--) {
    i -= 8;
    fputc(value >> i, f);
  }
}

/* Search for the next difference between the target file and a number of
** source files */

static long get_next_difference(
        long       ofs,
        FILE     **source_file,
  const long      *source_size,
        unsigned   source_nfiles,
        FILE      *target_file,
        long       target_size
) {
  unsigned i;
  if(ofs >= target_size) return target_size;
  fseek(target_file, ofs, SEEK_SET);
  for(i = 0; i < source_nfiles; i++) {
    if(ofs >= source_size[i]) return ofs;
  }
  for(i = 0; i < source_nfiles; i++) {
    fseek(source_file[i], ofs, SEEK_SET);
  }
  for(;;) {
    int tc = fgetc(target_file);
    if(tc == EOF) return target_size;
    for(i = 0; i < source_nfiles; i++) {
      if(fgetc(source_file[i]) != tc) return ofs;
    }
    ofs++;
  }
}

/* Search for the end of a difference block */
static long get_difference_end(
        long       ofs,
        int        similar_limit,
        FILE     **source_file,
  const long      *source_size,
        unsigned   source_nfiles,
        FILE      *target_file,
        long       target_size
) {
  unsigned i;
  int      similar_rl = 0;
  if(ofs >= target_size) return target_size;
  fseek(target_file, ofs, SEEK_SET);
  for(i = 0; i < source_nfiles; i++) {
    if(ofs >= source_size[i]) return target_size;
  }
  for(i = 0; i < source_nfiles; i++) {
    fseek(source_file[i], ofs, SEEK_SET);
  }
  for(;;) {
    char is_different = 0;
    int tc = fgetc(target_file);
    if(tc == EOF) return target_size;
    for(i = 0; i < source_nfiles; i++) {
      int fc = fgetc(source_file[i]);
      if(fc == EOF) return target_size;
      if(fc != tc) is_different = 1;
    }
    ofs++;
    if(is_different) {
      similar_rl = 0;
    } else {
      similar_rl++;
      if(similar_rl == similar_limit) break;
    }
  }
  return ofs - similar_limit;
}

/* Encode a difference block into a patch file */
static void encode_patch_block(
  FILE *patch_file,
  FILE *target_file,
  long  ofs,
  long  ofs_end
) {
  while(ofs < ofs_end) {
    long ofs_block_end, rl;
    int c;
    /* Avoid accidental "EOF" marker */
    if(ofs == IPS_EOF) ofs--;
    /* Write the offset to the patch file */
    writevalue(ofs, patch_file, 3);
    fseek(target_file, ofs, SEEK_SET);
    /* If there is a beginning run of at least 9 bytes, use it */
    c = fgetc(target_file);
    rl = 1;
    while(
      (fgetc(target_file) == c) &&
      (rl < 0xFFFF) &&
      ((ofs + rl) < ofs_end)
    ) rl++;
    /* Encode a run, if the run was long enough */
    if(rl >= 9) {
      writevalue( 0, patch_file, 2);
      writevalue(rl, patch_file, 2);
      writevalue( c, patch_file, 1);
      ofs += rl;
      continue;
    }
    /* Search for the end of the block.
    ** The block ends if there's an internal run of at least 14, or an ending
    ** run of at least 9, or the block length == 0xFFFF, or the block reaches
    ** ofs_end. */

    fseek(target_file, ofs, SEEK_SET);
    ofs_block_end = ofs;
    c = -1;
    while(
      (ofs_block_end < ofs_end) &&
      ((ofs_block_end - ofs) < 0xFFFF)
    ) {
      int c2 = fgetc(target_file);
      ofs_block_end++;
      if(c == c2) {
        rl++;
        if(rl == 14) {
          ofs_block_end -= 14;
          break;
        }
      } else {
        rl = 1;
        c = c2;
      }
    }
    /* Look for a sufficiently long ending run */
    if((ofs_block_end == ofs_end) && (rl >= 9)) {
      ofs_block_end -= rl;
      if(ofs_block_end == IPS_EOF) ofs_block_end++;
    }
    /* Encode a regular patch block */
    writevalue(ofs_block_end - ofs, patch_file, 2);
    fseek(target_file, ofs, SEEK_SET);
    while(ofs < ofs_block_end) {
      fputc(fgetc(target_file), patch_file);
      ofs++;
    }
  }
}

/* Create a patch given a list of source filenames and a target filename.
** Returns 0 on success. */

static int create_patch(
  const char  *patch_filename,
  unsigned     source_nfiles,
  const char **source_filename,
  const char  *target_filename
) {
  FILE    *patch_file  = NULL;
  FILE   **source_file = NULL;
  long    *source_size = NULL;
  FILE    *target_file = NULL;
  long     target_size;
  long     ofs;
  int      e = 0;
  unsigned i;
  char     will_truncate = 0;
  /* Allocate memory for list of source file streams and sizes */
  if(
    (!(source_file = malloc(sizeof(FILE*) * source_nfiles))) ||
    (!(source_size = malloc(sizeof(long)  * source_nfiles)))
  ) {
    fprintf(stderr, "Out of memory\n");
    goto err;
  }
  for(i = 0; i < source_nfiles; i++) source_file[i] = NULL;
  /* Open target file */
  target_file = my_fopen(target_filename, "rb", &target_size);
  if(!target_file) goto err;
  /* Open source files */
  for(i = 0; i < source_nfiles; i++) {
    source_file[i] = my_fopen(source_filename[i], "rb", source_size + i);
    if(!source_file[i]) goto err;
    if(source_size[i] > target_size) will_truncate = 1;
  }
  /* Create patch file */
  patch_file = my_fopen(patch_filename, "wb", NULL);
  if(!patch_file) goto err;
  fprintf(stderr, "Creating %s...\n", patch_filename);
  /* Write "PATCH" signature */
  if(fwrite("PATCH", 1, 5, patch_file) != 5) {
    perror(patch_filename);
    goto err;
  }
  /* Main patch creation loop */
  ofs = 0;
  for(;;) {
    long ofs_end;
    /* Search for next difference */
    ofs = get_next_difference(
      ofs,
      source_file,
      source_size,
      source_nfiles,
      target_file,
      target_size
    );
    if(ofs == target_size) break;
    if(ofs >= IPS_LIMIT) {
      fprintf(stderr, "Warning: Differences beyond 16MB were ignored\n");
      break;
    }
    /* Determine the length of the difference block */
    ofs_end = get_difference_end(
      ofs,
      6,
      source_file,
      source_size,
      source_nfiles,
      target_file,
      target_size
    );
    /* Progress indicator */
    fprintf(stderr, "%06lX %06lX\r", ofs, ofs_end - ofs);
    /* Encode the difference block into the patch file */
    encode_patch_block(patch_file, target_file, ofs, ofs_end);
    ofs = ofs_end;
  }
  /* Write EOF marker */
  writevalue(IPS_EOF, patch_file, 3);
  if(will_truncate) {
    if(target_size >= IPS_LIMIT) {
      fprintf(stderr, "Warning: Can't truncate beyond 16MB\n");
    } else {
      writevalue(target_size, patch_file, 3);
    }
  }
  /* Finished */
  fprintf(stderr, "\nDone\n");
  goto no_err;
  err:
  e = 1;
  no_err:
  if(patch_file) fclose(patch_file);
  for(i = 0; i < source_nfiles; i++) {
    if(source_file[i]) fclose(source_file[i]);
  }
  if(target_file) fclose(target_file);
  if(source_file) free(source_file);
  if(source_size) free(source_size);
  return e;
}

/* Apply a patch to a given target.
** Returns 0 on success. */

static int apply_patch(
  const char *patch_filename,
  const char *target_filename
) {
  FILE *patch_file  = NULL;
  FILE *target_file = NULL;
  long  target_size;
  long  ofs;
  int   e = 0;
  /* Open patch file */
  patch_file = my_fopen(patch_filename, "rb", NULL);
  if(!patch_file) goto err;
  /* Verify first five characters */
  if(
    (fgetc(patch_file) != 'P') ||
    (fgetc(patch_file) != 'A') ||
    (fgetc(patch_file) != 'T') ||
    (fgetc(patch_file) != 'C') ||
    (fgetc(patch_file) != 'H')
  ) {
    fprintf(stderr, "%s: Invalid patch file format\n", patch_filename);
    goto err;
  }
  /* Open target file */
  target_file = my_fopen(target_filename, "r+b", &target_size);
  if(!target_file) goto err;
  fprintf(stderr, "Applying %s...\n", patch_filename);
  /* Main patch application loop */
  for(;;) {
    long ofs, len;
    long rlen  = 0;
    int  rchar = 0;
    /* Read the beginning of a patch record */
    ofs = readvalue(patch_file, 3);
    if(ofs == -1) goto err_eof;
    if(ofs == IPS_EOF) break;
    len = readvalue(patch_file, 2);
    if(len == -1) goto err_eof;
    if(!len) {
      rlen = readvalue(patch_file, 2);
      if(rlen == -1) goto err_eof;
      rchar = fgetc(patch_file);
      if(rchar == EOF) goto err_eof;
    }
    /* Seek to the appropriate position in the target file */
    if(ofs <= target_size) {
      fseek(target_file, ofs, SEEK_SET);
    } else {
      fseek(target_file, 0, SEEK_END);
      while(target_size < ofs) {
        fputc(0, target_file);
        target_size++;
      }
    }
    /* Apply patch block */
    if(len) {
      fprintf(stderr, "regular  %06lX %04lX\r", ofs, len);
      ofs += len;
      if(ofs > target_size) target_size = ofs;
      while(len--) {
        rchar = fgetc(patch_file);
        if(rchar == EOF) goto err_eof;
        fputc(rchar, target_file);
      }
    } else {
      fprintf(stderr, "run      %06lX %04lX\r", ofs, rlen);
      ofs += rlen;
      if(ofs > target_size) target_size = ofs;
      while(rlen--) fputc(rchar, target_file);
    }
  }
  /* Perform truncation if necessary */
  fclose(target_file);
  target_file = NULL;
  ofs = readvalue(patch_file, 3);
  if(ofs != -1) {
    fprintf(stderr, "truncate %06lX     ", ofs);
    truncate(target_filename, ofs);
  }
  /* Finished */
  fprintf(stderr, "\nDone\n");
  goto no_err;
  err_eof:
  fprintf(stderr,
    "%s: Unexpected end-of-file, patch incomplete\n",
    patch_filename
  );
  err:
  e = 1;
  no_err:
  if(target_file) fclose(target_file);
  if(patch_file) fclose(patch_file);
  return e;
}

int main(
  int argc,
  char **argv
) {
  char cmd;
  if(argc < 2) {
    banner();
    usage(argv[0]);
    return 1;
  }
  cmd = argv[1][0];
  if(cmd && argv[1][1]) cmd = 0;
  switch(cmd) {
  case 'c':
  case 'C':
    if(argc < 5) {
      fprintf(stderr, "Not enough parameters\n");
      usage(argv[0]);
      return 1;
    }
    if(create_patch(
      argv[2],
      argc - 4,
      (const char**)(argv + 3),
      argv[argc - 1]
    )) return 1;
    break;
  case 'a':
  case 'A':
    if(argc < 4) usage(argv[0]);
    if(apply_patch(argv[2], argv[3])) return 1;
    break;
  default:
    fprintf(stderr, "Unknown command: %s\n", argv[1]);
    usage(argv[0]);
    return 1;
  }
  return 0;
}