#include <string.h>
#include <stdlib.h>
#include <zlib.h>
+#include <bzlib.h>
#include "cdrcimg.h"
+#define PFX "cdrcimg: "
+#define err(f, ...) fprintf(stderr, PFX f, ##__VA_ARGS__)
+
#define CD_FRAMESIZE_RAW 2352
+enum {
+ CDRC_ZLIB,
+ CDRC_ZLIB2,
+ CDRC_BZ,
+};
+
static const char *cd_fname;
static unsigned int *cd_index_table;
static unsigned int cd_index_len;
+static unsigned int cd_sectors_per_blk;
+static int cd_compression;
static FILE *cd_file;
-static unsigned char cdbuffer[CD_FRAMESIZE_RAW];
-static unsigned char cdbuffer_compressed[CD_FRAMESIZE_RAW + 100];
-static int current_sector;
+static struct {
+ unsigned char raw[16][CD_FRAMESIZE_RAW];
+ unsigned char compressed[CD_FRAMESIZE_RAW * 16 + 100];
+} *cdbuffer;
+static int current_block, current_sect_in_blk;
struct CdrStat;
extern long CDR__getStatus(struct CdrStat *stat);
return 0;
}
+int uncompress2(void *out, unsigned long *out_size, void *in, unsigned long in_size)
+{
+ static z_stream z;
+ int ret = 0;
+
+ if (z.zalloc == NULL) {
+ // XXX: one-time leak here..
+ z.next_in = Z_NULL;
+ z.avail_in = 0;
+ z.zalloc = Z_NULL;
+ z.zfree = Z_NULL;
+ z.opaque = Z_NULL;
+ ret = inflateInit2(&z, -15);
+ }
+ else
+ ret = inflateReset(&z);
+ if (ret != Z_OK)
+ return ret;
+
+ z.next_in = in;
+ z.avail_in = in_size;
+ z.next_out = out;
+ z.avail_out = *out_size;
+
+ ret = inflate(&z, Z_NO_FLUSH);
+ //inflateEnd(&z);
+
+ *out_size -= z.avail_out;
+ return ret == 1 ? 0 : ret;
+}
+
// read track
// time: byte 0 - minute; byte 1 - second; byte 2 - frame
// uses bcd format
{
unsigned int start_byte, size;
unsigned long cdbuffer_size;
- int ret, sector;
+ int ret, sector, block;
if (cd_file == NULL)
return -1;
sector = MSF2SECT(btoi(time[0]), btoi(time[1]), btoi(time[2]));
- if (sector == current_sector) {
+
+ // avoid division if possible
+ switch (cd_sectors_per_blk) {
+ case 1:
+ block = sector;
+ current_sect_in_blk = 0;
+ break;
+ case 10:
+ block = sector / 10;
+ current_sect_in_blk = sector % 10;
+ break;
+ case 16:
+ block = sector >> 4;
+ current_sect_in_blk = sector & 15;
+ break;
+ default:
+ err("unhandled cd_sectors_per_blk: %d\n", cd_sectors_per_blk);
+ return -1;
+ }
+
+ if (block == current_block) {
// it's already there, nothing to do
//printf("hit sect %d\n", sector);
return 0;
}
- if (sector >= cd_index_len) {
- fprintf(stderr, "sector %d is past track end\n", sector);
+ if (sector >= cd_index_len * cd_sectors_per_blk) {
+ err("sector %d is past track end\n", sector);
return -1;
}
- start_byte = cd_index_table[sector];
+ start_byte = cd_index_table[block];
if (fseek(cd_file, start_byte, SEEK_SET) != 0) {
- fprintf(stderr, "seek error for sector %d at %x: ",
- sector, start_byte);
+ err("seek error for block %d at %x: ",
+ block, start_byte);
perror(NULL);
return -1;
}
- size = cd_index_table[sector + 1] - start_byte;
- if (size > sizeof(cdbuffer_compressed)) {
- fprintf(stderr, "sector %d is too large: %u\n", sector, size);
+ size = cd_index_table[block + 1] - start_byte;
+ if (size > sizeof(cdbuffer->compressed)) {
+ err("block %d is too large: %u\n", block, size);
return -1;
}
- if (fread(cdbuffer_compressed, 1, size, cd_file) != size) {
- fprintf(stderr, "read error for sector %d at %x: ",
- sector, start_byte);
+ if (fread(cdbuffer->compressed, 1, size, cd_file) != size) {
+ err("read error for block %d at %x: ", block, start_byte);
perror(NULL);
return -1;
}
- cdbuffer_size = sizeof(cdbuffer);
- ret = uncompress(cdbuffer, &cdbuffer_size, cdbuffer_compressed, size);
+ cdbuffer_size = sizeof(cdbuffer->raw[0]) * cd_sectors_per_blk;
+ switch (cd_compression) {
+ case CDRC_ZLIB:
+ ret = uncompress(cdbuffer->raw[0], &cdbuffer_size, cdbuffer->compressed, size);
+ break;
+ case CDRC_ZLIB2:
+ ret = uncompress2(cdbuffer->raw[0], &cdbuffer_size, cdbuffer->compressed, size);
+ break;
+ case CDRC_BZ:
+ ret = BZ2_bzBuffToBuffDecompress((char *)cdbuffer->raw, (unsigned int *)&cdbuffer_size,
+ (char *)cdbuffer->compressed, size, 0, 0);
+ break;
+ default:
+ err("bad cd_compression: %d\n", cd_compression);
+ return -1;
+ }
+
if (ret != 0) {
- fprintf(stderr, "uncompress failed with %d for sector %d\n", ret, sector);
+ err("uncompress failed with %d for block %d, sector %d\n",
+ ret, block, sector);
return -1;
}
- if (cdbuffer_size != sizeof(cdbuffer))
- printf("%lu != %d\n", cdbuffer_size, sizeof(cdbuffer));
+ if (cdbuffer_size != sizeof(cdbuffer->raw[0]) * cd_sectors_per_blk)
+ err("cdbuffer_size: %lu != %d, sector %d\n", cdbuffer_size,
+ sizeof(cdbuffer->raw[0]) * cd_sectors_per_blk, sector);
// done at last!
- current_sector = sector;
+ current_block = block;
return 0;
}
// return read track
static unsigned char *CDRgetBuffer(void)
{
- return cdbuffer + 12;
+ return cdbuffer->raw[current_sect_in_blk] + 12;
}
// plays cdda audio
static long CDRinit(void)
{
- return 0; // do nothing
+ if (cdbuffer == NULL) {
+ cdbuffer = malloc(sizeof(*cdbuffer));
+ if (cdbuffer == NULL) {
+ err("OOM\n");
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static long handle_eboot(void)
+{
+ struct {
+ unsigned int sig;
+ unsigned int dontcare[8];
+ unsigned int psar_offs;
+ } pbp_hdr;
+ struct {
+ unsigned int offset;
+ unsigned int size;
+ unsigned int dontcare[6];
+ } index_entry;
+ char psar_sig[9];
+ unsigned int cdimg_base;
+ int i, ret;
+ FILE *f;
+
+ f = fopen(cd_fname, "rb");
+ if (f == NULL) {
+ err("missing file: %s: ", cd_fname);
+ perror(NULL);
+ return -1;
+ }
+
+ ret = fread(&pbp_hdr, 1, sizeof(pbp_hdr), f);
+ if (ret != sizeof(pbp_hdr)) {
+ err("failed to read pbp\n");
+ goto fail_io;
+ }
+
+ ret = fseek(f, pbp_hdr.psar_offs, SEEK_SET);
+ if (ret != 0) {
+ err("failed to seek to %x\n", pbp_hdr.psar_offs);
+ goto fail_io;
+ }
+
+ ret = fread(psar_sig, 1, sizeof(psar_sig), f);
+ if (ret != sizeof(psar_sig)) {
+ err("failed to read psar_sig\n");
+ goto fail_io;
+ }
+
+ psar_sig[8] = 0;
+ if (strcmp(psar_sig, "PSISOIMG") != 0) {
+ err("bad psar_sig: %s\n", psar_sig);
+ goto fail_io;
+ }
+
+ // seek to ISO index
+ ret = fseek(f, 0x4000 - sizeof(psar_sig), SEEK_CUR);
+ if (ret != 0) {
+ err("failed to seek to ISO index\n");
+ goto fail_io;
+ }
+
+ cd_index_len = (0x100000 - 0x4000) / sizeof(index_entry);
+ cd_index_table = malloc((cd_index_len + 1) * sizeof(cd_index_table[0]));
+ if (cd_index_table == NULL)
+ goto fail_io;
+
+ cdimg_base = pbp_hdr.psar_offs + 0x100000;
+ for (i = 0; i < cd_index_len; i++) {
+ ret = fread(&index_entry, 1, sizeof(index_entry), f);
+ if (ret != sizeof(index_entry)) {
+ err("failed to read index_entry #%d\n", i);
+ goto fail_index;
+ }
+
+ if (index_entry.size == 0)
+ break;
+
+ cd_index_table[i] = cdimg_base + index_entry.offset;
+ }
+ cd_index_table[i] = cdimg_base + index_entry.offset + index_entry.size;
+
+ cd_compression = CDRC_ZLIB2;
+ cd_sectors_per_blk = 16;
+ cd_file = f;
+
+ printf(PFX "Loaded EBOOT CD Image: %s.\n", cd_fname);
+ return 0;
+
+fail_index:
+ free(cd_index_table);
+ cd_index_table = NULL;
+fail_io:
+ fclose(f);
+ return -1;
}
// This function is invoked by the front-end when opening an ISO
// file for playback
static long CDRopen(void)
{
- // a Z.table file is binary where each element represents
- // one compressed frame.
- // 4 bytes: the offset of the frame in the .Z file
- // 2 bytes: the length of the compressed frame
- struct {
- unsigned int offset;
- unsigned short size;
- } __attribute__((packed)) ztab_entry;
+ union {
+ struct {
+ unsigned int offset;
+ unsigned short size;
+ } __attribute__((packed)) ztab_entry;
+ struct {
+ unsigned int offset;
+ unsigned short size;
+ unsigned int dontcare;
+ } __attribute__((packed)) znxtab_entry;
+ unsigned int bztab_entry;
+ } u;
+ int tabentry_size;
char table_fname[256];
long table_size;
int i, ret;
- FILE *f;
+ char *ext;
+ FILE *f = NULL;
if (cd_file != NULL)
return 0; // it's already open
numtracks = 0;
+ current_block = -1;
+ current_sect_in_blk = 0;
if (cd_fname == NULL)
return -1;
- snprintf(table_fname, sizeof(table_fname), "%s.table", cd_fname);
+ ext = strrchr(cd_fname, '.');
+ if (ext == NULL)
+ return -1;
+
+ if (strcasecmp(ext, ".pbp") == 0) {
+ return handle_eboot();
+ }
+
+ // pocketiso stuff
+ else if (strcasecmp(ext, ".z") == 0) {
+ cd_compression = CDRC_ZLIB;
+ tabentry_size = sizeof(u.ztab_entry);
+ snprintf(table_fname, sizeof(table_fname), "%s.table", cd_fname);
+ }
+ else if (strcasecmp(ext, ".znx") == 0) {
+ cd_compression = CDRC_ZLIB;
+ tabentry_size = sizeof(u.znxtab_entry);
+ snprintf(table_fname, sizeof(table_fname), "%s.table", cd_fname);
+ }
+ else if (strcasecmp(ext, ".bz") == 0) {
+ cd_compression = CDRC_BZ;
+ tabentry_size = sizeof(u.bztab_entry);
+ snprintf(table_fname, sizeof(table_fname), "%s.index", cd_fname);
+ }
+ else {
+ err("unhandled extension: %s\n", ext);
+ return -1;
+ }
+
f = fopen(table_fname, "rb");
if (f == NULL) {
- fprintf(stderr, "missing file: %s: ", table_fname);
+ err("missing file: %s: ", table_fname);
perror(NULL);
return -1;
}
ret = fseek(f, 0, SEEK_END);
if (ret != 0) {
- fprintf(stderr, "failed to seek\n");
+ err("failed to seek\n");
goto fail_table_io;
}
table_size = ftell(f);
fseek(f, 0, SEEK_SET);
- if (table_size > 2 * 1024 * 1024) {
- fprintf(stderr, ".table too large\n");
+ if (table_size > 4 * 1024 * 1024) {
+ err(".table too large\n");
goto fail_table_io;
}
- cd_index_len = table_size / 6;
+ cd_index_len = table_size / tabentry_size;
+
cd_index_table = malloc((cd_index_len + 1) * sizeof(cd_index_table[0]));
if (cd_index_table == NULL)
goto fail_table_io;
- for (i = 0; i < cd_index_len; i++) {
- ret = fread(&ztab_entry, 1, sizeof(ztab_entry), f);
- if (ret != sizeof(ztab_entry)) {
- fprintf(stderr, ".table read failed on entry %d/%d\n", i, cd_index_len);
- goto fail_table_io_read;
+ switch (cd_compression) {
+ case CDRC_ZLIB:
+ // a Z.table file is binary where each element represents
+ // one compressed frame.
+ // 4 bytes: the offset of the frame in the .Z file
+ // 2 bytes: the length of the compressed frame
+ // .znx.table has 4 additional bytes (xa header??)
+ u.znxtab_entry.dontcare = 0;
+ for (i = 0; i < cd_index_len; i++) {
+ ret = fread(&u, 1, tabentry_size, f);
+ if (ret != tabentry_size) {
+ err(".table read failed on entry %d/%d\n", i, cd_index_len);
+ goto fail_table_io_read;
+ }
+ cd_index_table[i] = u.ztab_entry.offset;
+ //if (u.znxtab_entry.dontcare != 0)
+ // printf("znx %08x!\n", u.znxtab_entry.dontcare);
+ }
+ // fake entry, so that we know last compressed block size
+ cd_index_table[i] = u.ztab_entry.offset + u.ztab_entry.size;
+ cd_sectors_per_blk = 1;
+ break;
+ case CDRC_BZ:
+ // the .BZ.table file is arranged so that one entry represents
+ // 10 compressed frames. Each element is a 4 byte unsigned integer
+ // representing the offset in the .BZ file. Last entry is the size
+ // of the compressed file.
+ for (i = 0; i < cd_index_len; i++) {
+ ret = fread(&u.bztab_entry, 1, sizeof(u.bztab_entry), f);
+ if (ret != sizeof(u.bztab_entry)) {
+ err(".table read failed on entry %d/%d\n", i, cd_index_len);
+ goto fail_table_io_read;
+ }
+ cd_index_table[i] = u.bztab_entry;
}
- cd_index_table[i] = ztab_entry.offset;
+ cd_sectors_per_blk = 10;
+ break;
}
- // fake entry, so that we know last compressed block size
- cd_index_table[i] = ztab_entry.offset + ztab_entry.size;
cd_file = fopen(cd_fname, "rb");
if (cd_file == NULL) {
- fprintf(stderr, "faied to open: %s: ", table_fname);
+ err("failed to open: %s: ", table_fname);
perror(NULL);
goto fail_img;
}
fclose(f);
- printf("Loaded compressed CD Image: %s.\n", cd_fname);
- current_sector = -1;
+ printf(PFX "Loaded compressed CD Image: %s.\n", cd_fname);
return 0;