+static int opensbifile(const char *isoname) {
+ char sbiname[MAXPATHLEN], disknum[MAXPATHLEN] = "0";
+ int s;
+
+ strncpy(sbiname, isoname, sizeof(sbiname));
+ sbiname[MAXPATHLEN - 1] = '\0';
+ if (strlen(sbiname) >= 4) {
+ if (cdrIsoMultidiskCount > 1) {
+ sprintf(disknum, "_%i.sbi", cdrIsoMultidiskSelect + 1);
+ strcpy(sbiname + strlen(sbiname) - 4, disknum);
+ }
+ else
+ strcpy(sbiname + strlen(sbiname) - 4, ".sbi");
+ }
+ else {
+ return -1;
+ }
+
+ fseek(cdHandle, 0, SEEK_END);
+ s = ftell(cdHandle) / 2352;
+
+ return LoadSBI(sbiname, s);
+}
+
+#if !USE_READ_THREAD
+static void readThreadStop() {}
+static void readThreadStart() {}
+#else
+static pthread_t read_thread_id;
+
+static pthread_cond_t read_thread_msg_avail;
+static pthread_cond_t read_thread_msg_done;
+static pthread_mutex_t read_thread_msg_lock;
+
+static pthread_cond_t sectorbuffer_cond;
+static pthread_mutex_t sectorbuffer_lock;
+
+static boolean read_thread_running = FALSE;
+static int read_thread_sector_start = -1;
+static int read_thread_sector_end = -1;
+
+typedef struct {
+ int sector;
+ long ret;
+ unsigned char data[CD_FRAMESIZE_RAW];
+} SectorBufferEntry;
+
+#define SECTOR_BUFFER_SIZE 4096
+
+static SectorBufferEntry *sectorbuffer;
+static size_t sectorbuffer_index;
+
+int (*sync_cdimg_read_func)(FILE *f, unsigned int base, void *dest, int sector);
+unsigned char *(*sync_CDR_getBuffer)(void);
+
+static unsigned char * CALLBACK ISOgetBuffer_async(void);
+static int cdread_async(FILE *f, unsigned int base, void *dest, int sector);
+
+static void *readThreadMain(void *param) {
+ int max_sector = -1;
+ int requested_sector_start = -1;
+ int requested_sector_end = -1;
+ int last_read_sector = -1;
+ int index = 0;
+
+ int ra_sector = -1;
+ int max_ra = 128;
+ int initial_ra = 1;
+ int speedmult_ra = 4;
+
+ int ra_count = 0;
+ int how_far_ahead = 0;
+
+ unsigned char tmpdata[CD_FRAMESIZE_RAW];
+ long ret;
+
+ max_sector = msf2sec(ti[numtracks].start) + msf2sec(ti[numtracks].length);
+
+ while(1) {
+ pthread_mutex_lock(&read_thread_msg_lock);
+
+ // If we don't have readahead and we don't have a sector request, wait for one.
+ // If we still have readahead to go, don't block, just keep going.
+ // And if we ever have a sector request pending, acknowledge and reset it.
+
+ if (!ra_count) {
+ if (read_thread_sector_start == -1 && read_thread_running) {
+ pthread_cond_wait(&read_thread_msg_avail, &read_thread_msg_lock);
+ }
+ }
+
+ if (read_thread_sector_start != -1) {
+ requested_sector_start = read_thread_sector_start;
+ requested_sector_end = read_thread_sector_end;
+ read_thread_sector_start = -1;
+ read_thread_sector_end = -1;
+ pthread_cond_signal(&read_thread_msg_done);
+ }
+
+ pthread_mutex_unlock(&read_thread_msg_lock);
+
+ if (!read_thread_running)
+ break;
+
+ // Readahead code, based on the implementation in mednafen psx's cdromif.cpp
+ if (requested_sector_start != -1) {
+ if (last_read_sector != -1 && last_read_sector == (requested_sector_start - 1)) {
+ how_far_ahead = ra_sector - requested_sector_end;
+
+ if(how_far_ahead <= max_ra)
+ ra_count = (max_ra - how_far_ahead + 1 ? max_ra - how_far_ahead + 1 : speedmult_ra);
+ else
+ ra_count++;
+ } else if (requested_sector_end != last_read_sector) {
+ ra_sector = requested_sector_end;
+ ra_count = initial_ra;
+ }
+
+ last_read_sector = requested_sector_end;
+ }
+
+ index = ra_sector % SECTOR_BUFFER_SIZE;
+
+ // check for end of CD
+ if (ra_count && ra_sector >= max_sector) {
+ ra_count = 0;
+ pthread_mutex_lock(§orbuffer_lock);
+ sectorbuffer[index].ret = -1;
+ sectorbuffer[index].sector = ra_sector;
+ pthread_cond_signal(§orbuffer_cond);
+ pthread_mutex_unlock(§orbuffer_lock);
+ }
+
+ if (ra_count) {
+ pthread_mutex_lock(§orbuffer_lock);
+ if (sectorbuffer[index].sector != ra_sector) {
+ pthread_mutex_unlock(§orbuffer_lock);
+
+ ret = sync_cdimg_read_func(cdHandle, 0, tmpdata, ra_sector);
+
+ pthread_mutex_lock(§orbuffer_lock);
+ sectorbuffer[index].ret = ret;
+ sectorbuffer[index].sector = ra_sector;
+ memcpy(sectorbuffer[index].data, tmpdata, CD_FRAMESIZE_RAW);
+ }
+ pthread_cond_signal(§orbuffer_cond);
+ pthread_mutex_unlock(§orbuffer_lock);
+
+ ra_sector++;
+ ra_count--;
+ }
+ }
+
+ return NULL;
+}
+
+static void readThreadStop() {
+ if (read_thread_running == TRUE) {
+ read_thread_running = FALSE;
+ pthread_cond_signal(&read_thread_msg_avail);
+ pthread_join(read_thread_id, NULL);
+ }
+
+ pthread_cond_destroy(&read_thread_msg_done);
+ pthread_cond_destroy(&read_thread_msg_avail);
+ pthread_mutex_destroy(&read_thread_msg_lock);
+
+ pthread_cond_destroy(§orbuffer_cond);
+ pthread_mutex_destroy(§orbuffer_lock);
+
+ CDR_getBuffer = sync_CDR_getBuffer;
+ cdimg_read_func = sync_cdimg_read_func;
+
+ free(sectorbuffer);
+ sectorbuffer = NULL;
+}
+
+static void readThreadStart() {
+ SysPrintf("Starting async CD thread\n");
+
+ if (read_thread_running == TRUE)
+ return;
+
+ read_thread_running = TRUE;
+ read_thread_sector_start = -1;
+ read_thread_sector_end = -1;
+ sectorbuffer_index = 0;
+
+ sectorbuffer = calloc(SECTOR_BUFFER_SIZE, sizeof(SectorBufferEntry));
+ if(!sectorbuffer)
+ goto error;
+
+ sectorbuffer[0].sector = -1; // Otherwise we might think we've already fetched sector 0!
+
+ sync_CDR_getBuffer = CDR_getBuffer;
+ CDR_getBuffer = ISOgetBuffer_async;
+ sync_cdimg_read_func = cdimg_read_func;
+ cdimg_read_func = cdread_async;
+
+ if (pthread_cond_init(&read_thread_msg_avail, NULL) ||
+ pthread_cond_init(&read_thread_msg_done, NULL) ||
+ pthread_mutex_init(&read_thread_msg_lock, NULL) ||
+ pthread_cond_init(§orbuffer_cond, NULL) ||
+ pthread_mutex_init(§orbuffer_lock, NULL) ||
+ pthread_create(&read_thread_id, NULL, readThreadMain, NULL))
+ goto error;
+
+ return;
+
+ error:
+ SysPrintf("Error starting async CD thread\n");
+ SysPrintf("Falling back to sync\n");
+
+ readThreadStop();
+}
+#endif
+
+static int cdread_normal(FILE *f, unsigned int base, void *dest, int sector)
+{
+ int ret;
+ if (fseek(f, base + sector * CD_FRAMESIZE_RAW, SEEK_SET))
+ goto fail_io;
+ ret = fread(dest, 1, CD_FRAMESIZE_RAW, f);
+ if (ret <= 0)
+ goto fail_io;
+ return ret;
+
+fail_io:
+ // often happens in cdda gaps of a split cue/bin, so not logged
+ //SysPrintf("File IO error %d, base %u, sector %u\n", errno, base, sector);
+ return -1;
+}
+
+static int cdread_sub_mixed(FILE *f, unsigned int base, void *dest, int sector)
+{
+ int ret;
+
+ if (fseek(f, base + sector * (CD_FRAMESIZE_RAW + SUB_FRAMESIZE), SEEK_SET))
+ goto fail_io;
+ ret = fread(dest, 1, CD_FRAMESIZE_RAW, f);
+ if (ret <= 0)
+ goto fail_io;
+ return ret;
+
+fail_io:
+ //SysPrintf("File IO error %d, base %u, sector %u\n", errno, base, sector);
+ return -1;
+}
+
+static int cdread_sub_sub_mixed(FILE *f, int sector)
+{
+ if (fseek(f, sector * (CD_FRAMESIZE_RAW + SUB_FRAMESIZE) + CD_FRAMESIZE_RAW, SEEK_SET))
+ goto fail_io;
+ if (fread(subbuffer, 1, SUB_FRAMESIZE, f) != SUB_FRAMESIZE)
+ goto fail_io;
+
+ return SUB_FRAMESIZE;
+
+fail_io:
+ SysPrintf("subchannel: file IO error %d, sector %u\n", errno, sector);
+ return -1;
+}
+
+static int uncompress2_pcsx(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;
+}
+
+static int cdread_compressed(FILE *f, unsigned int base, void *dest, int sector)
+{
+ unsigned long cdbuffer_size, cdbuffer_size_expect;
+ unsigned int size;
+ int is_compressed;
+ off_t start_byte;
+ int ret, block;
+
+ if (base)
+ sector += base / 2352;
+
+ block = sector >> compr_img->block_shift;
+ compr_img->sector_in_blk = sector & ((1 << compr_img->block_shift) - 1);
+
+ if (block == compr_img->current_block) {
+ //printf("hit sect %d\n", sector);
+ goto finish;
+ }
+
+ if (sector >= compr_img->index_len * 16) {
+ SysPrintf("sector %d is past img end\n", sector);
+ return -1;
+ }
+
+ start_byte = compr_img->index_table[block] & ~OFF_T_MSB;
+ if (fseeko(cdHandle, start_byte, SEEK_SET) != 0) {
+ SysPrintf("seek error for block %d at %llx: ",
+ block, (long long)start_byte);
+ perror(NULL);
+ return -1;
+ }
+
+ is_compressed = !(compr_img->index_table[block] & OFF_T_MSB);
+ size = (compr_img->index_table[block + 1] & ~OFF_T_MSB) - start_byte;
+ if (size > sizeof(compr_img->buff_compressed)) {
+ SysPrintf("block %d is too large: %u\n", block, size);
+ return -1;
+ }
+
+ if (fread(is_compressed ? compr_img->buff_compressed : compr_img->buff_raw[0],
+ 1, size, cdHandle) != size) {
+ SysPrintf("read error for block %d at %x: ", block, start_byte);
+ perror(NULL);
+ return -1;
+ }
+
+ if (is_compressed) {
+ cdbuffer_size_expect = sizeof(compr_img->buff_raw[0]) << compr_img->block_shift;
+ cdbuffer_size = cdbuffer_size_expect;
+ ret = uncompress2_pcsx(compr_img->buff_raw[0], &cdbuffer_size, compr_img->buff_compressed, size);
+ if (ret != 0) {
+ SysPrintf("uncompress failed with %d for block %d, sector %d\n",
+ ret, block, sector);
+ return -1;
+ }
+ if (cdbuffer_size != cdbuffer_size_expect)
+ SysPrintf("cdbuffer_size: %lu != %lu, sector %d\n", cdbuffer_size,
+ cdbuffer_size_expect, sector);
+ }
+
+ // done at last!
+ compr_img->current_block = block;
+
+finish:
+ if (dest != cdbuffer) // copy avoid HACK
+ memcpy(dest, compr_img->buff_raw[compr_img->sector_in_blk],
+ CD_FRAMESIZE_RAW);
+ return CD_FRAMESIZE_RAW;
+}
+
+#ifdef HAVE_CHD
+static unsigned char *chd_get_sector(unsigned int current_buffer, unsigned int sector_in_hunk)
+{
+ return chd_img->buffer
+ + current_buffer * chd_img->header->hunkbytes
+ + sector_in_hunk * (CD_FRAMESIZE_RAW + SUB_FRAMESIZE);
+}
+
+static int cdread_chd(FILE *f, unsigned int base, void *dest, int sector)
+{
+ int hunk;
+
+ sector += base;
+
+ hunk = sector / chd_img->sectors_per_hunk;
+ chd_img->sector_in_hunk = sector % chd_img->sectors_per_hunk;
+
+ if (hunk == chd_img->current_hunk[0])
+ chd_img->current_buffer = 0;
+ else if (hunk == chd_img->current_hunk[1])
+ chd_img->current_buffer = 1;
+ else
+ {
+ chd_read(chd_img->chd, hunk, chd_img->buffer +
+ chd_img->current_buffer * chd_img->header->hunkbytes);
+ chd_img->current_hunk[chd_img->current_buffer] = hunk;
+ }
+
+ if (dest != cdbuffer) // copy avoid HACK
+ memcpy(dest, chd_get_sector(chd_img->current_buffer, chd_img->sector_in_hunk),
+ CD_FRAMESIZE_RAW);
+ return CD_FRAMESIZE_RAW;
+}
+
+static int cdread_sub_chd(FILE *f, int sector)
+{
+ unsigned int sector_in_hunk;
+ unsigned int buffer;
+ int hunk;
+
+ if (!subChanMixed)
+ return -1;
+
+ hunk = sector / chd_img->sectors_per_hunk;
+ sector_in_hunk = sector % chd_img->sectors_per_hunk;
+
+ if (hunk == chd_img->current_hunk[0])
+ buffer = 0;
+ else if (hunk == chd_img->current_hunk[1])
+ buffer = 1;
+ else
+ {
+ buffer = chd_img->current_buffer ^ 1;
+ chd_read(chd_img->chd, hunk, chd_img->buffer +
+ buffer * chd_img->header->hunkbytes);
+ chd_img->current_hunk[buffer] = hunk;
+ }
+
+ memcpy(subbuffer, chd_get_sector(buffer, sector_in_hunk) + CD_FRAMESIZE_RAW, SUB_FRAMESIZE);
+ return SUB_FRAMESIZE;
+}
+#endif
+
+static int cdread_2048(FILE *f, unsigned int base, void *dest, int sector)
+{
+ int ret;
+
+ fseek(f, base + sector * 2048, SEEK_SET);
+ ret = fread((char *)dest + 12 * 2, 1, 2048, f);
+
+ // not really necessary, fake mode 2 header
+ memset(cdbuffer, 0, 12 * 2);
+ sec2msf(sector + 2 * 75, (char *)&cdbuffer[12]);
+ cdbuffer[12 + 3] = 1;
+
+ return 12*2 + ret;
+}
+
+#if USE_READ_THREAD
+
+static int cdread_async(FILE *f, unsigned int base, void *dest, int sector) {
+ boolean found = FALSE;
+ int i = sector % SECTOR_BUFFER_SIZE;
+ long ret;
+
+ if (f != cdHandle || base != 0 || dest != cdbuffer) {
+ // Async reads are only supported for cdbuffer, so call the sync
+ // function directly.
+ return sync_cdimg_read_func(f, base, dest, sector);
+ }
+
+ pthread_mutex_lock(&read_thread_msg_lock);
+
+ // Only wait if we're not trying to read the next sector and
+ // sector_start is set (meaning the last request hasn't been
+ // processed yet)
+ while(read_thread_sector_start != -1 && read_thread_sector_end + 1 != sector) {
+ pthread_cond_wait(&read_thread_msg_done, &read_thread_msg_lock);
+ }
+
+ if (read_thread_sector_start == -1)
+ read_thread_sector_start = sector;
+
+ read_thread_sector_end = sector;
+ pthread_cond_signal(&read_thread_msg_avail);
+ pthread_mutex_unlock(&read_thread_msg_lock);
+
+ do {
+ pthread_mutex_lock(§orbuffer_lock);
+ if (sectorbuffer[i].sector == sector) {
+ sectorbuffer_index = i;
+ ret = sectorbuffer[i].ret;
+ found = TRUE;
+ }
+
+ if (!found) {
+ pthread_cond_wait(§orbuffer_cond, §orbuffer_lock);
+ }
+ pthread_mutex_unlock(§orbuffer_lock);
+ } while (!found);
+
+ return ret;
+}
+
+#endif
+
+static unsigned char * CALLBACK ISOgetBuffer_compr(void) {
+ return compr_img->buff_raw[compr_img->sector_in_blk] + 12;
+}
+
+#ifdef HAVE_CHD
+static unsigned char * CALLBACK ISOgetBuffer_chd(void) {
+ return chd_get_sector(chd_img->current_buffer, chd_img->sector_in_hunk) + 12;
+}
+#endif
+
+#if USE_READ_THREAD
+static unsigned char * CALLBACK ISOgetBuffer_async(void) {
+ unsigned char *buffer;
+ pthread_mutex_lock(§orbuffer_lock);
+ buffer = sectorbuffer[sectorbuffer_index].data;
+ pthread_mutex_unlock(§orbuffer_lock);
+ return buffer + 12;
+}
+#endif
+
+static unsigned char * CALLBACK ISOgetBuffer(void) {
+ return cdbuffer + 12;
+}
+