diff --git a/Makefile.common b/Makefile.common index a90c7a8bf1..c25fce09f2 100644 --- a/Makefile.common +++ b/Makefile.common @@ -1399,6 +1399,19 @@ OBJ += $(ZLIB_OBJS) endif endif +ifeq ($(HAVE_7ZIP),1) +ifeq ($(HAVE_FLAC),1) + DEFINES += -DHAVE_CHD -DWANT_SUBCODE -DWANT_RAW_DATA_SECTOR + CFLAGS += -I$(LIBRETRO_COMM_DIR)/formats/libchdr + OBJ += $(LIBRETRO_COMM_DIR)/formats/libchdr/bitstream.o \ + $(LIBRETRO_COMM_DIR)/formats/libchdr/cdrom.o \ + $(LIBRETRO_COMM_DIR)/formats/libchdr/chd.o \ + $(LIBRETRO_COMM_DIR)/formats/libchdr/flac.o \ + $(LIBRETRO_COMM_DIR)/formats/libchdr/huffman.o \ + $(LIBRETRO_COMM_DIR)/streams/chd_stream.o +endif +endif + # Video4Linux 2 ifeq ($(HAVE_V4L2),1) diff --git a/libretro-common/include/streams/chd_stream.h b/libretro-common/include/streams/chd_stream.h new file mode 100644 index 0000000000..6c57c0e4c9 --- /dev/null +++ b/libretro-common/include/streams/chd_stream.h @@ -0,0 +1,58 @@ +/* Copyright (C) 2010-2017 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this file (memory_stream.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 _LIBRETRO_SDK_FILE_CHD_STREAM_H +#define _LIBRETRO_SDK_FILE_CHD_STREAM_H + +#include +#include + +#include + +RETRO_BEGIN_DECLS + +typedef struct _chd_file chd_file; + +typedef struct chdstream chdstream_t; + +#define CHDSTREAM_TRACK_FIRST_DATA (-1) +#define CHDSTREAM_TRACK_LAST (-2) + +chdstream_t *chdstream_open(const char *path, int32_t track); + +void chdstream_close(chdstream_t *stream); + +ssize_t chdstream_read(chdstream_t *stream, void *data, size_t bytes); + +int chdstream_getc(chdstream_t *stream); + +char *chdstream_gets(chdstream_t *stream, char *buffer, size_t len); + +size_t chdstream_tell(chdstream_t *stream); + +void chdstream_rewind(chdstream_t *stream); + +int chdstream_seek(chdstream_t *stream, ssize_t offset, int whence); + +RETRO_END_DECLS + +#endif diff --git a/libretro-common/streams/chd_stream.c b/libretro-common/streams/chd_stream.c new file mode 100644 index 0000000000..c7665c8070 --- /dev/null +++ b/libretro-common/streams/chd_stream.c @@ -0,0 +1,406 @@ +/* Copyright (C) 2010-2017 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this file (memory_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; + // 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) +{ + chd_error err; + char meta[256]; + uint32_t meta_size = 0; + + 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; + } + + return false; +} + +static bool +chdstream_find_track(chd_file *fd, int32_t track, metadata_t *meta) +{ + uint32_t i; + + memset(meta, 0, sizeof(*meta)); + + for (i = 0; true; ++i) + { + if (!chdstream_get_meta(fd, i, meta)) + { + if (track == CHDSTREAM_TRACK_LAST) + { + meta->frame_offset -= meta->frames + meta->extra; + return true; + } + else + { + return false; + } + } + + if ((track == CHDSTREAM_TRACK_FIRST_DATA && + strcmp(meta->type, "AUDIO")) || + (track > 0 && track == meta->track)) + { + return true; + } + + meta->frame_offset += meta->frames + meta->extra; + } +} + +chdstream_t *chdstream_open(const char *path, int32_t track) +{ + chdstream_t *stream = NULL; + chd_file *chd = NULL; + metadata_t meta; + chd_error err; + const chd_header *hd; + uint32_t pregap; + + 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 = calloc(1, sizeof(*stream)); + if (!stream) + { + goto error; + } + + hd = chd_get_header(chd); + stream->hunkmem = 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 = 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 = end - stream->offset; + } + if (stream->offset < stream->track_start) + { + // In pregap + memset(out + data_offset, 0, amount); + } + else + { + chd_frame = 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)) + { + abort(); + return -1; + } + memcpy(out + data_offset, + stream->hunkmem + frame_offset + hunk_offset + stream->frame_offset, amount); + } + + data_offset += amount; + stream->offset += amount; + } + + return bytes; +} + +int chdstream_getc(chdstream_t *stream) +{ + char c = 0; + + if (chdstream_read(stream, &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; +} + +size_t chdstream_tell(chdstream_t *stream) +{ + return stream->offset; +} + +void chdstream_rewind(chdstream_t *stream) +{ + stream->offset = 0; +} + +int chdstream_seek(chdstream_t *stream, ssize_t offset, int whence) +{ + ssize_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; +}