47bf65ab |
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 | |
270c6dd1 |
11 | #include <compat/fopen_utf8.h> |
12 | |
47bf65ab |
13 | #include <stdio.h> |
14 | #include <string.h> |
15 | #include <stdlib.h> |
16 | #include <zlib.h> |
003cfc63 |
17 | #ifndef _WIN32 |
18 | #define CALLBACK |
c4052f4d |
19 | #ifndef NO_DYLIB |
bb5cf0fc |
20 | #include <dlfcn.h> |
fc99395c |
21 | #endif |
003cfc63 |
22 | #else |
23 | #define WIN32_LEAN_AND_MEAN |
24 | #include <windows.h> |
25 | #endif |
47bf65ab |
26 | |
27 | #include "cdrcimg.h" |
28 | |
33716956 |
29 | #define PFX "cdrcimg: " |
30 | #define err(f, ...) fprintf(stderr, PFX f, ##__VA_ARGS__) |
31 | |
47bf65ab |
32 | #define CD_FRAMESIZE_RAW 2352 |
33 | |
33716956 |
34 | enum { |
35 | CDRC_ZLIB, |
36 | CDRC_ZLIB2, |
37 | CDRC_BZ, |
38 | }; |
39 | |
47bf65ab |
40 | static const char *cd_fname; |
41 | static unsigned int *cd_index_table; |
42 | static unsigned int cd_index_len; |
33716956 |
43 | static unsigned int cd_sectors_per_blk; |
44 | static int cd_compression; |
47bf65ab |
45 | static FILE *cd_file; |
46 | |
bb5cf0fc |
47 | static int (*pBZ2_bzBuffToBuffDecompress)(char *dest, unsigned int *destLen, char *source, |
48 | unsigned int sourceLen, int small, int verbosity); |
49 | |
33716956 |
50 | static struct { |
51 | unsigned char raw[16][CD_FRAMESIZE_RAW]; |
52 | unsigned char compressed[CD_FRAMESIZE_RAW * 16 + 100]; |
53 | } *cdbuffer; |
54 | static int current_block, current_sect_in_blk; |
47bf65ab |
55 | |
56 | struct CdrStat; |
003cfc63 |
57 | extern long CALLBACK CDR__getStatus(struct CdrStat *stat); |
47bf65ab |
58 | |
59 | struct CdrStat |
60 | { |
c668f248 |
61 | unsigned int Type; |
62 | unsigned int Status; |
47bf65ab |
63 | unsigned char Time[3]; // current playing time |
64 | }; |
65 | |
66 | struct trackinfo { |
67 | enum {DATA, CDDA} type; |
68 | char start[3]; // MSF-format |
69 | char length[3]; // MSF-format |
70 | }; |
71 | |
72 | #define MAXTRACKS 100 /* How many tracks can a CD hold? */ |
73 | |
74 | static int numtracks = 0; |
75 | |
76 | #define btoi(b) ((b) / 16 * 10 + (b) % 16) /* BCD to u_char */ |
77 | #define MSF2SECT(m, s, f) (((m) * 60 + (s) - 2) * 75 + (f)) |
78 | |
79 | // return Starting and Ending Track |
80 | // buffer: |
81 | // byte 0 - start track |
82 | // byte 1 - end track |
83 | static long CDRgetTN(unsigned char *buffer) |
84 | { |
85 | buffer[0] = 1; |
86 | buffer[1] = numtracks > 0 ? numtracks : 1; |
87 | |
88 | return 0; |
89 | } |
90 | |
91 | // return Track Time |
92 | // buffer: |
93 | // byte 0 - frame |
94 | // byte 1 - second |
95 | // byte 2 - minute |
96 | static long CDRgetTD(unsigned char track, unsigned char *buffer) |
97 | { |
98 | buffer[2] = 0; |
99 | buffer[1] = 2; |
100 | buffer[0] = 0; |
101 | |
102 | return 0; |
103 | } |
104 | |
2dd6a826 |
105 | static int uncompress2_pcsx(void *out, unsigned long *out_size, void *in, unsigned long in_size) |
33716956 |
106 | { |
107 | static z_stream z; |
108 | int ret = 0; |
109 | |
110 | if (z.zalloc == NULL) { |
111 | // XXX: one-time leak here.. |
112 | z.next_in = Z_NULL; |
113 | z.avail_in = 0; |
114 | z.zalloc = Z_NULL; |
115 | z.zfree = Z_NULL; |
116 | z.opaque = Z_NULL; |
117 | ret = inflateInit2(&z, -15); |
118 | } |
119 | else |
120 | ret = inflateReset(&z); |
121 | if (ret != Z_OK) |
122 | return ret; |
123 | |
124 | z.next_in = in; |
125 | z.avail_in = in_size; |
126 | z.next_out = out; |
127 | z.avail_out = *out_size; |
128 | |
129 | ret = inflate(&z, Z_NO_FLUSH); |
130 | //inflateEnd(&z); |
131 | |
132 | *out_size -= z.avail_out; |
133 | return ret == 1 ? 0 : ret; |
134 | } |
135 | |
47bf65ab |
136 | // read track |
137 | // time: byte 0 - minute; byte 1 - second; byte 2 - frame |
138 | // uses bcd format |
139 | static long CDRreadTrack(unsigned char *time) |
140 | { |
141 | unsigned int start_byte, size; |
142 | unsigned long cdbuffer_size; |
33716956 |
143 | int ret, sector, block; |
47bf65ab |
144 | |
145 | if (cd_file == NULL) |
146 | return -1; |
147 | |
148 | sector = MSF2SECT(btoi(time[0]), btoi(time[1]), btoi(time[2])); |
33716956 |
149 | |
150 | // avoid division if possible |
151 | switch (cd_sectors_per_blk) { |
152 | case 1: |
153 | block = sector; |
154 | current_sect_in_blk = 0; |
155 | break; |
156 | case 10: |
157 | block = sector / 10; |
158 | current_sect_in_blk = sector % 10; |
159 | break; |
160 | case 16: |
161 | block = sector >> 4; |
162 | current_sect_in_blk = sector & 15; |
163 | break; |
164 | default: |
165 | err("unhandled cd_sectors_per_blk: %d\n", cd_sectors_per_blk); |
166 | return -1; |
167 | } |
168 | |
169 | if (block == current_block) { |
47bf65ab |
170 | // it's already there, nothing to do |
171 | //printf("hit sect %d\n", sector); |
172 | return 0; |
173 | } |
174 | |
33716956 |
175 | if (sector >= cd_index_len * cd_sectors_per_blk) { |
176 | err("sector %d is past track end\n", sector); |
47bf65ab |
177 | return -1; |
178 | } |
179 | |
33716956 |
180 | start_byte = cd_index_table[block]; |
47bf65ab |
181 | if (fseek(cd_file, start_byte, SEEK_SET) != 0) { |
33716956 |
182 | err("seek error for block %d at %x: ", |
183 | block, start_byte); |
47bf65ab |
184 | perror(NULL); |
185 | return -1; |
186 | } |
187 | |
33716956 |
188 | size = cd_index_table[block + 1] - start_byte; |
189 | if (size > sizeof(cdbuffer->compressed)) { |
190 | err("block %d is too large: %u\n", block, size); |
47bf65ab |
191 | return -1; |
192 | } |
193 | |
33716956 |
194 | if (fread(cdbuffer->compressed, 1, size, cd_file) != size) { |
195 | err("read error for block %d at %x: ", block, start_byte); |
47bf65ab |
196 | perror(NULL); |
197 | return -1; |
198 | } |
199 | |
33716956 |
200 | cdbuffer_size = sizeof(cdbuffer->raw[0]) * cd_sectors_per_blk; |
201 | switch (cd_compression) { |
202 | case CDRC_ZLIB: |
203 | ret = uncompress(cdbuffer->raw[0], &cdbuffer_size, cdbuffer->compressed, size); |
204 | break; |
205 | case CDRC_ZLIB2: |
2dd6a826 |
206 | ret = uncompress2_pcsx(cdbuffer->raw[0], &cdbuffer_size, cdbuffer->compressed, size); |
33716956 |
207 | break; |
208 | case CDRC_BZ: |
bb5cf0fc |
209 | ret = pBZ2_bzBuffToBuffDecompress((char *)cdbuffer->raw, (unsigned int *)&cdbuffer_size, |
33716956 |
210 | (char *)cdbuffer->compressed, size, 0, 0); |
211 | break; |
212 | default: |
213 | err("bad cd_compression: %d\n", cd_compression); |
214 | return -1; |
215 | } |
216 | |
47bf65ab |
217 | if (ret != 0) { |
33716956 |
218 | err("uncompress failed with %d for block %d, sector %d\n", |
219 | ret, block, sector); |
47bf65ab |
220 | return -1; |
221 | } |
33716956 |
222 | if (cdbuffer_size != sizeof(cdbuffer->raw[0]) * cd_sectors_per_blk) |
223 | err("cdbuffer_size: %lu != %d, sector %d\n", cdbuffer_size, |
c979c3ee |
224 | (int)sizeof(cdbuffer->raw[0]) * cd_sectors_per_blk, sector); |
47bf65ab |
225 | |
226 | // done at last! |
33716956 |
227 | current_block = block; |
47bf65ab |
228 | return 0; |
229 | } |
230 | |
231 | // return read track |
232 | static unsigned char *CDRgetBuffer(void) |
233 | { |
33716956 |
234 | return cdbuffer->raw[current_sect_in_blk] + 12; |
47bf65ab |
235 | } |
236 | |
237 | // plays cdda audio |
238 | // sector: byte 0 - minute; byte 1 - second; byte 2 - frame |
239 | // does NOT uses bcd format |
240 | static long CDRplay(unsigned char *time) |
241 | { |
242 | return 0; |
243 | } |
244 | |
245 | // stops cdda audio |
246 | static long CDRstop(void) |
247 | { |
248 | return 0; |
249 | } |
250 | |
251 | // gets subchannel data |
252 | static unsigned char* CDRgetBufferSub(void) |
253 | { |
254 | return NULL; |
255 | } |
256 | |
257 | static long CDRgetStatus(struct CdrStat *stat) { |
258 | CDR__getStatus(stat); |
259 | |
260 | stat->Type = 0x01; |
261 | |
262 | return 0; |
263 | } |
264 | |
265 | static long CDRclose(void) |
266 | { |
267 | if (cd_file != NULL) { |
268 | fclose(cd_file); |
269 | cd_file = NULL; |
270 | } |
271 | if (cd_index_table != NULL) { |
272 | free(cd_index_table); |
273 | cd_index_table = NULL; |
274 | } |
275 | return 0; |
276 | } |
277 | |
278 | static long CDRshutdown(void) |
279 | { |
280 | return CDRclose(); |
281 | } |
282 | |
283 | static long CDRinit(void) |
284 | { |
33716956 |
285 | if (cdbuffer == NULL) { |
286 | cdbuffer = malloc(sizeof(*cdbuffer)); |
287 | if (cdbuffer == NULL) { |
288 | err("OOM\n"); |
289 | return -1; |
290 | } |
291 | } |
c4052f4d |
292 | #if !defined(_WIN32) && !defined(NO_DYLIB) |
bb5cf0fc |
293 | if (pBZ2_bzBuffToBuffDecompress == NULL) { |
294 | void *h = dlopen("/usr/lib/libbz2.so.1", RTLD_LAZY); |
295 | if (h == NULL) |
296 | h = dlopen("./lib/libbz2.so.1", RTLD_LAZY); |
297 | if (h != NULL) { |
298 | pBZ2_bzBuffToBuffDecompress = dlsym(h, "BZ2_bzBuffToBuffDecompress"); |
299 | if (pBZ2_bzBuffToBuffDecompress == NULL) { |
300 | err("dlsym bz2: %s", dlerror()); |
301 | dlclose(h); |
302 | } |
303 | } |
304 | } |
003cfc63 |
305 | #endif |
33716956 |
306 | return 0; |
307 | } |
308 | |
309 | static long handle_eboot(void) |
310 | { |
311 | struct { |
312 | unsigned int sig; |
313 | unsigned int dontcare[8]; |
314 | unsigned int psar_offs; |
315 | } pbp_hdr; |
316 | struct { |
317 | unsigned int offset; |
318 | unsigned int size; |
319 | unsigned int dontcare[6]; |
320 | } index_entry; |
321 | char psar_sig[9]; |
322 | unsigned int cdimg_base; |
323 | int i, ret; |
324 | FILE *f; |
325 | |
270c6dd1 |
326 | f = fopen_utf8(cd_fname, "rb"); |
33716956 |
327 | if (f == NULL) { |
328 | err("missing file: %s: ", cd_fname); |
329 | perror(NULL); |
330 | return -1; |
331 | } |
332 | |
333 | ret = fread(&pbp_hdr, 1, sizeof(pbp_hdr), f); |
334 | if (ret != sizeof(pbp_hdr)) { |
335 | err("failed to read pbp\n"); |
336 | goto fail_io; |
337 | } |
338 | |
339 | ret = fseek(f, pbp_hdr.psar_offs, SEEK_SET); |
340 | if (ret != 0) { |
341 | err("failed to seek to %x\n", pbp_hdr.psar_offs); |
342 | goto fail_io; |
343 | } |
344 | |
345 | ret = fread(psar_sig, 1, sizeof(psar_sig), f); |
346 | if (ret != sizeof(psar_sig)) { |
347 | err("failed to read psar_sig\n"); |
348 | goto fail_io; |
349 | } |
350 | |
351 | psar_sig[8] = 0; |
352 | if (strcmp(psar_sig, "PSISOIMG") != 0) { |
353 | err("bad psar_sig: %s\n", psar_sig); |
354 | goto fail_io; |
355 | } |
356 | |
357 | // seek to ISO index |
358 | ret = fseek(f, 0x4000 - sizeof(psar_sig), SEEK_CUR); |
359 | if (ret != 0) { |
360 | err("failed to seek to ISO index\n"); |
361 | goto fail_io; |
362 | } |
363 | |
364 | cd_index_len = (0x100000 - 0x4000) / sizeof(index_entry); |
365 | cd_index_table = malloc((cd_index_len + 1) * sizeof(cd_index_table[0])); |
366 | if (cd_index_table == NULL) |
367 | goto fail_io; |
368 | |
369 | cdimg_base = pbp_hdr.psar_offs + 0x100000; |
370 | for (i = 0; i < cd_index_len; i++) { |
371 | ret = fread(&index_entry, 1, sizeof(index_entry), f); |
372 | if (ret != sizeof(index_entry)) { |
373 | err("failed to read index_entry #%d\n", i); |
374 | goto fail_index; |
375 | } |
376 | |
377 | if (index_entry.size == 0) |
378 | break; |
379 | |
380 | cd_index_table[i] = cdimg_base + index_entry.offset; |
381 | } |
382 | cd_index_table[i] = cdimg_base + index_entry.offset + index_entry.size; |
383 | |
384 | cd_compression = CDRC_ZLIB2; |
385 | cd_sectors_per_blk = 16; |
386 | cd_file = f; |
387 | |
388 | printf(PFX "Loaded EBOOT CD Image: %s.\n", cd_fname); |
389 | return 0; |
390 | |
391 | fail_index: |
392 | free(cd_index_table); |
393 | cd_index_table = NULL; |
394 | fail_io: |
395 | fclose(f); |
396 | return -1; |
47bf65ab |
397 | } |
398 | |
399 | // This function is invoked by the front-end when opening an ISO |
400 | // file for playback |
401 | static long CDRopen(void) |
402 | { |
33716956 |
403 | union { |
404 | struct { |
405 | unsigned int offset; |
406 | unsigned short size; |
407 | } __attribute__((packed)) ztab_entry; |
408 | struct { |
409 | unsigned int offset; |
410 | unsigned short size; |
411 | unsigned int dontcare; |
412 | } __attribute__((packed)) znxtab_entry; |
413 | unsigned int bztab_entry; |
414 | } u; |
415 | int tabentry_size; |
47bf65ab |
416 | char table_fname[256]; |
417 | long table_size; |
418 | int i, ret; |
33716956 |
419 | char *ext; |
420 | FILE *f = NULL; |
47bf65ab |
421 | |
422 | if (cd_file != NULL) |
423 | return 0; // it's already open |
424 | |
425 | numtracks = 0; |
33716956 |
426 | current_block = -1; |
427 | current_sect_in_blk = 0; |
47bf65ab |
428 | |
429 | if (cd_fname == NULL) |
430 | return -1; |
431 | |
33716956 |
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) { |
bb5cf0fc |
452 | if (pBZ2_bzBuffToBuffDecompress == NULL) { |
453 | err("libbz2 unavailable for .bz2 handling\n"); |
454 | return -1; |
455 | } |
33716956 |
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 | |
270c6dd1 |
465 | f = fopen_utf8(table_fname, "rb"); |
47bf65ab |
466 | if (f == NULL) { |
33716956 |
467 | err("missing file: %s: ", table_fname); |
47bf65ab |
468 | perror(NULL); |
469 | return -1; |
470 | } |
471 | |
472 | ret = fseek(f, 0, SEEK_END); |
473 | if (ret != 0) { |
33716956 |
474 | err("failed to seek\n"); |
47bf65ab |
475 | goto fail_table_io; |
476 | } |
477 | table_size = ftell(f); |
478 | fseek(f, 0, SEEK_SET); |
479 | |
33716956 |
480 | if (table_size > 4 * 1024 * 1024) { |
481 | err(".table too large\n"); |
47bf65ab |
482 | goto fail_table_io; |
483 | } |
484 | |
33716956 |
485 | cd_index_len = table_size / tabentry_size; |
486 | |
47bf65ab |
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 | |
33716956 |
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; |
47bf65ab |
525 | } |
33716956 |
526 | cd_sectors_per_blk = 10; |
527 | break; |
47bf65ab |
528 | } |
47bf65ab |
529 | |
270c6dd1 |
530 | cd_file = fopen_utf8(cd_fname, "rb"); |
47bf65ab |
531 | if (cd_file == NULL) { |
33716956 |
532 | err("failed to open: %s: ", table_fname); |
47bf65ab |
533 | perror(NULL); |
534 | goto fail_img; |
535 | } |
536 | fclose(f); |
537 | |
33716956 |
538 | printf(PFX "Loaded compressed CD Image: %s.\n", cd_fname); |
47bf65ab |
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 | |