From b1fb7a1c1ec05072f6738c0fe5be097a3c939cd2 Mon Sep 17 00:00:00 2001 From: Jamiras Date: Fri, 30 Aug 2019 19:38:58 -0600 Subject: [PATCH] add hashing support for PSX cheevos (bin/cue, chd, or real CD) --- Makefile.common | 1 + cheevos-new/cheevos.c | 183 ++++++- griffin/griffin.c | 1 + libretro-common/formats/cdfs/cdfs.c | 477 ++++++++++++++++++ libretro-common/formats/libchdr/libchdr_chd.c | 3 + libretro-common/include/formats/cdfs.h | 92 ++++ 6 files changed, 750 insertions(+), 7 deletions(-) create mode 100644 libretro-common/formats/cdfs/cdfs.c create mode 100644 libretro-common/include/formats/cdfs.h diff --git a/Makefile.common b/Makefile.common index 0b2889f0b2b..352d15c64de 100644 --- a/Makefile.common +++ b/Makefile.common @@ -1777,6 +1777,7 @@ ifeq ($(HAVE_NETWORKING), 1) cheevos-new/fixup.o \ cheevos-new/parser.o \ cheevos-new/hash.o \ + $(LIBRETRO_COMM_DIR)/formats/cdfs/cdfs.o \ deps/rcheevos/src/rcheevos/trigger.o \ deps/rcheevos/src/rcheevos/condset.o \ deps/rcheevos/src/rcheevos/condition.o \ diff --git a/cheevos-new/cheevos.c b/cheevos-new/cheevos.c index caf85fbc943..831fa9be5db 100644 --- a/cheevos-new/cheevos.c +++ b/cheevos-new/cheevos.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -1123,6 +1124,7 @@ typedef struct struct http_connection_t *conn; struct http_t *http; const rcheevos_cheevo_t *cheevo_end; + cdfs_file_t cdfp; /* co-routine required fields */ CORO_FIELDS @@ -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,13 @@ 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; + size_t exe_name_size = 0; + char exe_name_buffer[64]; + char* exe_name = NULL; + char* scan = NULL; + char buffer[2048]; static const uint32_t genesis_exts[] = { @@ -1192,12 +1200,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,15 +1263,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 +1563,157 @@ static int rcheevos_iterate(rcheevos_coro_t* coro) 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) + { + MD5_Init(&coro->md5); + + /* if we're looking at an m3u file, get the first disc from the playlist */ + end = path_get_extension(coro->path); + if (string_is_equal_noncase(end, "m3u")) + { + intfstream_t* m3u_stream = intfstream_open_file(coro->path, RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE); + if (m3u_stream) + { + char disc_path[PATH_MAX_LENGTH]; + char* tmp; + + intfstream_read(m3u_stream, buffer, sizeof(buffer)); + intfstream_close(m3u_stream); + + 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); + } + } + + /* find the data track - it should be the first one */ + coro->stream = cdfs_open_data_track(coro->path); + if (coro->stream) + { + /* open the SYSTEM.CNF file and find the BOOT= record */ + if (cdfs_open_file(&coro->cdfp, coro->stream, "SYSTEM.CNF")) + { + cdfs_read_file(&coro->cdfp, buffer, sizeof(buffer)); + + 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; + } + + cdfs_close_file(&coro->cdfp); + + 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); + + /* open the file pointed to by the BOOT= record */ + if (exe_name_buffer[0] && cdfs_open_file(&coro->cdfp, coro->stream, exe_name_buffer)) + { + cdfs_read_file(&coro->cdfp, 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->cdfp->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 */ + { + cdfs_close_file(&coro->cdfp); + } + else + { + /* there's 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; + + memcpy((uint8_t*)coro->data + coro->len, buffer, sizeof(buffer)); + coro->len += sizeof(buffer); + + while (coro->len < coro->count) + { + CORO_YIELD(); + + to_read = coro->count - coro->len; + if (to_read > 2048) + to_read = 2048; + + cdfs_read_file(&coro->cdfp, (uint8_t*)coro->data + coro->len, to_read); + + coro->len += to_read; + }; + + CORO_GOSUB(RCHEEVOS_EVAL_MD5); + MD5_Final(coro->hash, &coro->md5); + + cdfs_close_file(&coro->cdfp); + + intfstream_close(coro->stream); + CHEEVOS_FREE(coro->stream); + + CORO_GOTO(RCHEEVOS_GET_GAMEID); + } + } + } + } + + intfstream_close(coro->stream); + CHEEVOS_FREE(coro->stream); + } + + coro->gameid = 0; + CORO_RET(); + } + + /************************************************************************** * Info Tries to identify a "generic" game * Input CHEEVOS_VAR_INFO the content info diff --git a/griffin/griffin.c b/griffin/griffin.c index dd834caebd7..9e40b2d0953 100644 --- a/griffin/griffin.c +++ b/griffin/griffin.c @@ -157,6 +157,7 @@ ACHIEVEMENTS #endif #include "../libretro-common/formats/json/jsonsax.c" +#include "../libretro-common/formats/cdfs/cdfs.c" #include "../network/net_http_special.c" #include "../cheevos-new/cheevos.c" diff --git a/libretro-common/formats/cdfs/cdfs.c b/libretro-common/formats/cdfs/cdfs.c new file mode 100644 index 00000000000..efe71fbbaeb --- /dev/null +++ b/libretro-common/formats/cdfs/cdfs.c @@ -0,0 +1,477 @@ +#include "formats/cdfs.h" + +#include +#include +#include +#include + +static void cdfs_determine_sector_size(cdfs_file_t* file) +{ + uint8_t buffer[32]; + int64_t stream_size; + + /* 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. + */ + + /* The boot record or primary volume descriptor is always at sector 16 and will contain a "CD001" marker */ + intfstream_seek(file->stream, 16 * 2352, SEEK_SET); + if (intfstream_read(file->stream, buffer, sizeof(buffer)) < sizeof(buffer)) + return; + + /* if this is a CDROM-XA data source, the "CD001" tag will be 25 bytes into the sector */ + if (buffer[25] == 0x43 && buffer[26] == 0x44 && + buffer[27] == 0x30 && buffer[28] == 0x30 && buffer[29] == 0x31) + { + file->stream_sector_size = 2352; + file->stream_sector_header_size = 24; + } + /* otherwise it should be 17 bytes into the sector */ + else if (buffer[17] == 0x43 && buffer[18] == 0x44 && + buffer[19] == 0x30 && buffer[20] == 0x30 && buffer[21] == 0x31) + { + file->stream_sector_size = 2352; + file->stream_sector_header_size = 16; + } + else + { + /* 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 (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) + { + /* don't actually expect to get here - a properly headered sector should have had the CD001 tag */ + + /* after the 12 byte sync pattern is three bytes identifying the sector and then one byte for the mode (total 16 bytes) */ + file->stream_sector_size = 2352; + file->stream_sector_header_size = 16; + } + else + { + /* no recognizable header - attempt to determine sector size from stream size */ + stream_size = intfstream_get_size(file->stream); + + if ((stream_size % 2352) == 0) + { + /* audio tracks use all 2352 bytes without a header */ + file->stream_sector_size = 2352; + } + else if ((stream_size % 2048) == 0) + { + /* cooked tracks eliminate all header/footer data */ + file->stream_sector_size = 2048; + } + else if ((stream_size % 2336) == 0) + { + /* MODE 2 format without 16-byte sync data */ + file->stream_sector_size = 2336; + file->stream_sector_header_size = 8; + } + } + } +} + +static void cdfs_seek_sector(cdfs_file_t* file, unsigned int sector) +{ + intfstream_seek(file->stream, sector * file->stream_sector_size + file->stream_sector_header_size, SEEK_SET); +} + +static int cdfs_find_file(cdfs_file_t* file, const char* path) +{ + uint8_t buffer[2048], *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 = cdfs_find_file(file, (const char*)buffer); + if (sector < 0) + return sector; + + path += dir_length + 1; + } + else + { + int offset; + + /* find the cd information (always 16 frames in) */ + cdfs_seek_sector(file, 16); + intfstream_read(file->stream, buffer, sizeof(buffer)); + + /* the directory_record starts at 156 bytes into the sector. + * the sector containing the root directory contents is a 3 byte value that is 2 bytes into the directory_record. */ + offset = 156 + 2; + sector = buffer[offset] | (buffer[offset + 1] << 8) | (buffer[offset + 2] << 16); + } + + /* process the contents of the directory */ + cdfs_seek_sector(file, sector); + intfstream_read(file->stream, buffer, sizeof(buffer)); + + path_length = strlen(path); + tmp = buffer; + 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 size is in bytes 10-13 of the record */ + if (!slash) + file->size = tmp[10] | (tmp[11] << 8) | (tmp[12] << 16) | (tmp[13] << 24); + + /* 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 sector; + } + + /* the first byte of the record is the length of the record */ + tmp += tmp[0]; + } + + return -1; +} + +int cdfs_open_file(cdfs_file_t* file, intfstream_t* stream, const char* path) +{ + if (!file || !stream || !path) + return 0; + + memset(file, 0, sizeof(*file)); + + file->stream = stream; + cdfs_determine_sector_size(file); + + file->current_sector = -1; + file->first_sector = cdfs_find_file(file, path); + + return (file->first_sector > 0); +} + +int64_t cdfs_read_file(cdfs_file_t* file, void* buffer, uint64_t len) +{ + int bytes_read = 0; + + if (!file || !file->first_sector || !buffer) + return 0; + + if (len > file->size - file->pos) + len = file->size - file->pos; + + if (len == 0) + return 0; + + if (file->sector_buffer_valid) + { + size_t remaining = 2048 - file->current_sector_offset; + if (remaining > 0) + { + if (remaining >= len) + { + memcpy(buffer, &file->sector_buffer[file->current_sector_offset], len); + file->current_sector_offset += len; + return len; + } + + memcpy(buffer, &file->sector_buffer[file->current_sector_offset], remaining); + buffer = (char*)buffer + remaining; + bytes_read += remaining; + len -= remaining; + + file->current_sector_offset += remaining; + } + + ++file->current_sector; + file->current_sector_offset = 0; + file->sector_buffer_valid = 0; + } + else if (file->current_sector < file->first_sector) + { + file->current_sector = file->first_sector; + file->current_sector_offset = 0; + } + + while (len >= 2048) + { + cdfs_seek_sector(file, file->current_sector); + intfstream_read(file->stream, buffer, 2048); + + buffer = (char*)buffer + 2048; + bytes_read += 2048; + ++file->current_sector; + + len -= 2048; + } + + if (len > 0) + { + cdfs_seek_sector(file, file->current_sector); + intfstream_read(file->stream, file->sector_buffer, 2048); + memcpy(buffer, file->sector_buffer, len); + file->current_sector_offset = len; + file->sector_buffer_valid = 1; + + bytes_read += len; + } + + file->pos += bytes_read; + return bytes_read; +} + +void cdfs_close_file(cdfs_file_t* file) +{ + if (file) + { + /* not really anything to do here, just clear out the first_sector so read() won't do anything */ + file->first_sector = 0; + } +} + +int64_t cdfs_get_size(cdfs_file_t* file) +{ + if (!file || !file->first_sector) + return 0; + + return file->size; +} + +int64_t cdfs_tell(cdfs_file_t* file) +{ + if (!file || !file->first_sector) + return -1; + + return file->pos; +} + +int64_t cdfs_seek(cdfs_file_t* file, int64_t offset, int whence) +{ + int64_t new_pos; + int new_sector; + + if (!file || !file->first_sector) + return -1; + + switch (whence) + { + case SEEK_SET: + new_pos = offset; + break; + + case SEEK_CUR: + new_pos = file->pos + offset; + break; + + case SEEK_END: + new_pos = file->size - offset; + break; + + default: + return -1; + } + + if (new_pos < 0) + return -1; + else if (new_pos > file->size) + return -1; + + file->pos = (unsigned int)new_pos; + file->current_sector_offset = file->pos % 2048; + + new_sector = file->pos / 2048; + if (new_sector != file->current_sector) + { + file->current_sector = new_sector; + file->sector_buffer_valid = false; + } + + return 0; +} + +static void cdfs_skip_spaces(const char** ptr) +{ + while (**ptr && (**ptr == ' ' || **ptr == '\t')) + ++(*ptr); +} + +static intfstream_t* cdfs_open_cue_track(const char* path, unsigned int track_index) +{ + char* cue_contents = NULL; + char* cue = NULL; + const char* line = NULL; + int found_track = 0; + char current_track_path[PATH_MAX_LENGTH] = {0}; + char track_path[PATH_MAX_LENGTH] = {0}; + intfstream_t* cue_stream = NULL; + int64_t stream_size = 0; + + cue_stream = intfstream_open_file(path, RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE); + + stream_size = intfstream_get_size(cue_stream); + cue_contents = (char*)malloc(stream_size + 1); + if (!cue_contents) + { + intfstream_close(cue_stream); + return NULL; + } + + intfstream_read(cue_stream, cue_contents, stream_size); + intfstream_close(cue_stream); + + cue_contents[stream_size] = '\0'; + + cue = cue_contents; + while (*cue) + { + cdfs_skip_spaces((const char**)&cue); + line = cue; + + while (*cue && *cue != '\n') + ++cue; + if (cue == line) + continue; + if (*cue) + *cue++ = '\0'; + + if (!strncasecmp(line, "FILE", 4)) + { + const char *file = line + 4; + cdfs_skip_spaces(&file); + + if (file[0]) + { + const char *file_end = cue - 1; + while (file_end > file && *file_end != ' ' && *file_end != '\t') + --file_end; + + if (file[0] == '"' && file_end[-1] == '"') + { + ++file; + --file_end; + } + + memcpy(current_track_path, file, file_end - file); + current_track_path[file_end - file] = '\0'; + } + } + else if (!strncasecmp(line, "TRACK", 5)) + { + unsigned track_number = 0; + + const char *track = line + 5; + cdfs_skip_spaces(&track); + + sscanf(track, "%d", &track_number); + + if (track_index) + { + if (track_index == track_number) + found_track = track_number; + } + else /* track_index = 0 means find the first data track */ + { + while (track[0] && track[0] != ' ' && track[0] != '\t') + track++; + + if (track[0]) + { + cdfs_skip_spaces(&track); + + if (!strncasecmp(track, "MODE", 4)) + found_track = track_number; + } + } + } + else if (found_track && !strncasecmp(line, "INDEX", 5)) + { + const char *index = line + 5; + cdfs_skip_spaces(&index); + + if (index[0]) + { + unsigned index_number = 0; + sscanf(index, "%u", &index_number); + + if (index_number == 1) + { + if (strstr(current_track_path, "/") || strstr(current_track_path, "\\")) + { + strncpy(track_path, current_track_path, sizeof(track_path)); + } + else + { + fill_pathname_basedir(track_path, path, sizeof(track_path)); + strlcat(track_path, current_track_path, sizeof(track_path)); + } + + break; + } + } + } + } + + free(cue_contents); + + if (string_is_empty(track_path)) + return NULL; + + return intfstream_open_file(track_path, RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE); +} + +intfstream_t* cdfs_open_track(const char* path, unsigned int track_index) +{ + const char* ext = path_get_extension(path); + + if (string_is_equal_noncase(ext, "cue")) + return cdfs_open_cue_track(path, track_index); + + if (string_is_equal_noncase(ext, "chd")) + return intfstream_open_chd_track(path, RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE, track_index); + + /* unsupported file type */ + return NULL; +} + +intfstream_t* cdfs_open_data_track(const char* path) +{ + const char* ext = path_get_extension(path); + + if (string_is_equal_noncase(ext, "cue")) + return cdfs_open_cue_track(path, 0); + + if (string_is_equal_noncase(ext, "chd")) + { + /* TODO: determine data track */ + return intfstream_open_chd_track(path, RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE, 1); + } + + /* unsupported file type */ + return NULL; +} + +intfstream_t* cdfs_open_raw_track(const char* path) +{ + const char* ext = path_get_extension(path); + + if (string_is_equal_noncase(ext, "bin") || string_is_equal_noncase(ext, "iso")) + return intfstream_open_file(path, RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE); + + /* unsupported file type */ + return NULL; +} diff --git a/libretro-common/formats/libchdr/libchdr_chd.c b/libretro-common/formats/libchdr/libchdr_chd.c index 3e3de49c261..1f36c060ee2 100644 --- a/libretro-common/formats/libchdr/libchdr_chd.c +++ b/libretro-common/formats/libchdr/libchdr_chd.c @@ -1037,6 +1037,9 @@ 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/formats/cdfs.h b/libretro-common/include/formats/cdfs.h new file mode 100644 index 00000000000..ff9cd0da0f8 --- /dev/null +++ b/libretro-common/include/formats/cdfs.h @@ -0,0 +1,92 @@ +/* Copyright (C) 2010-2019 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this file (cdfs.h). + * --------------------------------------------------------------------------------------- + * + * Permission is hereby granted, free of charge, + * to any person obtaining a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef __RARCH_CDFS_H +#define __RARCH_CDFS_H + +#include + +RETRO_BEGIN_DECLS + +/* these functions provide an interface for locating and reading files within a data track + * of a CD (following the ISO-9660 directory structure definition) + */ + +typedef struct cdfs_file_t +{ + int first_sector; + int current_sector; + unsigned int current_sector_offset; + int sector_buffer_valid; + unsigned int stream_sector_size; + unsigned int stream_sector_header_size; + unsigned int size; + unsigned int pos; + intfstream_t* stream; + uint8_t sector_buffer[2048]; +} cdfs_file_t; + +int cdfs_open_file(cdfs_file_t* file, intfstream_t* stream, const char* path); + +void cdfs_close_file(cdfs_file_t* file); + +int64_t cdfs_read_file(cdfs_file_t* file, void* buffer, uint64_t len); + +int64_t cdfs_get_size(cdfs_file_t* file); + +int64_t cdfs_tell(cdfs_file_t* file); + +int64_t cdfs_seek(cdfs_file_t* file, int64_t offset, int whence); + +/* opens the specified track in a CD or virtual CD file - the resulting stream should be passed to + * cdfs_open_file to get access to a file within the CD. + * + * supported files: + * real CD - path will be in the form "cdrom://drive1.cue" or "cdrom://d:/drive.cue" + * bin/cue - path will point to the cue file + * chd - path will point to the chd file + * + * for bin/cue files, the following storage modes are supported: + * MODE2/2352 + * MODE1/2352 + * MODE1/2048 - untested + * MODE2/2336 - untested + */ +intfstream_t* cdfs_open_track(const char* path, unsigned int track_index); + +/* opens the first data track in a CD or virtual CD file. see cdfs_open_track for supported file formats + */ +intfstream_t* cdfs_open_data_track(const char* path); + +/* opens a raw track file for a CD or virtual CD. + * + * supported files: + * real CD - path will be in the form "cdrom://drive1-track01.bin" or "cdrom://d:/drive-track01.bin" + * NOTE: cue file for CD must be opened first to populate vfs_cdrom_toc. + * bin - path will point to the bin file + * iso - path will point to the iso file + */ +intfstream_t* cdfs_open_raw_track(const char* path); + +RETRO_END_DECLS + +#endif /* __RARCH_CDFS_H */