/*
** 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) {
"Universal IPS create/apply utility\n"
"Written by Neill Corlett - Copyright 1999\n"
);
}
/* Show usage info */
static void usage(const char *prgname) {
"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) {
return NULL;
}
if(size) {
}
return f;
}
/* Read a number from a file, MSB first */
static long readvalue(FILE *f, unsigned nbytes) {
long v = 0;
while(nbytes--) {
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;
}
}
/* 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 */
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) {
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) {
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 */
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) {
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 */
target_file = NULL;
ofs = readvalue(patch_file, 3);
if(ofs != -1) {
fprintf(stderr
, "truncate %06lX ", ofs
);
truncate(target_filename, ofs);
}
/* Finished */
goto no_err;
err_eof:
"%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;
}