9a74908159e9c41c27add2c9ca302822989a20c7
[pcsx_rearmed.git] / plugins / cdrcimg / cdrcimg.c
1 /*
2  * (C) GraÅžvydas "notaz" Ignotas, 2010
3  *
4  * This work is licensed under the terms of any of these licenses
5  * (at your option):
6  *  - GNU GPL, version 2 or later.
7  *  - GNU LGPL, version 2.1 or later.
8  * See the COPYING file in the top-level directory.
9  */
10
11 #include <stdio.h>
12 #include <string.h>
13 #include <stdlib.h>
14 #include <zlib.h>
15 #ifndef _WIN32
16 #define CALLBACK
17 #ifndef NO_DYLIB
18 #include <dlfcn.h>
19 #endif
20 #else
21 #define WIN32_LEAN_AND_MEAN
22 #include <windows.h>
23 #endif
24
25 #include "cdrcimg.h"
26
27 #define PFX "cdrcimg: "
28 #define err(f, ...) fprintf(stderr, PFX f, ##__VA_ARGS__)
29
30 #define CD_FRAMESIZE_RAW 2352
31
32 enum {
33         CDRC_ZLIB,
34         CDRC_ZLIB2,
35         CDRC_BZ,
36 };
37
38 static const char *cd_fname;
39 static unsigned int *cd_index_table;
40 static unsigned int  cd_index_len;
41 static unsigned int  cd_sectors_per_blk;
42 static int cd_compression;
43 static FILE *cd_file;
44
45 static int (*pBZ2_bzBuffToBuffDecompress)(char *dest, unsigned int *destLen, char *source,
46                 unsigned int sourceLen, int small, int verbosity);
47
48 static struct {
49         unsigned char raw[16][CD_FRAMESIZE_RAW];
50         unsigned char compressed[CD_FRAMESIZE_RAW * 16 + 100];
51 } *cdbuffer;
52 static int current_block, current_sect_in_blk;
53
54 struct CdrStat;
55 extern long CALLBACK CDR__getStatus(struct CdrStat *stat);
56
57 struct CdrStat
58 {
59         unsigned int Type;
60         unsigned int Status;
61         unsigned char Time[3]; // current playing time
62 };
63
64 struct trackinfo {
65         enum {DATA, CDDA} type;
66         char start[3];          // MSF-format
67         char length[3];         // MSF-format
68 };
69
70 #define MAXTRACKS 100 /* How many tracks can a CD hold? */
71
72 static int numtracks = 0;
73
74 #define btoi(b)           ((b) / 16 * 10 + (b) % 16) /* BCD to u_char */
75 #define MSF2SECT(m, s, f) (((m) * 60 + (s) - 2) * 75 + (f))
76
77 // return Starting and Ending Track
78 // buffer:
79 //  byte 0 - start track
80 //  byte 1 - end track
81 static long CDRgetTN(unsigned char *buffer)
82 {
83         buffer[0] = 1;
84         buffer[1] = numtracks > 0 ? numtracks : 1;
85
86         return 0;
87 }
88
89 // return Track Time
90 // buffer:
91 //  byte 0 - frame
92 //  byte 1 - second
93 //  byte 2 - minute
94 static long CDRgetTD(unsigned char track, unsigned char *buffer)
95 {
96         buffer[2] = 0;
97         buffer[1] = 2;
98         buffer[0] = 0;
99
100         return 0;
101 }
102
103 static int uncompress2_pcsx(void *out, unsigned long *out_size, void *in, unsigned long in_size)
104 {
105         static z_stream z;
106         int ret = 0;
107
108         if (z.zalloc == NULL) {
109                 // XXX: one-time leak here..
110                 z.next_in = Z_NULL;
111                 z.avail_in = 0;
112                 z.zalloc = Z_NULL;
113                 z.zfree = Z_NULL;
114                 z.opaque = Z_NULL;
115                 ret = inflateInit2(&z, -15);
116         }
117         else
118                 ret = inflateReset(&z);
119         if (ret != Z_OK)
120                 return ret;
121
122         z.next_in = in;
123         z.avail_in = in_size;
124         z.next_out = out;
125         z.avail_out = *out_size;
126
127         ret = inflate(&z, Z_NO_FLUSH);
128         //inflateEnd(&z);
129
130         *out_size -= z.avail_out;
131         return ret == 1 ? 0 : ret;
132 }
133
134 // read track
135 // time: byte 0 - minute; byte 1 - second; byte 2 - frame
136 // uses bcd format
137 static long CDRreadTrack(unsigned char *time)
138 {
139         unsigned int start_byte, size;
140         unsigned long cdbuffer_size;
141         int ret, sector, block;
142
143         if (cd_file == NULL)
144                 return -1;
145
146         sector = MSF2SECT(btoi(time[0]), btoi(time[1]), btoi(time[2]));
147
148         // avoid division if possible
149         switch (cd_sectors_per_blk) {
150         case 1:
151                 block = sector;
152                 current_sect_in_blk = 0;
153                 break;
154         case 10:
155                 block = sector / 10;
156                 current_sect_in_blk = sector % 10;
157                 break;
158         case 16:
159                 block = sector >> 4;
160                 current_sect_in_blk = sector & 15;
161                 break;
162         default:
163                 err("unhandled cd_sectors_per_blk: %d\n", cd_sectors_per_blk);
164                 return -1;
165         }
166
167         if (block == current_block) {
168                 // it's already there, nothing to do
169                 //printf("hit sect %d\n", sector);
170                 return 0;
171         }
172
173         if (sector >= cd_index_len * cd_sectors_per_blk) {
174                 err("sector %d is past track end\n", sector);
175                 return -1;
176         }
177
178         start_byte = cd_index_table[block];
179         if (fseek(cd_file, start_byte, SEEK_SET) != 0) {
180                 err("seek error for block %d at %x: ",
181                         block, start_byte);
182                 perror(NULL);
183                 return -1;
184         }
185
186         size = cd_index_table[block + 1] - start_byte;
187         if (size > sizeof(cdbuffer->compressed)) {
188                 err("block %d is too large: %u\n", block, size);
189                 return -1;
190         }
191
192         if (fread(cdbuffer->compressed, 1, size, cd_file) != size) {
193                 err("read error for block %d at %x: ", block, start_byte);
194                 perror(NULL);
195                 return -1;
196         }
197
198         cdbuffer_size = sizeof(cdbuffer->raw[0]) * cd_sectors_per_blk;
199         switch (cd_compression) {
200         case CDRC_ZLIB:
201                 ret = uncompress(cdbuffer->raw[0], &cdbuffer_size, cdbuffer->compressed, size);
202                 break;
203         case CDRC_ZLIB2:
204                 ret = uncompress2_pcsx(cdbuffer->raw[0], &cdbuffer_size, cdbuffer->compressed, size);
205                 break;
206         case CDRC_BZ:
207                 ret = pBZ2_bzBuffToBuffDecompress((char *)cdbuffer->raw, (unsigned int *)&cdbuffer_size,
208                         (char *)cdbuffer->compressed, size, 0, 0);
209                 break;
210         default:
211                 err("bad cd_compression: %d\n", cd_compression);
212                 return -1;
213         }
214
215         if (ret != 0) {
216                 err("uncompress failed with %d for block %d, sector %d\n",
217                         ret, block, sector);
218                 return -1;
219         }
220         if (cdbuffer_size != sizeof(cdbuffer->raw[0]) * cd_sectors_per_blk)
221                 err("cdbuffer_size: %lu != %d, sector %d\n", cdbuffer_size,
222                         (int)sizeof(cdbuffer->raw[0]) * cd_sectors_per_blk, sector);
223
224         // done at last!
225         current_block = block;
226         return 0;
227 }
228
229 // return read track
230 static unsigned char *CDRgetBuffer(void)
231 {
232         return cdbuffer->raw[current_sect_in_blk] + 12;
233 }
234
235 // plays cdda audio
236 // sector: byte 0 - minute; byte 1 - second; byte 2 - frame
237 // does NOT uses bcd format
238 static long CDRplay(unsigned char *time)
239 {
240         return 0;
241 }
242
243 // stops cdda audio
244 static long CDRstop(void)
245 {
246         return 0;
247 }
248
249 // gets subchannel data
250 static unsigned char* CDRgetBufferSub(void)
251 {
252         return NULL;
253 }
254
255 static long CDRgetStatus(struct CdrStat *stat) {
256         CDR__getStatus(stat);
257
258         stat->Type = 0x01;
259
260         return 0;
261 }
262
263 static long CDRclose(void)
264 {
265         if (cd_file != NULL) {
266                 fclose(cd_file);
267                 cd_file = NULL;
268         }
269         if (cd_index_table != NULL) {
270                 free(cd_index_table);
271                 cd_index_table = NULL;
272         }
273         return 0;
274 }
275
276 static long CDRshutdown(void)
277 {
278         return CDRclose();
279 }
280
281 static long CDRinit(void)
282 {
283         if (cdbuffer == NULL) {
284                 cdbuffer = malloc(sizeof(*cdbuffer));
285                 if (cdbuffer == NULL) {
286                         err("OOM\n");
287                         return -1;
288                 }
289         }
290 #if !defined(_WIN32) && !defined(NO_DYLIB)
291         if (pBZ2_bzBuffToBuffDecompress == NULL) {
292                 void *h = dlopen("/usr/lib/libbz2.so.1", RTLD_LAZY);
293                 if (h == NULL)
294                         h = dlopen("./lib/libbz2.so.1", RTLD_LAZY);
295                 if (h != NULL) {
296                         pBZ2_bzBuffToBuffDecompress = dlsym(h, "BZ2_bzBuffToBuffDecompress");
297                         if (pBZ2_bzBuffToBuffDecompress == NULL) {
298                                 err("dlsym bz2: %s", dlerror());
299                                 dlclose(h);
300                         }
301                 }
302         }
303 #endif
304         return 0;
305 }
306
307 static long handle_eboot(void)
308 {
309         struct {
310                 unsigned int sig;
311                 unsigned int dontcare[8];
312                 unsigned int psar_offs;
313         } pbp_hdr;
314         struct {
315                 unsigned int offset;
316                 unsigned int size;
317                 unsigned int dontcare[6];
318         } index_entry;
319         char psar_sig[9];
320         unsigned int cdimg_base;
321         int i, ret;
322         FILE *f;
323
324         f = fopen(cd_fname, "rb");
325         if (f == NULL) {
326                 err("missing file: %s: ", cd_fname);
327                 perror(NULL);
328                 return -1;
329         }
330
331         ret = fread(&pbp_hdr, 1, sizeof(pbp_hdr), f);
332         if (ret != sizeof(pbp_hdr)) {
333                 err("failed to read pbp\n");
334                 goto fail_io;
335         }
336
337         ret = fseek(f, pbp_hdr.psar_offs, SEEK_SET);
338         if (ret != 0) {
339                 err("failed to seek to %x\n", pbp_hdr.psar_offs);
340                 goto fail_io;
341         }
342
343         ret = fread(psar_sig, 1, sizeof(psar_sig), f);
344         if (ret != sizeof(psar_sig)) {
345                 err("failed to read psar_sig\n");
346                 goto fail_io;
347         }
348
349         psar_sig[8] = 0;
350         if (strcmp(psar_sig, "PSISOIMG") != 0) {
351                 err("bad psar_sig: %s\n", psar_sig);
352                 goto fail_io;
353         }
354
355         // seek to ISO index
356         ret = fseek(f, 0x4000 - sizeof(psar_sig), SEEK_CUR);
357         if (ret != 0) {
358                 err("failed to seek to ISO index\n");
359                 goto fail_io;
360         }
361
362         cd_index_len = (0x100000 - 0x4000) / sizeof(index_entry);
363         cd_index_table = malloc((cd_index_len + 1) * sizeof(cd_index_table[0]));
364         if (cd_index_table == NULL)
365                 goto fail_io;
366
367         cdimg_base = pbp_hdr.psar_offs + 0x100000;
368         for (i = 0; i < cd_index_len; i++) {
369                 ret = fread(&index_entry, 1, sizeof(index_entry), f);
370                 if (ret != sizeof(index_entry)) {
371                         err("failed to read index_entry #%d\n", i);
372                         goto fail_index;
373                 }
374
375                 if (index_entry.size == 0)
376                         break;
377
378                 cd_index_table[i] = cdimg_base + index_entry.offset;
379         }
380         cd_index_table[i] = cdimg_base + index_entry.offset + index_entry.size;
381
382         cd_compression = CDRC_ZLIB2;
383         cd_sectors_per_blk = 16;
384         cd_file = f;
385
386         printf(PFX "Loaded EBOOT CD Image: %s.\n", cd_fname);
387         return 0;
388
389 fail_index:
390         free(cd_index_table);
391         cd_index_table = NULL;
392 fail_io:
393         fclose(f);
394         return -1;
395 }
396
397 // This function is invoked by the front-end when opening an ISO
398 // file for playback
399 static long CDRopen(void)
400 {
401         union {
402                 struct {
403                         unsigned int offset;
404                         unsigned short size;
405                 } __attribute__((packed)) ztab_entry;
406                 struct {
407                         unsigned int offset;
408                         unsigned short size;
409                         unsigned int dontcare;
410                 } __attribute__((packed)) znxtab_entry;
411                 unsigned int bztab_entry;
412         } u;
413         int tabentry_size;
414         char table_fname[256];
415         long table_size;
416         int i, ret;
417         char *ext;
418         FILE *f = NULL;
419
420         printf("%s cd_file=%d\n", __func__, cd_file == NULL ? 0 : 1);
421
422         if (cd_file != NULL)
423                 return 0; // it's already open
424
425         numtracks = 0;
426         current_block = -1;
427         current_sect_in_blk = 0;
428
429         if (cd_fname == NULL)
430                 return -1;
431
432         ext = strrchr(cd_fname, '.');
433         if (ext == NULL)
434                 return -1;
435
436         if (strcasecmp(ext, ".pbp") == 0) {
437                 return handle_eboot();
438         }
439
440         // pocketiso stuff
441         else if (strcasecmp(ext, ".z") == 0) {
442                 cd_compression = CDRC_ZLIB;
443                 tabentry_size = sizeof(u.ztab_entry);
444                 snprintf(table_fname, sizeof(table_fname), "%s.table", cd_fname);
445         }
446         else if (strcasecmp(ext, ".znx") == 0) {
447                 cd_compression = CDRC_ZLIB;
448                 tabentry_size = sizeof(u.znxtab_entry);
449                 snprintf(table_fname, sizeof(table_fname), "%s.table", cd_fname);
450         }
451         else if (strcasecmp(ext, ".bz") == 0) {
452                 if (pBZ2_bzBuffToBuffDecompress == NULL) {
453                         err("libbz2 unavailable for .bz2 handling\n");
454                         return -1;
455                 }
456                 cd_compression = CDRC_BZ;
457                 tabentry_size = sizeof(u.bztab_entry);
458                 snprintf(table_fname, sizeof(table_fname), "%s.index", cd_fname);
459         }
460         else {
461                 err("unhandled extension: %s\n", ext);
462                 return -1;
463         }
464
465         f = fopen(table_fname, "rb");
466         if (f == NULL) {
467                 err("missing file: %s: ", table_fname);
468                 perror(NULL);
469                 return -1;
470         }
471
472         ret = fseek(f, 0, SEEK_END);
473         if (ret != 0) {
474                 err("failed to seek\n");
475                 goto fail_table_io;
476         }
477         table_size = ftell(f);
478         fseek(f, 0, SEEK_SET);
479
480         if (table_size > 4 * 1024 * 1024) {
481                 err(".table too large\n");
482                 goto fail_table_io;
483         }
484
485         cd_index_len = table_size / tabentry_size;
486
487         cd_index_table = malloc((cd_index_len + 1) * sizeof(cd_index_table[0]));
488         if (cd_index_table == NULL)
489                 goto fail_table_io;
490
491         switch (cd_compression) {
492         case CDRC_ZLIB:
493                 // a Z.table file is binary where each element represents
494                 // one compressed frame.  
495                 //    4 bytes: the offset of the frame in the .Z file
496                 //    2 bytes: the length of the compressed frame
497                 // .znx.table has 4 additional bytes (xa header??)
498                 u.znxtab_entry.dontcare = 0;
499                 for (i = 0; i < cd_index_len; i++) {
500                         ret = fread(&u, 1, tabentry_size, f);
501                         if (ret != tabentry_size) {
502                                 err(".table read failed on entry %d/%d\n", i, cd_index_len);
503                                 goto fail_table_io_read;
504                         }
505                         cd_index_table[i] = u.ztab_entry.offset;
506                         //if (u.znxtab_entry.dontcare != 0)
507                         //      printf("znx %08x!\n", u.znxtab_entry.dontcare);
508                 }
509                 // fake entry, so that we know last compressed block size
510                 cd_index_table[i] = u.ztab_entry.offset + u.ztab_entry.size;
511                 cd_sectors_per_blk = 1;
512                 break;
513         case CDRC_BZ:
514                 // the .BZ.table file is arranged so that one entry represents
515                 // 10 compressed frames. Each element is a 4 byte unsigned integer
516                 // representing the offset in the .BZ file. Last entry is the size
517                 // of the compressed file.
518                 for (i = 0; i < cd_index_len; i++) {
519                         ret = fread(&u.bztab_entry, 1, sizeof(u.bztab_entry), f);
520                         if (ret != sizeof(u.bztab_entry)) {
521                                 err(".table read failed on entry %d/%d\n", i, cd_index_len);
522                                 goto fail_table_io_read;
523                         }
524                         cd_index_table[i] = u.bztab_entry;
525                 }
526                 cd_sectors_per_blk = 10;
527                 break;
528         }
529
530         cd_file = fopen(cd_fname, "rb");
531         if (cd_file == NULL) {
532                 err("failed to open: %s: ", table_fname);
533                 perror(NULL);
534                 goto fail_img;
535         }
536         fclose(f);
537
538         printf(PFX "Loaded compressed CD Image: %s.\n", cd_fname);
539
540         return 0;
541
542 fail_img:
543 fail_table_io_read:
544         free(cd_index_table);
545         cd_index_table = NULL;
546 fail_table_io:
547         fclose(f);
548         return -1;
549 }
550
551 #define FUNC(n) { #n, n }
552
553 static const struct {
554         const char *name;
555         void *func;
556 } plugin_funcs[] = {
557         /* CDR */
558         FUNC(CDRinit),
559         FUNC(CDRshutdown),
560         FUNC(CDRopen),
561         FUNC(CDRclose),
562         FUNC(CDRgetTN),
563         FUNC(CDRgetTD),
564         FUNC(CDRreadTrack),
565         FUNC(CDRgetBuffer),
566         FUNC(CDRgetBufferSub),
567         FUNC(CDRplay),
568         FUNC(CDRstop),
569         FUNC(CDRgetStatus),
570 };
571
572 void cdrcimg_set_fname(const char *fname)
573 {
574         cd_fname = fname;
575 }
576
577 void *cdrcimg_get_sym(const char *sym)
578 {
579         int i;
580         for (i = 0; i < sizeof(plugin_funcs) / sizeof(plugin_funcs[0]); i++)
581                 if (strcmp(plugin_funcs[i].name, sym) == 0)
582                         return plugin_funcs[i].func;
583         return NULL;
584 }
585