Subversion Repositories nw_plus

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
1 h266 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
}