spu: fix saving bug
[pcsx_rearmed.git] / plugins / cdrcimg / cdrcimg.c
index 608fdc4..e04ec93 100644 (file)
 #include <string.h>
 #include <stdlib.h>
 #include <zlib.h>
+#include <bzlib.h>
+#include <dlfcn.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 int (*pBZ2_bzBuffToBuffDecompress)(char *dest, unsigned int *destLen, char *source,
+               unsigned int sourceLen, int small, int verbosity);
+
+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);
 
 struct CdrStat
 {
-       unsigned long Type;
-       unsigned long Status;
+       unsigned int Type;
+       unsigned int Status;
        unsigned char Time[3]; // current playing time
 };
 
@@ -75,6 +93,37 @@ static long CDRgetTD(unsigned char track, unsigned char *buffer)
        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
@@ -82,62 +131,98 @@ static long CDRreadTrack(unsigned char *time)
 {
        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 = pBZ2_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,
+                       (int)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
@@ -188,81 +273,258 @@ static long CDRshutdown(void)
 
 static long CDRinit(void)
 {
-       return 0; // do nothing
+       if (cdbuffer == NULL) {
+               cdbuffer = malloc(sizeof(*cdbuffer));
+               if (cdbuffer == NULL) {
+                       err("OOM\n");
+                       return -1;
+               }
+       }
+       if (pBZ2_bzBuffToBuffDecompress == NULL) {
+               void *h = dlopen("/usr/lib/libbz2.so.1", RTLD_LAZY);
+               if (h == NULL)
+                       h = dlopen("./lib/libbz2.so.1", RTLD_LAZY);
+               if (h != NULL) {
+                       pBZ2_bzBuffToBuffDecompress = dlsym(h, "BZ2_bzBuffToBuffDecompress");
+                       if (pBZ2_bzBuffToBuffDecompress == NULL) {
+                               err("dlsym bz2: %s", dlerror());
+                               dlclose(h);
+                       }
+               }
+       }
+       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) {
+               if (pBZ2_bzBuffToBuffDecompress == NULL) {
+                       err("libbz2 unavailable for .bz2 handling\n");
+                       return -1;
+               }
+               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;