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