mirror of
https://github.com/libretro/RetroArch
synced 2025-02-07 21:39:54 +00:00
Merge pull request #9405 from Jamiras/psx_cheevos_cdfs
add hashing support for PSX cheevos (bin/cue, chd, or real CD)
This commit is contained in:
commit
85f07b6f27
@ -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 \
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include <streams/interface_stream.h>
|
||||
#include <streams/file_stream.h>
|
||||
#include <features/features_cpu.h>
|
||||
#include <formats/cdfs.h>
|
||||
#include <compat/strl.h>
|
||||
#include <rhash.h>
|
||||
#include <retro_miscellaneous.h>
|
||||
@ -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 @@ 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)
|
||||
{
|
||||
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
|
||||
|
@ -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"
|
||||
|
477
libretro-common/formats/cdfs/cdfs.c
Normal file
477
libretro-common/formats/cdfs/cdfs.c
Normal file
@ -0,0 +1,477 @@
|
||||
#include "formats/cdfs.h"
|
||||
|
||||
#include <retro_miscellaneous.h>
|
||||
#include <compat/strl.h>
|
||||
#include <file/file_path.h>
|
||||
#include <string/stdstring.h>
|
||||
|
||||
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;
|
||||
}
|
@ -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:
|
||||
|
92
libretro-common/include/formats/cdfs.h
Normal file
92
libretro-common/include/formats/cdfs.h
Normal file
@ -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 <streams/interface_stream.h>
|
||||
|
||||
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 */
|
Loading…
x
Reference in New Issue
Block a user