Subversion Repositories nw_plus

Rev

Blame | Last modification | View Log | RSS feed

  1. /*
  2. ** Universal IPS patch create/apply utility
  3. ** Written by Neill Corlett - Copyright 1999
  4. ** See UIPS.TXT for terms of use and disclaimer.
  5. **
  6. ** To compile this, if you have gcc installed:
  7. **
  8. **    gcc uips.c -o uips
  9. **
  10. ** (Add optimization options to taste.)
  11. **
  12. ** If you don't have gcc, figure something else out.
  13. */
  14.  
  15. #include <stdio.h>
  16. #include <stdlib.h>
  17.  
  18. /* Define truncate(2) for systems that don't have it */
  19.  
  20. #if defined(__MSDOS__) && defined(__TURBOC__)
  21.  
  22. #include <dos.h>
  23. #include <io.h>
  24. #include <fcntl.h>
  25. static void truncate(const char *filename, long size) {
  26.   int handle;
  27.   unsigned nwritten;
  28.   if(_dos_open(filename, O_WRONLY, &handle)) return;
  29.   if(lseek(handle, size, SEEK_SET) != -1L) {
  30.     _dos_write(handle, (void far*)(&handle), 0, &nwritten);
  31.   }
  32.   _dos_close(handle);
  33. }
  34.  
  35. #elif defined(__WIN32__)
  36.  
  37. #include <windows.h>
  38. static void truncate(const char *filename, long size) {
  39.   HANDLE f = CreateFile(
  40.     filename,
  41.     GENERIC_WRITE,
  42.     0,
  43.     NULL,
  44.     OPEN_EXISTING,
  45.     FILE_ATTRIBUTE_NORMAL,
  46.     NULL
  47.   );
  48.   if(f == INVALID_HANDLE_VALUE) return;
  49.   SetFilePointer(f, size, NULL, FILE_BEGIN);
  50.   if(GetLastError() == NO_ERROR) SetEndOfFile(f);
  51.   CloseHandle(f);
  52. }
  53.  
  54. #else
  55.  
  56. #include <unistd.h>
  57.  
  58. #endif
  59.  
  60. #define IPS_EOF   (0x00454F46l)
  61. #define IPS_LIMIT (0x01000000l)
  62.  
  63. /* Show program banner */
  64. static void banner(void) {
  65.   fprintf(stderr,
  66.     "Universal IPS create/apply utility\n"
  67.     "Written by Neill Corlett - Copyright 1999\n"
  68.   );
  69. }
  70.  
  71. /* Show usage info */
  72. static void usage(const char *prgname) {
  73.   fprintf(stderr,
  74.     "Usage:\n"
  75.     "To create an IPS patch:\n"
  76.     "  %s c patch_file source_file(s) target_file\n"
  77.     "To apply an IPS patch:\n"
  78.     "  %s a patch_file target_file\n",
  79.     prgname, prgname
  80.   );
  81. }
  82.  
  83. /* Wrapper for fopen that does various things */
  84. static FILE *my_fopen(const char *filename, const char *mode, long *size) {
  85.   FILE *f = fopen(filename, mode);
  86.   if(!f) {
  87.     perror(filename);
  88.     return NULL;
  89.   }
  90.   if(size) {
  91.     fseek(f, 0, SEEK_END);
  92.     *size = ftell(f);
  93.     fseek(f, 0, SEEK_SET);
  94.   }
  95.   return f;
  96. }
  97.  
  98. /* Read a number from a file, MSB first */
  99. static long readvalue(FILE *f, unsigned nbytes) {
  100.   long v = 0;
  101.   while(nbytes--) {
  102.     int c = fgetc(f);
  103.     if(c == EOF) return -1;
  104.     v = (v << 8) | (c & 0xFF);
  105.   }
  106.   return v;
  107. }
  108.  
  109. /* Write a number to a file, MSB first */
  110. static void writevalue(long value, FILE *f, unsigned nbytes) {
  111.   unsigned i = nbytes << 3;
  112.   while(nbytes--) {
  113.     i -= 8;
  114.     fputc(value >> i, f);
  115.   }
  116. }
  117.  
  118. /* Search for the next difference between the target file and a number of
  119. ** source files */
  120. static long get_next_difference(
  121.         long       ofs,
  122.         FILE     **source_file,
  123.   const long      *source_size,
  124.         unsigned   source_nfiles,
  125.         FILE      *target_file,
  126.         long       target_size
  127. ) {
  128.   unsigned i;
  129.   if(ofs >= target_size) return target_size;
  130.   fseek(target_file, ofs, SEEK_SET);
  131.   for(i = 0; i < source_nfiles; i++) {
  132.     if(ofs >= source_size[i]) return ofs;
  133.   }
  134.   for(i = 0; i < source_nfiles; i++) {
  135.     fseek(source_file[i], ofs, SEEK_SET);
  136.   }
  137.   for(;;) {
  138.     int tc = fgetc(target_file);
  139.     if(tc == EOF) return target_size;
  140.     for(i = 0; i < source_nfiles; i++) {
  141.       if(fgetc(source_file[i]) != tc) return ofs;
  142.     }
  143.     ofs++;
  144.   }
  145. }
  146.  
  147. /* Search for the end of a difference block */
  148. static long get_difference_end(
  149.         long       ofs,
  150.         int        similar_limit,
  151.         FILE     **source_file,
  152.   const long      *source_size,
  153.         unsigned   source_nfiles,
  154.         FILE      *target_file,
  155.         long       target_size
  156. ) {
  157.   unsigned i;
  158.   int      similar_rl = 0;
  159.   if(ofs >= target_size) return target_size;
  160.   fseek(target_file, ofs, SEEK_SET);
  161.   for(i = 0; i < source_nfiles; i++) {
  162.     if(ofs >= source_size[i]) return target_size;
  163.   }
  164.   for(i = 0; i < source_nfiles; i++) {
  165.     fseek(source_file[i], ofs, SEEK_SET);
  166.   }
  167.   for(;;) {
  168.     char is_different = 0;
  169.     int tc = fgetc(target_file);
  170.     if(tc == EOF) return target_size;
  171.     for(i = 0; i < source_nfiles; i++) {
  172.       int fc = fgetc(source_file[i]);
  173.       if(fc == EOF) return target_size;
  174.       if(fc != tc) is_different = 1;
  175.     }
  176.     ofs++;
  177.     if(is_different) {
  178.       similar_rl = 0;
  179.     } else {
  180.       similar_rl++;
  181.       if(similar_rl == similar_limit) break;
  182.     }
  183.   }
  184.   return ofs - similar_limit;
  185. }
  186.  
  187. /* Encode a difference block into a patch file */
  188. static void encode_patch_block(
  189.   FILE *patch_file,
  190.   FILE *target_file,
  191.   long  ofs,
  192.   long  ofs_end
  193. ) {
  194.   while(ofs < ofs_end) {
  195.     long ofs_block_end, rl;
  196.     int c;
  197.     /* Avoid accidental "EOF" marker */
  198.     if(ofs == IPS_EOF) ofs--;
  199.     /* Write the offset to the patch file */
  200.     writevalue(ofs, patch_file, 3);
  201.     fseek(target_file, ofs, SEEK_SET);
  202.     /* If there is a beginning run of at least 9 bytes, use it */
  203.     c = fgetc(target_file);
  204.     rl = 1;
  205.     while(
  206.       (fgetc(target_file) == c) &&
  207.       (rl < 0xFFFF) &&
  208.       ((ofs + rl) < ofs_end)
  209.     ) rl++;
  210.     /* Encode a run, if the run was long enough */
  211.     if(rl >= 9) {
  212.       writevalue( 0, patch_file, 2);
  213.       writevalue(rl, patch_file, 2);
  214.       writevalue( c, patch_file, 1);
  215.       ofs += rl;
  216.       continue;
  217.     }
  218.     /* Search for the end of the block.
  219.     ** The block ends if there's an internal run of at least 14, or an ending
  220.     ** run of at least 9, or the block length == 0xFFFF, or the block reaches
  221.     ** ofs_end. */
  222.     fseek(target_file, ofs, SEEK_SET);
  223.     ofs_block_end = ofs;
  224.     c = -1;
  225.     while(
  226.       (ofs_block_end < ofs_end) &&
  227.       ((ofs_block_end - ofs) < 0xFFFF)
  228.     ) {
  229.       int c2 = fgetc(target_file);
  230.       ofs_block_end++;
  231.       if(c == c2) {
  232.         rl++;
  233.         if(rl == 14) {
  234.           ofs_block_end -= 14;
  235.           break;
  236.         }
  237.       } else {
  238.         rl = 1;
  239.         c = c2;
  240.       }
  241.     }
  242.     /* Look for a sufficiently long ending run */
  243.     if((ofs_block_end == ofs_end) && (rl >= 9)) {
  244.       ofs_block_end -= rl;
  245.       if(ofs_block_end == IPS_EOF) ofs_block_end++;
  246.     }
  247.     /* Encode a regular patch block */
  248.     writevalue(ofs_block_end - ofs, patch_file, 2);
  249.     fseek(target_file, ofs, SEEK_SET);
  250.     while(ofs < ofs_block_end) {
  251.       fputc(fgetc(target_file), patch_file);
  252.       ofs++;
  253.     }
  254.   }
  255. }
  256.  
  257. /* Create a patch given a list of source filenames and a target filename.
  258. ** Returns 0 on success. */
  259. static int create_patch(
  260.   const char  *patch_filename,
  261.   unsigned     source_nfiles,
  262.   const char **source_filename,
  263.   const char  *target_filename
  264. ) {
  265.   FILE    *patch_file  = NULL;
  266.   FILE   **source_file = NULL;
  267.   long    *source_size = NULL;
  268.   FILE    *target_file = NULL;
  269.   long     target_size;
  270.   long     ofs;
  271.   int      e = 0;
  272.   unsigned i;
  273.   char     will_truncate = 0;
  274.   /* Allocate memory for list of source file streams and sizes */
  275.   if(
  276.     (!(source_file = malloc(sizeof(FILE*) * source_nfiles))) ||
  277.     (!(source_size = malloc(sizeof(long)  * source_nfiles)))
  278.   ) {
  279.     fprintf(stderr, "Out of memory\n");
  280.     goto err;
  281.   }
  282.   for(i = 0; i < source_nfiles; i++) source_file[i] = NULL;
  283.   /* Open target file */
  284.   target_file = my_fopen(target_filename, "rb", &target_size);
  285.   if(!target_file) goto err;
  286.   /* Open source files */
  287.   for(i = 0; i < source_nfiles; i++) {
  288.     source_file[i] = my_fopen(source_filename[i], "rb", source_size + i);
  289.     if(!source_file[i]) goto err;
  290.     if(source_size[i] > target_size) will_truncate = 1;
  291.   }
  292.   /* Create patch file */
  293.   patch_file = my_fopen(patch_filename, "wb", NULL);
  294.   if(!patch_file) goto err;
  295.   fprintf(stderr, "Creating %s...\n", patch_filename);
  296.   /* Write "PATCH" signature */
  297.   if(fwrite("PATCH", 1, 5, patch_file) != 5) {
  298.     perror(patch_filename);
  299.     goto err;
  300.   }
  301.   /* Main patch creation loop */
  302.   ofs = 0;
  303.   for(;;) {
  304.     long ofs_end;
  305.     /* Search for next difference */
  306.     ofs = get_next_difference(
  307.       ofs,
  308.       source_file,
  309.       source_size,
  310.       source_nfiles,
  311.       target_file,
  312.       target_size
  313.     );
  314.     if(ofs == target_size) break;
  315.     if(ofs >= IPS_LIMIT) {
  316.       fprintf(stderr, "Warning: Differences beyond 16MB were ignored\n");
  317.       break;
  318.     }
  319.     /* Determine the length of the difference block */
  320.     ofs_end = get_difference_end(
  321.       ofs,
  322.       6,
  323.       source_file,
  324.       source_size,
  325.       source_nfiles,
  326.       target_file,
  327.       target_size
  328.     );
  329.     /* Progress indicator */
  330.     fprintf(stderr, "%06lX %06lX\r", ofs, ofs_end - ofs);
  331.     /* Encode the difference block into the patch file */
  332.     encode_patch_block(patch_file, target_file, ofs, ofs_end);
  333.     ofs = ofs_end;
  334.   }
  335.   /* Write EOF marker */
  336.   writevalue(IPS_EOF, patch_file, 3);
  337.   if(will_truncate) {
  338.     if(target_size >= IPS_LIMIT) {
  339.       fprintf(stderr, "Warning: Can't truncate beyond 16MB\n");
  340.     } else {
  341.       writevalue(target_size, patch_file, 3);
  342.     }
  343.   }
  344.   /* Finished */
  345.   fprintf(stderr, "\nDone\n");
  346.   goto no_err;
  347.   err:
  348.   e = 1;
  349.   no_err:
  350.   if(patch_file) fclose(patch_file);
  351.   for(i = 0; i < source_nfiles; i++) {
  352.     if(source_file[i]) fclose(source_file[i]);
  353.   }
  354.   if(target_file) fclose(target_file);
  355.   if(source_file) free(source_file);
  356.   if(source_size) free(source_size);
  357.   return e;
  358. }
  359.  
  360. /* Apply a patch to a given target.
  361. ** Returns 0 on success. */
  362. static int apply_patch(
  363.   const char *patch_filename,
  364.   const char *target_filename
  365. ) {
  366.   FILE *patch_file  = NULL;
  367.   FILE *target_file = NULL;
  368.   long  target_size;
  369.   long  ofs;
  370.   int   e = 0;
  371.   /* Open patch file */
  372.   patch_file = my_fopen(patch_filename, "rb", NULL);
  373.   if(!patch_file) goto err;
  374.   /* Verify first five characters */
  375.   if(
  376.     (fgetc(patch_file) != 'P') ||
  377.     (fgetc(patch_file) != 'A') ||
  378.     (fgetc(patch_file) != 'T') ||
  379.     (fgetc(patch_file) != 'C') ||
  380.     (fgetc(patch_file) != 'H')
  381.   ) {
  382.     fprintf(stderr, "%s: Invalid patch file format\n", patch_filename);
  383.     goto err;
  384.   }
  385.   /* Open target file */
  386.   target_file = my_fopen(target_filename, "r+b", &target_size);
  387.   if(!target_file) goto err;
  388.   fprintf(stderr, "Applying %s...\n", patch_filename);
  389.   /* Main patch application loop */
  390.   for(;;) {
  391.     long ofs, len;
  392.     long rlen  = 0;
  393.     int  rchar = 0;
  394.     /* Read the beginning of a patch record */
  395.     ofs = readvalue(patch_file, 3);
  396.     if(ofs == -1) goto err_eof;
  397.     if(ofs == IPS_EOF) break;
  398.     len = readvalue(patch_file, 2);
  399.     if(len == -1) goto err_eof;
  400.     if(!len) {
  401.       rlen = readvalue(patch_file, 2);
  402.       if(rlen == -1) goto err_eof;
  403.       rchar = fgetc(patch_file);
  404.       if(rchar == EOF) goto err_eof;
  405.     }
  406.     /* Seek to the appropriate position in the target file */
  407.     if(ofs <= target_size) {
  408.       fseek(target_file, ofs, SEEK_SET);
  409.     } else {
  410.       fseek(target_file, 0, SEEK_END);
  411.       while(target_size < ofs) {
  412.         fputc(0, target_file);
  413.         target_size++;
  414.       }
  415.     }
  416.     /* Apply patch block */
  417.     if(len) {
  418.       fprintf(stderr, "regular  %06lX %04lX\r", ofs, len);
  419.       ofs += len;
  420.       if(ofs > target_size) target_size = ofs;
  421.       while(len--) {
  422.         rchar = fgetc(patch_file);
  423.         if(rchar == EOF) goto err_eof;
  424.         fputc(rchar, target_file);
  425.       }
  426.     } else {
  427.       fprintf(stderr, "run      %06lX %04lX\r", ofs, rlen);
  428.       ofs += rlen;
  429.       if(ofs > target_size) target_size = ofs;
  430.       while(rlen--) fputc(rchar, target_file);
  431.     }
  432.   }
  433.   /* Perform truncation if necessary */
  434.   fclose(target_file);
  435.   target_file = NULL;
  436.   ofs = readvalue(patch_file, 3);
  437.   if(ofs != -1) {
  438.     fprintf(stderr, "truncate %06lX     ", ofs);
  439.     truncate(target_filename, ofs);
  440.   }
  441.   /* Finished */
  442.   fprintf(stderr, "\nDone\n");
  443.   goto no_err;
  444.   err_eof:
  445.   fprintf(stderr,
  446.     "%s: Unexpected end-of-file, patch incomplete\n",
  447.     patch_filename
  448.   );
  449.   err:
  450.   e = 1;
  451.   no_err:
  452.   if(target_file) fclose(target_file);
  453.   if(patch_file) fclose(patch_file);
  454.   return e;
  455. }
  456.  
  457. int main(
  458.   int argc,
  459.   char **argv
  460. ) {
  461.   char cmd;
  462.   if(argc < 2) {
  463.     banner();
  464.     usage(argv[0]);
  465.     return 1;
  466.   }
  467.   cmd = argv[1][0];
  468.   if(cmd && argv[1][1]) cmd = 0;
  469.   switch(cmd) {
  470.   case 'c':
  471.   case 'C':
  472.     if(argc < 5) {
  473.       fprintf(stderr, "Not enough parameters\n");
  474.       usage(argv[0]);
  475.       return 1;
  476.     }
  477.     if(create_patch(
  478.       argv[2],
  479.       argc - 4,
  480.       (const char**)(argv + 3),
  481.       argv[argc - 1]
  482.     )) return 1;
  483.     break;
  484.   case 'a':
  485.   case 'A':
  486.     if(argc < 4) usage(argv[0]);
  487.     if(apply_patch(argv[2], argv[3])) return 1;
  488.     break;
  489.   default:
  490.     fprintf(stderr, "Unknown command: %s\n", argv[1]);
  491.     usage(argv[0]);
  492.     return 1;
  493.   }
  494.   return 0;
  495. }
  496.