/* ** 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 #include /* Define truncate(2) for systems that don't have it */ #if defined(__MSDOS__) && defined(__TURBOC__) #include #include #include 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 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 #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; }