/* Copyright (C) 2010-2018 The RetroArch team * * --------------------------------------------------------------------------------------- * The following license statement only applies to this file (chd_stream.c). * --------------------------------------------------------------------------------------- * * 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. */ #include #include #include #include #include #include #include #define SECTOR_SIZE 2352 #define SUBCODE_SIZE 96 #define TRACK_PAD 4 struct chdstream { chd_file *chd; /* Should we swap bytes? */ bool swab; /* Size of frame taken from each hunk */ 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 */ uint32_t track_frame; /* Byte offset where track data starts (after pregap) */ size_t track_start; /* Byte offset where track data ends */ size_t track_end; /* Byte offset of read cursor */ size_t offset; /* Loaded hunk number */ int32_t hunknum; /* Loaded hunk */ uint8_t *hunkmem; }; typedef struct metadata { char type[64]; char subtype[32]; char pgtype[32]; char pgsub[32]; uint32_t frame_offset; uint32_t frames; uint32_t pad; uint32_t extra; uint32_t pregap; uint32_t postgap; uint32_t track; } metadata_t; static uint32_t padding_frames(uint32_t frames) { return ((frames + TRACK_PAD - 1) & ~(TRACK_PAD - 1)) - frames; } static bool chdstream_get_meta(chd_file *chd, int idx, metadata_t *md) { char meta[256]; uint32_t meta_size = 0; chd_error err; memset(md, 0, sizeof(*md)); err = chd_get_metadata(chd, CDROM_TRACK_METADATA2_TAG, idx, meta, sizeof(meta), &meta_size, NULL, NULL); if (err == CHDERR_NONE) { sscanf(meta, CDROM_TRACK_METADATA2_FORMAT, &md->track, md->type, md->subtype, &md->frames, &md->pregap, md->pgtype, md->pgsub, &md->postgap); md->extra = padding_frames(md->frames); return true; } err = chd_get_metadata(chd, CDROM_TRACK_METADATA_TAG, idx, meta, sizeof(meta), &meta_size, NULL, NULL); if (err == CHDERR_NONE) { sscanf(meta, CDROM_TRACK_METADATA_FORMAT, &md->track, md->type, md->subtype, &md->frames); md->extra = padding_frames(md->frames); return true; } err = chd_get_metadata(chd, GDROM_TRACK_METADATA_TAG, idx, meta, sizeof(meta), &meta_size, NULL, NULL); if (err == CHDERR_NONE) { sscanf(meta, GDROM_TRACK_METADATA_FORMAT, &md->track, md->type, md->subtype, &md->frames, &md->pad, &md->pregap, md->pgtype, md->pgsub, &md->postgap); md->extra = padding_frames(md->frames); return true; } return false; } static bool chdstream_find_track_number(chd_file *fd, int32_t track, metadata_t *meta) { uint32_t i; uint32_t frame_offset = 0; for (i = 0; true; ++i) { if (!chdstream_get_meta(fd, i, meta)) return false; if (track == meta->track) { meta->frame_offset = frame_offset; return true; } frame_offset += meta->frames + meta->extra; } } static bool chdstream_find_special_track(chd_file *fd, int32_t track, metadata_t *meta) { int32_t i; metadata_t iter; int32_t largest_track = 0; uint32_t largest_size = 0; for (i = 1; true; ++i) { if (!chdstream_find_track_number(fd, i, &iter)) { if (track == CHDSTREAM_TRACK_LAST && i > 1) { *meta = iter; return true; } else if (track == CHDSTREAM_TRACK_PRIMARY && largest_track != 0) return chdstream_find_track_number(fd, largest_track, meta); } switch (track) { case CHDSTREAM_TRACK_FIRST_DATA: if (strcmp(iter.type, "AUDIO")) { *meta = iter; return true; } break; case CHDSTREAM_TRACK_PRIMARY: if (strcmp(iter.type, "AUDIO") && iter.frames > largest_size) { largest_size = iter.frames; largest_track = iter.track; } break; default: break; } } } static bool chdstream_find_track(chd_file *fd, int32_t track, metadata_t *meta) { if (track < 0) return chdstream_find_special_track(fd, track, meta); return chdstream_find_track_number(fd, track, meta); } chdstream_t *chdstream_open(const char *path, int32_t track) { metadata_t meta; uint32_t pregap = 0; const chd_header *hd = NULL; chdstream_t *stream = NULL; chd_file *chd = NULL; chd_error err = chd_open(path, CHD_OPEN_READ, NULL, &chd); if (err != CHDERR_NONE) goto error; if (!chdstream_find_track(chd, track, &meta)) goto error; stream = (chdstream_t*)calloc(1, sizeof(*stream)); if (!stream) goto error; hd = chd_get_header(chd); stream->hunkmem = (uint8_t*)malloc(hd->hunkbytes); if (!stream->hunkmem) goto error; if (!strcmp(meta.type, "MODE1_RAW")) { stream->frame_size = SECTOR_SIZE; stream->frame_offset = 0; } else if (!strcmp(meta.type, "MODE2_RAW")) { stream->frame_size = SECTOR_SIZE; stream->frame_offset = 0; } else if (!strcmp(meta.type, "AUDIO")) { stream->frame_size = SECTOR_SIZE; stream->frame_offset = 0; stream->swab = true; } else { stream->frame_size = hd->unitbytes; stream->frame_offset = 0; } /* Only include pregap data if it was in the track file */ if (!strcmp(meta.type, meta.pgtype)) pregap = meta.pregap; else pregap = 0; stream->chd = chd; stream->frames_per_hunk = hd->hunkbytes / hd->unitbytes; stream->track_frame = meta.frame_offset; stream->track_start = (size_t) pregap * stream->frame_size; stream->track_end = stream->track_start + (size_t) meta.frames * stream->frame_size; stream->offset = 0; stream->hunknum = -1; return stream; error: chdstream_close(stream); if (chd) chd_close(chd); return NULL; } void chdstream_close(chdstream_t *stream) { if (stream) { if (stream->hunkmem) free(stream->hunkmem); if (stream->chd) chd_close(stream->chd); free(stream); } } static bool chdstream_load_hunk(chdstream_t *stream, uint32_t hunknum) { chd_error err; uint16_t *array; uint32_t i; uint32_t count; if (hunknum == stream->hunknum) return true; err = chd_read(stream->chd, hunknum, stream->hunkmem); if (err != CHDERR_NONE) return false; if (stream->swab) { count = chd_get_header(stream->chd)->hunkbytes / 2; array = (uint16_t*) stream->hunkmem; for (i = 0; i < count; ++i) array[i] = SWAP16(array[i]); } stream->hunknum = hunknum; return true; } ssize_t chdstream_read(chdstream_t *stream, void *data, size_t bytes) { size_t end; uint32_t frame_offset; uint32_t hunk_offset; uint32_t chd_frame; uint32_t hunk; uint32_t amount; size_t data_offset = 0; const chd_header *hd = chd_get_header(stream->chd); uint8_t *out = (uint8_t*)data; if (stream->track_end - stream->offset < bytes) bytes = stream->track_end - stream->offset; end = stream->offset + bytes; while (stream->offset < end) { frame_offset = stream->offset % stream->frame_size; amount = stream->frame_size - frame_offset; if (amount > end - stream->offset) amount = (uint32_t)(end - stream->offset); /* In pregap */ if (stream->offset < stream->track_start) memset(out + data_offset, 0, amount); else { chd_frame = (uint32_t)(stream->track_frame + (stream->offset - stream->track_start) / stream->frame_size); hunk = chd_frame / stream->frames_per_hunk; hunk_offset = (chd_frame % stream->frames_per_hunk) * hd->unitbytes; if (!chdstream_load_hunk(stream, hunk)) { return -1; } memcpy(out + data_offset, stream->hunkmem + frame_offset + hunk_offset + stream->frame_offset, amount); } data_offset += amount; stream->offset += amount; } 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)) 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; } char *chdstream_gets(chdstream_t *stream, char *buffer, size_t len) { int c; size_t offset = 0; while (offset < len && (c = chdstream_getc(stream)) != EOF) buffer[offset++] = c; if (offset < len) buffer[offset] = '\0'; 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; switch (whence) { case SEEK_SET: new_offset = offset; break; case SEEK_CUR: new_offset = stream->offset + offset; break; case SEEK_END: new_offset = stream->track_end + offset; break; default: return -1; } if (new_offset < 0) return -1; if (new_offset > stream->track_end) new_offset = stream->track_end; stream->offset = new_offset; 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; }