diff --git a/Makefile.common b/Makefile.common index a678148426..8be4846b9e 100644 --- a/Makefile.common +++ b/Makefile.common @@ -177,6 +177,7 @@ OBJ += frontend/frontend_driver.o \ $(LIBRETRO_COMM_DIR)/streams/interface_stream.o \ $(LIBRETRO_COMM_DIR)/streams/memory_stream.o \ $(LIBRETRO_COMM_DIR)/vfs/vfs_implementation.o \ + $(LIBRETRO_COMM_DIR)/vfs/vfs_implementation_cdrom.o \ $(LIBRETRO_COMM_DIR)/media/media_detect_cd.o \ $(LIBRETRO_COMM_DIR)/lists/string_list.o \ $(LIBRETRO_COMM_DIR)/string/stdstring.o \ @@ -1668,8 +1669,7 @@ ifeq ($(HAVE_CDROM), 1) endif DEFINES += -DHAVE_CDROM - OBJ += $(LIBRETRO_COMM_DIR)/cdrom/cdrom.o \ - $(LIBRETRO_COMM_DIR)/vfs/vfs_implementation_cdrom.o + OBJ += $(LIBRETRO_COMM_DIR)/cdrom/cdrom.o endif ifeq ($(HAVE_RTGA), 1) diff --git a/cheevos-new/cheevos.c b/cheevos-new/cheevos.c index a2f2c17719..9b84eecfc2 100644 --- a/cheevos-new/cheevos.c +++ b/cheevos-new/cheevos.c @@ -1118,6 +1118,8 @@ typedef struct const char *path; const char *ext; intfstream_t *stream; + intfstream_t *cdrom_stream; + intfstream_t *cdrom_track_stream; rcheevos_cheevo_t *cheevo; settings_t *settings; struct http_connection_t *conn; @@ -1146,7 +1148,8 @@ enum RCHEEVOS_HTTP_GET = -13, RCHEEVOS_DEACTIVATE = -14, RCHEEVOS_PLAYING = -15, - RCHEEVOS_DELAY = -16 + RCHEEVOS_DELAY = -16, + RCHEEVOS_PSX_MD5 = -17 }; static int rcheevos_iterate(rcheevos_coro_t* coro) @@ -1155,8 +1158,14 @@ static int rcheevos_iterate(rcheevos_coro_t* coro) const int lynx_header_len = 0x40; ssize_t num_read = 0; size_t to_read = 4096; - uint8_t *buffer = NULL; + uint8_t *ptr = NULL; const char *end = NULL; + const char *ext = NULL; + size_t exe_name_size = 0; + char exe_name_buffer[32]; + char *exe_name = NULL; + char *scan = NULL; + char buffer[2048]; static const uint32_t genesis_exts[] = { @@ -1192,12 +1201,24 @@ static int rcheevos_iterate(rcheevos_coro_t* coro) 0 }; + static const uint32_t psx_exts[] = + { + 0x0b886782U, /* cue */ + 0x0b88899aU, /* m3u */ + /*0x0b88af0bU,* toc */ + /*0x0b88652fU,* ccd */ + /*0x0b889c67U,* pbp */ + 0x0b8865d4U, /* chd */ + 0 + }; + static rcheevos_finder_t finders[] = { {RCHEEVOS_SNES_MD5, "SNES (discards header)", snes_exts}, {RCHEEVOS_GENESIS_MD5, "Genesis (6Mb padding)", genesis_exts}, - {RCHEEVOS_LYNX_MD5, "Atari Lynx (discards header)", lynx_exts}, + {RCHEEVOS_LYNX_MD5, "Atari Lynx (discards header)", lynx_exts}, {RCHEEVOS_NES_MD5, "NES (discards header)", NULL}, + {RCHEEVOS_PSX_MD5, "Playstation (main executable)", psx_exts}, {RCHEEVOS_GENERIC_MD5, "Generic (plain content)", NULL}, {RCHEEVOS_FILENAME_MD5, "Generic (filename)", NULL} }; @@ -1243,14 +1264,13 @@ static int rcheevos_iterate(rcheevos_coro_t* coro) for (;;) { - buffer = (uint8_t*)coro->data + coro->len; + ptr = (uint8_t*)coro->data + coro->len; to_read = 4096; if (to_read > coro->count) to_read = coro->count; - num_read = intfstream_read(coro->stream, - (void*)buffer, to_read); + num_read = intfstream_read(coro->stream, (void*)ptr, to_read); if (num_read <= 0) break; @@ -1545,6 +1565,186 @@ found: MD5_Final(coro->hash, &coro->md5); CORO_GOTO(RCHEEVOS_GET_GAMEID); + /************************************************************************** + * Info Tries to identify a Playstation game + * Input CHEEVOS_VAR_INFO the content info + * Output CHEEVOS_VAR_GAMEID the Retro Achievements game ID, or 0 if not found + *************************************************************************/ + CORO_SUB(RCHEEVOS_PSX_MD5) + { + ext = path_get_extension(coro->path); + + MD5_Init(&coro->md5); + + /* if we're looking at an m3u file, get the first disc from the playlist */ + if (string_is_equal_noncase(ext, "m3u")) + { + coro->cdrom_stream = intfstream_open_file(coro->path, RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE); + if (coro->cdrom_stream) + { + char disc_path[PATH_MAX_LENGTH]; + char* tmp; + + intfstream_read(coro->cdrom_stream, buffer, sizeof(buffer)); + intfstream_close(coro->cdrom_stream); + CHEEVOS_FREE(coro->cdrom_stream); + coro->cdrom_stream = NULL; + + tmp = buffer; + while (*tmp && *tmp != '\n') + ++tmp; + if (tmp > buffer && tmp[-1] == '\r') + --tmp; + *tmp = '\0'; + + fill_pathname_basedir(disc_path, coro->path, sizeof(disc_path)); + strlcat(disc_path, buffer, sizeof(disc_path)); + + free((void*)coro->path); + coro->path = strdup(disc_path); + ext = path_get_extension(coro->path); + } + } + + if (string_is_equal_noncase(ext, "chd")) + { + coro->cdrom_track_stream = intfstream_open_chd_track(coro->path, RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE, 1); + } + else + { + coro->cdrom_stream = intfstream_open_file(coro->path, RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE); + if (!coro->cdrom_stream) + CORO_STOP(); + + coro->cdrom_track_stream = intfstream_open_file_child(coro->cdrom_stream, "TRACK 01"); + } + + if (coro->cdrom_track_stream) + { + coro->stream = intfstream_open_file_child(coro->cdrom_track_stream, "SYSTEM.CNF"); + if (coro->stream) + { + intfstream_read(coro->stream, buffer, sizeof(buffer)); + intfstream_close(coro->stream); + CHEEVOS_FREE(coro->stream); + coro->stream = NULL; + + for (scan = buffer; scan < &buffer[sizeof(buffer)] && *scan; ++scan) + { + if (strncmp(scan, "BOOT", 4) == 0) + { + exe_name = scan + 4; + while (isspace(*exe_name)) + ++exe_name; + if (*exe_name == '=') + { + ++exe_name; + while (isspace(*exe_name)) + ++exe_name; + + if (strncmp(exe_name, "cdrom:", 6) == 0) + exe_name += 6; + if (*exe_name == '\\') + ++exe_name; + break; + } + } + + while (*scan && *scan != '\n') + ++scan; + } + + if (exe_name) + { + scan = exe_name; + while (*scan != '\n' && *scan != ';' && *scan != ' ') + ++scan; + *scan = '\0'; + + exe_name_size = scan - exe_name; + if (exe_name_size < sizeof(exe_name_buffer)) + { + strcpy(exe_name_buffer, exe_name); + coro->stream = intfstream_open_file_child(coro->cdrom_track_stream, exe_name); + } + + if (coro->stream) + { + intfstream_read(coro->stream, buffer, sizeof(buffer)); + + /* the PSX-E header specifies the executable size as a 4-byte value 28 bytes into the header, which doesn't + * include the header itself. We want to include the header in the hash, so append another 2048 to that value. + * ASSERT: this results in the same value as coro->stream->file.fp->size */ + coro->count = 2048 + (((uint8_t)buffer[28 + 3] << 24) | ((uint8_t)buffer[28 + 2] << 16) | + ((uint8_t)buffer[28 + 1] << 8) | (uint8_t)buffer[28]); + + if (coro->count < CHEEVOS_MB(16)) /* sanity check */ + { + /* there's also a few games that are use a singular engine and only differ via their data files. + * luckily, they have unique serial numbers, and use the serial number as the boot file in the + * standard way. include the boot executable name in the hash */ + coro->count += exe_name_size; + + free(coro->data); + coro->data = (uint8_t*)malloc(coro->count); + memcpy(coro->data, exe_name_buffer, exe_name_size); + coro->len = exe_name_size; + + do + { + to_read = coro->count - coro->len; + if (to_read > sizeof(buffer)) + to_read = sizeof(buffer); + + memcpy((uint8_t*)coro->data + coro->len, buffer, to_read); + coro->len += to_read; + + if (coro->len == coro->count) + break; + + CORO_YIELD(); + + intfstream_read(coro->stream, buffer, sizeof(buffer)); + } while (true); + + CORO_GOSUB(RCHEEVOS_EVAL_MD5); + MD5_Final(coro->hash, &coro->md5); + + intfstream_close(coro->stream); + CHEEVOS_FREE(coro->stream); + + intfstream_close(coro->cdrom_track_stream); + CHEEVOS_FREE(coro->cdrom_track_stream); + + if (coro->cdrom_stream) + { + intfstream_close(coro->cdrom_stream); + CHEEVOS_FREE(coro->cdrom_stream); + } + + CORO_GOTO(RCHEEVOS_GET_GAMEID); + } + } + } + + intfstream_close(coro->stream); + CHEEVOS_FREE(coro->stream); + } + + intfstream_close(coro->cdrom_track_stream); + CHEEVOS_FREE(coro->cdrom_track_stream); + } + + if (coro->cdrom_stream) + { + intfstream_close(coro->cdrom_stream); + CHEEVOS_FREE(coro->cdrom_stream); + } + + coro->gameid = 0; + CORO_RET(); + } + /************************************************************************** * Info Tries to identify a "generic" game * Input CHEEVOS_VAR_INFO the content info @@ -1640,6 +1840,11 @@ found: { int size; + + CHEEVOS_LOG(RCHEEVOS_TAG "checking hash %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n", + coro->hash[ 0], coro->hash[ 1], coro->hash[ 2], coro->hash[ 3], coro->hash[ 4], coro->hash[ 5], coro->hash[ 6], coro->hash[ 7], + coro->hash[ 8], coro->hash[ 9], coro->hash[10], coro->hash[11], coro->hash[12], coro->hash[13], coro->hash[14], coro->hash[15]); + size = rc_url_get_gameid(coro->url, sizeof(coro->url), coro->hash); if (size < 0) diff --git a/cheevos-new/parser.c b/cheevos-new/parser.c index eed9a88ecb..b28ac7b20e 100644 --- a/cheevos-new/parser.c +++ b/cheevos-new/parser.c @@ -550,8 +550,8 @@ Frees the patchdata void rcheevos_free_patchdata(rcheevos_rapatchdata_t* patchdata) { unsigned i = 0, count = 0; - const rcheevos_racheevo_t* cheevo = NULL; - const rcheevos_ralboard_t* lboard = NULL; + rcheevos_racheevo_t* cheevo = NULL; + rcheevos_ralboard_t* lboard = NULL; cheevo = patchdata->core; diff --git a/cheevos-new/util.h b/cheevos-new/util.h index 1794ef3ebb..fbd882b66a 100644 --- a/cheevos-new/util.h +++ b/cheevos-new/util.h @@ -32,7 +32,12 @@ End of setup *****************************************************************************/ #define RCHEEVOS_TAG "[RCHEEVOS]: " + +#ifdef _DEBUG +#define CHEEVOS_FREE(p) do { void* q = (void*)p; p = NULL; if (q != NULL) free(q); } while (0) +#else #define CHEEVOS_FREE(p) do { void* q = (void*)p; if (q != NULL) free(q); } while (0) +#endif #ifdef CHEEVOS_VERBOSE diff --git a/griffin/griffin.c b/griffin/griffin.c index dd834caebd..40e59a643a 100644 --- a/griffin/griffin.c +++ b/griffin/griffin.c @@ -973,11 +973,11 @@ FILE #include "../libretro-common/streams/memory_stream.c" #ifndef __WINRT__ #include "../libretro-common/vfs/vfs_implementation.c" +#include "../libretro-common/vfs/vfs_implementation_cdrom.c" #endif #ifdef HAVE_CDROM #include "../libretro-common/cdrom/cdrom.c" -#include "../libretro-common/vfs/vfs_implementation_cdrom.c" #include "../libretro-common/media/media_detect_cd.c" #endif diff --git a/libretro-common/cdrom/cdrom.c b/libretro-common/cdrom/cdrom.c index cb5ff8c110..1909fb6556 100644 --- a/libretro-common/cdrom/cdrom.c +++ b/libretro-common/cdrom/cdrom.c @@ -301,7 +301,7 @@ static int cdrom_send_command_win32(const libretro_vfs_implementation_file *stre memcpy(sptd.s.Cdb, cmd, cmd_len); - ioctl_rv = DeviceIoControl(stream->fh, IOCTL_SCSI_PASS_THROUGH_DIRECT, &sptd, + ioctl_rv = DeviceIoControl(stream->parent ? stream->parent->fh : stream->fh, IOCTL_SCSI_PASS_THROUGH_DIRECT, &sptd, sizeof(sptd), &sptd, sizeof(sptd), &ioctl_bytes, NULL); #ifdef CDROM_DEBUG @@ -313,7 +313,8 @@ static int cdrom_send_command_win32(const libretro_vfs_implementation_file *stre if (cmd[0] == 0xB9) { double time_taken = (double)(((clock() - t) * 1000) / CLOCKS_PER_SEC); - printf("time taken %f ms for DT received length %ld of %" PRId64 " for %02d:%02d:%02d to %02d:%02d:%02d%s req %d cur %d cur_lba %d\n", time_taken, sptd.s.DataTransferLength, len, cmd[3], cmd[4], cmd[5], cmd[6], cmd[7], cmd[8], extra, lba_req, lba_cur, stream->cdrom.cur_lba); + printf("time taken %f ms for DT received length %ld of %" PRId64 " for %02d:%02d:%02d to %02d:%02d:%02d%s req %d cur %d cur_lba %d\n", + time_taken, sptd.s.DataTransferLength, len, cmd[3], cmd[4], cmd[5], cmd[6], cmd[7], cmd[8], extra, lba_req, lba_cur, stream->track ? stream->track->cur_lba : -1); fflush(stdout); } @@ -359,7 +360,7 @@ static int cdrom_send_command_linux(const libretro_vfs_implementation_file *stre sgio.mx_sb_len = sense_len; sgio.timeout = 5000; - rv = ioctl(fileno(stream->fp), SG_IO, &sgio); + rv = ioctl(fileno(stream->parent ? stream->parent->fp : stream->fp), SG_IO, &sgio); if (rv == -1 || sgio.info & SG_INFO_CHECK) return 1; @@ -442,7 +443,7 @@ static int cdrom_send_command(libretro_vfs_implementation_file *stream, CDROM_CM lba_req = cdrom_msf_to_lba(cmd[3], cmd[4], cmd[5]); - if (stream->cdrom.last_frame_valid && lba_req == stream->cdrom.last_frame_lba) + if (lba_req == stream->track->last_frame_lba) { /* use cached frame */ cached_read = true; @@ -451,7 +452,7 @@ static int cdrom_send_command(libretro_vfs_implementation_file *stream, CDROM_CM fflush(stdout); #endif /* assumes request_len is always equal to the size of last_frame */ - memcpy(xfer_buf_pos, stream->cdrom.last_frame, sizeof(stream->cdrom.last_frame)); + memcpy(xfer_buf_pos, stream->track->last_frame, sizeof(stream->track->last_frame)); } } @@ -497,18 +498,20 @@ retry: memcpy((char*)buf + copied_bytes, xfer_buf_pos + skip, copy_len); copied_bytes += copy_len; - if (read_cd && !cached_read && request_len >= 2352) + if (stream->track) { - unsigned frame_end = cdrom_msf_to_lba(cmd[6], cmd[7], cmd[8]); + if (read_cd && !cached_read && request_len >= 2352) + { + unsigned frame_end = cdrom_msf_to_lba(cmd[6], cmd[7], cmd[8]); - /* cache the last received frame */ - memcpy(stream->cdrom.last_frame, xfer_buf_pos, sizeof(stream->cdrom.last_frame)); - stream->cdrom.last_frame_valid = true; - /* the ending frame is never actually read, so what we really just read is the one right before that */ - stream->cdrom.last_frame_lba = frame_end - 1; + /* cache the last received frame */ + memcpy(stream->track->last_frame, xfer_buf_pos, sizeof(stream->track->last_frame)); + /* the ending frame is never actually read, so what we really just read is the one right before that */ + stream->track->last_frame_lba = frame_end - 1; + } + else + stream->track->last_frame_lba = (unsigned)-1; } - else - stream->cdrom.last_frame_valid = false; #if 0 printf("Frame %d, adding %" PRId64 " to buf_pos, is now %" PRId64 ". skip is %" PRId64 "\n", i, request_len, (xfer_buf_pos + request_len) - xfer_buf, skip); @@ -1231,7 +1234,7 @@ int cdrom_read(libretro_vfs_implementation_file *stream, cdrom_group_timeouts_t if (rv) { - stream->cdrom.last_frame_valid = false; + stream->track->last_frame_lba = (unsigned)-1; return 1; } @@ -1428,7 +1431,7 @@ struct string_list* cdrom_get_available_drives(void) for (i = 0; i < sizeof(DWORD) * 8; i++) { char path[] = {"a:\\"}; - char cdrom_path[] = {"cdrom://a:/drive-track01.bin"}; + char cdrom_path[] = {"cdrom://a:/drive.cue"}; path[0] += i; cdrom_path[8] += i; @@ -1444,16 +1447,16 @@ struct string_list* cdrom_get_available_drives(void) char drive_model[32] = {0}; char drive_string[33] = {0}; union string_list_elem_attr attr = {0}; - RFILE *file = filestream_open(cdrom_path, RETRO_VFS_FILE_ACCESS_READ, 0); - libretro_vfs_implementation_file *stream; + RFILE *cdrom_file = filestream_open(cdrom_path, RETRO_VFS_FILE_ACCESS_READ, 0); bool is_cdrom = false; - if (!file) - continue; + if (cdrom_file) + { + libretro_vfs_implementation_file* stream = filestream_get_vfs_handle(cdrom_file); + cdrom_get_inquiry(stream, drive_model, sizeof(drive_model), &is_cdrom); - stream = filestream_get_vfs_handle(file); - cdrom_get_inquiry(stream, drive_model, sizeof(drive_model), &is_cdrom); - filestream_close(file); + filestream_close(cdrom_file); + } if (!is_cdrom) continue; diff --git a/libretro-common/formats/libchdr/libchdr_chd.c b/libretro-common/formats/libchdr/libchdr_chd.c index 3e3de49c26..fd48b9e87d 100644 --- a/libretro-common/formats/libchdr/libchdr_chd.c +++ b/libretro-common/formats/libchdr/libchdr_chd.c @@ -1037,6 +1037,10 @@ void chd_close(chd_file *chd) for (i = 0 ; i < 4 ; i++) { void* codec = NULL; + + if (!chd->codecintf[i]) + continue; + switch (chd->codecintf[i]->compression) { case CHD_CODEC_CD_LZMA: diff --git a/libretro-common/include/streams/chd_stream.h b/libretro-common/include/streams/chd_stream.h index 246a2dcfc9..7fde65b386 100644 --- a/libretro-common/include/streams/chd_stream.h +++ b/libretro-common/include/streams/chd_stream.h @@ -45,16 +45,28 @@ void chdstream_close(chdstream_t *stream); ssize_t chdstream_read(chdstream_t *stream, void *data, size_t bytes); +ssize_t chdstream_read_file(chdstream_t *stream, int64_t file_start, void *data, size_t bytes); + int chdstream_getc(chdstream_t *stream); +int chdstream_getc_file(chdstream_t *stream, int64_t file_start); + char *chdstream_gets(chdstream_t *stream, char *buffer, size_t len); +char *chdstream_gets_file(chdstream_t *stream, int64_t file_start, char *buffer, size_t len); + uint64_t chdstream_tell(chdstream_t *stream); +uint64_t chdstream_tell_file(chdstream_t *stream, int64_t file_start); + void chdstream_rewind(chdstream_t *stream); +void chdstream_rewind_file(chdstream_t *stream, int64_t file_start); + int64_t chdstream_seek(chdstream_t *stream, int64_t offset, int whence); +int64_t chdstream_seek_file(chdstream_t* stream, const char* filename); + ssize_t chdstream_get_size(chdstream_t *stream); RETRO_END_DECLS diff --git a/libretro-common/include/streams/file_stream.h b/libretro-common/include/streams/file_stream.h index 0cfadad83c..64b7afbb25 100644 --- a/libretro-common/include/streams/file_stream.h +++ b/libretro-common/include/streams/file_stream.h @@ -63,6 +63,8 @@ int64_t filestream_truncate(RFILE *stream, int64_t length); **/ RFILE* filestream_open(const char *path, unsigned mode, unsigned hints); +RFILE* filestream_open_child(RFILE *stream, const char* path); + int64_t filestream_seek(RFILE *stream, int64_t offset, int seek_position); int64_t filestream_read(RFILE *stream, void *data, int64_t len); diff --git a/libretro-common/include/streams/interface_stream.h b/libretro-common/include/streams/interface_stream.h index ec480c6624..aeec984272 100644 --- a/libretro-common/include/streams/interface_stream.h +++ b/libretro-common/include/streams/interface_stream.h @@ -36,7 +36,8 @@ enum intfstream_type { INTFSTREAM_FILE = 0, INTFSTREAM_MEMORY, - INTFSTREAM_CHD + INTFSTREAM_CHD, + INTFSTREAM_CHD_FILE }; typedef struct intfstream_internal intfstream_internal_t, intfstream_t; @@ -97,6 +98,9 @@ int intfstream_flush(intfstream_internal_t *intf); intfstream_t* intfstream_open_file(const char *path, unsigned mode, unsigned hints); +intfstream_t* intfstream_open_file_child(intfstream_internal_t *intf, + const char* path); + intfstream_t *intfstream_open_memory(void *data, unsigned mode, unsigned hints, uint64_t size); diff --git a/libretro-common/include/vfs/vfs.h b/libretro-common/include/vfs/vfs.h index b876f438b6..2f24597c82 100644 --- a/libretro-common/include/vfs/vfs.h +++ b/libretro-common/include/vfs/vfs.h @@ -38,28 +38,40 @@ RETRO_BEGIN_DECLS typedef void* HANDLE; #endif -#ifdef HAVE_CDROM typedef struct { - char *cue_buf; + char* cue_buf; size_t cue_len; - int64_t byte_pos; + size_t cue_pos; char drive; +} vfs_cdrom_t; + +typedef struct +{ + int64_t byte_pos; + unsigned cur_lba; + unsigned char cur_track; unsigned char cur_min; unsigned char cur_sec; unsigned char cur_frame; - unsigned char cur_track; - unsigned cur_lba; + + unsigned sector_size; + unsigned char sector_header_size; + unsigned char mode; + unsigned last_frame_lba; unsigned char last_frame[2352]; - bool last_frame_valid; -} vfs_cdrom_t; -#endif +} vfs_cdrom_track_t; enum vfs_scheme { VFS_SCHEME_NONE = 0, - VFS_SCHEME_CDROM + VFS_SCHEME_CDROM, + VFS_SCHEME_CDROM_TRACK, + VFS_SCHEME_CDROM_FILE, + VFS_SCHEME_CUE, + VFS_SCHEME_CUE_BIN, + VFS_SCHEME_CUE_BIN_FILE }; #ifndef __WINRT__ @@ -81,10 +93,18 @@ struct libretro_vfs_implementation_file uint64_t mappos; uint64_t mapsize; uint8_t *mapped; - enum vfs_scheme scheme; -#ifdef HAVE_CDROM - vfs_cdrom_t cdrom; + +#ifdef VFS_FRONTEND + struct retro_vfs_file_handle* parent; +#else + struct libretro_vfs_implementation_file* parent; #endif + uint64_t parent_offset; + + enum vfs_scheme scheme; + + vfs_cdrom_t cdrom; + vfs_cdrom_track_t* track; }; #endif diff --git a/libretro-common/include/vfs/vfs_implementation_cdrom.h b/libretro-common/include/vfs/vfs_implementation_cdrom.h index 3996fbe877..43d1ace50e 100644 --- a/libretro-common/include/vfs/vfs_implementation_cdrom.h +++ b/libretro-common/include/vfs/vfs_implementation_cdrom.h @@ -24,13 +24,16 @@ #define __LIBRETRO_SDK_VFS_IMPLEMENTATION_CDROM_H #include + +#ifdef HAVE_CDROM #include +#endif RETRO_BEGIN_DECLS int64_t retro_vfs_file_seek_cdrom(libretro_vfs_implementation_file *stream, int64_t offset, int whence); -void retro_vfs_file_open_cdrom( +bool retro_vfs_file_open_cdrom( libretro_vfs_implementation_file *stream, const char *path, unsigned mode, unsigned hints); @@ -43,9 +46,30 @@ int64_t retro_vfs_file_read_cdrom(libretro_vfs_implementation_file *stream, int retro_vfs_file_error_cdrom(libretro_vfs_implementation_file *stream); +#ifdef HAVE_CDROM const cdrom_toc_t* retro_vfs_file_get_cdrom_toc(void); +#endif -const vfs_cdrom_t* retro_vfs_file_get_cdrom_position(const libretro_vfs_implementation_file *stream); +libretro_vfs_implementation_file* retro_vfs_file_open_cdrom_track( + libretro_vfs_implementation_file* stream, const char* track); + +int retro_vfs_file_close_cdrom_track(libretro_vfs_implementation_file *stream); + +int64_t retro_vfs_file_seek_cdrom_track(libretro_vfs_implementation_file *stream, int64_t offset, int whence); + +int64_t retro_vfs_file_read_cdrom_track(libretro_vfs_implementation_file *stream, void *s, uint64_t len); + +int64_t retro_vfs_file_tell_cdrom_track(libretro_vfs_implementation_file *stream); + + +libretro_vfs_implementation_file* retro_vfs_file_open_cdrom_file( + libretro_vfs_implementation_file* stream, const char* path); + +int64_t retro_vfs_file_seek_cdrom_file(libretro_vfs_implementation_file *stream, int64_t offset, int whence); + +int64_t retro_vfs_file_read_cdrom_file(libretro_vfs_implementation_file *stream, void *s, uint64_t len); + +int64_t retro_vfs_file_tell_cdrom_file(libretro_vfs_implementation_file *stream); RETRO_END_DECLS diff --git a/libretro-common/media/media_detect_cd.c b/libretro-common/media/media_detect_cd.c index 9faa747a5a..8d06d59dfa 100644 --- a/libretro-common/media/media_detect_cd.c +++ b/libretro-common/media/media_detect_cd.c @@ -26,8 +26,6 @@ #include #include -/*#define MEDIA_CUE_PARSE_DEBUG*/ - static void media_zero_trailing_spaces(char *buf, size_t len) { int i; @@ -65,203 +63,6 @@ static bool media_skip_spaces(const char **buf, size_t len) return false; } -/* Fill in "info" with detected CD info. Use this when you have a cue file and want it parsed to find the first data track and any pregap info. */ -bool media_detect_cd_info_cue(const char *path, media_detect_cd_info_t *info) -{ - RFILE *file = NULL; - char *line = NULL; - char track_path[PATH_MAX_LENGTH] = {0}; - char track_abs_path[PATH_MAX_LENGTH] = {0}; - char track_mode[11] = {0}; - bool found_file = false; - bool found_track = false; - unsigned first_data_track = 0; - uint64_t data_track_pregap_bytes = 0; - - if (string_is_empty(path) || !info) - return false; - - file = filestream_open(path, RETRO_VFS_FILE_ACCESS_READ, 0); - - if (!file) - { - printf("[MEDIA] Could not open cue path for reading: %s\n", path); - fflush(stdout); - return false; - } - - while (!filestream_eof(file) && (line = filestream_getline(file))) - { - size_t len = 0; - const char *command = NULL; - - if (string_is_empty(line)) - { - free(line); - continue; - } - - len = strlen(line); - - command = line; - - media_skip_spaces(&command, len); - - if (!found_file && !strncasecmp(command, "FILE", 4)) - { - const char *file = command + 4; - media_skip_spaces(&file, len - 4); - - if (!string_is_empty(file)) - { - const char *file_end = NULL; - size_t file_len = 0; - bool quoted = false; - - if (file[0] == '"') - { - quoted = true; - file++; - } - - if (quoted) - file_end = strchr(file, '\"'); - else - file_end = strchr(file, ' '); - - if (file_end) - { - file_len = file_end - file; - memcpy(track_path, file, file_len); - found_file = true; -#ifdef MEDIA_CUE_PARSE_DEBUG - printf("Found file: %s\n", track_path); - fflush(stdout); -#endif - } - } - } - else if (found_file && !found_track && !strncasecmp(command, "TRACK", 5)) - { - const char *track = command + 5; - media_skip_spaces(&track, len - 5); - - if (!string_is_empty(track)) - { - unsigned track_number = 0; - sscanf(track, "%d", &track_number); -#ifdef MEDIA_CUE_PARSE_DEBUG - printf("Found track: %d\n", track_number); - fflush(stdout); -#endif - track++; - - if (track[0] && track[0] != ' ' && track[0] != '\t') - track++; - - if (!string_is_empty(track)) - { - media_skip_spaces(&track, strlen(track)); -#ifdef MEDIA_CUE_PARSE_DEBUG - printf("Found track type: %s\n", track); - fflush(stdout); -#endif - if (!strncasecmp(track, "MODE", 4)) - { - first_data_track = track_number; - found_track = true; - strlcpy(track_mode, track, sizeof(track_mode)); - } - else - found_file = false; - } - } - } - else if (found_file && found_track && first_data_track && !strncasecmp(command, "INDEX", 5)) - { - const char *index = command + 5; - media_skip_spaces(&index, len - 5); - - if (!string_is_empty(index)) - { - unsigned index_number = 0; - sscanf(index, "%d", &index_number); - - if (index_number == 1) - { - const char *pregap = index + 1; - - if (pregap[0] && pregap[0] != ' ' && pregap[0] != '\t') - pregap++; - - if (!string_is_empty(pregap)) - { - media_skip_spaces(&pregap, strlen(pregap)); - found_file = false; - found_track = false; - - if (first_data_track && !string_is_empty(track_mode)) - { - unsigned track_sector_size = 0; - unsigned track_mode_number = 0; - - if (strlen(track_mode) == 10) - { - sscanf(track_mode, "MODE%d/%d", &track_mode_number, &track_sector_size); -#ifdef MEDIA_CUE_PARSE_DEBUG - printf("Found track mode %d with sector size %d\n", track_mode_number, track_sector_size); - fflush(stdout); -#endif - if ((track_mode_number == 1 || track_mode_number == 2) && track_sector_size) - { - unsigned min = 0; - unsigned sec = 0; - unsigned frame = 0; - sscanf(pregap, "%02d:%02d:%02d", &min, &sec, &frame); - - if (min || sec || frame || strstr(pregap, "00:00:00")) - { - data_track_pregap_bytes = ((min * 60 + sec) * 75 + frame) * track_sector_size; -#ifdef MEDIA_CUE_PARSE_DEBUG - printf("Found pregap of %02d:%02d:%02d (bytes: %" PRIu64 ")\n", min, sec, frame, data_track_pregap_bytes); - fflush(stdout); -#endif - break; - } - } - } - } - } - } - } - } - - free(line); - } - - filestream_close(file); - - if (!string_is_empty(track_path)) - { - if (strstr(track_path, "/") || strstr(track_path, "\\")) - { - printf("using path %s\n", track_path); - fflush(stdout); - return media_detect_cd_info(track_path, data_track_pregap_bytes, info); - } - else - { - fill_pathname_basedir(track_abs_path, path, sizeof(track_abs_path)); - strlcat(track_abs_path, track_path, sizeof(track_abs_path)); - printf("using abs path %s\n", track_abs_path); - fflush(stdout); - return media_detect_cd_info(track_abs_path, data_track_pregap_bytes, info); - } - } - - return true; -} - /* Fill in "info" with detected CD info. Use this when you want to open a specific track file directly, and the pregap is known. */ bool media_detect_cd_info(const char *path, uint64_t pregap_bytes, media_detect_cd_info_t *info) { diff --git a/libretro-common/streams/chd_stream.c b/libretro-common/streams/chd_stream.c index 97d3eb4699..af0dbe070d 100644 --- a/libretro-common/streams/chd_stream.c +++ b/libretro-common/streams/chd_stream.c @@ -43,6 +43,8 @@ struct chdstream uint32_t frame_size; /* Offset of data within frame */ uint32_t frame_offset; + /* Size of frame header */ + uint32_t frame_header_size; /* Number of frames per hunk */ uint32_t frames_per_hunk; /* First frame of track in chd */ @@ -360,11 +362,62 @@ ssize_t chdstream_read(chdstream_t *stream, void *data, size_t bytes) return bytes; } +ssize_t chdstream_read_file(chdstream_t *stream, int64_t file_start, void *data, size_t bytes) +{ + uint8_t buffer[SECTOR_SIZE]; + int64_t file_frame = (stream->offset - file_start) / stream->frame_size; + int64_t file_frame_offset = (stream->offset - file_start) - (file_frame * stream->frame_size) - stream->frame_header_size; + ssize_t bytes_read = 0; + + if (file_frame_offset >= stream->frame_header_size + 2048) + { + ++file_frame; + file_frame_offset = -1; + } + if (file_frame_offset < 0) + { + stream->offset = file_start + (file_frame * stream->frame_size) + stream->frame_header_size; + file_frame_offset = 0; + } + + do + { + const int64_t remaining = 2048 - file_frame_offset; + if (bytes < remaining) + { + if (bytes > 0) + { + chdstream_read(stream, data, bytes); + bytes_read += bytes; + } + return bytes_read; + } + + chdstream_read(stream, data, remaining); + bytes_read += remaining; + bytes -= remaining; + + ++file_frame; + stream->offset = file_start + (file_frame * stream->frame_size) + stream->frame_header_size; + file_frame_offset = 0; + } while (true); +} + int chdstream_getc(chdstream_t *stream) { char c = 0; - if (chdstream_read(stream, &c, sizeof(c) != sizeof(c))) + if (chdstream_read(stream, &c, sizeof(c)) != sizeof(c)) + return EOF; + + return c; +} + +int chdstream_getc_file(chdstream_t *stream, int64_t file_start) +{ + char c = 0; + + if (chdstream_read_file(stream, file_start, &c, sizeof(c)) != sizeof(c)) return EOF; return c; @@ -385,16 +438,43 @@ char *chdstream_gets(chdstream_t *stream, char *buffer, size_t len) return buffer; } +char *chdstream_gets_file(chdstream_t *stream, int64_t file_start, char *buffer, size_t len) +{ + int c; + + size_t offset = 0; + + while (offset < len && (c = chdstream_getc_file(stream, file_start)) != EOF) + buffer[offset++] = c; + + if (offset < len) + buffer[offset] = '\0'; + + return buffer; +} + uint64_t chdstream_tell(chdstream_t *stream) { return stream->offset; } +uint64_t chdstream_tell_file(chdstream_t *stream, int64_t file_start) +{ + const int64_t file_frame = (stream->offset - file_start) / stream->frame_size; + const int64_t file_frame_offset = (stream->offset - file_start) - (file_frame * stream->frame_size) - stream->frame_header_size; + return (file_frame * 2048) + file_frame_offset; +} + void chdstream_rewind(chdstream_t *stream) { stream->offset = 0; } +void chdstream_rewind_file(chdstream_t *stream, int64_t file_start) +{ + stream->offset = file_start; +} + int64_t chdstream_seek(chdstream_t *stream, int64_t offset, int whence) { int64_t new_offset; @@ -424,6 +504,74 @@ int64_t chdstream_seek(chdstream_t *stream, int64_t offset, int whence) return 0; } +int64_t chdstream_seek_file(chdstream_t* stream, const char* path) +{ + uint8_t buffer[SECTOR_SIZE], *tmp; + int sector, path_length; + + const char* slash = strrchr(path, '\\'); + if (slash) + { + /* navigate the path to the directory record for the file */ + const int dir_length = (int)(slash - path); + memcpy(buffer, path, dir_length); + buffer[dir_length] = '\0'; + + if (chdstream_seek_file(stream, (const char*)buffer) < 0) + return -1; + + path += dir_length + 1; + } + else + { + /* The boot record or primary volume descriptor is always at sector 16 and will contain a "CD001" marker */ + chdstream_seek(stream, 16 * stream->frame_size, SEEK_SET); + chdstream_read(stream, buffer, sizeof(buffer)); + + if (stream->frame_header_size == 0) + { + if (memcmp(&buffer[25], "CD001", 5) == 0) + stream->frame_header_size = 24; + else + stream->frame_header_size = 16; + } + + /* the directory_record starts at 156 bytes into the sector. + * the sector containing the table of contents is a 3 byte value that is 2 bytes into the directory_record. */ + { + const int offset = stream->frame_header_size + 156 + 2; + sector = buffer[offset] | (buffer[offset + 1] << 8) | (buffer[offset + 2] << 16); + } + chdstream_seek(stream, sector * stream->frame_size, SEEK_SET); + } + + /* process the table of contents */ + chdstream_read(stream, buffer, sizeof(buffer)); + + path_length = strlen(path); + tmp = buffer + stream->frame_header_size; + while (tmp < buffer + sizeof(buffer)) + { + /* the first byte of the record is the length of the record - if 0, we reached the end of the data */ + if (!*tmp) + break; + + /* filename is 33 bytes into the record and the format is "FILENAME;version" or "DIRECTORY" */ + if ((tmp[33 + path_length] == ';' || tmp[33 + path_length] == '\0') && + strncasecmp((const char*)(tmp + 33), path, path_length) == 0) + { + /* the file contents are in the sector identified in bytes 2-4 of the record */ + sector = tmp[2] | (tmp[3] << 8) | (tmp[4] << 16); + return chdstream_seek(stream, sector * stream->frame_size, SEEK_SET); + } + + /* the first byte of the record is the length of the record */ + tmp += tmp[0]; + } + + return -1; +} + ssize_t chdstream_get_size(chdstream_t *stream) { return stream->track_end; diff --git a/libretro-common/streams/file_stream.c b/libretro-common/streams/file_stream.c index e3e4590752..bbf892858d 100644 --- a/libretro-common/streams/file_stream.c +++ b/libretro-common/streams/file_stream.c @@ -34,6 +34,7 @@ #include #define VFS_FRONTEND #include +#include static const int64_t vfs_error_return_value = -1; @@ -176,6 +177,37 @@ RFILE* filestream_open(const char *path, unsigned mode, unsigned hints) return output; } +RFILE* filestream_open_child(RFILE *stream, const char* path) +{ + struct retro_vfs_file_handle *fp = NULL; + RFILE* output = NULL; + + switch (stream->hfile->scheme) + { + case VFS_SCHEME_CDROM: + case VFS_SCHEME_CUE: + fp = retro_vfs_file_open_cdrom_track(stream->hfile, path); + break; + + case VFS_SCHEME_CDROM_TRACK: + case VFS_SCHEME_CUE_BIN: + fp = retro_vfs_file_open_cdrom_file(stream->hfile, path); + break; + + default: + return NULL; + } + + if (!fp) + return NULL; + + output = (RFILE*)malloc(sizeof(RFILE)); + output->error_flag = false; + output->eof_flag = false; + output->hfile = fp; + return output; +} + char* filestream_gets(RFILE *stream, char *s, size_t len) { int c = 0; diff --git a/libretro-common/streams/interface_stream.c b/libretro-common/streams/interface_stream.c index 0207823ce2..d6a85c738b 100644 --- a/libretro-common/streams/interface_stream.c +++ b/libretro-common/streams/interface_stream.c @@ -52,6 +52,7 @@ struct intfstream_internal struct { int32_t track; + int32_t file_start; chdstream_t *fp; } chd; #endif @@ -68,12 +69,12 @@ int64_t intfstream_get_size(intfstream_internal_t *intf) return filestream_get_size(intf->file.fp); case INTFSTREAM_MEMORY: return intf->memory.buf.size; - case INTFSTREAM_CHD: #ifdef HAVE_CHD - return chdstream_get_size(intf->chd.fp); -#else - break; + case INTFSTREAM_CHD: + return chdstream_get_size(intf->chd.fp); #endif + default: + break; } return 0; @@ -95,9 +96,11 @@ bool intfstream_resize(intfstream_internal_t *intf, intfstream_info_t *info) memstream_set_buffer(intf->memory.buf.data, intf->memory.buf.size); break; - case INTFSTREAM_CHD: #ifdef HAVE_CHD + case INTFSTREAM_CHD: + break; #endif + default: break; } @@ -122,15 +125,15 @@ bool intfstream_open(intfstream_internal_t *intf, const char *path, if (!intf->memory.fp) return false; break; - case INTFSTREAM_CHD: #ifdef HAVE_CHD + case INTFSTREAM_CHD: intf->chd.fp = chdstream_open(path, intf->chd.track); if (!intf->chd.fp) return false; break; -#else - return false; #endif + default: + return false; } return true; @@ -149,6 +152,8 @@ int intfstream_flush(intfstream_internal_t *intf) case INTFSTREAM_CHD: /* Should we stub this for these interfaces? */ break; + default: + break; } return 0; @@ -169,12 +174,17 @@ int intfstream_close(intfstream_internal_t *intf) if (intf->memory.fp) memstream_close(intf->memory.fp); return 0; - case INTFSTREAM_CHD: #ifdef HAVE_CHD + case INTFSTREAM_CHD: if (intf->chd.fp) chdstream_close(intf->chd.fp); -#endif return 0; + case INTFSTREAM_CHD_FILE: + /* handle owned by INTFSTREAM_CHD */ + return 0; +#endif + default: + break; } return -1; @@ -202,13 +212,13 @@ void *intfstream_init(intfstream_info_t *info) if (!intfstream_resize(intf, info)) goto error; break; - case INTFSTREAM_CHD: #ifdef HAVE_CHD + case INTFSTREAM_CHD: intf->chd.track = info->chd.track; break; -#else - goto error; #endif + default: + goto error; } return intf; @@ -246,12 +256,12 @@ int64_t intfstream_seek(intfstream_internal_t *intf, int64_t offset, int whence) } case INTFSTREAM_MEMORY: return (int64_t)memstream_seek(intf->memory.fp, offset, whence); - case INTFSTREAM_CHD: #ifdef HAVE_CHD + case INTFSTREAM_CHD: return (int64_t)chdstream_seek(intf->chd.fp, offset, whence); -#else - break; #endif + default: + break; } return -1; @@ -268,12 +278,14 @@ int64_t intfstream_read(intfstream_internal_t *intf, void *s, uint64_t len) return filestream_read(intf->file.fp, s, len); case INTFSTREAM_MEMORY: return memstream_read(intf->memory.fp, s, len); - case INTFSTREAM_CHD: #ifdef HAVE_CHD + case INTFSTREAM_CHD: return chdstream_read(intf->chd.fp, s, len); -#else - break; + case INTFSTREAM_CHD_FILE: + return chdstream_read_file(intf->chd.fp, intf->chd.file_start, s, len); #endif + default: + break; } return -1; @@ -292,6 +304,7 @@ int64_t intfstream_write(intfstream_internal_t *intf, case INTFSTREAM_MEMORY: return memstream_write(intf->memory.fp, s, len); case INTFSTREAM_CHD: + case INTFSTREAM_CHD_FILE: return -1; } @@ -312,12 +325,14 @@ char *intfstream_gets(intfstream_internal_t *intf, case INTFSTREAM_MEMORY: return memstream_gets(intf->memory.fp, buffer, (size_t)len); - case INTFSTREAM_CHD: #ifdef HAVE_CHD + case INTFSTREAM_CHD: return chdstream_gets(intf->chd.fp, buffer, len); -#else - break; + case INTFSTREAM_CHD_FILE: + return chdstream_gets_file(intf->chd.fp, intf->chd.file_start, buffer, len); #endif + default: + break; } return NULL; @@ -334,12 +349,14 @@ int intfstream_getc(intfstream_internal_t *intf) return filestream_getc(intf->file.fp); case INTFSTREAM_MEMORY: return memstream_getc(intf->memory.fp); - case INTFSTREAM_CHD: #ifdef HAVE_CHD + case INTFSTREAM_CHD: return chdstream_getc(intf->chd.fp); -#else - break; + case INTFSTREAM_CHD_FILE: + return chdstream_getc_file(intf->chd.fp, intf->chd.file_start); #endif + default: + break; } return -1; @@ -356,12 +373,14 @@ int64_t intfstream_tell(intfstream_internal_t *intf) return (int64_t)filestream_tell(intf->file.fp); case INTFSTREAM_MEMORY: return (int64_t)memstream_pos(intf->memory.fp); - case INTFSTREAM_CHD: #ifdef HAVE_CHD + case INTFSTREAM_CHD: return (int64_t)chdstream_tell(intf->chd.fp); -#else - break; + case INTFSTREAM_CHD_FILE: + return (int64_t)chdstream_tell_file(intf->chd.fp, intf->chd.file_start); #endif + default: + break; } return -1; @@ -377,10 +396,15 @@ void intfstream_rewind(intfstream_internal_t *intf) case INTFSTREAM_MEMORY: memstream_rewind(intf->memory.fp); break; - case INTFSTREAM_CHD: #ifdef HAVE_CHD + case INTFSTREAM_CHD: chdstream_rewind(intf->chd.fp); + break; + case INTFSTREAM_CHD_FILE: + chdstream_rewind_file(intf->chd.fp, intf->chd.file_start); + break; #endif + default: break; } } @@ -398,7 +422,7 @@ void intfstream_putc(intfstream_internal_t *intf, int c) case INTFSTREAM_MEMORY: memstream_putc(intf->memory.fp, c); break; - case INTFSTREAM_CHD: + default: break; } } @@ -429,6 +453,55 @@ error: return NULL; } +intfstream_t* intfstream_open_file_child(intfstream_internal_t* intf, const char* path) +{ + intfstream_info_t info; + intfstream_t *fd = NULL; + + if (!intf) + return NULL; + + info.type = INTFSTREAM_FILE; + fd = (intfstream_t*)intfstream_init(&info); + + if (!fd) + return NULL; + + switch (intf->type) + { + case INTFSTREAM_FILE: + fd->file.fp = filestream_open_child(intf->file.fp, path); + break; + case INTFSTREAM_MEMORY: + break; +#ifdef HAVE_CHD + case INTFSTREAM_CHD: + if (chdstream_seek_file(intf->chd.fp, path) < 0) + goto error; + + fd->type = INTFSTREAM_CHD_FILE; + fd->chd.fp = intf->chd.fp; + fd->chd.file_start = chdstream_tell(intf->chd.fp); + return fd; +#endif + default: + goto error; + } + + if (!fd->file.fp) + goto error; + + return fd; + +error: + if (fd) + { + intfstream_close(fd); + free(fd); + } + return NULL; +} + intfstream_t *intfstream_open_memory(void *data, unsigned mode, unsigned hints, uint64_t size) { diff --git a/libretro-common/vfs/vfs_implementation.c b/libretro-common/vfs/vfs_implementation.c index ebe5228c1d..5d157b85b9 100644 --- a/libretro-common/vfs/vfs_implementation.c +++ b/libretro-common/vfs/vfs_implementation.c @@ -179,16 +179,13 @@ #endif #include +#include #include #include #include #include #include -#ifdef HAVE_CDROM -#include -#endif - #define RFILE_HINT_UNBUFFERED (1 << 8) int64_t retro_vfs_file_seek_internal(libretro_vfs_implementation_file *stream, int64_t offset, int whence) @@ -198,10 +195,25 @@ int64_t retro_vfs_file_seek_internal(libretro_vfs_implementation_file *stream, i if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0) { + switch (stream->scheme) + { #ifdef HAVE_CDROM - if (stream->scheme == VFS_SCHEME_CDROM) - return retro_vfs_file_seek_cdrom(stream, offset, whence); + case VFS_SCHEME_CDROM: + return retro_vfs_file_seek_cdrom(stream, offset, whence); + case VFS_SCHEME_CDROM_TRACK: + return retro_vfs_file_seek_cdrom_track(stream, offset, whence); + case VFS_SCHEME_CDROM_FILE: + return retro_vfs_file_seek_cdrom_file(stream, offset, whence); #endif + case VFS_SCHEME_CUE: + break; + case VFS_SCHEME_CUE_BIN: + return retro_vfs_file_seek_cdrom_track(stream, offset, whence); + case VFS_SCHEME_CUE_BIN_FILE: + return retro_vfs_file_seek_cdrom_file(stream, offset, whence); + default: + break; + } /* VC2005 and up have a special 64-bit fseek */ #ifdef ATLEAST_VC2005 return _fseeki64(stream->fp, offset, whence); @@ -403,28 +415,28 @@ libretro_vfs_implementation_file *retro_vfs_file_open_impl( } stream->fd = fd; #else - FILE *fp; #ifdef HAVE_CDROM if (stream->scheme == VFS_SCHEME_CDROM) { - retro_vfs_file_open_cdrom(stream, path, mode, hints); -#if defined(_WIN32) && !defined(_XBOX) - if (!stream->fh) + if (!retro_vfs_file_open_cdrom(stream, path, mode, hints)) goto error; -#else - if (!stream->fp) - goto error; -#endif } else #endif { + FILE *fp; + const char* ext; + fp = (FILE*)fopen_utf8(path, mode_str); if (!fp) goto error; stream->fp = fp; + + ext = path_get_extension(path); + if (string_is_equal_case_insensitive(ext, "cue")) + stream->scheme = VFS_SCHEME_CUE; } /* Regarding setvbuf: * @@ -437,11 +449,17 @@ libretro_vfs_implementation_file *retro_vfs_file_open_impl( */ /* TODO: this is only useful for a few platforms, find which and add ifdef */ #if !defined(PS2) && !defined(PSP) - if (stream->scheme != VFS_SCHEME_CDROM) + switch (stream->scheme) { - stream->buf = (char*)calloc(1, 0x4000); - if (stream->fp) - setvbuf(stream->fp, stream->buf, _IOFBF, 0x4000); + case VFS_SCHEME_CDROM: + case VFS_SCHEME_CDROM_FILE: + case VFS_SCHEME_CDROM_TRACK: + break; + default: + stream->buf = (char*)calloc(1, 0x4000); + if (stream->fp) + setvbuf(stream->fp, stream->buf, _IOFBF, 0x4000); + break; } #endif #endif @@ -491,18 +509,6 @@ libretro_vfs_implementation_file *retro_vfs_file_open_impl( stream->size = orbisLseek(stream->fd, 0, SEEK_END); orbisLseek(stream->fd, 0, SEEK_SET); #else -#ifdef HAVE_CDROM - if (stream->scheme == VFS_SCHEME_CDROM) - { - retro_vfs_file_seek_cdrom(stream, 0, SEEK_SET); - retro_vfs_file_seek_cdrom(stream, 0, SEEK_END); - - stream->size = retro_vfs_file_tell_impl(stream); - - retro_vfs_file_seek_cdrom(stream, 0, SEEK_SET); - } - else -#endif { retro_vfs_file_seek_internal(stream, 0, SEEK_SET); retro_vfs_file_seek_internal(stream, 0, SEEK_END); @@ -524,14 +530,28 @@ int retro_vfs_file_close_impl(libretro_vfs_implementation_file *stream) if (!stream) return -1; -#ifdef HAVE_CDROM - if (stream->scheme == VFS_SCHEME_CDROM) + switch (stream->scheme) { - retro_vfs_file_close_cdrom(stream); - goto end; - } +#ifdef HAVE_CDROM + case VFS_SCHEME_CDROM: + retro_vfs_file_close_cdrom(stream); + goto end; + + case VFS_SCHEME_CDROM_TRACK: + retro_vfs_file_close_cdrom_track(stream); + goto end; + + case VFS_SCHEME_CDROM_FILE: + break; #endif + case VFS_SCHEME_CUE_BIN: + retro_vfs_file_close_cdrom_track(stream); + goto end; + default: + break; + } + if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0) { if (stream->fp) @@ -556,8 +576,9 @@ int retro_vfs_file_close_impl(libretro_vfs_implementation_file *stream) close(stream->fd); #endif } -#ifdef HAVE_CDROM + end: +#ifdef HAVE_CDROM if (stream->cdrom.cue_buf) free(stream->cdrom.cue_buf); #endif @@ -616,10 +637,23 @@ int64_t retro_vfs_file_tell_impl(libretro_vfs_implementation_file *stream) if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0) { + switch (stream->scheme) + { #ifdef HAVE_CDROM - if (stream->scheme == VFS_SCHEME_CDROM) - return retro_vfs_file_tell_cdrom(stream); + case VFS_SCHEME_CDROM: + return retro_vfs_file_tell_cdrom(stream); + case VFS_SCHEME_CDROM_TRACK: + return retro_vfs_file_tell_cdrom_track(stream); + case VFS_SCHEME_CDROM_FILE: + return retro_vfs_file_tell_cdrom_file(stream); #endif + case VFS_SCHEME_CUE_BIN: + return retro_vfs_file_tell_cdrom_track(stream); + case VFS_SCHEME_CUE_BIN_FILE: + return retro_vfs_file_tell_cdrom_file(stream); + default: + break; + } #ifdef ORBIS { int64_t ret = orbisLseek(stream->fd, 0, SEEK_CUR); @@ -676,10 +710,25 @@ int64_t retro_vfs_file_read_impl(libretro_vfs_implementation_file *stream, if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0) { + switch (stream->scheme) + { #ifdef HAVE_CDROM - if (stream->scheme == VFS_SCHEME_CDROM) - return retro_vfs_file_read_cdrom(stream, s, len); + case VFS_SCHEME_CDROM: + return retro_vfs_file_read_cdrom(stream, s, len); + case VFS_SCHEME_CDROM_TRACK: + return retro_vfs_file_read_cdrom_track(stream, s, len); + case VFS_SCHEME_CDROM_FILE: + return retro_vfs_file_read_cdrom_file(stream, s, len); #endif + case VFS_SCHEME_CUE: + break; + case VFS_SCHEME_CUE_BIN: + return retro_vfs_file_read_cdrom_track(stream, s, len); + case VFS_SCHEME_CUE_BIN_FILE: + return retro_vfs_file_read_cdrom_file(stream, s, len); + default: + break; + } #ifdef ORBIS if (orbisRead(stream->fd, s, (size_t)len) < 0) return -1; diff --git a/libretro-common/vfs/vfs_implementation_cdrom.c b/libretro-common/vfs/vfs_implementation_cdrom.c index 537dc7acac..c1cd6cc29e 100644 --- a/libretro-common/vfs/vfs_implementation_cdrom.c +++ b/libretro-common/vfs/vfs_implementation_cdrom.c @@ -21,370 +21,695 @@ */ #include +#include #include #include #include +#include + +#ifdef HAVE_CDROM #include +#endif #if defined(_WIN32) && !defined(_XBOX) #include #endif +/*#define CDROM_CUE_PARSE_DEBUG*/ + +#ifdef HAVE_CDROM static cdrom_toc_t vfs_cdrom_toc = {0}; const cdrom_toc_t* retro_vfs_file_get_cdrom_toc(void) { return &vfs_cdrom_toc; } +#endif -int64_t retro_vfs_file_seek_cdrom(libretro_vfs_implementation_file *stream, int64_t offset, int whence) +static void retro_vfs_file_seek_cdrom_track_sector(libretro_vfs_implementation_file* stream, unsigned sector) { - const char *ext = path_get_extension(stream->orig_path); - - if (string_is_equal_noncase(ext, "cue")) - { - switch (whence) - { - case SEEK_SET: - stream->cdrom.byte_pos = offset; - break; - case SEEK_CUR: - stream->cdrom.byte_pos += offset; - break; - case SEEK_END: - stream->cdrom.byte_pos = (stream->cdrom.cue_len - 1) + offset; - break; - } - -#ifdef CDROM_DEBUG - printf("[CDROM] Seek: Path %s Offset %" PRIu64 " is now at %" PRIu64 "\n", stream->orig_path, offset, stream->cdrom.byte_pos); - fflush(stdout); -#endif - } - else if (string_is_equal_noncase(ext, "bin")) - { - int lba = (offset / 2352); - unsigned char min = 0; - unsigned char sec = 0; - unsigned char frame = 0; - const char *seek_type = "SEEK_SET"; - - (void)seek_type; - - switch (whence) - { - case SEEK_CUR: - { - unsigned new_lba; - - stream->cdrom.byte_pos += offset; - new_lba = vfs_cdrom_toc.track[stream->cdrom.cur_track - 1].lba + (stream->cdrom.byte_pos / 2352); - seek_type = "SEEK_CUR"; - - cdrom_lba_to_msf(new_lba, &min, &sec, &frame); - - break; - } - case SEEK_END: - { - ssize_t pregap_lba_len = (vfs_cdrom_toc.track[stream->cdrom.cur_track - 1].audio ? 0 : (vfs_cdrom_toc.track[stream->cdrom.cur_track - 1].lba - vfs_cdrom_toc.track[stream->cdrom.cur_track - 1].lba_start)); - ssize_t lba_len = vfs_cdrom_toc.track[stream->cdrom.cur_track - 1].track_size - pregap_lba_len; - - cdrom_lba_to_msf(lba_len + lba, &min, &sec, &frame); - - stream->cdrom.byte_pos = lba_len * 2352; - seek_type = "SEEK_END"; - - break; - } - case SEEK_SET: - default: - { - seek_type = "SEEK_SET"; - stream->cdrom.byte_pos = offset; - cdrom_lba_to_msf(vfs_cdrom_toc.track[stream->cdrom.cur_track - 1].lba + (stream->cdrom.byte_pos / 2352), &min, &sec, &frame); - break; - } - } - - stream->cdrom.cur_min = min; - stream->cdrom.cur_sec = sec; - stream->cdrom.cur_frame = frame; - stream->cdrom.cur_lba = cdrom_msf_to_lba(min, sec, frame); - -#ifdef CDROM_DEBUG - printf("[CDROM] Seek %s: Path %s Offset %" PRIu64 " is now at %" PRIu64 " (MSF %02u:%02u:%02u) (LBA %u)...\n", seek_type, stream->orig_path, offset, stream->cdrom.byte_pos, (unsigned)stream->cdrom.cur_min, (unsigned)stream->cdrom.cur_sec, (unsigned)stream->cdrom.cur_frame, stream->cdrom.cur_lba); - fflush(stdout); -#endif - } - else - return -1; - - return 0; + retro_vfs_file_seek_cdrom_track(stream, sector * stream->track->sector_size, SEEK_SET); } -void retro_vfs_file_open_cdrom( - libretro_vfs_implementation_file *stream, - const char *path, unsigned mode, unsigned hints) +static unsigned char retro_vs_file_get_sector_header_size(libretro_vfs_implementation_file* track_stream) { -#if defined(__linux__) && !defined(ANDROID) - char cdrom_path[] = "/dev/sg1"; - size_t path_len = strlen(path); - const char *ext = path_get_extension(path); + unsigned char buffer[32]; + unsigned char sector_header_size = 0; - stream->cdrom.cur_track = 1; + if (!track_stream || !track_stream->track) + return 0; - if (!string_is_equal_noncase(ext, "cue") && !string_is_equal_noncase(ext, "bin")) - return; + /* MODE information is normally found in the CUE sheet, but we can try to determine it from the raw data. + * + * MODE1/2048 - CDROM Mode1 Data (cooked) [no header, no footer] + * MODE1/2352 - CDROM Mode1 Data (raw) [16 byte header, 288 byte footer] + * MODE2/2336 - CDROM-XA Mode2 Data [8 byte header, 280 byte footer] + * MODE2/2352 - CDROM-XA Mode2 Data [24 byte header, 280 byte footer] + * + * Note that MODE is actually a property on each sector and can change between 1 and 2 depending on how much error + * correction the author desired. To support that, the data format must be "/2352" to include the full header and + * data without error correction information, at which point the CUE sheet information becomes just a hint. + * For simplicitly, we currently only handle "/2352" modes + */ - if (path_len >= strlen("drive1-track01.bin")) + /* The boot record or primary volume descriptor is always at sector 16 and will contain a "CD001" marker */ + retro_vfs_file_seek_cdrom_track_sector(track_stream, 16); + if (retro_vfs_file_read_cdrom_track(track_stream, buffer, sizeof(buffer)) == 0) + return 0; + + /* ISO-9660 says the first twelve bytes of a sector should be the sync pattern 00 FF FF FF FF FF FF FF FF FF FF 00 + * if it's not, then assume the track is audio data and has no header or MODE2/2336 which we don't support */ + if (buffer[0] == 0 && buffer[1] == 0xFF && buffer[2] == 0xFF && buffer[3] == 0xFF && + buffer[4] == 0xFF && buffer[5] == 0xFF && buffer[6] == 0xFF && buffer[7] == 0xFF && + buffer[8] == 0xFF && buffer[9] == 0xFF && buffer[10] == 0xFF && buffer[11] == 0) { - if (!memcmp(path, "drive", strlen("drive"))) + /* after the 12 byte sync pattern is three bytes identifying the sector and then one byte for the mode (total 16 bytes) */ + sector_header_size = 16; + track_stream->track->mode = buffer[15]; + + /* if this is a CDROM-XA data source, the "CD001" tag will be offset by an additional 8 bytes */ + if (buffer[25] == 0x43 && buffer[26] == 0x44 && + buffer[27] == 0x30 && buffer[28] == 0x30 && buffer[29] == 0x31) { - if (!memcmp(path + 6, "-track", strlen("-track"))) - { - if (sscanf(path + 12, "%02u", (unsigned*)&stream->cdrom.cur_track)) - { -#ifdef CDROM_DEBUG - printf("[CDROM] Opening track %d\n", stream->cdrom.cur_track); - fflush(stdout); -#endif - } - } + sector_header_size = 24; } } - if (path_len >= strlen("drive1.cue")) - { - if (!memcmp(path, "drive", strlen("drive"))) - { - if (path[5] >= '0' && path[5] <= '9') - { - cdrom_path[7] = path[5]; - stream->cdrom.drive = path[5]; - vfs_cdrom_toc.drive = stream->cdrom.drive; - } - } - } + return sector_header_size; +} +#ifdef HAVE_CDROM + +static bool retro_vfs_file_open_cdrom_handle(libretro_vfs_implementation_file* stream, const char* path, char drive) +{ #ifdef CDROM_DEBUG - printf("[CDROM] Open: Path %s URI %s\n", cdrom_path, path); + printf("[CDROM] Open: Path %s\n", path); fflush(stdout); #endif + +#if defined(__linux__) && !defined(ANDROID) + char cdrom_path[] = "/dev/sg1"; + cdrom_path[7] = drive; + stream->fp = (FILE*)fopen_utf8(cdrom_path, "r+b"); if (!stream->fp) - return; - - if (string_is_equal_noncase(ext, "cue")) - { - if (stream->cdrom.cue_buf) - { - free(stream->cdrom.cue_buf); - stream->cdrom.cue_buf = NULL; - } - - cdrom_write_cue(stream, &stream->cdrom.cue_buf, &stream->cdrom.cue_len, stream->cdrom.drive, &vfs_cdrom_toc.num_tracks, &vfs_cdrom_toc); - cdrom_get_timeouts(stream, &vfs_cdrom_toc.timeouts); - -#ifdef CDROM_DEBUG - if (string_is_empty(stream->cdrom.cue_buf)) - { - printf("[CDROM] Error writing cue sheet.\n"); - fflush(stdout); - } - else - { - printf("[CDROM] CUE Sheet:\n%s\n", stream->cdrom.cue_buf); - fflush(stdout); - } -#endif - } -#endif -#if defined(_WIN32) && !defined(_XBOX) + return false; +#elif defined(_WIN32) && !defined(_XBOX) char cdrom_path[] = "\\\\.\\D:"; - size_t path_len = strlen(path); - const char *ext = path_get_extension(path); + cdrom_path[4] = drive; - if (!string_is_equal_noncase(ext, "cue") && !string_is_equal_noncase(ext, "bin")) - return; - - if (path_len >= strlen("d:/drive-track01.bin")) - { - if (!memcmp(path + 1, ":/drive-track", strlen(":/drive-track"))) - { - if (sscanf(path + 14, "%02u", (unsigned*)&stream->cdrom.cur_track)) - { -#ifdef CDROM_DEBUG - printf("[CDROM] Opening track %d\n", stream->cdrom.cur_track); - fflush(stdout); -#endif - } - } - } - - if (path_len >= strlen("d:/drive.cue")) - { - if (!memcmp(path + 1, ":/drive", strlen(":/drive"))) - { - if ((path[0] >= 'A' && path[0] <= 'Z') || (path[0] >= 'a' && path[0] <= 'z')) - { - cdrom_path[4] = path[0]; - stream->cdrom.drive = path[0]; - vfs_cdrom_toc.drive = stream->cdrom.drive; - } - } - } - -#ifdef CDROM_DEBUG - printf("[CDROM] Open: Path %s URI %s\n", cdrom_path, path); - fflush(stdout); -#endif stream->fh = CreateFile(cdrom_path, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (stream->fh == INVALID_HANDLE_VALUE) - return; + { + stream->fh = NULL; + return false; + } +#endif + + stream->orig_path = strdup(path); + + cdrom_write_cue(stream, &stream->cdrom.cue_buf, &stream->cdrom.cue_len, stream->cdrom.drive, &vfs_cdrom_toc.num_tracks, &vfs_cdrom_toc); + cdrom_get_timeouts(stream, &vfs_cdrom_toc.timeouts); + + return true; +} + +#endif + +bool retro_vfs_file_open_cdrom( + libretro_vfs_implementation_file* stream, + const char* path, unsigned mode, unsigned hints) +{ + size_t path_len = strlen(path); + const char* ext = path_get_extension(path); if (string_is_equal_noncase(ext, "cue")) { - if (stream->cdrom.cue_buf) + stream->cdrom.drive = '\0'; + +#ifdef HAVE_CDROM +#if defined(__linux__) && !defined(ANDROID) + if (path_len >= strlen("drive1.cue")) { - free(stream->cdrom.cue_buf); - stream->cdrom.cue_buf = NULL; + if (!memcmp(path, "drive", strlen("drive"))) + { + if (path[5] >= '0' && path[5] <= '9') + { + stream->cdrom.drive = path[5]; + vfs_cdrom_toc.drive = stream->cdrom.drive; + + return retro_vfs_file_open_cdrom_handle(stream, path, stream->cdrom.drive); + } + } } +#elif defined(_WIN32) && !defined(_XBOX) + if (path_len >= strlen("d:/drive.cue")) + { + if (!memcmp(path + 1, ":/drive", strlen(":/drive"))) + { + if ((path[0] >= 'A' && path[0] <= 'Z') || (path[0] >= 'a' && path[0] <= 'z')) + { + stream->cdrom.drive = path[0]; + vfs_cdrom_toc.drive = stream->cdrom.drive; - cdrom_write_cue(stream, &stream->cdrom.cue_buf, &stream->cdrom.cue_len, stream->cdrom.drive, &vfs_cdrom_toc.num_tracks, &vfs_cdrom_toc); - cdrom_get_timeouts(stream, &vfs_cdrom_toc.timeouts); + return retro_vfs_file_open_cdrom_handle(stream, path, stream->cdrom.drive); + } + } + } +#endif +#endif + stream->fp = (FILE*)fopen_utf8(path, "r+b"); + if (stream->fp) + { + stream->scheme = VFS_SCHEME_CUE; + return true; + } + } +#ifdef HAVE_CDROM + else if (string_is_equal_noncase(ext, "bin")) + { + unsigned track = 0; + +#if defined(__linux__) && !defined(ANDROID) + if (path_len >= strlen("drive1-track01.bin")) + { + if (!memcmp(path, "drive", strlen("drive"))) + { + if (!memcmp(path + 6, "-track", strlen("-track"))) + sscanf(path + 12, "%02u", (unsigned*)& track); + + stream->cdrom.drive = path[5]; + if (!retro_vfs_file_open_cdrom_handle(stream, path, stream->cdrom.drive)) + return false; + } + } +#elif defined(_WIN32) && !defined(_XBOX) + if (path_len >= strlen("d:/drive-track01.bin")) + { + if (!memcmp(path + 1, ":/drive-track", strlen(":/drive-track"))) + { + sscanf(path + 14, "%02u", (unsigned*)& track); + + stream->cdrom.drive = path[0]; + vfs_cdrom_toc.drive = stream->cdrom.drive; + + if (!retro_vfs_file_open_cdrom_handle(stream, path, stream->cdrom.drive)) + return false; + } + } +#endif + + if (track) + { #ifdef CDROM_DEBUG - if (string_is_empty(stream->cdrom.cue_buf)) - { - printf("[CDROM] Error writing cue sheet.\n"); + printf("[CDROM] Opening track %u\n", track); fflush(stdout); - } - else - { - printf("[CDROM] CUE Sheet:\n%s\n", stream->cdrom.cue_buf); - fflush(stdout); - } #endif + + if (track > vfs_cdrom_toc.num_tracks) + track = 1; + + stream->scheme = VFS_SCHEME_CDROM_TRACK; + stream->parent = stream; + stream->size = vfs_cdrom_toc.track[track - 1].track_bytes; + stream->track = (vfs_cdrom_track_t*)calloc(1, sizeof(*stream->track)); + stream->track->sector_size = 2352; + stream->track->cur_track = track; + stream->track->cur_min = vfs_cdrom_toc.track[track - 1].min; + stream->track->cur_sec = vfs_cdrom_toc.track[track - 1].sec; + stream->track->cur_frame = vfs_cdrom_toc.track[track - 1].frame; + stream->track->cur_lba = cdrom_msf_to_lba(stream->track->cur_min, stream->track->cur_sec, stream->track->cur_frame); + stream->track->last_frame_lba = (unsigned)-1; + return true; + } } #endif - if (vfs_cdrom_toc.num_tracks > 1 && stream->cdrom.cur_track) - { - stream->cdrom.cur_min = vfs_cdrom_toc.track[stream->cdrom.cur_track - 1].min; - stream->cdrom.cur_sec = vfs_cdrom_toc.track[stream->cdrom.cur_track - 1].sec; - stream->cdrom.cur_frame = vfs_cdrom_toc.track[stream->cdrom.cur_track - 1].frame; - stream->cdrom.cur_lba = cdrom_msf_to_lba(stream->cdrom.cur_min, stream->cdrom.cur_sec, stream->cdrom.cur_frame); - } - else - { - stream->cdrom.cur_min = vfs_cdrom_toc.track[0].min; - stream->cdrom.cur_sec = vfs_cdrom_toc.track[0].sec; - stream->cdrom.cur_frame = vfs_cdrom_toc.track[0].frame; - stream->cdrom.cur_lba = cdrom_msf_to_lba(stream->cdrom.cur_min, stream->cdrom.cur_sec, stream->cdrom.cur_frame); - } + + return false; } int retro_vfs_file_close_cdrom(libretro_vfs_implementation_file *stream) { +#ifdef HAVE_CDROM #ifdef CDROM_DEBUG printf("[CDROM] Close: Path %s\n", stream->orig_path); fflush(stdout); #endif #if defined(_WIN32) && !defined(_XBOX) - if (!stream->fh || !CloseHandle(stream->fh)) + if (stream->fh && !CloseHandle(stream->fh)) return -1; #else if (!stream->fp || fclose(stream->fp)) return -1; #endif +#endif + + return 0; +} + +int64_t retro_vfs_file_seek_cdrom(libretro_vfs_implementation_file *stream, int64_t offset, int whence) +{ + switch (whence) + { + case SEEK_SET: + stream->cdrom.cue_pos = offset; + break; + + case SEEK_CUR: + stream->cdrom.cue_pos += offset; + break; + + case SEEK_END: + stream->cdrom.cue_pos = stream->cdrom.cue_len - offset; + break; + } + + if (stream->cdrom.cue_pos > stream->cdrom.cue_len) + stream->cdrom.cue_pos = stream->cdrom.cue_len; + if (stream->cdrom.cue_pos < 0) + stream->cdrom.cue_pos = 0; return 0; } int64_t retro_vfs_file_tell_cdrom(libretro_vfs_implementation_file *stream) { - const char *ext = NULL; - if (!stream) - return -1; - - ext = path_get_extension(stream->orig_path); - - if (string_is_equal_noncase(ext, "cue")) - { -#ifdef CDROM_DEBUG - printf("[CDROM] (cue) Tell: Path %s Position %" PRIu64 "\n", stream->orig_path, stream->cdrom.byte_pos); - fflush(stdout); -#endif - return stream->cdrom.byte_pos; - } - else if (string_is_equal_noncase(ext, "bin")) - { -#ifdef CDROM_DEBUG - printf("[CDROM] (bin) Tell: Path %s Position %" PRId64 "\n", stream->orig_path, stream->cdrom.byte_pos); - fflush(stdout); -#endif - return stream->cdrom.byte_pos; - } - - return -1; + return stream->cdrom.cue_pos; } -int64_t retro_vfs_file_read_cdrom(libretro_vfs_implementation_file *stream, - void *s, uint64_t len) +int64_t retro_vfs_file_read_cdrom(libretro_vfs_implementation_file* stream, + void* s, uint64_t len) { - int rv; - const char *ext = path_get_extension(stream->orig_path); + size_t bytes_to_copy; - if (string_is_equal_noncase(ext, "cue")) + if (!stream->cdrom.cue_buf) + return 0; + + bytes_to_copy = stream->cdrom.cue_len - stream->cdrom.cue_pos; + if (bytes_to_copy > len) + bytes_to_copy = len; + if (bytes_to_copy > 0) { - if ((int64_t)len < (int64_t)stream->cdrom.cue_len - stream->cdrom.byte_pos) - { -#ifdef CDROM_DEBUG - printf("[CDROM] Read: Reading %" PRIu64 " bytes from cuesheet starting at %" PRIu64 "...\n", len, stream->cdrom.byte_pos); - fflush(stdout); -#endif - memcpy(s, stream->cdrom.cue_buf + stream->cdrom.byte_pos, len); - stream->cdrom.byte_pos += len; + memcpy(s, &stream->cdrom.cue_buf[stream->cdrom.cue_pos], bytes_to_copy); + stream->cdrom.cue_pos += bytes_to_copy; + } - return len; - } - else + return bytes_to_copy; +} + +int retro_vfs_file_error_cdrom(libretro_vfs_implementation_file *stream) +{ + return 0; +} + +static void retro_vfs_skip_spaces(const char **buf, size_t len) +{ + if (!buf || !*buf) + return; + + while (len-- && (**buf == ' ' || **buf == '\t')) + ++(*buf); +} + +static bool retro_vfs_file_get_cue_track_path(const char* cue_contents, const char* cue_path, unsigned track, char* track_path, size_t max_track_path, int* track_pregap_bytes, int* sector_size) +{ + const char *line = NULL; + const char* cue = cue_contents; + char current_track_path[PATH_MAX_LENGTH] = {0}; + char track_mode[11] = {0}; + bool found_file = false; + unsigned found_track = 0; + + if (!cue_contents) + return false; + + track_path[0] = '\0'; + + while (*cue) + { + size_t len = 0; + + while (*cue && (*cue == ' ' || *cue == '\t')) + ++cue; + line = cue; + while (*cue && *cue != '\n') + ++cue; + len = cue - line; + if (*cue) + ++cue; + if (len == 0) + continue; + + if (!found_file && !strncasecmp(line, "FILE", 4)) { -#ifdef CDROM_DEBUG - printf("[CDROM] Read: Reading %" PRIu64 " bytes from cuesheet starting at %" PRIu64 " failed.\n", len, stream->cdrom.byte_pos); - fflush(stdout); + const char *file = line + 4; + retro_vfs_skip_spaces(&file, len - 4); + + if (!string_is_empty(file)) + { + const char *file_end = NULL; + size_t file_len = 0; + bool quoted = false; + + if (file[0] == '"') + { + quoted = true; + file++; + } + + if (quoted) + file_end = strchr(file, '\"'); + else + file_end = strchr(file, ' '); + + if (file_end) + { + file_len = file_end - file; + memcpy(current_track_path, file, file_len); + found_file = true; +#ifdef CDROM_CUE_PARSE_DEBUG + printf("Found file: %s\n", current_track_path); + fflush(stdout); #endif - return 0; + } + } + } + else if (found_file && !found_track && !strncasecmp(line, "TRACK", 5)) + { + const char *track = line + 5; + retro_vfs_skip_spaces(&track, len - 5); + + if (!string_is_empty(track)) + { + unsigned track_number = 0; + sscanf(track, "%u", &track_number); +#ifdef CDROM_CUE_PARSE_DEBUG + printf("Found track: %d\n", track_number); + fflush(stdout); +#endif + track++; + + if (track[0] && track[0] != ' ' && track[0] != '\t') + track++; + + if (!string_is_empty(track)) + { + retro_vfs_skip_spaces(&track, strlen(track)); +#ifdef CDROM_CUE_PARSE_DEBUG + printf("Found track type: %s\n", track); + fflush(stdout); +#endif + if (!strncasecmp(track, "MODE", 4)) + { + found_track = track_number; + strlcpy(track_mode, track, sizeof(track_mode)); + } + else + found_track = 0; + } + } + } + else if (found_file && found_track == track && !strncasecmp(line, "INDEX", 5)) + { + const char *index = line + 5; + retro_vfs_skip_spaces(&index, len - 5); + + if (!string_is_empty(index)) + { + unsigned index_number = 0; + sscanf(index, "%u", &index_number); + + if (index_number == 1) + { + const char *pregap = index + 1; + if (pregap[0] && pregap[0] != ' ' && pregap[0] != '\t') + pregap++; + + if (strstr(current_track_path, "/") || strstr(current_track_path, "\\")) + { + strncpy(track_path, current_track_path, max_track_path); +#ifdef CDROM_CUE_PARSE_DEBUG + printf("using path %s\n", track_path); + fflush(stdout); +#endif + } + else + { + fill_pathname_basedir(track_path, cue_path, max_track_path); + strlcat(track_path, current_track_path, max_track_path); +#ifdef CDROM_CUE_PARSE_DEBUG + printf("using absolute path %s\n", track_path); + fflush(stdout); +#endif + } + + if (!string_is_empty(pregap)) + { + retro_vfs_skip_spaces(&pregap, strlen(pregap)); + found_file = false; + found_track = false; + + if (!string_is_empty(track_mode)) + { + unsigned track_sector_size = 0; + unsigned track_mode_number = 0; + + if (strlen(track_mode) == 10) + { + sscanf(track_mode, "MODE%u/%u", &track_mode_number, &track_sector_size); +#ifdef CDROM_CUE_PARSE_DEBUG + printf("Found track mode %d with sector size %d\n", track_mode_number, track_sector_size); + fflush(stdout); +#endif + if ((track_mode_number == 1 || track_mode_number == 2) && track_sector_size) + { + unsigned min = 0; + unsigned sec = 0; + unsigned frame = 0; + sscanf(pregap, "%02u:%02u:%02u", &min, &sec, &frame); + + if (min || sec || frame || strstr(pregap, "00:00:00")) + { + if (sector_size) + *sector_size = track_sector_size; + if (track_pregap_bytes) + *track_pregap_bytes = ((min * 60 + sec) * 75 + frame) * track_sector_size; +#ifdef CDROM_CUE_PARSE_DEBUG + printf("Found pregap of %02u:%02u:%02u (bytes: %" PRIu64 ")\n", min, sec, frame, data_track_pregap_bytes); + fflush(stdout); +#endif + break; + } + } + } + } + } + } + } } } - else if (string_is_equal_noncase(ext, "bin")) + + return !string_is_empty(track_path); +} + +libretro_vfs_implementation_file* retro_vfs_file_open_cdrom_track(libretro_vfs_implementation_file* stream, const char* track) +{ + char track_path[PATH_MAX_LENGTH]; + unsigned track_index = 0; + + if (!stream->cdrom.cue_buf) { - size_t skip = stream->cdrom.byte_pos % 2352; + switch (stream->scheme) + { + case VFS_SCHEME_CUE: + stream->cdrom.cue_len = stream->size; + stream->cdrom.cue_buf = (char*)malloc(stream->size + 1); + if (stream->cdrom.cue_buf) + { + retro_vfs_file_seek_impl(stream, 0, RETRO_VFS_SEEK_POSITION_START); + retro_vfs_file_read_impl(stream, stream->cdrom.cue_buf, stream->cdrom.cue_len); + stream->cdrom.cue_buf[stream->cdrom.cue_len] = '\0'; + } + break; + +#ifdef HAVE_CDROM + case VFS_SCHEME_CDROM: + cdrom_write_cue(stream, &stream->cdrom.cue_buf, &stream->cdrom.cue_len, stream->cdrom.drive, &vfs_cdrom_toc.num_tracks, &vfs_cdrom_toc); + cdrom_get_timeouts(stream, &vfs_cdrom_toc.timeouts); + +#ifdef CDROM_DEBUG + if (string_is_empty(stream->cdrom.cue_buf)) + { + printf("[CDROM] Error writing cue sheet.\n"); + fflush(stdout); + } + else + { + printf("[CDROM] CUE Sheet:\n%s\n", stream->cdrom.cue_buf); + fflush(stdout); + } +#endif + break; +#endif + + default: + return NULL; + } + + if (!stream->cdrom.cue_buf) + return NULL; + } + + while (*track && (*track < '0' || *track > '9')) + ++track; + + sscanf(track, "%u", &track_index); + + if (track_index > 0) + { + int pregap_bytes = 0; + int sector_size = 2352; + if (retro_vfs_file_get_cue_track_path(stream->cdrom.cue_buf, stream->orig_path, track_index, track_path, PATH_MAX_LENGTH, &pregap_bytes, §or_size)) + { + libretro_vfs_implementation_file* track_stream; + if (stream->scheme == VFS_SCHEME_CDROM) + { +#ifdef HAVE_CDROM + track_stream = (libretro_vfs_implementation_file*)calloc(1, sizeof(*stream)); + track_stream->size = vfs_cdrom_toc.track[track_index - 1].track_bytes; +#endif + } + else + { + track_stream = retro_vfs_file_open_impl(track_path, RETRO_VFS_FILE_ACCESS_READ, stream->hints); + } + + if (track_stream) + { + track_stream->scheme = VFS_SCHEME_CDROM_TRACK; + track_stream->cdrom.drive = stream->cdrom.drive; + track_stream->parent = stream; + track_stream->parent_offset = pregap_bytes; + + track_stream->track = (vfs_cdrom_track_t*)calloc(1, sizeof(*track_stream->track)); + track_stream->track->cur_track = track_index; + track_stream->track->last_frame_lba = (unsigned)-1; + track_stream->track->sector_size = sector_size; + track_stream->track->sector_header_size = retro_vs_file_get_sector_header_size(track_stream); + + return track_stream; + } + } + } + + return NULL; +} + +int retro_vfs_file_close_cdrom_track(libretro_vfs_implementation_file* stream) +{ + if (stream->track) + free(stream->track); + + if (stream->parent == stream) + retro_vfs_file_close_cdrom(stream); + + return 0; +} + +int64_t retro_vfs_file_seek_cdrom_track(libretro_vfs_implementation_file* stream, int64_t offset, int whence) +{ + if (!stream->parent) + return -1; + + switch (whence) + { + case SEEK_SET: + stream->track->byte_pos = offset; + break; + + case SEEK_CUR: + stream->track->byte_pos += offset; + break; + + case SEEK_END: + stream->track->byte_pos = stream->size - offset; + break; + } + + if (stream->track->byte_pos >= stream->size) + stream->track->byte_pos = stream->size - 1; + if (stream->track->byte_pos < 0) + stream->track->byte_pos = 0; + + if (stream->parent->scheme == VFS_SCHEME_CUE) + { + int64_t rv; + + /* temporarily change scheme to prevent infinite recusion */ + stream->scheme = VFS_SCHEME_NONE; + rv = retro_vfs_file_seek_impl(stream, stream->track->byte_pos + stream->parent_offset, SEEK_SET); + stream->scheme = VFS_SCHEME_CUE_BIN; + + if (rv >= 0) + stream->track->cur_lba = stream->track->byte_pos / stream->track->sector_size; + + return rv; + } + +#ifdef HAVE_CDROM + stream->track->cur_lba = vfs_cdrom_toc.track[stream->track->cur_track - 1].lba + (stream->track->byte_pos / stream->track->sector_size); + cdrom_lba_to_msf(stream->track->cur_lba, &stream->track->cur_min, &stream->track->cur_sec, &stream->track->cur_frame); + +#ifdef CDROM_DEBUG + printf("[CDROM] Seek: Path %s Offset %" PRIu64 " is now at %" PRIu64 " (MSF %02u:%02u:%02u) (LBA %u)...\n", + stream->orig_path, offset, stream->track->byte_pos, (unsigned)stream->track->cur_min, (unsigned)stream->track->cur_sec, (unsigned)stream->track->cur_frame, stream->track->cur_lba); + fflush(stdout); +#endif +#endif + + return 0; +} + +static void retro_vfs_file_read_cdrom_track_sector(libretro_vfs_implementation_file* stream, void* s, uint64_t len) +{ + if (stream->parent->scheme == VFS_SCHEME_CUE) + { + /* temporarily change the scheme to prevent recursion */ + stream->scheme = VFS_SCHEME_NONE; + retro_vfs_file_read_impl(stream, s, len); + stream->scheme = VFS_SCHEME_CUE_BIN; + } +#ifdef HAVE_CDROM + else + { + int rv; unsigned char min = 0; unsigned char sec = 0; unsigned char frame = 0; unsigned char rmin = 0; unsigned char rsec = 0; unsigned char rframe = 0; + uint64_t byte_pos = stream->track->cur_lba * stream->track->sector_size; - if (stream->cdrom.byte_pos >= vfs_cdrom_toc.track[stream->cdrom.cur_track - 1].track_bytes) - return 0; + if (byte_pos >= vfs_cdrom_toc.track[stream->track->cur_track - 1].track_bytes) + return; - if (stream->cdrom.byte_pos + len > vfs_cdrom_toc.track[stream->cdrom.cur_track - 1].track_bytes) - len -= (stream->cdrom.byte_pos + len) - vfs_cdrom_toc.track[stream->cdrom.cur_track - 1].track_bytes; + if (byte_pos + len > vfs_cdrom_toc.track[stream->track->cur_track - 1].track_bytes) + len -= (byte_pos + len) - vfs_cdrom_toc.track[stream->track->cur_track - 1].track_bytes; - cdrom_lba_to_msf(stream->cdrom.cur_lba, &min, &sec, &frame); - cdrom_lba_to_msf(stream->cdrom.cur_lba - vfs_cdrom_toc.track[stream->cdrom.cur_track - 1].lba, &rmin, &rsec, &rframe); + cdrom_lba_to_msf(stream->track->cur_lba, &min, &sec, &frame); + cdrom_lba_to_msf(stream->track->cur_lba - vfs_cdrom_toc.track[stream->track->cur_track - 1].lba, &rmin, &rsec, &rframe); #ifdef CDROM_DEBUG - printf("[CDROM] Read: Reading %" PRIu64 " bytes from %s starting at byte offset %" PRIu64 " (rMSF %02u:%02u:%02u aMSF %02u:%02u:%02u) (LBA %u) skip %" PRIu64 "...\n", len, stream->orig_path, stream->cdrom.byte_pos, (unsigned)rmin, (unsigned)rsec, (unsigned)rframe, (unsigned)min, (unsigned)sec, (unsigned)frame, stream->cdrom.cur_lba, skip); + printf("[CDROM] Read: Reading %" PRIu64 " bytes from %s starting at byte offset %" PRIu64 " (rMSF %02u:%02u:%02u aMSF %02u:%02u:%02u) (LBA %u)...\n", len, stream->orig_path, byte_pos, (unsigned)rmin, (unsigned)rsec, (unsigned)rframe, (unsigned)min, (unsigned)sec, (unsigned)frame, stream->track->cur_lba); fflush(stdout); #endif - rv = cdrom_read(stream, &vfs_cdrom_toc.timeouts, min, sec, frame, s, (size_t)len, skip); + rv = cdrom_read(stream, &vfs_cdrom_toc.timeouts, min, sec, frame, s, (size_t)len, 0); /*rv = cdrom_read_lba(stream, stream->cdrom.cur_lba, s, (size_t)len, skip);*/ if (rv) @@ -393,31 +718,256 @@ int64_t retro_vfs_file_read_cdrom(libretro_vfs_implementation_file *stream, printf("[CDROM] Failed to read %" PRIu64 " bytes from CD.\n", len); fflush(stdout); #endif - return 0; + } + } +#endif +} + +int64_t retro_vfs_file_read_cdrom_track(libretro_vfs_implementation_file *stream, void *s, uint64_t len) +{ + uint64_t bytes_read = 0; + + if (!stream->parent) + return -1; + + if (stream->track->last_frame_lba == stream->track->cur_lba) + { + const int used = stream->track->byte_pos % stream->track->sector_size; + int remaining = stream->track->sector_size - used; + if (remaining > len) + { + memcpy(s, stream->track->last_frame + used, len); + stream->track->byte_pos += len; + return len; } - stream->cdrom.byte_pos += len; - stream->cdrom.cur_lba = vfs_cdrom_toc.track[stream->cdrom.cur_track - 1].lba + (stream->cdrom.byte_pos / 2352); + memcpy(s, stream->track->last_frame + used, remaining); + s = (char*)s + remaining; + len -= remaining; + bytes_read += remaining; - cdrom_lba_to_msf(stream->cdrom.cur_lba, &stream->cdrom.cur_min, &stream->cdrom.cur_sec, &stream->cdrom.cur_frame); - -#ifdef CDROM_DEBUG - printf("[CDROM] read %" PRIu64 " bytes, position is now: %" PRIu64 " (MSF %02u:%02u:%02u) (LBA %u)\n", len, stream->cdrom.byte_pos, (unsigned)stream->cdrom.cur_min, (unsigned)stream->cdrom.cur_sec, (unsigned)stream->cdrom.cur_frame, cdrom_msf_to_lba(stream->cdrom.cur_min, stream->cdrom.cur_sec, stream->cdrom.cur_frame)); - fflush(stdout); -#endif - - return len; + stream->track->cur_lba++; + retro_vfs_file_seek_cdrom_track_sector(stream, stream->track->cur_lba); } - return 0; + while (len >= stream->track->sector_size) + { + retro_vfs_file_read_cdrom_track_sector(stream, s, stream->track->sector_size); + s = (char*)s + 2352; + + stream->track->cur_lba++; + + bytes_read += 2352; + len -= 2352; + } + + stream->track->byte_pos = stream->track->cur_lba * stream->track->sector_size; + + if (len > 0) + { + retro_vfs_file_read_cdrom_track_sector(stream, stream->track->last_frame, stream->track->sector_size); + memcpy(s, stream->track->last_frame, len); + bytes_read += len; + + stream->track->byte_pos += len; + stream->track->last_frame_lba = stream->track->cur_lba; + +#ifdef HAVE_CDROM + cdrom_lba_to_msf(stream->track->cur_lba, &stream->track->cur_min, &stream->track->cur_sec, &stream->track->cur_frame); +#endif + } + + return bytes_read; } -int retro_vfs_file_error_cdrom(libretro_vfs_implementation_file *stream) +int64_t retro_vfs_file_tell_cdrom_track(libretro_vfs_implementation_file* stream) { - return 0; + return stream->track->byte_pos; } -const vfs_cdrom_t* retro_vfs_file_get_cdrom_position(const libretro_vfs_implementation_file *stream) +static int retro_vfs_file_find_cdrom_file_sector(libretro_vfs_implementation_file* track_stream, const char* path, unsigned* file_size) { - return &stream->cdrom; + uint8_t buffer[2352], *tmp; + int sector, path_length; + + const char* slash = strrchr(path, '\\'); + if (slash) + { + /* navigate the path to the directory record for the file */ + const int dir_length = (int)(slash - path); + memcpy(buffer, path, dir_length); + buffer[dir_length] = '\0'; + + sector = retro_vfs_file_find_cdrom_file_sector(track_stream, (const char*)buffer, NULL); + if (sector < 0) + return sector; + + path += dir_length + 1; + } + else + { + int offset; + + /* find the cd information (always 16 frames in) */ + retro_vfs_file_seek_cdrom_track_sector(track_stream, 16); + retro_vfs_file_read_cdrom_track(track_stream, buffer, sizeof(buffer)); + + /* the directory_record starts at 156 bytes into the sector. + * the sector containing the table of contents is a 3 byte value that is 2 bytes into the directory_record. */ + offset = track_stream->track->sector_header_size + 156 + 2; + sector = buffer[offset] | (buffer[offset + 1] << 8) | (buffer[offset + 2] << 16); + } + + /* process the table of contents */ + retro_vfs_file_seek_cdrom_track_sector(track_stream, sector); + retro_vfs_file_read_cdrom_track(track_stream, buffer, sizeof(buffer)); + + path_length = strlen(path); + tmp = buffer + track_stream->track->sector_header_size; + while (tmp < buffer + sizeof(buffer)) + { + /* the first byte of the record is the length of the record - if 0, we reached the end of the data */ + if (!*tmp) + break; + + /* filename is 33 bytes into the record and the format is "FILENAME;version" or "DIRECTORY" */ + if ((tmp[33 + path_length] == ';' || tmp[33 + path_length] == '\0') && + strncasecmp((const char*)(tmp + 33), path, path_length) == 0) + { + /* the file contents are in the sector identified in bytes 2-4 of the record */ + sector = tmp[2] | (tmp[3] << 8) | (tmp[4] << 16); + retro_vfs_file_seek_cdrom_track_sector(track_stream, sector); + + /* the file size is in bytes 10-13 of the record */ + if (file_size) + *file_size = tmp[10] | (tmp[11] << 8) | (tmp[12] << 16) | (tmp[13] << 24); + +#ifdef CDROM_DEBUG + { + unsigned char min, sec, frame; + cdrom_lba_to_msf(sector, &min, &sec, &frame); + printf("[CDROM] found %s (MSF %02u:%02u:%02u) (LBA %u)\n", path, (unsigned)min, (unsigned)sec, (unsigned)frame, sector); + fflush(stdout); + } +#endif + + return sector; + } + + /* the first byte of the record is the length of the record */ + tmp += tmp[0]; + } + +#ifdef CDROM_DEBUG + printf("[CDROM] did not find %s\n", path); + fflush(stdout); +#endif + + return -1; +} + +libretro_vfs_implementation_file* retro_vfs_file_open_cdrom_file(libretro_vfs_implementation_file* track_stream, const char* path) +{ + libretro_vfs_implementation_file* file_stream; + unsigned file_size = 0; + + int sector = retro_vfs_file_find_cdrom_file_sector(track_stream, path, &file_size); + if (sector < 0) + return NULL; + + file_stream = (libretro_vfs_implementation_file*)calloc(1, sizeof(*file_stream)); + file_stream->scheme = (track_stream->scheme == VFS_SCHEME_CDROM_TRACK) ? VFS_SCHEME_CDROM_FILE : VFS_SCHEME_CUE_BIN_FILE; + file_stream->parent = track_stream; + file_stream->parent_offset = sector * 2352 + track_stream->track->sector_header_size; + file_stream->orig_path = strdup(path); + file_stream->size = file_size; + + return file_stream; +} + +int64_t retro_vfs_file_tell_cdrom_file(libretro_vfs_implementation_file* stream) +{ + if (stream->parent != NULL && stream->parent->track) + { + const int64_t current_offset_raw = stream->parent->track->byte_pos - stream->parent_offset; + return (current_offset_raw / stream->parent->track->sector_size) * 2048 + (current_offset_raw % stream->parent->track->sector_size); + } + + return -1; +} + +int64_t retro_vfs_file_seek_cdrom_file(libretro_vfs_implementation_file* stream, int64_t offset, int whence) +{ + int64_t offset_raw; + switch (whence) + { + case SEEK_CUR: + { + const int64_t current_offset_raw = stream->parent->track->byte_pos - stream->parent_offset; + const int64_t current_offset = (current_offset_raw / stream->parent->track->sector_size) * 2048 + (current_offset_raw % stream->parent->track->sector_size); + offset = current_offset + offset; + break; + } + + case SEEK_END: + offset = stream->size - offset; + break; + } + + offset_raw = (offset / 2048) * stream->parent->track->sector_size + (offset % 2048); + + return retro_vfs_file_seek_cdrom_track(stream->parent, offset_raw, SEEK_SET); +} + +int64_t retro_vfs_file_read_cdrom_file(libretro_vfs_implementation_file* stream, void* s, uint64_t len) +{ + uint8_t buffer[2352]; + int64_t bytes_read = 0; + + if (!stream || !stream->parent || !stream->parent->track) + return 0; + + if (stream->parent->track->last_frame_lba == stream->parent->track->cur_lba) + { + const int used = stream->parent->track->byte_pos % stream->parent->track->sector_size - stream->parent->track->sector_header_size; + int remaining = 2048 - used; + if (remaining > 0) + { + if (remaining > len) + { + retro_vfs_file_read_cdrom_track(stream->parent, s, len); + return len; + } + + retro_vfs_file_read_cdrom_track(stream->parent, s, remaining); + s = (char*)s + remaining; + bytes_read += remaining; + } + + stream->parent->track->cur_lba = stream->parent->track->last_frame_lba + 1; + } + + while (len >= 2048) + { + retro_vfs_file_read_cdrom_track_sector(stream->parent, buffer, stream->parent->track->sector_size); + stream->parent->track->cur_lba++; + + memcpy(s, buffer + stream->parent->track->sector_header_size, 2048); + s = (char*)s + 2048; + bytes_read += 2048; + len -= 2048; + } + + stream->parent->track->byte_pos = stream->parent->track->cur_lba * stream->parent->track->sector_size; + + if (len > 0) + { + retro_vfs_file_read_cdrom_track(stream->parent, buffer, len + stream->parent->track->sector_header_size); + memcpy(s, buffer + stream->parent->track->sector_header_size, len); + + stream->parent->track->byte_pos += len; + bytes_read += len; + } + + return bytes_read; }