1 /*
2 ppf.c - Playstation Patch File support for uCON64
3
4 Copyright (c) ???? - ???? Icarus/Paradox
5 Copyright (c) 2001 NoisyB
6 Copyright (c) 2002 - 2005, 2015, 2017, 2019 - 2021 dbjh
7
8
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22 */
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
26 #ifdef _MSC_VER
27 #pragma warning(push)
28 #pragma warning(disable: 4668) // 'symbol' is not defined as a preprocessor macro, replacing with '0' for 'directives'
29 #endif
30 #include <stdlib.h>
31 #ifdef _MSC_VER
32 #pragma warning(pop)
33 #endif
34 #ifdef HAVE_UNISTD_H
35 #include <unistd.h>
36 #endif
37 #include "misc/archive.h"
38 #include "misc/bswap.h"
39 #include "misc/file.h"
40 #include "misc/string.h" // MEMCMP2_CASE
41 #include "ucon64.h"
42 #include "ucon64_misc.h"
43 #include "patch/ppf.h"
44
45
46 #define MAX_ID_SIZE 3072
47 #define DIFF_FSIZE
48 /*
49 I (dbjh) couldn't tell from the specification below if it is required that
50 the original file and the modified file have the same size. By defining
51 DIFF_FSIZE, PPF as I understand it becomes quite a generic patch file
52 format. It can be used to patch any file up to 4 GB.
53 */
54
55
56 static st_ucon64_obj_t ppf_obj[] =
57 {
58 {0, WF_STOP}
59 };
60
61 const st_getopt2_t ppf_usage[] =
62 {
63 {
64 "ppf", 0, 0, UCON64_PPF,
65 NULL, "apply PPF PATCH to IMAGE (PPF<=v2.0); ROM should be an IMAGE",
66 &ppf_obj[0]
67 },
68 {
69 "mkppf", 1, 0, UCON64_MKPPF,
70 "ORG_IMG", "create PPF patch; ROM should be the modified IMAGE",
71 &ppf_obj[0]
72 },
73 {
74 "nppf", 1, 0, UCON64_NPPF,
75 "DESC", "change PPF single line DESCRIPTION",
76 NULL
77 },
78 {
79 "idppf", 1, 0, UCON64_IDPPF,
80 "FILE_ID.DIZ", "change FILE_ID.DIZ of PPF PATCH (PPF v2.0)",
81 NULL
82 },
83 {NULL, 0, 0, 0, NULL, NULL, NULL}
84 };
85
86 /*
87
88 .-----------------------------------------------------------------.
89 | PLAYSTATION PATCH FILE VERSION 2.0 FILE-STRUCTURE FOR DEVELOPERS|
90 '-----------------------------------------------------------------'
91
92 1. The PPF 2.0 Header:
93
94 @START_PPF20HEADER
95 .----------+--------+---------------------------------------------.
96 | POSITION | SIZE | E X P L A N A T I O N |
97 +----------|--------|---------------------------------------------+
98 | 00-04 | 05 | PPF-magic: "PPF20" |
99 +----------|--------|---------------------------------------------+
100 | 05 | 01 | Encoding method: |
101 | | | - If $00 then it is a PPF 1.0 patch |
102 | | | - If $01 then it is a PPF 2.0 patch |
103 +----------|--------|---------------------------------------------+
104 | 06-55 | 50 | Patch description |
105 +----------|--------|---------------------------------------------+
106 | 56-59 | 04 | Size of the file (e.g. CDRWin binfile) this |
107 | | | patch was made of. Used for identification |
108 +----------|--------|---------------------------------------------+
109 | 60-1083 | 1024 | This is a binary block of 1024 byte taken |
110 | | | from position $9320 of the file (e.g. CDRWin|
111 | | | binfile) this patch was made of. Used for |
112 | | | identification. |
113 +----------|--------|---------------------------------------------+
114 | 1084-X | XX | The patch itself. See below for structure! |
115 '----------+--------+---------------------------------------------'
116 @END_PPF20HEADER - total headersize = 1084 bytes.
117
118
119 2. The PPF 2.0 patch itself (encoding method #1)
120
121 @START_PPF20PATCH
122 FORMAT : xxxx,y,zzzz
123
124 xxxx = 4 byte file offset.
125
126 y = Number of bytes that will be changed.
127
128 zzzz = New data to be written ('y' number of bytes).
129
130 Example
131 ~~~~~~~
132
133 Starting from file offset 0x0015F9D0 replace 3 bytes with 01,02,03
134 D0 F9 15 00 03 01 02 03
135
136 Be careful! Watch the endian format! If you own an Amiga and want
137 to do a PPF2-patcher for Amiga don't forget to swap the endian-format
138 of the offset to avoid seek errors!
139
140 @END_PPF20PATCH
141
142
143 3. The PPF 2.0 fileid area
144
145 @START_FILEID
146
147 The fileid area is used to store additional patch information of
148 the PPF 2.0 file. I implemented this following the AMIGA standard
149 of adding a fileid to e.g. .txt files. You can add a FILE_ID to a
150 PPF 2.0 patch by using the tool 'PPFdiz.exe' or "PPF-O-MATIC2"
151 included in this package. You don't have to add a FILE_ID to your
152 PPF 2.0 patch. It's only for your pleasure! :)
153
154 For developers: a file_id area begins with @BEGIN_FILE_ID.DIZ and
155 ends with @END_FILE_ID.DIZ (Amiga BBS standard).
156 Between @BEGIN_FILE_ID.DIZ and @END_FILE_ID.DIZ you will find
157 the fileid and followed after @END_FILE_ID.DIZ you will find an
158 integer (4 bytes long) with the length of the FILE_ID.DIZ!
159
160 A FILE_ID.DIZ file cannot be greater than 3072 bytes.
161
162 If you do a PPF 2.0 applier be sure to check for an existing FILE_ID
163 AREA, because it is located after the patch data!
164
165 @END_FILEID
166 */
167
168
169 // based on source code of ApplyPPF v2.0 for Linux/UNIX by Icarus/Paradox
170 int
ppf_apply(const char * mod,const char * ppfname)171 ppf_apply (const char *mod, const char *ppfname)
172 {
173 FILE *modfile, *ppffile;
174 char desc[50 + 1], buffer[1024], modname[FILENAME_MAX];
175 int x, method, dizlen = 0, ppfsize, bytes_to_skip = 0, n_changes;
176 unsigned int pos;
177
178 strcpy (modname, mod);
179 ucon64_file_handler (modname, NULL, 0);
180 fcopy (mod, 0, fsizeof (mod), modname, "wb"); // no copy if one file
181
182 if ((modfile = fopen (modname, "r+b")) == NULL)
183 {
184 fprintf (stderr, ucon64_msg[OPEN_WRITE_ERROR], modname);
185 exit (1);
186 }
187 if ((ppffile = fopen (ppfname, "rb")) == NULL)
188 {
189 fprintf (stderr, ucon64_msg[OPEN_READ_ERROR], ppfname);
190 exit (1);
191 }
192
193 ppfsize = (int) fsizeof (ppfname);
194
195 // Is it a PPF File?
196 if (ppfsize < 56 || !fread (buffer, 3, 1, ppffile) || memcmp ("PPF", buffer, 3))
197 {
198 fprintf (stderr, "ERROR: %s is not a valid PPF file\n", ppfname);
199 exit (1);
200 }
201
202 // What encoding method? PPF 1.0 or PPF 2.0?
203 fseek (ppffile, 5, SEEK_SET);
204 method = fgetc (ppffile);
205 if (method != 0 && method != 1)
206 {
207 fputs ("ERROR: Unknown encoding method\n", stderr);
208 exit (1);
209 }
210
211 // Show PPF information
212 fseek (ppffile, 6, SEEK_SET); // Read description line
213 fread_checked (desc, 50, 1, ppffile);
214 desc[50] = '\0'; // terminate string
215 printf ("\n" // print a newline between
216 "Filename : %s\n", ppfname); // backup message and PPF info
217 printf ("Encoding method : %d (PPF %d.0)\n", method, method + 1);
218 printf ("Description : %s\n", desc);
219
220 if (method == 0) // PPF 1.0
221 {
222 puts ("FILE_ID.DIZ : No\n");
223 x = 56; // file pointer is at right position (56)
224 }
225 else // method == 1 // PPF 2.0
226 {
227 char ppfblock[1024];
228 int modlen;
229
230 fseek (ppffile, ppfsize - 8, SEEK_SET);
231 fread_checked (buffer, 4, 1, ppffile);
232
233 // Is there a file id?
234 if (memcmp (".DIZ", buffer, 4))
235 puts ("FILE_ID.DIZ : No\n");
236 else
237 {
238 char diz[MAX_ID_SIZE + 1];
239
240 puts ("FILE_ID.DIZ : Yes, showing...");
241 fread_checked (&dizlen, 4, 1, ppffile);
242 #ifdef WORDS_BIGENDIAN
243 dizlen = bswap_32 (dizlen); // FILE_ID.DIZ size is in little-endian format
244 #endif
245 fseek (ppffile, ppfsize - dizlen - (16 + 4), SEEK_SET);
246 bytes_to_skip = dizlen + 18 + 16 + 4; // +4 for FILE_ID.DIZ size integer
247 if (dizlen > MAX_ID_SIZE)
248 dizlen = MAX_ID_SIZE; // do this after setting bytes_to_skip!
249 fread_checked (diz, dizlen, 1, ppffile);
250 diz[dizlen] = '\0'; // terminate string
251 puts (diz);
252 }
253
254 // Do the file size check
255 fseek (ppffile, 56, SEEK_SET);
256 fread_checked (&x, 4, 1, ppffile);
257 #ifdef WORDS_BIGENDIAN
258 x = bswap_32 (x); // file size is stored in little-endian format
259 #endif
260 modlen = (int) fsizeof (modname);
261 if (x != modlen)
262 {
263 fprintf (stderr, "ERROR: The size of %s is not %d bytes\n", modname, x);
264 exit (1);
265 }
266
267 // Do the binary block check
268 fseek (ppffile, 60, SEEK_SET);
269 fread_checked (ppfblock, 1024, 1, ppffile);
270 fseek (modfile, 0x9320, SEEK_SET);
271 memset (buffer, 0, 1024); // one little hack that makes PPF
272 fread_checked2 (buffer, 1024, 1, modfile);// suitable for files < 38688 bytes
273 if (memcmp (ppfblock, buffer, 1024))
274 {
275 fputs ("ERROR: This patch does not belong to this image\n", stderr);
276 exit (1);
277 }
278
279 fseek (ppffile, 1084, SEEK_SET);
280 x = 1084;
281 }
282
283 // Patch the image
284 puts ("Patching...");
285 for (; x < ppfsize - bytes_to_skip; x += 4 + 1 + n_changes)
286 {
287 fread_checked2 (&pos, 4, 1, ppffile); // Get position for modfile
288 #ifdef WORDS_BIGENDIAN
289 pos = bswap_32 (pos);
290 #endif
291 n_changes = fgetc (ppffile); // How many bytes do we have to write?
292 if (n_changes == EOF)
293 {
294 fputs ("ERROR: Unexpected end of patch file\n", stderr);
295 fclose (ppffile);
296 fclose (modfile);
297
298 printf ("Removing %s\n", modname);
299 remove (modname);
300 return -1;
301 }
302 fread_checked (buffer, n_changes, 1, ppffile); // And this is what we have to write
303 fseek (modfile, pos, SEEK_SET); // Go to the right position in the modfile
304 fwrite (buffer, n_changes, 1, modfile); // Write n_changes bytes to that pos
305 }
306
307 puts ("Done");
308 fclose (ppffile);
309 fclose (modfile);
310
311 printf (ucon64_msg[WROTE], modname);
312 return 0;
313 }
314
315
316 // based on sourcecode of MakePPF v2.0 Linux/UNIX by Icarus/Paradox
317 int
ppf_create(const char * orgname,const char * modname)318 ppf_create (const char *orgname, const char *modname)
319 {
320 FILE *orgfile, *modfile, *ppffile;
321 char ppfname[FILENAME_MAX], buffer[MAX_ID_SIZE], obuf[512], mbuf[512];
322 #if 0
323 char *fidname = "FILE_ID.DIZ";
324 #endif
325 unsigned int x, osize, msize, blocksize, n_changes, total_changes = 0,
326 seekpos = 0, pos;
327
328 osize = (unsigned int) fsizeof (orgname);
329 msize = (unsigned int) fsizeof (modname);
330 #ifndef DIFF_FSIZE
331 if (osize != msize)
332 {
333 fputs ("ERROR: File sizes do not match\n", stderr);
334 return -1;
335 }
336 #endif
337
338 if ((orgfile = fopen (orgname, "rb")) == NULL)
339 {
340 fprintf (stderr, ucon64_msg[OPEN_READ_ERROR], orgname);
341 exit (1);
342 }
343 if ((modfile = fopen (modname, "rb")) == NULL)
344 {
345 fprintf (stderr, ucon64_msg[OPEN_READ_ERROR], modname);
346 exit (1);
347 }
348 strcpy (ppfname, modname);
349 set_suffix (ppfname, ".ppf");
350 ucon64_file_handler (ppfname, NULL, 0);
351 if ((ppffile = fopen (ppfname, "wb")) == NULL)
352 {
353 fprintf (stderr, ucon64_msg[OPEN_WRITE_ERROR], ppfname);
354 exit (1);
355 }
356
357 // creating PPF 2.0 header
358 fwrite ("PPF20", 5, 1, ppffile); // magic
359 fputc (1, ppffile); // encoding method
360 memset (buffer, ' ', 50);
361 fwrite (buffer, 50, 1, ppffile); // description line
362 #ifdef WORDS_BIGENDIAN
363 x = bswap_32 (osize);
364 fwrite (&x, 4, 1, ppffile);
365 #else
366 fwrite (&osize, 4, 1, ppffile); // orgfile size
367 #endif
368 fseek (orgfile, 0x9320, SEEK_SET);
369 memset (buffer, 0, 1024); // one little hack that makes PPF
370 fread_checked2 (buffer, 1024, 1, orgfile); // suitable for files < 38688 bytes
371 fwrite (buffer, 1024, 1, ppffile); // 1024 byte block
372
373 puts ("Writing patch data, please wait...");
374 // finding changes
375 fseek (orgfile, 0, SEEK_SET);
376 fseek (modfile, 0, SEEK_SET);
377 while ((blocksize = (unsigned int) fread (obuf, 1, 255, orgfile)) != 0)
378 {
379 blocksize = (unsigned int) fread (mbuf, 1, blocksize, modfile);
380 #ifdef DIFF_FSIZE
381 if (blocksize == 0)
382 break;
383 #endif
384 pos = seekpos;
385 x = 0;
386 while (x != blocksize)
387 {
388 if (obuf[x] != mbuf[x])
389 {
390 pos = seekpos + x;
391 n_changes = 0;
392 do
393 {
394 buffer[n_changes] = mbuf[x];
395 n_changes++;
396 x++;
397 }
398 while (x != blocksize && obuf[x] != mbuf[x]);
399 total_changes += n_changes;
400 #ifdef WORDS_BIGENDIAN
401 pos = bswap_32 (pos);
402 #endif
403 fwrite (&pos, 4, 1, ppffile);
404 fputc (n_changes, ppffile);
405 fwrite (buffer, n_changes, 1, ppffile);
406 }
407 else
408 x++;
409 }
410 seekpos += blocksize;
411 }
412
413 #ifdef DIFF_FSIZE
414 if (msize > osize)
415 {
416 pos = seekpos;
417 while ((blocksize = (unsigned int) fread (buffer, 1, 255, modfile)) != 0)
418 {
419 total_changes += blocksize;
420 #ifdef WORDS_BIGENDIAN
421 x = bswap_32 (pos);
422 fwrite (&x, 4, 1, ppffile);
423 #else
424 fwrite (&pos, 4, 1, ppffile);
425 #endif
426 fputc (blocksize, ppffile);
427 fwrite (buffer, blocksize, 1, ppffile);
428 pos += blocksize;
429 }
430 }
431 else if (msize < osize)
432 printf ("WARNING: %s is smaller than %s\n"
433 " PPF cannot store information about that fact\n",
434 modname, orgname);
435 #endif
436
437 fclose (orgfile);
438 fclose (modfile);
439
440 if (total_changes == 0)
441 {
442 printf ("%s and %s are identical\n"
443 "Removing %s\n", orgname, modname, ppfname);
444 fclose (ppffile);
445 remove (ppfname);
446 return -1;
447 }
448
449 #if 0
450 if (fidname)
451 {
452 unsigned int fsize = fsizeof (fidname);
453 if (fsize > MAX_ID_SIZE)
454 fsize = MAX_ID_SIZE; // File id only up to 3072 bytes!
455 printf ("Adding FILE_ID.DIZ (%s)...\n", fidname);
456 ucon64_fread (buffer, 0, fsize, fidname);
457 fwrite ("@BEGIN_FILE_ID.DIZ", 18, 1, ppffile);
458 fwrite (buffer, fsize, 1, ppffile);
459 fwrite ("@END_FILE_ID.DIZ", 16, 1, ppffile);
460 #ifdef WORDS_BIGENDIAN
461 fsize = bswap_32 (fsize); // Write file size in little-endian format
462 #endif
463 fwrite (&fsize, 4, 1, ppffile);
464 }
465 #endif
466 fclose (ppffile);
467
468 printf (ucon64_msg[WROTE], ppfname);
469 return 0;
470 }
471
472
473 int
ppf_set_desc(const char * ppf,const char * description)474 ppf_set_desc (const char *ppf, const char *description)
475 {
476 char desc[50], ppfname[FILENAME_MAX];
477 size_t len = strnlen (description, sizeof desc);
478
479 strcpy (ppfname, ppf);
480 if (len < sizeof desc) // warning remover
481 memset (desc + len, ' ', sizeof desc - len);
482 strncpy (desc, description, len);
483 ucon64_file_handler (ppfname, NULL, 0);
484 fcopy (ppf, 0, fsizeof (ppf), ppfname, "wb"); // no copy if one file
485 ucon64_fwrite (desc, 6, sizeof desc, ppfname, "r+b");
486
487 printf (ucon64_msg[WROTE], ppfname);
488 return 0;
489 }
490
491
492 int
ppf_set_fid(const char * ppf,const char * fidname)493 ppf_set_fid (const char *ppf, const char *fidname)
494 {
495 int fidsize, ppfsize, pos;
496 char ppfname[FILENAME_MAX],
497 fidbuf[MAX_ID_SIZE + 34 + 1] = "@BEGIN_FILE_ID.DIZ"; // +1 for string terminator
498
499 strcpy (ppfname, ppf);
500 ucon64_file_handler (ppfname, NULL, 0);
501 fcopy (ppf, 0, fsizeof (ppf), ppfname, "wb"); // no copy if one file
502
503 printf ("Adding FILE_ID.DIZ (%s)...\n", fidname);
504 fidsize = (int) ucon64_fread (fidbuf + 18, 0, MAX_ID_SIZE, fidname);
505 memcpy (fidbuf + 18 + fidsize, "@END_FILE_ID.DIZ", 16);
506
507 ppfsize = (int) fsizeof (ppfname);
508 pos = (int) ucon64_find (ppfname, 0, ppfsize, "@BEGIN_FILE_ID.DIZ", 18,
509 MEMCMP2_CASE | UCON64_FIND_QUIET);
510 if (pos == -1)
511 pos = ppfsize;
512
513 ucon64_fwrite (fidbuf, pos, fidsize + 18 + 16, ppfname, "r+b");
514 pos += fidsize + 18 + 16;
515 #ifdef WORDS_BIGENDIAN
516 fidsize = bswap_32 (fidsize); // Write file size in little-endian format
517 #endif
518 ucon64_fwrite (&fidsize, pos, 4, ppfname, "r+b");
519 pos += 4;
520 if (ppfsize > pos && truncate (ppfname, pos))
521 fprintf (stderr, "ERROR: Truncating \"%s\" failed", ppfname);
522
523 printf (ucon64_msg[WROTE], ppfname);
524 return 0;
525 }
526