Merge branch 'mainline' into libretro
[pcsx_rearmed.git] / plugins / cdrcimg / cdrcimg.c
... / ...
CommitLineData
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
32enum {
33 CDRC_ZLIB,
34 CDRC_ZLIB2,
35 CDRC_BZ,
36};
37
38static const char *cd_fname;
39static unsigned int *cd_index_table;
40static unsigned int cd_index_len;
41static unsigned int cd_sectors_per_blk;
42static int cd_compression;
43static FILE *cd_file;
44
45static int (*pBZ2_bzBuffToBuffDecompress)(char *dest, unsigned int *destLen, char *source,
46 unsigned int sourceLen, int small, int verbosity);
47
48static struct {
49 unsigned char raw[16][CD_FRAMESIZE_RAW];
50 unsigned char compressed[CD_FRAMESIZE_RAW * 16 + 100];
51} *cdbuffer;
52static int current_block, current_sect_in_blk;
53
54struct CdrStat;
55extern long CALLBACK CDR__getStatus(struct CdrStat *stat);
56
57struct CdrStat
58{
59 unsigned int Type;
60 unsigned int Status;
61 unsigned char Time[3]; // current playing time
62};
63
64struct 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
72static 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
81static 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
94static 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
103int uncompress2(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
137static 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(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
230static 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
238static long CDRplay(unsigned char *time)
239{
240 return 0;
241}
242
243// stops cdda audio
244static long CDRstop(void)
245{
246 return 0;
247}
248
249// gets subchannel data
250static unsigned char* CDRgetBufferSub(void)
251{
252 return NULL;
253}
254
255static long CDRgetStatus(struct CdrStat *stat) {
256 CDR__getStatus(stat);
257
258 stat->Type = 0x01;
259
260 return 0;
261}
262
263static 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
276static long CDRshutdown(void)
277{
278 return CDRclose();
279}
280
281static 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
307static 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
389fail_index:
390 free(cd_index_table);
391 cd_index_table = NULL;
392fail_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
399static 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 if (cd_file != NULL)
421 return 0; // it's already open
422
423 numtracks = 0;
424 current_block = -1;
425 current_sect_in_blk = 0;
426
427 if (cd_fname == NULL)
428 return -1;
429
430 ext = strrchr(cd_fname, '.');
431 if (ext == NULL)
432 return -1;
433
434 if (strcasecmp(ext, ".pbp") == 0) {
435 return handle_eboot();
436 }
437
438 // pocketiso stuff
439 else if (strcasecmp(ext, ".z") == 0) {
440 cd_compression = CDRC_ZLIB;
441 tabentry_size = sizeof(u.ztab_entry);
442 snprintf(table_fname, sizeof(table_fname), "%s.table", cd_fname);
443 }
444 else if (strcasecmp(ext, ".znx") == 0) {
445 cd_compression = CDRC_ZLIB;
446 tabentry_size = sizeof(u.znxtab_entry);
447 snprintf(table_fname, sizeof(table_fname), "%s.table", cd_fname);
448 }
449 else if (strcasecmp(ext, ".bz") == 0) {
450 if (pBZ2_bzBuffToBuffDecompress == NULL) {
451 err("libbz2 unavailable for .bz2 handling\n");
452 return -1;
453 }
454 cd_compression = CDRC_BZ;
455 tabentry_size = sizeof(u.bztab_entry);
456 snprintf(table_fname, sizeof(table_fname), "%s.index", cd_fname);
457 }
458 else {
459 err("unhandled extension: %s\n", ext);
460 return -1;
461 }
462
463 f = fopen(table_fname, "rb");
464 if (f == NULL) {
465 err("missing file: %s: ", table_fname);
466 perror(NULL);
467 return -1;
468 }
469
470 ret = fseek(f, 0, SEEK_END);
471 if (ret != 0) {
472 err("failed to seek\n");
473 goto fail_table_io;
474 }
475 table_size = ftell(f);
476 fseek(f, 0, SEEK_SET);
477
478 if (table_size > 4 * 1024 * 1024) {
479 err(".table too large\n");
480 goto fail_table_io;
481 }
482
483 cd_index_len = table_size / tabentry_size;
484
485 cd_index_table = malloc((cd_index_len + 1) * sizeof(cd_index_table[0]));
486 if (cd_index_table == NULL)
487 goto fail_table_io;
488
489 switch (cd_compression) {
490 case CDRC_ZLIB:
491 // a Z.table file is binary where each element represents
492 // one compressed frame.
493 // 4 bytes: the offset of the frame in the .Z file
494 // 2 bytes: the length of the compressed frame
495 // .znx.table has 4 additional bytes (xa header??)
496 u.znxtab_entry.dontcare = 0;
497 for (i = 0; i < cd_index_len; i++) {
498 ret = fread(&u, 1, tabentry_size, f);
499 if (ret != tabentry_size) {
500 err(".table read failed on entry %d/%d\n", i, cd_index_len);
501 goto fail_table_io_read;
502 }
503 cd_index_table[i] = u.ztab_entry.offset;
504 //if (u.znxtab_entry.dontcare != 0)
505 // printf("znx %08x!\n", u.znxtab_entry.dontcare);
506 }
507 // fake entry, so that we know last compressed block size
508 cd_index_table[i] = u.ztab_entry.offset + u.ztab_entry.size;
509 cd_sectors_per_blk = 1;
510 break;
511 case CDRC_BZ:
512 // the .BZ.table file is arranged so that one entry represents
513 // 10 compressed frames. Each element is a 4 byte unsigned integer
514 // representing the offset in the .BZ file. Last entry is the size
515 // of the compressed file.
516 for (i = 0; i < cd_index_len; i++) {
517 ret = fread(&u.bztab_entry, 1, sizeof(u.bztab_entry), f);
518 if (ret != sizeof(u.bztab_entry)) {
519 err(".table read failed on entry %d/%d\n", i, cd_index_len);
520 goto fail_table_io_read;
521 }
522 cd_index_table[i] = u.bztab_entry;
523 }
524 cd_sectors_per_blk = 10;
525 break;
526 }
527
528 cd_file = fopen(cd_fname, "rb");
529 if (cd_file == NULL) {
530 err("failed to open: %s: ", table_fname);
531 perror(NULL);
532 goto fail_img;
533 }
534 fclose(f);
535
536 printf(PFX "Loaded compressed CD Image: %s.\n", cd_fname);
537
538 return 0;
539
540fail_img:
541fail_table_io_read:
542 free(cd_index_table);
543 cd_index_table = NULL;
544fail_table_io:
545 fclose(f);
546 return -1;
547}
548
549#define FUNC(n) { #n, n }
550
551static const struct {
552 const char *name;
553 void *func;
554} plugin_funcs[] = {
555 /* CDR */
556 FUNC(CDRinit),
557 FUNC(CDRshutdown),
558 FUNC(CDRopen),
559 FUNC(CDRclose),
560 FUNC(CDRgetTN),
561 FUNC(CDRgetTD),
562 FUNC(CDRreadTrack),
563 FUNC(CDRgetBuffer),
564 FUNC(CDRgetBufferSub),
565 FUNC(CDRplay),
566 FUNC(CDRstop),
567 FUNC(CDRgetStatus),
568};
569
570void cdrcimg_set_fname(const char *fname)
571{
572 cd_fname = fname;
573}
574
575void *cdrcimg_get_sym(const char *sym)
576{
577 int i;
578 for (i = 0; i < sizeof(plugin_funcs) / sizeof(plugin_funcs[0]); i++)
579 if (strcmp(plugin_funcs[i].name, sym) == 0)
580 return plugin_funcs[i].func;
581 return NULL;
582}
583