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