#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <time.h>
#include <libchdr/chd.h>
#include <libchdr/cdrom.h>
#include <libchdr/flac.h>
#include <libchdr/huffman.h>
#include <zstd.h>
#include "LzmaEnc.h"
#include "LzmaDec.h"
#if defined(__PS3__) || defined(__PSL1GHT__)
#define __MACTYPES__
#endif
#include <zlib.h>
#undef TRUE
#undef FALSE
#define TRUE 1
#define FALSE 0
#undef MAX
#undef MIN
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
#define SHA1_DIGEST_SIZE 20
#define PRINTF_MAX_HUNK (0)
#define MAP_STACK_ENTRIES 512
#define MAP_ENTRY_SIZE 16
#define OLD_MAP_ENTRY_SIZE 8
#define METADATA_HEADER_SIZE 16
#define MAP_ENTRY_FLAG_TYPE_MASK 0x0f
#define MAP_ENTRY_FLAG_NO_CRC 0x10
#define CHD_V1_SECTOR_SIZE 512
#define CHD_MAX_HUNK_SIZE (128 * 1024 * 1024)
#define CHD_MAX_FILE_SIZE (10ULL * 1024 * 1024 * 1024)
#define COOKIE_VALUE 0xbaadf00d
#define MAX_ZLIB_ALLOCS 64
#define END_OF_LIST_COOKIE "EndOfListCookie"
#define NO_MATCH (~0)
#ifdef WANT_RAW_DATA_SECTOR
static const uint8_t s_cd_sync_header[12] = { 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00 };
#endif
enum
{
V34_MAP_ENTRY_TYPE_INVALID = 0,
V34_MAP_ENTRY_TYPE_COMPRESSED = 1,
V34_MAP_ENTRY_TYPE_UNCOMPRESSED = 2,
V34_MAP_ENTRY_TYPE_MINI = 3,
V34_MAP_ENTRY_TYPE_SELF_HUNK = 4,
V34_MAP_ENTRY_TYPE_PARENT_HUNK = 5,
V34_MAP_ENTRY_TYPE_2ND_COMPRESSED = 6
};
enum
{
COMPRESSION_TYPE_0 = 0,
COMPRESSION_TYPE_1 = 1,
COMPRESSION_TYPE_2 = 2,
COMPRESSION_TYPE_3 = 3,
COMPRESSION_NONE = 4,
COMPRESSION_SELF = 5,
COMPRESSION_PARENT = 6,
COMPRESSION_RLE_SMALL,
COMPRESSION_RLE_LARGE,
COMPRESSION_SELF_0,
COMPRESSION_SELF_1,
COMPRESSION_PARENT_SELF,
COMPRESSION_PARENT_0,
COMPRESSION_PARENT_1
};
#define EARLY_EXIT(x) do { (void)(x); goto cleanup; } while (0)
typedef struct _codec_interface codec_interface;
struct _codec_interface
{
uint32_t compression;
const char *compname;
uint8_t lossy;
chd_error (*init)(void *codec, uint32_t hunkbytes);
void (*free)(void *codec);
chd_error (*decompress)(void *codec, const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen);
chd_error (*config)(void *codec, int param, void *config);
};
typedef struct _map_entry map_entry;
struct _map_entry
{
uint64_t offset;
uint32_t crc;
uint32_t length;
uint8_t flags;
};
typedef struct _metadata_entry metadata_entry;
struct _metadata_entry
{
uint64_t offset;
uint64_t next;
uint64_t prev;
uint32_t length;
uint32_t metatag;
uint8_t flags;
};
typedef struct _zlib_allocator zlib_allocator;
struct _zlib_allocator
{
uint32_t * allocptr[MAX_ZLIB_ALLOCS];
uint32_t * allocptr2[MAX_ZLIB_ALLOCS];
};
typedef struct _zlib_codec_data zlib_codec_data;
struct _zlib_codec_data
{
z_stream inflater;
zlib_allocator allocator;
};
#define MAX_LZMA_ALLOCS 64
typedef struct _lzma_allocator lzma_allocator;
struct _lzma_allocator
{
void *(*Alloc)(ISzAllocPtr p, size_t size);
void (*Free)(ISzAllocPtr p, void *address);
void (*FreeSz)(ISzAllocPtr p, void *address, size_t size);
uint32_t* allocptr[MAX_LZMA_ALLOCS];
uint32_t* allocptr2[MAX_LZMA_ALLOCS];
};
typedef struct _lzma_codec_data lzma_codec_data;
struct _lzma_codec_data
{
CLzmaDec decoder;
lzma_allocator allocator;
};
typedef struct _huff_codec_data huff_codec_data;
struct _huff_codec_data
{
struct huffman_decoder* decoder;
};
typedef struct _zstd_codec_data zstd_codec_data;
struct _zstd_codec_data
{
ZSTD_DStream *dstream;
};
typedef struct _cdzl_codec_data cdzl_codec_data;
struct _cdzl_codec_data {
zlib_codec_data base_decompressor;
#ifdef WANT_SUBCODE
zlib_codec_data subcode_decompressor;
#endif
uint8_t* buffer;
};
typedef struct _cdlz_codec_data cdlz_codec_data;
struct _cdlz_codec_data {
lzma_codec_data base_decompressor;
#ifdef WANT_SUBCODE
zlib_codec_data subcode_decompressor;
#endif
uint8_t* buffer;
};
typedef struct _flac_codec_data flac_codec_data;
struct _flac_codec_data {
int native_endian;
flac_decoder decoder;
};
typedef struct _cdfl_codec_data cdfl_codec_data;
struct _cdfl_codec_data {
int swap_endian;
flac_decoder decoder;
#ifdef WANT_SUBCODE
zlib_codec_data subcode_decompressor;
#endif
uint8_t* buffer;
};
typedef struct _cdzs_codec_data cdzs_codec_data;
struct _cdzs_codec_data
{
zstd_codec_data base_decompressor;
#ifdef WANT_SUBCODE
zstd_codec_data subcode_decompressor;
#endif
uint8_t* buffer;
};
struct _chd_file
{
uint32_t cookie;
core_file * file;
uint64_t file_size;
chd_header header;
chd_file * parent;
map_entry * map;
#ifdef NEED_CACHE_HUNK
uint8_t * cache;
uint32_t cachehunk;
uint8_t * compare;
uint32_t comparehunk;
#endif
uint8_t * compressed;
const codec_interface * codecintf[4];
zlib_codec_data zlib_codec_data;
lzma_codec_data lzma_codec_data;
huff_codec_data huff_codec_data;
flac_codec_data flac_codec_data;
zstd_codec_data zstd_codec_data;
cdzl_codec_data cdzl_codec_data;
cdlz_codec_data cdlz_codec_data;
cdfl_codec_data cdfl_codec_data;
cdzs_codec_data cdzs_codec_data;
#ifdef NEED_CACHE_HUNK
uint32_t maxhunk;
#endif
uint8_t * file_cache;
};
static const uint8_t nullmd5[CHD_MD5_BYTES] = { 0 };
static const uint8_t nullsha1[CHD_SHA1_BYTES] = { 0 };
static core_file *core_stdio_fopen(char const *path);
static uint64_t core_stdio_fsize(core_file *file);
static size_t core_stdio_fread(void *ptr, size_t size, size_t nmemb, core_file *file);
static int core_stdio_fclose(core_file *file);
static int core_stdio_fclose_nonowner(core_file *file);
static int core_stdio_fseek(core_file* file, int64_t offset, int whence);
static chd_error header_validate(const chd_header *header);
static chd_error header_read(chd_file *chd, chd_header *header);
#ifdef NEED_CACHE_HUNK
static chd_error hunk_read_into_cache(chd_file *chd, uint32_t hunknum);
#endif
static chd_error hunk_read_into_memory(chd_file *chd, uint32_t hunknum, uint8_t *dest);
static chd_error map_read(chd_file *chd);
static chd_error metadata_find_entry(chd_file *chd, uint32_t metatag, uint32_t metaindex, metadata_entry *metaentry);
static chd_error zlib_codec_init(void *codec, uint32_t hunkbytes);
static void zlib_codec_free(void *codec);
static chd_error zlib_codec_decompress(void *codec, const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen);
static voidpf zlib_fast_alloc(voidpf opaque, uInt items, uInt size);
static void zlib_fast_free(voidpf opaque, voidpf address);
static void zlib_allocator_free(voidpf opaque);
static chd_error lzma_codec_init(void *codec, uint32_t hunkbytes);
static void lzma_codec_free(void *codec);
static chd_error lzma_codec_decompress(void *codec, const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen);
static chd_error huff_codec_init(void *codec, uint32_t hunkbytes);
static void huff_codec_free(void *codec);
static chd_error huff_codec_decompress(void *codec, const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen);
static chd_error flac_codec_init(void *codec, uint32_t hunkbytes);
static void flac_codec_free(void *codec);
static chd_error flac_codec_decompress(void *codec, const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen);
static chd_error zstd_codec_init(void *codec, uint32_t hunkbytes);
static void zstd_codec_free(void *codec);
static chd_error zstd_codec_decompress(void *codec, const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen);
static chd_error cdzl_codec_init(void* codec, uint32_t hunkbytes);
static void cdzl_codec_free(void* codec);
static chd_error cdzl_codec_decompress(void *codec, const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen);
static chd_error cdlz_codec_init(void* codec, uint32_t hunkbytes);
static void cdlz_codec_free(void* codec);
static chd_error cdlz_codec_decompress(void *codec, const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen);
static chd_error cdfl_codec_init(void* codec, uint32_t hunkbytes);
static void cdfl_codec_free(void* codec);
static chd_error cdfl_codec_decompress(void *codec, const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen);
static chd_error cdzs_codec_init(void *codec, uint32_t hunkbytes);
static void cdzs_codec_free(void *codec);
static chd_error cdzs_codec_decompress(void *codec, const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen);
static void *lzma_fast_alloc(ISzAllocPtr p, size_t size);
static void lzma_fast_free(ISzAllocPtr p, void *address);
static void lzma_allocator_init(void* p)
{
lzma_allocator *codec = (lzma_allocator *)(p);
memset(codec->allocptr, 0, sizeof(codec->allocptr));
memset(codec->allocptr2, 0, sizeof(codec->allocptr2));
codec->Alloc = lzma_fast_alloc;
codec->Free = lzma_fast_free;
}
static void lzma_allocator_free(void* p )
{
int i;
lzma_allocator *codec = (lzma_allocator *)(p);
for (i = 0 ; i < MAX_LZMA_ALLOCS ; i++)
{
if (codec->allocptr[i] != NULL)
free(codec->allocptr[i]);
}
}
static void lzma_allocator_free_unused(lzma_allocator *codec)
{
int i;
for (i = 0; i < MAX_LZMA_ALLOCS; i++)
{
uint32_t *ptr = codec->allocptr[i];
if (ptr && (*ptr & 1) == 0)
{
free(codec->allocptr[i]);
codec->allocptr[i] = NULL;
codec->allocptr2[i] = NULL;
}
}
}
#define LZMA_MIN_ALIGNMENT_BITS 512
#define LZMA_MIN_ALIGNMENT_BYTES (LZMA_MIN_ALIGNMENT_BITS / 8)
static void *lzma_fast_alloc(ISzAllocPtr p, size_t size)
{
int scan;
uint32_t *addr = NULL;
lzma_allocator *codec = (lzma_allocator *)(p);
uintptr_t vaddr = 0;
size = (size + 0x3ff) & ~0x3ff;
for (scan = 0; scan < MAX_LZMA_ALLOCS; scan++)
{
uint32_t *ptr = codec->allocptr[scan];
if (ptr != NULL && size == *ptr)
{
*ptr |= 1;
return codec->allocptr2[scan];
}
}
addr = (uint32_t *)malloc(size + sizeof(uint32_t) + LZMA_MIN_ALIGNMENT_BYTES);
if (addr==NULL)
return NULL;
for (scan = 0; scan < MAX_LZMA_ALLOCS; scan++)
{
if (codec->allocptr[scan] == NULL)
{
codec->allocptr[scan] = addr;
vaddr = (uintptr_t)addr;
vaddr = (vaddr + sizeof(uint32_t) + (LZMA_MIN_ALIGNMENT_BYTES-1)) & (~(LZMA_MIN_ALIGNMENT_BYTES-1));
codec->allocptr2[scan] = (uint32_t*)vaddr;
break;
}
}
*addr = size | 1;
return (void*)vaddr;
}
static void lzma_fast_free(ISzAllocPtr p, void *address)
{
int scan;
uint32_t *ptr = NULL;
lzma_allocator *codec = NULL;
if (address == NULL)
return;
codec = (lzma_allocator *)(p);
ptr = (uint32_t *)address;
for (scan = 0; scan < MAX_LZMA_ALLOCS; scan++)
{
if (ptr == codec->allocptr2[scan])
{
*codec->allocptr[scan] &= ~1;
return;
}
}
}
static chd_error lzma_codec_init(void* codec, uint32_t hunkbytes)
{
CLzmaEncHandle enc;
CLzmaEncProps encoder_props;
Byte decoder_props[LZMA_PROPS_SIZE];
SizeT props_size;
lzma_allocator* alloc;
lzma_codec_data* lzma_codec = (lzma_codec_data*) codec;
LzmaDec_Construct(&lzma_codec->decoder);
LzmaEncProps_Init(&encoder_props);
encoder_props.level = 9;
encoder_props.reduceSize = hunkbytes;
LzmaEncProps_Normalize(&encoder_props);
alloc = &lzma_codec->allocator;
lzma_allocator_init(alloc);
enc = LzmaEnc_Create((ISzAlloc*)alloc);
if (!enc)
return CHDERR_DECOMPRESSION_ERROR;
if (LzmaEnc_SetProps(enc, &encoder_props) != SZ_OK)
{
LzmaEnc_Destroy(enc, (ISzAlloc*)&alloc, (ISzAlloc*)&alloc);
return CHDERR_DECOMPRESSION_ERROR;
}
props_size = sizeof(decoder_props);
if (LzmaEnc_WriteProperties(enc, decoder_props, &props_size) != SZ_OK)
{
LzmaEnc_Destroy(enc, (ISzAlloc*)alloc, (ISzAlloc*)alloc);
return CHDERR_DECOMPRESSION_ERROR;
}
LzmaEnc_Destroy(enc, (ISzAlloc*)alloc, (ISzAlloc*)alloc);
lzma_allocator_free_unused(alloc);
if (LzmaDec_Allocate(&lzma_codec->decoder, decoder_props, LZMA_PROPS_SIZE, (ISzAlloc*)alloc) != SZ_OK)
return CHDERR_DECOMPRESSION_ERROR;
return CHDERR_NONE;
}
static void lzma_codec_free(void* codec)
{
lzma_codec_data* lzma_codec = (lzma_codec_data*) codec;
LzmaDec_Free(&lzma_codec->decoder, (ISzAlloc*)&lzma_codec->allocator);
lzma_allocator_free(&lzma_codec->allocator);
}
static chd_error lzma_codec_decompress(void* codec, const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen)
{
ELzmaStatus status;
SRes res;
SizeT consumedlen, decodedlen;
lzma_codec_data* lzma_codec = (lzma_codec_data*) codec;
LzmaDec_Init(&lzma_codec->decoder);
consumedlen = complen;
decodedlen = destlen;
res = LzmaDec_DecodeToBuf(&lzma_codec->decoder, dest, &decodedlen, src, &consumedlen, LZMA_FINISH_END, &status);
if ((res != SZ_OK && res != LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK) || consumedlen != complen || decodedlen != destlen)
return CHDERR_DECOMPRESSION_ERROR;
return CHDERR_NONE;
}
static chd_error cdlz_codec_init(void* codec, uint32_t hunkbytes)
{
chd_error ret;
cdlz_codec_data* cdlz = (cdlz_codec_data*) codec;
cdlz->buffer = (uint8_t*)malloc(sizeof(uint8_t) * hunkbytes);
if (cdlz->buffer == NULL)
return CHDERR_OUT_OF_MEMORY;
ret = lzma_codec_init(&cdlz->base_decompressor, (hunkbytes / CD_FRAME_SIZE) * CD_MAX_SECTOR_DATA);
if (ret != CHDERR_NONE)
return ret;
#ifdef WANT_SUBCODE
ret = zlib_codec_init(&cdlz->subcode_decompressor, (hunkbytes / CD_FRAME_SIZE) * CD_MAX_SUBCODE_DATA);
if (ret != CHDERR_NONE)
return ret;
#endif
if (hunkbytes % CD_FRAME_SIZE != 0)
return CHDERR_CODEC_ERROR;
return CHDERR_NONE;
}
static void cdlz_codec_free(void* codec)
{
cdlz_codec_data* cdlz = (cdlz_codec_data*) codec;
free(cdlz->buffer);
lzma_codec_free(&cdlz->base_decompressor);
#ifdef WANT_SUBCODE
zlib_codec_free(&cdlz->subcode_decompressor);
#endif
}
static chd_error cdlz_codec_decompress(void *codec, const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen)
{
uint32_t framenum;
cdlz_codec_data* cdlz = (cdlz_codec_data*)codec;
chd_error decomp_err;
uint32_t complen_base;
const uint32_t frames = destlen / CD_FRAME_SIZE;
const uint32_t complen_bytes = (destlen < 65536) ? 2 : 3;
const uint32_t ecc_bytes = (frames + 7) / 8;
const uint32_t header_bytes = ecc_bytes + complen_bytes;
if (complen < (ecc_bytes + 2))
return CHDERR_DECOMPRESSION_ERROR;
complen_base = (src[ecc_bytes + 0] << 8) | src[ecc_bytes + 1];
if (complen_bytes > 2)
{
if (complen < (ecc_bytes + 3))
return CHDERR_DECOMPRESSION_ERROR;
complen_base = (complen_base << 8) | src[ecc_bytes + 2];
}
if (complen < (header_bytes + complen_base))
return CHDERR_DECOMPRESSION_ERROR;
decomp_err = lzma_codec_decompress(&cdlz->base_decompressor, &src[header_bytes], complen_base, &cdlz->buffer[0], frames * CD_MAX_SECTOR_DATA);
if (decomp_err != CHDERR_NONE)
return decomp_err;
#ifdef WANT_SUBCODE
decomp_err = zlib_codec_decompress(&cdlz->subcode_decompressor, &src[header_bytes + complen_base], complen - complen_base - header_bytes, &cdlz->buffer[frames * CD_MAX_SECTOR_DATA], frames * CD_MAX_SUBCODE_DATA);
if (decomp_err != CHDERR_NONE)
return decomp_err;
#endif
for (framenum = 0; framenum < frames; framenum++)
{
uint8_t *sector;
memcpy(&dest[framenum * CD_FRAME_SIZE], &cdlz->buffer[framenum * CD_MAX_SECTOR_DATA], CD_MAX_SECTOR_DATA);
#ifdef WANT_SUBCODE
memcpy(&dest[framenum * CD_FRAME_SIZE + CD_MAX_SECTOR_DATA], &cdlz->buffer[frames * CD_MAX_SECTOR_DATA + framenum * CD_MAX_SUBCODE_DATA], CD_MAX_SUBCODE_DATA);
#endif
#ifdef WANT_RAW_DATA_SECTOR
sector = (uint8_t *)&dest[framenum * CD_FRAME_SIZE];
if ((src[framenum / 8] & (1 << (framenum % 8))) != 0)
{
memcpy(sector, s_cd_sync_header, sizeof(s_cd_sync_header));
ecc_generate(sector);
}
#endif
}
return CHDERR_NONE;
}
static chd_error cdzl_codec_init(void *codec, uint32_t hunkbytes)
{
chd_error ret;
cdzl_codec_data* cdzl = (cdzl_codec_data*)codec;
if (hunkbytes % CD_FRAME_SIZE != 0)
return CHDERR_CODEC_ERROR;
cdzl->buffer = (uint8_t*)malloc(sizeof(uint8_t) * hunkbytes);
if (cdzl->buffer == NULL)
return CHDERR_OUT_OF_MEMORY;
ret = zlib_codec_init(&cdzl->base_decompressor, (hunkbytes / CD_FRAME_SIZE) * CD_MAX_SECTOR_DATA);
if (ret != CHDERR_NONE)
return ret;
#ifdef WANT_SUBCODE
ret = zlib_codec_init(&cdzl->subcode_decompressor, (hunkbytes / CD_FRAME_SIZE) * CD_MAX_SUBCODE_DATA);
if (ret != CHDERR_NONE)
return ret;
#endif
return CHDERR_NONE;
}
static void cdzl_codec_free(void *codec)
{
cdzl_codec_data* cdzl = (cdzl_codec_data*)codec;
zlib_codec_free(&cdzl->base_decompressor);
#ifdef WANT_SUBCODE
zlib_codec_free(&cdzl->subcode_decompressor);
#endif
free(cdzl->buffer);
}
static chd_error cdzl_codec_decompress(void *codec, const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen)
{
uint32_t framenum;
cdzl_codec_data* cdzl = (cdzl_codec_data*)codec;
chd_error decomp_err;
uint32_t complen_base;
const uint32_t frames = destlen / CD_FRAME_SIZE;
const uint32_t complen_bytes = (destlen < 65536) ? 2 : 3;
const uint32_t ecc_bytes = (frames + 7) / 8;
const uint32_t header_bytes = ecc_bytes + complen_bytes;
if (complen < (ecc_bytes + 2))
return CHDERR_DECOMPRESSION_ERROR;
complen_base = (src[ecc_bytes + 0] << 8) | src[ecc_bytes + 1];
if (complen_bytes > 2)
{
if (complen < (ecc_bytes + 3))
return CHDERR_DECOMPRESSION_ERROR;
complen_base = (complen_base << 8) | src[ecc_bytes + 2];
}
if (complen < (header_bytes + complen_base))
return CHDERR_DECOMPRESSION_ERROR;
decomp_err = zlib_codec_decompress(&cdzl->base_decompressor, &src[header_bytes], complen_base, &cdzl->buffer[0], frames * CD_MAX_SECTOR_DATA);
if (decomp_err != CHDERR_NONE)
return decomp_err;
#ifdef WANT_SUBCODE
decomp_err = zlib_codec_decompress(&cdzl->subcode_decompressor, &src[header_bytes + complen_base], complen - complen_base - header_bytes, &cdzl->buffer[frames * CD_MAX_SECTOR_DATA], frames * CD_MAX_SUBCODE_DATA);
if (decomp_err != CHDERR_NONE)
return decomp_err;
#endif
for (framenum = 0; framenum < frames; framenum++)
{
uint8_t *sector;
memcpy(&dest[framenum * CD_FRAME_SIZE], &cdzl->buffer[framenum * CD_MAX_SECTOR_DATA], CD_MAX_SECTOR_DATA);
#ifdef WANT_SUBCODE
memcpy(&dest[framenum * CD_FRAME_SIZE + CD_MAX_SECTOR_DATA], &cdzl->buffer[frames * CD_MAX_SECTOR_DATA + framenum * CD_MAX_SUBCODE_DATA], CD_MAX_SUBCODE_DATA);
#endif
#ifdef WANT_RAW_DATA_SECTOR
sector = (uint8_t *)&dest[framenum * CD_FRAME_SIZE];
if ((src[framenum / 8] & (1 << (framenum % 8))) != 0)
{
memcpy(sector, s_cd_sync_header, sizeof(s_cd_sync_header));
ecc_generate(sector);
}
#endif
}
return CHDERR_NONE;
}
static chd_error huff_codec_init(void* codec, uint32_t hunkbytes)
{
huff_codec_data* huff_codec = (huff_codec_data*) codec;
huff_codec->decoder = create_huffman_decoder(256, 16);
return CHDERR_NONE;
}
static void huff_codec_free(void *codec)
{
huff_codec_data* huff_codec = (huff_codec_data*) codec;
delete_huffman_decoder(huff_codec->decoder);
}
static chd_error huff_codec_decompress(void *codec, const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen)
{
huff_codec_data* huff_codec = (huff_codec_data*) codec;
struct bitstream* bitbuf = create_bitstream(src, complen);
enum huffman_error err = huffman_import_tree_huffman(huff_codec->decoder, bitbuf);
if (err != HUFFERR_NONE)
{
free(bitbuf);
return CHDERR_DECOMPRESSION_ERROR;
}
uint32_t cur;
for (cur = 0; cur < destlen; cur++)
dest[cur] = huffman_decode_one(huff_codec->decoder, bitbuf);
bitstream_flush(bitbuf);
chd_error result = bitstream_overflow(bitbuf) ? CHDERR_DECOMPRESSION_ERROR : CHDERR_NONE;
free(bitbuf);
return result;
}
static uint32_t flac_codec_blocksize(uint32_t bytes)
{
uint32_t blocksize = bytes / 4;
while (blocksize > 2048)
blocksize /= 2;
return blocksize;
}
static chd_error flac_codec_init(void *codec, uint32_t hunkbytes)
{
uint16_t native_endian = 0;
flac_codec_data *flac = (flac_codec_data*)codec;
if (hunkbytes % 4 != 0)
return CHDERR_CODEC_ERROR;
*(uint8_t *)(&native_endian) = 1;
flac->native_endian = (native_endian & 1);
if (flac_decoder_init(&flac->decoder))
return CHDERR_OUT_OF_MEMORY;
return CHDERR_NONE;
}
static void flac_codec_free(void *codec)
{
flac_codec_data *flac = (flac_codec_data*)codec;
flac_decoder_free(&flac->decoder);
}
static chd_error flac_codec_decompress(void *codec, const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen)
{
flac_codec_data *flac = (flac_codec_data*)codec;
int swap_endian;
if (src[0] == 'L')
swap_endian = !flac->native_endian;
else if (src[0] == 'B')
swap_endian = flac->native_endian;
else
return CHDERR_DECOMPRESSION_ERROR;
if (!flac_decoder_reset(&flac->decoder, 44100, 2, flac_codec_blocksize(destlen), src + 1, complen - 1))
return CHDERR_DECOMPRESSION_ERROR;
if (!flac_decoder_decode_interleaved(&flac->decoder, (int16_t *)(dest), destlen/4, swap_endian))
return CHDERR_DECOMPRESSION_ERROR;
flac_decoder_finish(&flac->decoder);
return CHDERR_NONE;
}
static uint32_t cdfl_codec_blocksize(uint32_t bytes)
{
uint32_t blocksize = bytes / 4;
while (blocksize > CD_MAX_SECTOR_DATA)
blocksize /= 2;
return blocksize;
}
static chd_error cdfl_codec_init(void *codec, uint32_t hunkbytes)
{
#ifdef WANT_SUBCODE
chd_error ret;
#endif
uint16_t native_endian = 0;
cdfl_codec_data *cdfl = (cdfl_codec_data*)codec;
if (hunkbytes % CD_FRAME_SIZE != 0)
return CHDERR_CODEC_ERROR;
cdfl->buffer = (uint8_t*)malloc(sizeof(uint8_t) * hunkbytes);
if (cdfl->buffer == NULL)
return CHDERR_OUT_OF_MEMORY;
*(uint8_t *)(&native_endian) = 1;
cdfl->swap_endian = (native_endian & 1);
#ifdef WANT_SUBCODE
ret = zlib_codec_init(&cdfl->subcode_decompressor, (hunkbytes / CD_FRAME_SIZE) * CD_MAX_SECTOR_DATA);
if (ret != CHDERR_NONE)
return ret;
#endif
if (flac_decoder_init(&cdfl->decoder))
return CHDERR_OUT_OF_MEMORY;
return CHDERR_NONE;
}
static void cdfl_codec_free(void *codec)
{
cdfl_codec_data *cdfl = (cdfl_codec_data*)codec;
flac_decoder_free(&cdfl->decoder);
#ifdef WANT_SUBCODE
zlib_codec_free(&cdfl->subcode_decompressor);
#endif
if (cdfl->buffer)
free(cdfl->buffer);
}
static chd_error cdfl_codec_decompress(void *codec, const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen)
{
uint32_t framenum;
uint8_t *buffer;
#ifdef WANT_SUBCODE
uint32_t offset;
chd_error ret;
#endif
cdfl_codec_data *cdfl = (cdfl_codec_data*)codec;
uint32_t frames = destlen / CD_FRAME_SIZE;
if (!flac_decoder_reset(&cdfl->decoder, 44100, 2, cdfl_codec_blocksize(frames * CD_MAX_SECTOR_DATA), src, complen))
return CHDERR_DECOMPRESSION_ERROR;
buffer = &cdfl->buffer[0];
if (!flac_decoder_decode_interleaved(&cdfl->decoder, (int16_t *)(buffer), frames * CD_MAX_SECTOR_DATA/4, cdfl->swap_endian))
return CHDERR_DECOMPRESSION_ERROR;
#ifdef WANT_SUBCODE
offset = flac_decoder_finish(&cdfl->decoder);
ret = zlib_codec_decompress(&cdfl->subcode_decompressor, src + offset, complen - offset, &cdfl->buffer[frames * CD_MAX_SECTOR_DATA], frames * CD_MAX_SUBCODE_DATA);
if (ret != CHDERR_NONE)
return ret;
#else
flac_decoder_finish(&cdfl->decoder);
#endif
for (framenum = 0; framenum < frames; framenum++)
{
memcpy(&dest[framenum * CD_FRAME_SIZE], &cdfl->buffer[framenum * CD_MAX_SECTOR_DATA], CD_MAX_SECTOR_DATA);
#ifdef WANT_SUBCODE
memcpy(&dest[framenum * CD_FRAME_SIZE + CD_MAX_SECTOR_DATA], &cdfl->buffer[frames * CD_MAX_SECTOR_DATA + framenum * CD_MAX_SUBCODE_DATA], CD_MAX_SUBCODE_DATA);
#endif
}
return CHDERR_NONE;
}
static chd_error zstd_codec_init(void* codec, uint32_t hunkbytes)
{
zstd_codec_data* zstd_codec = (zstd_codec_data*) codec;
zstd_codec->dstream = ZSTD_createDStream();
if (!zstd_codec->dstream) {
printf("NO DSTREAM CREATED!\n");
return CHDERR_DECOMPRESSION_ERROR;
}
return CHDERR_NONE;
}
static void zstd_codec_free(void* codec)
{
zstd_codec_data* zstd_codec = (zstd_codec_data*) codec;
ZSTD_freeDStream(zstd_codec->dstream);
}
static chd_error zstd_codec_decompress(void* codec, const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen)
{
zstd_codec_data* zstd_codec = (zstd_codec_data*) codec;
size_t zstd_res = ZSTD_initDStream(zstd_codec->dstream);
if (ZSTD_isError(zstd_res))
{
printf("INITI DSTREAM FAILED!\n");
return CHDERR_DECOMPRESSION_ERROR;
}
ZSTD_inBuffer input = {src, complen, 0};
ZSTD_outBuffer output = {dest, destlen, 0 };
while ((input.pos < input.size) && (output.pos < output.size))
{
zstd_res = ZSTD_decompressStream(zstd_codec->dstream, &output, &input);
if (ZSTD_isError(zstd_res))
{
printf("DECOMPRESSION ERROR IN LOOP\n");
return CHDERR_DECOMPRESSION_ERROR;
}
}
if (output.pos != output.size)
{
printf("OUTPUT DOESN'T MATCH!\n");
return CHDERR_DECOMPRESSION_ERROR;
}
return CHDERR_NONE;
}
static chd_error cdzs_codec_init(void* codec, uint32_t hunkbytes)
{
chd_error ret;
cdzs_codec_data* cdzs = (cdzs_codec_data*) codec;
cdzs->buffer = (uint8_t*)malloc(sizeof(uint8_t) * hunkbytes);
if (cdzs->buffer == NULL)
return CHDERR_OUT_OF_MEMORY;
ret = zstd_codec_init(&cdzs->base_decompressor, (hunkbytes / CD_FRAME_SIZE) * CD_MAX_SECTOR_DATA);
if (ret != CHDERR_NONE)
return ret;
#ifdef WANT_SUBCODE
ret = zstd_codec_init(&cdzs->subcode_decompressor, (hunkbytes / CD_FRAME_SIZE) * CD_MAX_SUBCODE_DATA);
if (ret != CHDERR_NONE)
return ret;
#endif
if (hunkbytes % CD_FRAME_SIZE != 0)
return CHDERR_CODEC_ERROR;
return CHDERR_NONE;
}
static void cdzs_codec_free(void* codec)
{
cdzs_codec_data* cdzs = (cdzs_codec_data*) codec;
free(cdzs->buffer);
zstd_codec_free(&cdzs->base_decompressor);
#ifdef WANT_SUBCODE
zstd_codec_free(&cdzs->subcode_decompressor);
#endif
}
static chd_error cdzs_codec_decompress(void *codec, const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen)
{
uint32_t framenum;
cdzs_codec_data* cdzs = (cdzs_codec_data*)codec;
chd_error decomp_err;
uint32_t complen_base;
const uint32_t frames = destlen / CD_FRAME_SIZE;
const uint32_t complen_bytes = (destlen < 65536) ? 2 : 3;
const uint32_t ecc_bytes = (frames + 7) / 8;
const uint32_t header_bytes = ecc_bytes + complen_bytes;
if (complen < (ecc_bytes + 2))
return CHDERR_DECOMPRESSION_ERROR;
complen_base = (src[ecc_bytes + 0] << 8) | src[ecc_bytes + 1];
if (complen_bytes > 2)
{
if (complen < (ecc_bytes + 3))
return CHDERR_DECOMPRESSION_ERROR;
complen_base = (complen_base << 8) | src[ecc_bytes + 2];
}
if (complen < (header_bytes + complen_base))
return CHDERR_DECOMPRESSION_ERROR;
decomp_err = zstd_codec_decompress(&cdzs->base_decompressor, &src[header_bytes], complen_base, &cdzs->buffer[0], frames * CD_MAX_SECTOR_DATA);
if (decomp_err != CHDERR_NONE)
return decomp_err;
#ifdef WANT_SUBCODE
decomp_err = zstd_codec_decompress(&cdzs->subcode_decompressor, &src[header_bytes + complen_base], complen - complen_base - header_bytes, &cdzs->buffer[frames * CD_MAX_SECTOR_DATA], frames * CD_MAX_SUBCODE_DATA);
if (decomp_err != CHDERR_NONE)
return decomp_err;
#endif
for (framenum = 0; framenum < frames; framenum++)
{
uint8_t *sector;
memcpy(&dest[framenum * CD_FRAME_SIZE], &cdzs->buffer[framenum * CD_MAX_SECTOR_DATA], CD_MAX_SECTOR_DATA);
#ifdef WANT_SUBCODE
memcpy(&dest[framenum * CD_FRAME_SIZE + CD_MAX_SECTOR_DATA], &cdzs->buffer[frames * CD_MAX_SECTOR_DATA + framenum * CD_MAX_SUBCODE_DATA], CD_MAX_SUBCODE_DATA);
#endif
#ifdef WANT_RAW_DATA_SECTOR
sector = (uint8_t *)&dest[framenum * CD_FRAME_SIZE];
if ((src[framenum / 8] & (1 << (framenum % 8))) != 0)
{
memcpy(sector, s_cd_sync_header, sizeof(s_cd_sync_header));
ecc_generate(sector);
}
#endif
}
return CHDERR_NONE;
}
static const codec_interface codec_interfaces[] =
{
{
CHDCOMPRESSION_NONE,
"none",
FALSE,
NULL,
NULL,
NULL,
NULL
},
{
CHDCOMPRESSION_ZLIB,
"zlib",
FALSE,
zlib_codec_init,
zlib_codec_free,
zlib_codec_decompress,
NULL
},
{
CHDCOMPRESSION_ZLIB_PLUS,
"zlib+",
FALSE,
zlib_codec_init,
zlib_codec_free,
zlib_codec_decompress,
NULL
},
{
CHD_CODEC_ZLIB,
"zlib (Deflate)",
FALSE,
zlib_codec_init,
zlib_codec_free,
zlib_codec_decompress,
NULL
},
{
CHD_CODEC_LZMA,
"lzma (LZMA)",
FALSE,
lzma_codec_init,
lzma_codec_free,
lzma_codec_decompress,
NULL
},
{
CHD_CODEC_HUFFMAN,
"Huffman",
FALSE,
huff_codec_init,
huff_codec_free,
huff_codec_decompress,
NULL
},
{
CHD_CODEC_FLAC,
"flac (FLAC)",
FALSE,
flac_codec_init,
flac_codec_free,
flac_codec_decompress,
NULL
},
{
CHD_CODEC_ZSTD,
"ZStandard",
FALSE,
zstd_codec_init,
zstd_codec_free,
zstd_codec_decompress,
NULL
},
{
CHD_CODEC_CD_ZLIB,
"cdzl (CD Deflate)",
FALSE,
cdzl_codec_init,
cdzl_codec_free,
cdzl_codec_decompress,
NULL
},
{
CHD_CODEC_CD_LZMA,
"cdlz (CD LZMA)",
FALSE,
cdlz_codec_init,
cdlz_codec_free,
cdlz_codec_decompress,
NULL
},
{
CHD_CODEC_CD_FLAC,
"cdfl (CD FLAC)",
FALSE,
cdfl_codec_init,
cdfl_codec_free,
cdfl_codec_decompress,
NULL
},
{
CHD_CODEC_CD_ZSTD,
"cdzs (CD ZStandard)",
FALSE,
cdzs_codec_init,
cdzs_codec_free,
cdzs_codec_decompress,
NULL
}
};
static inline uint64_t get_bigendian_uint64_t(const uint8_t *base)
{
return ((uint64_t)base[0] << 56) | ((uint64_t)base[1] << 48) | ((uint64_t)base[2] << 40) | ((uint64_t)base[3] << 32) |
((uint64_t)base[4] << 24) | ((uint64_t)base[5] << 16) | ((uint64_t)base[6] << 8) | (uint64_t)base[7];
}
static inline void put_bigendian_uint64_t(uint8_t *base, uint64_t value)
{
base[0] = value >> 56;
base[1] = value >> 48;
base[2] = value >> 40;
base[3] = value >> 32;
base[4] = value >> 24;
base[5] = value >> 16;
base[6] = value >> 8;
base[7] = value;
}
static inline uint64_t get_bigendian_uint48(const uint8_t *base)
{
return ((uint64_t)base[0] << 40) | ((uint64_t)base[1] << 32) |
((uint64_t)base[2] << 24) | ((uint64_t)base[3] << 16) | ((uint64_t)base[4] << 8) | (uint64_t)base[5];
}
static inline void put_bigendian_uint48(uint8_t *base, uint64_t value)
{
value &= 0xffffffffffff;
base[0] = value >> 40;
base[1] = value >> 32;
base[2] = value >> 24;
base[3] = value >> 16;
base[4] = value >> 8;
base[5] = value;
}
static inline uint32_t get_bigendian_uint32_t(const uint8_t *base)
{
return (base[0] << 24) | (base[1] << 16) | (base[2] << 8) | base[3];
}
static inline void put_bigendian_uint32_t(uint8_t *base, uint32_t value)
{
base[0] = value >> 24;
base[1] = value >> 16;
base[2] = value >> 8;
base[3] = value;
}
static inline void put_bigendian_uint24(uint8_t *base, uint32_t value)
{
value &= 0xffffff;
base[0] = value >> 16;
base[1] = value >> 8;
base[2] = value;
}
static inline uint32_t get_bigendian_uint24(const uint8_t *base)
{
return (base[0] << 16) | (base[1] << 8) | base[2];
}
static inline uint16_t get_bigendian_uint16(const uint8_t *base)
{
return (base[0] << 8) | base[1];
}
static inline void put_bigendian_uint16(uint8_t *base, uint16_t value)
{
base[0] = value >> 8;
base[1] = value;
}
static inline void map_extract(const uint8_t *base, map_entry *entry)
{
entry->offset = get_bigendian_uint64_t(&base[0]);
entry->crc = get_bigendian_uint32_t(&base[8]);
entry->length = get_bigendian_uint16(&base[12]) | (base[14] << 16);
entry->flags = base[15];
}
static inline void map_assemble(uint8_t *base, map_entry *entry)
{
put_bigendian_uint64_t(&base[0], entry->offset);
put_bigendian_uint32_t(&base[8], entry->crc);
put_bigendian_uint16(&base[12], entry->length);
base[14] = entry->length >> 16;
base[15] = entry->flags;
}
static inline int map_size_v5(chd_header* header)
{
const uint32_t max_hunkcount = (UINT32_MAX / header->mapentrybytes);
if (header->hunkcount > max_hunkcount)
return -1;
return header->hunkcount * header->mapentrybytes;
}
uint16_t crc16(const void *data, uint32_t length)
{
uint16_t crc = 0xffff;
static const uint16_t s_table[256] =
{
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
};
const uint8_t *src = (uint8_t*)data;
while (length-- != 0)
crc = (crc << 8) ^ s_table[(crc >> 8) ^ *src++];
return crc;
}
static inline int chd_compressed(chd_header* header) {
return header->compression[0] != CHD_CODEC_NONE;
}
static chd_error decompress_v5_map(chd_file* chd, chd_header* header)
{
int result = 0;
uint32_t hunknum;
int repcount = 0;
uint8_t lastcomp = 0;
uint32_t last_self = 0;
uint64_t last_parent = 0;
struct bitstream* bitbuf;
uint32_t mapbytes;
uint64_t firstoffs;
uint16_t mapcrc;
uint8_t lengthbits;
uint8_t selfbits;
uint8_t parentbits;
uint8_t *compressed_ptr;
uint8_t rawbuf[16];
struct huffman_decoder* decoder;
enum huffman_error err;
uint64_t curoffset;
int rawmapsize = map_size_v5(header);
if (rawmapsize < 0)
return CHDERR_INVALID_FILE;
if (!chd_compressed(header))
{
if ((header->mapoffset + rawmapsize) >= chd->file_size || (header->mapoffset + rawmapsize) < header->mapoffset)
return CHDERR_INVALID_FILE;
header->rawmap = (uint8_t*)malloc(rawmapsize);
if (header->rawmap == NULL)
return CHDERR_OUT_OF_MEMORY;
core_fseek(chd->file, header->mapoffset, SEEK_SET);
result = core_fread(chd->file, header->rawmap, rawmapsize);
return CHDERR_NONE;
}
core_fseek(chd->file, header->mapoffset, SEEK_SET);
result = core_fread(chd->file, rawbuf, sizeof(rawbuf));
mapbytes = get_bigendian_uint32_t(&rawbuf[0]);
firstoffs = get_bigendian_uint48(&rawbuf[4]);
mapcrc = get_bigendian_uint16(&rawbuf[10]);
lengthbits = rawbuf[12];
selfbits = rawbuf[13];
parentbits = rawbuf[14];
if ((header->mapoffset + mapbytes) < header->mapoffset || (header->mapoffset + mapbytes) >= chd->file_size)
return CHDERR_INVALID_FILE;
compressed_ptr = (uint8_t*)malloc(sizeof(uint8_t) * mapbytes);
if (compressed_ptr == NULL)
return CHDERR_OUT_OF_MEMORY;
core_fseek(chd->file, header->mapoffset + 16, SEEK_SET);
result = core_fread(chd->file, compressed_ptr, mapbytes);
bitbuf = create_bitstream(compressed_ptr, sizeof(uint8_t) * mapbytes);
header->rawmap = (uint8_t*)malloc(rawmapsize);
if (header->rawmap == NULL)
{
free(compressed_ptr);
free(bitbuf);
return CHDERR_OUT_OF_MEMORY;
}
decoder = create_huffman_decoder(16, 8);
if (decoder == NULL)
{
free(compressed_ptr);
free(bitbuf);
return CHDERR_OUT_OF_MEMORY;
}
err = huffman_import_tree_rle(decoder, bitbuf);
if (err != HUFFERR_NONE)
{
free(compressed_ptr);
free(bitbuf);
delete_huffman_decoder(decoder);
return CHDERR_DECOMPRESSION_ERROR;
}
for (hunknum = 0; hunknum < header->hunkcount; hunknum++)
{
uint8_t *rawmap = header->rawmap + (hunknum * 12);
if (repcount > 0)
rawmap[0] = lastcomp, repcount--;
else
{
uint8_t val;
if (bitstream_overflow(bitbuf))
{
free(compressed_ptr);
free(bitbuf);
delete_huffman_decoder(decoder);
return CHDERR_DECOMPRESSION_ERROR;
}
val = huffman_decode_one(decoder, bitbuf);
if (val == COMPRESSION_RLE_SMALL)
rawmap[0] = lastcomp, repcount = 2 + huffman_decode_one(decoder, bitbuf);
else if (val == COMPRESSION_RLE_LARGE)
rawmap[0] = lastcomp, repcount = 2 + 16 + (huffman_decode_one(decoder, bitbuf) << 4), repcount += huffman_decode_one(decoder, bitbuf);
else
rawmap[0] = lastcomp = val;
}
}
curoffset = firstoffs;
for (hunknum = 0; hunknum < header->hunkcount; hunknum++)
{
uint8_t *rawmap = header->rawmap + (hunknum * 12);
uint64_t offset = curoffset;
uint32_t length = 0;
uint16_t crc = 0;
switch (rawmap[0])
{
case COMPRESSION_TYPE_0:
case COMPRESSION_TYPE_1:
case COMPRESSION_TYPE_2:
case COMPRESSION_TYPE_3:
curoffset += length = bitstream_read(bitbuf, lengthbits);
crc = bitstream_read(bitbuf, 16);
break;
case COMPRESSION_NONE:
curoffset += length = header->hunkbytes;
crc = bitstream_read(bitbuf, 16);
break;
case COMPRESSION_SELF:
last_self = offset = bitstream_read(bitbuf, selfbits);
break;
case COMPRESSION_PARENT:
offset = bitstream_read(bitbuf, parentbits);
last_parent = offset;
break;
case COMPRESSION_SELF_1:
last_self++;
case COMPRESSION_SELF_0:
rawmap[0] = COMPRESSION_SELF;
offset = last_self;
break;
case COMPRESSION_PARENT_SELF:
rawmap[0] = COMPRESSION_PARENT;
last_parent = offset = ( ((uint64_t)hunknum) * ((uint64_t)header->hunkbytes) ) / header->unitbytes;
break;
case COMPRESSION_PARENT_1:
last_parent += header->hunkbytes / header->unitbytes;
case COMPRESSION_PARENT_0:
rawmap[0] = COMPRESSION_PARENT;
offset = last_parent;
break;
}
put_bigendian_uint24(&rawmap[1], length);
put_bigendian_uint48(&rawmap[4], offset);
put_bigendian_uint16(&rawmap[10], crc);
}
free(compressed_ptr);
free(bitbuf);
delete_huffman_decoder(decoder);
if (crc16(&header->rawmap[0], header->hunkcount * 12) != mapcrc)
return CHDERR_DECOMPRESSION_ERROR;
return CHDERR_NONE;
}
static inline void map_extract_old(const uint8_t *base, map_entry *entry, uint32_t hunkbytes)
{
entry->offset = get_bigendian_uint64_t(&base[0]);
entry->crc = 0;
entry->length = entry->offset >> 44;
entry->flags = MAP_ENTRY_FLAG_NO_CRC | ((entry->length == hunkbytes) ? V34_MAP_ENTRY_TYPE_UNCOMPRESSED : V34_MAP_ENTRY_TYPE_COMPRESSED);
#ifdef __MWERKS__
entry->offset = entry->offset & 0x00000FFFFFFFFFFFLL;
#else
entry->offset = (entry->offset << 20) >> 20;
#endif
}
CHD_EXPORT chd_error chd_open_file(FILE *file, int mode, chd_file *parent, chd_file **chd) {
chd_error err;
core_file *stream = malloc(sizeof(core_file));
if (!stream)
return CHDERR_OUT_OF_MEMORY;
stream->argp = file;
stream->fsize = core_stdio_fsize;
stream->fread = core_stdio_fread;
stream->fclose = core_stdio_fclose_nonowner;
stream->fseek = core_stdio_fseek;
err = chd_open_core_file(stream, mode, parent, chd);
if (err != CHDERR_NONE)
return err;
if (mode & CHD_OPEN_TRANSFER_FILE)
stream->fclose = core_stdio_fclose;
return CHDERR_NONE;
}
CHD_EXPORT chd_error chd_open_core_file(core_file *file, int mode, chd_file *parent, chd_file **chd)
{
chd_file *newchd = NULL;
chd_error err;
int intfnum;
if (file == NULL)
EARLY_EXIT(err = CHDERR_INVALID_PARAMETER);
if (parent != NULL && parent->cookie != COOKIE_VALUE)
EARLY_EXIT(err = CHDERR_INVALID_PARAMETER);
newchd = (chd_file *)malloc(sizeof(**chd));
if (newchd == NULL)
EARLY_EXIT(err = CHDERR_OUT_OF_MEMORY);
memset(newchd, 0, sizeof(*newchd));
newchd->cookie = COOKIE_VALUE;
newchd->parent = parent;
newchd->file = file;
newchd->file_size = core_fsize(file);
if ((int64_t)newchd->file_size <= 0)
EARLY_EXIT(err = CHDERR_INVALID_FILE);
err = header_read(newchd, &newchd->header);
if (err != CHDERR_NONE)
EARLY_EXIT(err);
err = header_validate(&newchd->header);
if (err != CHDERR_NONE)
EARLY_EXIT(err);
if (mode == CHD_OPEN_READWRITE && !(newchd->header.flags & CHDFLAGS_IS_WRITEABLE))
EARLY_EXIT(err = CHDERR_FILE_NOT_WRITEABLE);
if (mode == CHD_OPEN_READWRITE && newchd->header.version < CHD_HEADER_VERSION)
EARLY_EXIT(err = CHDERR_UNSUPPORTED_VERSION);
if (parent == NULL)
{
if (newchd->header.version < 5 && newchd->header.flags & CHDFLAGS_HAS_PARENT)
EARLY_EXIT(err = CHDERR_REQUIRES_PARENT);
else if (newchd->header.version >= 5 && memcmp(nullsha1, newchd->header.parentsha1, sizeof(newchd->header.parentsha1)) != 0)
EARLY_EXIT(err = CHDERR_REQUIRES_PARENT);
}
if (parent != NULL)
{
if (memcmp(nullmd5, newchd->header.parentmd5, sizeof(newchd->header.parentmd5)) != 0 &&
memcmp(nullmd5, newchd->parent->header.md5, sizeof(newchd->parent->header.md5)) != 0 &&
memcmp(newchd->parent->header.md5, newchd->header.parentmd5, sizeof(newchd->header.parentmd5)) != 0)
EARLY_EXIT(err = CHDERR_INVALID_PARENT);
if (memcmp(nullsha1, newchd->header.parentsha1, sizeof(newchd->header.parentsha1)) != 0 &&
memcmp(nullsha1, newchd->parent->header.sha1, sizeof(newchd->parent->header.sha1)) != 0 &&
memcmp(newchd->parent->header.sha1, newchd->header.parentsha1, sizeof(newchd->header.parentsha1)) != 0)
EARLY_EXIT(err = CHDERR_INVALID_PARENT);
}
if (newchd->header.version < 5)
{
err = map_read(newchd);
if (err != CHDERR_NONE)
EARLY_EXIT(err);
}
else
{
err = decompress_v5_map(newchd, &(newchd->header));
}
if (err != CHDERR_NONE)
EARLY_EXIT(err);
#ifdef NEED_CACHE_HUNK
newchd->cache = (uint8_t *)malloc(newchd->header.hunkbytes);
newchd->compare = (uint8_t *)malloc(newchd->header.hunkbytes);
if (newchd->cache == NULL || newchd->compare == NULL)
EARLY_EXIT(err = CHDERR_OUT_OF_MEMORY);
newchd->cachehunk = ~0;
newchd->comparehunk = ~0;
#endif
newchd->compressed = (uint8_t *)malloc(newchd->header.hunkbytes);
if (newchd->compressed == NULL)
EARLY_EXIT(err = CHDERR_OUT_OF_MEMORY);
if (newchd->header.version < 5)
{
for (intfnum = 0; intfnum < ARRAY_LENGTH(codec_interfaces); intfnum++)
{
if (codec_interfaces[intfnum].compression == newchd->header.compression[0])
{
newchd->codecintf[0] = &codec_interfaces[intfnum];
break;
}
}
if (intfnum == ARRAY_LENGTH(codec_interfaces))
EARLY_EXIT(err = CHDERR_UNSUPPORTED_FORMAT);
if (newchd->codecintf[0]->init != NULL)
{
err = (*newchd->codecintf[0]->init)(&newchd->zlib_codec_data, newchd->header.hunkbytes);
if (err != CHDERR_NONE)
EARLY_EXIT(err);
}
}
else
{
int decompnum, needsinit;
for (decompnum = 0; decompnum < ARRAY_LENGTH(newchd->header.compression); decompnum++)
{
int i;
for (i = 0 ; i < ARRAY_LENGTH(codec_interfaces) ; i++)
{
if (codec_interfaces[i].compression == newchd->header.compression[decompnum])
{
newchd->codecintf[decompnum] = &codec_interfaces[i];
break;
}
}
if (newchd->codecintf[decompnum] == NULL && newchd->header.compression[decompnum] != 0)
EARLY_EXIT(err = CHDERR_UNSUPPORTED_FORMAT);
needsinit = (newchd->codecintf[decompnum]->init != NULL);
for (i = 0; i < decompnum; i++)
{
if (newchd->codecintf[decompnum] == newchd->codecintf[i])
{
needsinit = 0;
break;
}
}
if (needsinit)
{
void* codec = NULL;
switch (newchd->header.compression[decompnum])
{
case CHD_CODEC_ZLIB:
codec = &newchd->zlib_codec_data;
break;
case CHD_CODEC_LZMA:
codec = &newchd->lzma_codec_data;
break;
case CHD_CODEC_HUFFMAN:
codec = &newchd->huff_codec_data;
break;
case CHD_CODEC_FLAC:
codec = &newchd->flac_codec_data;
break;
case CHD_CODEC_ZSTD:
codec = &newchd->zstd_codec_data;
break;
case CHD_CODEC_CD_ZLIB:
codec = &newchd->cdzl_codec_data;
break;
case CHD_CODEC_CD_LZMA:
codec = &newchd->cdlz_codec_data;
break;
case CHD_CODEC_CD_FLAC:
codec = &newchd->cdfl_codec_data;
break;
case CHD_CODEC_CD_ZSTD:
codec = &newchd->cdzs_codec_data;
break;
}
if (codec == NULL)
EARLY_EXIT(err = CHDERR_UNSUPPORTED_FORMAT);
err = (*newchd->codecintf[decompnum]->init)(codec, newchd->header.hunkbytes);
if (err != CHDERR_NONE)
EARLY_EXIT(err);
}
}
}
*chd = newchd;
return CHDERR_NONE;
cleanup:
if (newchd != NULL)
chd_close(newchd);
return err;
}
CHD_EXPORT chd_error chd_precache(chd_file* chd)
{
return chd_precache_progress(chd, NULL, NULL);
}
CHD_EXPORT chd_error chd_precache_progress(chd_file* chd, void(*progress)(size_t pos, size_t total, void* param), void* param)
{
#define PRECACHE_CHUNK_SIZE 16 * 1024 * 1024
size_t count;
uint64_t done, req_count, last_update_done, update_interval;
if (chd->file_cache == NULL)
{
chd->file_cache = malloc(chd->file_size);
if (chd->file_cache == NULL)
return CHDERR_OUT_OF_MEMORY;
if (core_fseek(chd->file, 0, SEEK_SET) != 0)
return CHDERR_READ_ERROR;
done = 0;
last_update_done = 0;
update_interval = ((chd->file_size + 99) / 100);
while (done < chd->file_size)
{
req_count = chd->file_size - done;
if (req_count > PRECACHE_CHUNK_SIZE)
req_count = PRECACHE_CHUNK_SIZE;
count = core_fread(chd->file, chd->file_cache + (size_t)done, (size_t)req_count);
if (count != (size_t)req_count)
{
free(chd->file_cache);
chd->file_cache = NULL;
return CHDERR_READ_ERROR;
}
done += req_count;
if (progress != NULL && (done - last_update_done) >= update_interval && done != chd->file_size)
{
last_update_done = done;
progress(done, chd->file_size, param);
}
}
}
#undef PRECACHE_CHUNK_SIZE
return CHDERR_NONE;
}
CHD_EXPORT chd_error chd_open(const char *filename, int mode, chd_file *parent, chd_file **chd)
{
chd_error err;
core_file *file = NULL;
if (filename == NULL)
{
err = CHDERR_INVALID_PARAMETER;
goto cleanup;
}
switch(mode)
{
case CHD_OPEN_READ:
break;
default:
err = CHDERR_INVALID_PARAMETER;
goto cleanup;
}
file = core_stdio_fopen(filename);
if (file == 0)
{
err = CHDERR_FILE_NOT_FOUND;
goto cleanup;
}
return chd_open_core_file(file, mode, parent, chd);
cleanup:
if ((err != CHDERR_NONE) && (file != NULL))
core_fclose(file);
return err;
}
CHD_EXPORT void chd_close(chd_file *chd)
{
if (chd == NULL || chd->cookie != COOKIE_VALUE)
return;
if (chd->header.version < 5)
{
if (chd->codecintf[0] != NULL && chd->codecintf[0]->free != NULL)
(*chd->codecintf[0]->free)(&chd->zlib_codec_data);
}
else
{
int i;
for (i = 0 ; i < ARRAY_LENGTH(chd->codecintf); i++)
{
void* codec = NULL;
int j, needsfree;
if (chd->codecintf[i] == NULL)
continue;
needsfree = 1;
for (j = 0; j < i; j++)
{
if (chd->codecintf[i] == chd->codecintf[j])
{
needsfree = 0;
break;
}
}
if (!needsfree)
continue;
switch (chd->codecintf[i]->compression)
{
case CHD_CODEC_ZLIB:
codec = &chd->zlib_codec_data;
break;
case CHD_CODEC_LZMA:
codec = &chd->lzma_codec_data;
break;
case CHD_CODEC_HUFFMAN:
codec = &chd->huff_codec_data;
break;
case CHD_CODEC_FLAC:
codec = &chd->flac_codec_data;
break;
case CHD_CODEC_ZSTD:
codec = &chd->zstd_codec_data;
break;
case CHD_CODEC_CD_ZLIB:
codec = &chd->cdzl_codec_data;
break;
case CHD_CODEC_CD_LZMA:
codec = &chd->cdlz_codec_data;
break;
case CHD_CODEC_CD_FLAC:
codec = &chd->cdfl_codec_data;
break;
case CHD_CODEC_CD_ZSTD:
codec = &chd->cdzs_codec_data;
break;
}
if (codec)
{
(*chd->codecintf[i]->free)(codec);
}
}
if (chd->header.rawmap != NULL)
free(chd->header.rawmap);
}
if (chd->compressed != NULL)
free(chd->compressed);
#ifdef NEED_CACHE_HUNK
if (chd->compare != NULL)
free(chd->compare);
if (chd->cache != NULL)
free(chd->cache);
#endif
if (chd->map != NULL)
free(chd->map);
if (chd->file != NULL)
core_fclose(chd->file);
#ifdef NEED_CACHE_HUNK
if (PRINTF_MAX_HUNK) printf("Max hunk = %d/%d\n", chd->maxhunk, chd->header.totalhunks);
#endif
if (chd->file_cache)
free(chd->file_cache);
if (chd->parent)
chd_close(chd->parent);
free(chd);
}
CHD_EXPORT core_file *chd_core_file(chd_file *chd)
{
return chd->file;
}
CHD_EXPORT uint64_t chd_get_compressed_size(chd_file *chd)
{
uint64_t size = chd->file->fsize(chd->file);
if (chd->parent)
size += chd_get_compressed_size(chd->parent);
return size;
}
CHD_EXPORT const char *chd_error_string(chd_error err)
{
switch (err)
{
case CHDERR_NONE: return "no error";
case CHDERR_NO_INTERFACE: return "no drive interface";
case CHDERR_OUT_OF_MEMORY: return "out of memory";
case CHDERR_INVALID_FILE: return "invalid file";
case CHDERR_INVALID_PARAMETER: return "invalid parameter";
case CHDERR_INVALID_DATA: return "invalid data";
case CHDERR_FILE_NOT_FOUND: return "file not found";
case CHDERR_REQUIRES_PARENT: return "requires parent";
case CHDERR_FILE_NOT_WRITEABLE: return "file not writeable";
case CHDERR_READ_ERROR: return "read error";
case CHDERR_WRITE_ERROR: return "write error";
case CHDERR_CODEC_ERROR: return "codec error";
case CHDERR_INVALID_PARENT: return "invalid parent";
case CHDERR_HUNK_OUT_OF_RANGE: return "hunk out of range";
case CHDERR_DECOMPRESSION_ERROR: return "decompression error";
case CHDERR_COMPRESSION_ERROR: return "compression error";
case CHDERR_CANT_CREATE_FILE: return "can't create file";
case CHDERR_CANT_VERIFY: return "can't verify file";
case CHDERR_NOT_SUPPORTED: return "operation not supported";
case CHDERR_METADATA_NOT_FOUND: return "can't find metadata";
case CHDERR_INVALID_METADATA_SIZE: return "invalid metadata size";
case CHDERR_UNSUPPORTED_VERSION: return "unsupported CHD version";
case CHDERR_VERIFY_INCOMPLETE: return "incomplete verify";
case CHDERR_INVALID_METADATA: return "invalid metadata";
case CHDERR_INVALID_STATE: return "invalid state";
case CHDERR_OPERATION_PENDING: return "operation pending";
case CHDERR_NO_ASYNC_OPERATION: return "no async operation in progress";
case CHDERR_UNSUPPORTED_FORMAT: return "unsupported format";
default: return "undocumented error";
}
}
CHD_EXPORT const chd_header *chd_get_header(chd_file *chd)
{
if (chd == NULL || chd->cookie != COOKIE_VALUE)
return NULL;
return &chd->header;
}
CHD_EXPORT chd_error chd_read_header_core_file(core_file* file, chd_header* header)
{
chd_file chd;
chd_error err;
chd.file = file;
err = header_read(&chd, header);
if (err != CHDERR_NONE)
return err;
return header_validate(header);
}
CHD_EXPORT chd_error chd_read_header_file(FILE *file, chd_header *header)
{
core_file stream;
stream.argp = file;
stream.fsize = core_stdio_fsize;
stream.fread = core_stdio_fread;
stream.fclose = core_stdio_fclose_nonowner;
stream.fseek = core_stdio_fseek;
return chd_read_header_core_file(&stream, header);
}
CHD_EXPORT chd_error chd_read_header(const char *filename, chd_header *header)
{
core_file *file;
chd_error err;
if (filename == NULL)
return CHDERR_INVALID_PARAMETER;
file = core_stdio_fopen(filename);
if (file == NULL)
return CHDERR_FILE_NOT_FOUND;
err = chd_read_header_core_file(file, header);
core_fclose(file);
return err;
}
CHD_EXPORT int chd_is_matching_parent(const chd_header* header, const chd_header* parent_header)
{
if (memcmp(nullmd5, header->parentmd5, sizeof(header->parentmd5)) != 0 &&
memcmp(nullmd5, parent_header->md5, sizeof(parent_header->md5)) != 0 &&
memcmp(parent_header->md5, header->parentmd5, sizeof(header->parentmd5)) != 0)
{
return 0;
}
if (memcmp(nullsha1, header->parentsha1, sizeof(header->parentsha1)) != 0 &&
memcmp(nullsha1, parent_header->sha1, sizeof(parent_header->sha1)) != 0 &&
memcmp(parent_header->sha1, header->parentsha1, sizeof(header->parentsha1)) != 0)
{
return 0;
}
return 1;
}
CHD_EXPORT chd_error chd_read(chd_file *chd, uint32_t hunknum, void *buffer)
{
if (chd == NULL || chd->cookie != COOKIE_VALUE)
return CHDERR_INVALID_PARAMETER;
if (hunknum >= chd->header.totalhunks)
return CHDERR_HUNK_OUT_OF_RANGE;
return hunk_read_into_memory(chd, hunknum, (uint8_t *)buffer);
}
CHD_EXPORT chd_error chd_get_metadata(chd_file *chd, uint32_t searchtag, uint32_t searchindex, void *output, uint32_t outputlen, uint32_t *resultlen, uint32_t *resulttag, uint8_t *resultflags)
{
metadata_entry metaentry;
chd_error err;
uint32_t count;
err = metadata_find_entry(chd, searchtag, searchindex, &metaentry);
if (err != CHDERR_NONE)
{
if (chd->header.version < 3 && (searchtag == HARD_DISK_METADATA_TAG || searchtag == CHDMETATAG_WILDCARD) && searchindex == 0)
{
char faux_metadata[256];
uint32_t faux_length;
sprintf(faux_metadata, HARD_DISK_METADATA_FORMAT, chd->header.obsolete_cylinders, chd->header.obsolete_heads, chd->header.obsolete_sectors, (chd->header.obsolete_hunksize != 0) ? (chd->header.hunkbytes / chd->header.obsolete_hunksize) : 0);
faux_length = (uint32_t)strlen(faux_metadata) + 1;
memcpy(output, faux_metadata, MIN(outputlen, faux_length));
if (resultlen != NULL)
*resultlen = faux_length;
if (resulttag != NULL)
*resulttag = HARD_DISK_METADATA_TAG;
return CHDERR_NONE;
}
return err;
}
outputlen = MIN(outputlen, metaentry.length);
core_fseek(chd->file, metaentry.offset + METADATA_HEADER_SIZE, SEEK_SET);
count = core_fread(chd->file, output, outputlen);
if (count != outputlen)
return CHDERR_READ_ERROR;
if (resultlen != NULL)
*resultlen = metaentry.length;
if (resulttag != NULL)
*resulttag = metaentry.metatag;
if (resultflags != NULL)
*resultflags = metaentry.flags;
return CHDERR_NONE;
}
CHD_EXPORT chd_error chd_codec_config(chd_file *chd, int param, void *config)
{
return CHDERR_INVALID_PARAMETER;
}
CHD_EXPORT const char *chd_get_codec_name(uint32_t codec)
{
return "Unknown";
}
static chd_error header_validate(const chd_header *header)
{
int intfnum;
if (header->version == 0 || header->version > CHD_HEADER_VERSION)
return CHDERR_UNSUPPORTED_VERSION;
if ((header->version == 1 && header->length != CHD_V1_HEADER_SIZE) ||
(header->version == 2 && header->length != CHD_V2_HEADER_SIZE) ||
(header->version == 3 && header->length != CHD_V3_HEADER_SIZE) ||
(header->version == 4 && header->length != CHD_V4_HEADER_SIZE) ||
(header->version == 5 && header->length != CHD_V5_HEADER_SIZE))
return CHDERR_INVALID_PARAMETER;
if (header->version <= 4)
{
if (header->flags & CHDFLAGS_UNDEFINED)
return CHDERR_INVALID_PARAMETER;
for (intfnum = 0; intfnum < ARRAY_LENGTH(codec_interfaces); intfnum++)
if (codec_interfaces[intfnum].compression == header->compression[0])
break;
if (intfnum == ARRAY_LENGTH(codec_interfaces))
return CHDERR_INVALID_PARAMETER;
if (header->hunkbytes == 0 || header->hunkbytes >= 65536 * 256)
return CHDERR_INVALID_PARAMETER;
if (header->totalhunks == 0)
return CHDERR_INVALID_PARAMETER;
if ((header->flags & CHDFLAGS_HAS_PARENT) && memcmp(header->parentmd5, nullmd5, sizeof(nullmd5)) == 0 && memcmp(header->parentsha1, nullsha1, sizeof(nullsha1)) == 0)
return CHDERR_INVALID_PARAMETER;
if (header->version >= 3 &&
(header->obsolete_cylinders != 0 || header->obsolete_sectors != 0 ||
header->obsolete_heads != 0 || header->obsolete_hunksize != 0))
return CHDERR_INVALID_PARAMETER;
if (header->version < 3 &&
(header->obsolete_cylinders == 0 || header->obsolete_sectors == 0 ||
header->obsolete_heads == 0 || header->obsolete_hunksize == 0))
return CHDERR_INVALID_PARAMETER;
}
if (header->hunkbytes >= CHD_MAX_HUNK_SIZE || ((uint64_t)header->hunkbytes * (uint64_t)header->totalhunks) >= CHD_MAX_FILE_SIZE)
return CHDERR_INVALID_PARAMETER;
return CHDERR_NONE;
}
static uint32_t header_guess_unitbytes(chd_file *chd)
{
char metadata[512];
int i0, i1, i2, i3;
if (chd_get_metadata(chd, HARD_DISK_METADATA_TAG, 0, metadata, sizeof(metadata), NULL, NULL, NULL) == CHDERR_NONE &&
sscanf(metadata, HARD_DISK_METADATA_FORMAT, &i0, &i1, &i2, &i3) == 4)
return i3;
if (chd_get_metadata(chd, CDROM_OLD_METADATA_TAG, 0, metadata, sizeof(metadata), NULL, NULL, NULL) == CHDERR_NONE ||
chd_get_metadata(chd, CDROM_TRACK_METADATA_TAG, 0, metadata, sizeof(metadata), NULL, NULL, NULL) == CHDERR_NONE ||
chd_get_metadata(chd, CDROM_TRACK_METADATA2_TAG, 0, metadata, sizeof(metadata), NULL, NULL, NULL) == CHDERR_NONE ||
chd_get_metadata(chd, GDROM_OLD_METADATA_TAG, 0, metadata, sizeof(metadata), NULL, NULL, NULL) == CHDERR_NONE ||
chd_get_metadata(chd, GDROM_TRACK_METADATA_TAG, 0, metadata, sizeof(metadata), NULL, NULL, NULL) == CHDERR_NONE)
return CD_FRAME_SIZE;
return chd->header.hunkbytes;
}
static chd_error header_read(chd_file *chd, chd_header *header)
{
uint8_t rawheader[CHD_MAX_HEADER_SIZE];
uint32_t count;
if (header == NULL)
return CHDERR_INVALID_PARAMETER;
if (chd->file == NULL)
return CHDERR_INVALID_FILE;
core_fseek(chd->file, 0, SEEK_SET);
count = core_fread(chd->file, rawheader, sizeof(rawheader));
if (count != sizeof(rawheader))
return CHDERR_READ_ERROR;
if (strncmp((char *)rawheader, "MComprHD", 8) != 0)
return CHDERR_INVALID_DATA;
memset(header, 0, sizeof(*header));
header->length = get_bigendian_uint32_t(&rawheader[8]);
header->version = get_bigendian_uint32_t(&rawheader[12]);
if (header->version == 0 || header->version > CHD_HEADER_VERSION)
return CHDERR_UNSUPPORTED_VERSION;
if ((header->version == 1 && header->length != CHD_V1_HEADER_SIZE) ||
(header->version == 2 && header->length != CHD_V2_HEADER_SIZE) ||
(header->version == 3 && header->length != CHD_V3_HEADER_SIZE) ||
(header->version == 4 && header->length != CHD_V4_HEADER_SIZE) ||
(header->version == 5 && header->length != CHD_V5_HEADER_SIZE))
return CHDERR_INVALID_DATA;
header->flags = get_bigendian_uint32_t(&rawheader[16]);
header->compression[0] = get_bigendian_uint32_t(&rawheader[20]);
header->compression[1] = CHD_CODEC_NONE;
header->compression[2] = CHD_CODEC_NONE;
header->compression[3] = CHD_CODEC_NONE;
if (header->version < 3)
{
int seclen = (header->version == 1) ? CHD_V1_SECTOR_SIZE : get_bigendian_uint32_t(&rawheader[76]);
header->obsolete_hunksize = get_bigendian_uint32_t(&rawheader[24]);
header->totalhunks = get_bigendian_uint32_t(&rawheader[28]);
header->obsolete_cylinders = get_bigendian_uint32_t(&rawheader[32]);
header->obsolete_heads = get_bigendian_uint32_t(&rawheader[36]);
header->obsolete_sectors = get_bigendian_uint32_t(&rawheader[40]);
memcpy(header->md5, &rawheader[44], CHD_MD5_BYTES);
memcpy(header->parentmd5, &rawheader[60], CHD_MD5_BYTES);
header->logicalbytes = (uint64_t)header->obsolete_cylinders * (uint64_t)header->obsolete_heads * (uint64_t)header->obsolete_sectors * (uint64_t)seclen;
header->hunkbytes = seclen * header->obsolete_hunksize;
header->unitbytes = header_guess_unitbytes(chd);
if (header->unitbytes == 0)
return CHDERR_INVALID_DATA;
header->unitcount = (header->logicalbytes + header->unitbytes - 1) / header->unitbytes;
header->metaoffset = 0;
}
else if (header->version == 3)
{
header->totalhunks = get_bigendian_uint32_t(&rawheader[24]);
header->logicalbytes = get_bigendian_uint64_t(&rawheader[28]);
header->metaoffset = get_bigendian_uint64_t(&rawheader[36]);
memcpy(header->md5, &rawheader[44], CHD_MD5_BYTES);
memcpy(header->parentmd5, &rawheader[60], CHD_MD5_BYTES);
header->hunkbytes = get_bigendian_uint32_t(&rawheader[76]);
header->unitbytes = header_guess_unitbytes(chd);
if (header->unitbytes == 0)
return CHDERR_INVALID_DATA;
header->unitcount = (header->logicalbytes + header->unitbytes - 1) / header->unitbytes;
memcpy(header->sha1, &rawheader[80], CHD_SHA1_BYTES);
memcpy(header->parentsha1, &rawheader[100], CHD_SHA1_BYTES);
}
else if (header->version == 4)
{
header->totalhunks = get_bigendian_uint32_t(&rawheader[24]);
header->logicalbytes = get_bigendian_uint64_t(&rawheader[28]);
header->metaoffset = get_bigendian_uint64_t(&rawheader[36]);
header->hunkbytes = get_bigendian_uint32_t(&rawheader[44]);
header->unitbytes = header_guess_unitbytes(chd);
if (header->unitbytes == 0)
return CHDERR_INVALID_DATA;
header->unitcount = (header->logicalbytes + header->unitbytes - 1) / header->unitbytes;
memcpy(header->sha1, &rawheader[48], CHD_SHA1_BYTES);
memcpy(header->parentsha1, &rawheader[68], CHD_SHA1_BYTES);
memcpy(header->rawsha1, &rawheader[88], CHD_SHA1_BYTES);
}
else if (header->version == 5)
{
header->compression[0] = get_bigendian_uint32_t(&rawheader[16]);
header->compression[1] = get_bigendian_uint32_t(&rawheader[20]);
header->compression[2] = get_bigendian_uint32_t(&rawheader[24]);
header->compression[3] = get_bigendian_uint32_t(&rawheader[28]);
header->logicalbytes = get_bigendian_uint64_t(&rawheader[32]);
header->mapoffset = get_bigendian_uint64_t(&rawheader[40]);
header->metaoffset = get_bigendian_uint64_t(&rawheader[48]);
header->hunkbytes = get_bigendian_uint32_t(&rawheader[56]);
if (header->hunkbytes == 0)
return CHDERR_INVALID_DATA;
header->hunkcount = (header->logicalbytes + header->hunkbytes - 1) / header->hunkbytes;
header->unitbytes = get_bigendian_uint32_t(&rawheader[60]);
if (header->unitbytes == 0)
return CHDERR_INVALID_DATA;
header->unitcount = (header->logicalbytes + header->unitbytes - 1) / header->unitbytes;
memcpy(header->sha1, &rawheader[84], CHD_SHA1_BYTES);
memcpy(header->parentsha1, &rawheader[104], CHD_SHA1_BYTES);
memcpy(header->rawsha1, &rawheader[64], CHD_SHA1_BYTES);
header->mapentrybytes = chd_compressed(header) ? 12 : 4;
header->totalhunks = header->hunkcount;
}
else
{
}
return CHDERR_NONE;
}
static uint8_t* hunk_read_compressed(chd_file *chd, uint64_t offset, size_t size)
{
#ifdef _MSC_VER
size_t bytes;
#else
ssize_t bytes;
#endif
if (chd->file_cache != NULL)
{
if ((offset + size) > chd->file_size || (offset + size) < offset)
return NULL;
else
return chd->file_cache + offset;
}
else
{
if (size > chd->header.hunkbytes)
return NULL;
core_fseek(chd->file, offset, SEEK_SET);
bytes = core_fread(chd->file, chd->compressed, size);
if (bytes != size)
return NULL;
return chd->compressed;
}
}
static chd_error hunk_read_uncompressed(chd_file *chd, uint64_t offset, size_t size, uint8_t *dest)
{
#ifdef _MSC_VER
size_t bytes;
#else
ssize_t bytes;
#endif
if (chd->file_cache != NULL)
{
if ((offset + size) > chd->file_size || (offset + size) < offset)
return CHDERR_READ_ERROR;
memcpy(dest, chd->file_cache + offset, size);
}
else
{
core_fseek(chd->file, offset, SEEK_SET);
bytes = core_fread(chd->file, dest, size);
if (bytes != size)
return CHDERR_READ_ERROR;
}
return CHDERR_NONE;
}
#ifdef NEED_CACHE_HUNK
static chd_error hunk_read_into_cache(chd_file *chd, uint32_t hunknum)
{
chd_error err;
if (hunknum > chd->maxhunk)
chd->maxhunk = hunknum;
if (chd->cachehunk == hunknum)
return CHDERR_NONE;
chd->cachehunk = ~0;
err = hunk_read_into_memory(chd, hunknum, chd->cache);
if (err != CHDERR_NONE)
return err;
chd->cachehunk = hunknum;
return CHDERR_NONE;
}
#endif
static chd_error hunk_read_into_memory(chd_file *chd, uint32_t hunknum, uint8_t *dest)
{
chd_error err;
if (chd->file == NULL)
return CHDERR_INVALID_FILE;
if (hunknum >= chd->header.totalhunks)
return CHDERR_HUNK_OUT_OF_RANGE;
if (dest == NULL)
return CHDERR_INVALID_PARAMETER;
if (chd->header.version < 5)
{
map_entry *entry = &chd->map[hunknum];
uint32_t bytes;
uint8_t* compressed_bytes;
switch (entry->flags & MAP_ENTRY_FLAG_TYPE_MASK)
{
case V34_MAP_ENTRY_TYPE_COMPRESSED:
{
void *codec = NULL;
compressed_bytes = hunk_read_compressed(chd, entry->offset, entry->length);
if (compressed_bytes == NULL)
{
return CHDERR_READ_ERROR;
}
err = CHDERR_NONE;
codec = &chd->zlib_codec_data;
if (chd->codecintf[0]->decompress != NULL)
err = (*chd->codecintf[0]->decompress)(codec, compressed_bytes, entry->length, dest, chd->header.hunkbytes);
if (err != CHDERR_NONE)
return err;
break;
}
case V34_MAP_ENTRY_TYPE_UNCOMPRESSED:
err = hunk_read_uncompressed(chd, entry->offset, chd->header.hunkbytes, dest);
if (err != CHDERR_NONE)
return err;
break;
case V34_MAP_ENTRY_TYPE_MINI:
put_bigendian_uint64_t(&dest[0], entry->offset);
for (bytes = 8; bytes < chd->header.hunkbytes; bytes++)
dest[bytes] = dest[bytes - 8];
break;
case V34_MAP_ENTRY_TYPE_SELF_HUNK:
#ifdef NEED_CACHE_HUNK
if (chd->cachehunk == entry->offset && dest == chd->cache)
break;
#endif
return hunk_read_into_memory(chd, entry->offset, dest);
case V34_MAP_ENTRY_TYPE_PARENT_HUNK:
err = hunk_read_into_memory(chd->parent, entry->offset, dest);
if (err != CHDERR_NONE)
return err;
break;
}
return CHDERR_NONE;
}
else
{
void* codec = NULL;
uint64_t blockoffs;
uint32_t blocklen;
#ifdef VERIFY_BLOCK_CRC
uint16_t blockcrc;
#endif
uint8_t *rawmap = &chd->header.rawmap[chd->header.mapentrybytes * hunknum];
uint8_t* compressed_bytes;
if (!chd_compressed(&chd->header))
{
blockoffs = (uint64_t)get_bigendian_uint32_t(rawmap) * (uint64_t)chd->header.hunkbytes;
if (blockoffs != 0) {
core_fseek(chd->file, blockoffs, SEEK_SET);
int result = core_fread(chd->file, dest, chd->header.hunkbytes);
} else if (chd->parent) {
err = hunk_read_into_memory(chd->parent, hunknum, dest);
if (err != CHDERR_NONE)
return err;
} else {
memset(dest, 0, chd->header.hunkbytes);
}
return CHDERR_NONE;
}
blocklen = get_bigendian_uint24(&rawmap[1]);
blockoffs = get_bigendian_uint48(&rawmap[4]);
#ifdef VERIFY_BLOCK_CRC
blockcrc = get_bigendian_uint16(&rawmap[10]);
#endif
codec = NULL;
switch (rawmap[0])
{
case COMPRESSION_TYPE_0:
case COMPRESSION_TYPE_1:
case COMPRESSION_TYPE_2:
case COMPRESSION_TYPE_3:
compressed_bytes = hunk_read_compressed(chd, blockoffs, blocklen);
if (compressed_bytes == NULL)
return CHDERR_READ_ERROR;
switch (chd->codecintf[rawmap[0]]->compression)
{
case CHD_CODEC_ZLIB:
codec = &chd->zlib_codec_data;
break;
case CHD_CODEC_LZMA:
codec = &chd->lzma_codec_data;
break;
case CHD_CODEC_HUFFMAN:
codec = &chd->huff_codec_data;
break;
case CHD_CODEC_FLAC:
codec = &chd->flac_codec_data;
break;
case CHD_CODEC_ZSTD:
codec = &chd->zstd_codec_data;
break;
case CHD_CODEC_CD_ZLIB:
codec = &chd->cdzl_codec_data;
break;
case CHD_CODEC_CD_LZMA:
codec = &chd->cdlz_codec_data;
break;
case CHD_CODEC_CD_FLAC:
codec = &chd->cdfl_codec_data;
break;
case CHD_CODEC_CD_ZSTD:
codec = &chd->cdzs_codec_data;
break;
}
if (codec==NULL)
return CHDERR_CODEC_ERROR;
err = chd->codecintf[rawmap[0]]->decompress(codec, compressed_bytes, blocklen, dest, chd->header.hunkbytes);
if (err != CHDERR_NONE)
return err;
#ifdef VERIFY_BLOCK_CRC
if (crc16(dest, chd->header.hunkbytes) != blockcrc)
return CHDERR_DECOMPRESSION_ERROR;
#endif
return CHDERR_NONE;
case COMPRESSION_NONE:
err = hunk_read_uncompressed(chd, blockoffs, blocklen, dest);
if (err != CHDERR_NONE)
return err;
#ifdef VERIFY_BLOCK_CRC
if (crc16(dest, chd->header.hunkbytes) != blockcrc)
return CHDERR_DECOMPRESSION_ERROR;
#endif
return CHDERR_NONE;
case COMPRESSION_SELF:
return hunk_read_into_memory(chd, blockoffs, dest);
case COMPRESSION_PARENT:
if (chd->parent == NULL)
return CHDERR_REQUIRES_PARENT;
uint8_t units_in_hunk = chd->header.hunkbytes / chd->header.unitbytes;
if (blockoffs % units_in_hunk == 0) {
return hunk_read_into_memory(chd->parent, blockoffs / units_in_hunk, dest);
} else {
uint32_t unit_in_hunk = blockoffs % units_in_hunk;
uint8_t *buf = malloc(chd->header.hunkbytes);
err = hunk_read_into_memory(chd->parent, blockoffs / units_in_hunk, buf);
if (err != CHDERR_NONE) {
free(buf);
return err;
}
memcpy(dest, buf + unit_in_hunk * chd->header.unitbytes, (units_in_hunk - unit_in_hunk) * chd->header.unitbytes);
err = hunk_read_into_memory(chd->parent, (blockoffs / units_in_hunk) + 1, buf);
if (err != CHDERR_NONE) {
free(buf);
return err;
}
memcpy(dest + (units_in_hunk - unit_in_hunk) * chd->header.unitbytes, buf, unit_in_hunk * chd->header.unitbytes);
free(buf);
}
}
return CHDERR_NONE;
}
return CHDERR_DECOMPRESSION_ERROR;
}
static chd_error map_read(chd_file *chd)
{
uint32_t entrysize = (chd->header.version < 3) ? OLD_MAP_ENTRY_SIZE : MAP_ENTRY_SIZE;
uint8_t raw_map_entries[MAP_STACK_ENTRIES * MAP_ENTRY_SIZE];
uint64_t fileoffset, maxoffset = 0;
uint8_t cookie[MAP_ENTRY_SIZE];
uint32_t count;
chd_error err;
uint32_t i;
chd->map = (map_entry *)malloc(sizeof(chd->map[0]) * chd->header.totalhunks);
if (!chd->map)
return CHDERR_OUT_OF_MEMORY;
fileoffset = chd->header.length;
for (i = 0; i < chd->header.totalhunks; i += MAP_STACK_ENTRIES)
{
int entries = chd->header.totalhunks - i, j;
if (entries > MAP_STACK_ENTRIES)
entries = MAP_STACK_ENTRIES;
core_fseek(chd->file, fileoffset, SEEK_SET);
count = core_fread(chd->file, raw_map_entries, entries * entrysize);
if (count != entries * entrysize)
{
err = CHDERR_READ_ERROR;
goto cleanup;
}
fileoffset += entries * entrysize;
if (entrysize == MAP_ENTRY_SIZE)
{
for (j = 0; j < entries; j++)
map_extract(&raw_map_entries[j * MAP_ENTRY_SIZE], &chd->map[i + j]);
}
else
{
for (j = 0; j < entries; j++)
map_extract_old(&raw_map_entries[j * OLD_MAP_ENTRY_SIZE], &chd->map[i + j], chd->header.hunkbytes);
}
for (j = 0; j < entries; j++)
if ((chd->map[i + j].flags & MAP_ENTRY_FLAG_TYPE_MASK) == V34_MAP_ENTRY_TYPE_COMPRESSED ||
(chd->map[i + j].flags & MAP_ENTRY_FLAG_TYPE_MASK) == V34_MAP_ENTRY_TYPE_UNCOMPRESSED)
maxoffset = MAX(maxoffset, chd->map[i + j].offset + chd->map[i + j].length);
}
core_fseek(chd->file, fileoffset, SEEK_SET);
count = core_fread(chd->file, &cookie, entrysize);
if (count != entrysize || memcmp(&cookie, END_OF_LIST_COOKIE, entrysize))
{
err = CHDERR_INVALID_FILE;
goto cleanup;
}
if (maxoffset > chd->file_size)
{
err = CHDERR_INVALID_FILE;
goto cleanup;
}
return CHDERR_NONE;
cleanup:
if (chd->map)
free(chd->map);
chd->map = NULL;
return err;
}
static chd_error metadata_find_entry(chd_file *chd, uint32_t metatag, uint32_t metaindex, metadata_entry *metaentry)
{
metaentry->offset = chd->header.metaoffset;
metaentry->prev = 0;
while (metaentry->offset != 0)
{
uint8_t raw_meta_header[METADATA_HEADER_SIZE];
uint32_t count;
if (core_fseek(chd->file, metaentry->offset, SEEK_SET) != 0)
break;
count = core_fread(chd->file, raw_meta_header, sizeof(raw_meta_header));
if (count != sizeof(raw_meta_header))
break;
metaentry->metatag = get_bigendian_uint32_t(&raw_meta_header[0]);
metaentry->length = get_bigendian_uint32_t(&raw_meta_header[4]);
metaentry->next = get_bigendian_uint64_t(&raw_meta_header[8]);
metaentry->flags = metaentry->length >> 24;
metaentry->length &= 0x00ffffff;
if (metatag == CHDMETATAG_WILDCARD || metaentry->metatag == metatag)
if (metaindex-- == 0)
return CHDERR_NONE;
metaentry->prev = metaentry->offset;
metaentry->offset = metaentry->next;
}
return CHDERR_METADATA_NOT_FOUND;
}
static chd_error zlib_codec_init(void *codec, uint32_t hunkbytes)
{
int zerr;
chd_error err;
zlib_codec_data *data = (zlib_codec_data*)codec;
memset(data, 0, sizeof(zlib_codec_data));
data->inflater.next_in = (Bytef *)data;
data->inflater.avail_in = 0;
data->inflater.zalloc = zlib_fast_alloc;
data->inflater.zfree = zlib_fast_free;
data->inflater.opaque = &data->allocator;
zerr = inflateInit2(&data->inflater, -MAX_WBITS);
if (zerr == Z_MEM_ERROR)
err = CHDERR_OUT_OF_MEMORY;
else if (zerr != Z_OK)
err = CHDERR_CODEC_ERROR;
else
err = CHDERR_NONE;
return err;
}
static void zlib_codec_free(void *codec)
{
zlib_codec_data *data = (zlib_codec_data *)codec;
if (data != NULL)
{
inflateEnd(&data->inflater);
zlib_allocator_free(&data->allocator);
}
}
static chd_error zlib_codec_decompress(void *codec, const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen)
{
zlib_codec_data *data = (zlib_codec_data *)codec;
int zerr;
data->inflater.next_in = (Bytef *)src;
data->inflater.avail_in = complen;
data->inflater.total_in = 0;
data->inflater.next_out = (Bytef *)dest;
data->inflater.avail_out = destlen;
data->inflater.total_out = 0;
zerr = inflateReset(&data->inflater);
if (zerr != Z_OK)
return CHDERR_DECOMPRESSION_ERROR;
zerr = inflate(&data->inflater, Z_FINISH);
if (data->inflater.total_out != destlen)
return CHDERR_DECOMPRESSION_ERROR;
return CHDERR_NONE;
}
#define ZLIB_MIN_ALIGNMENT_BITS 512
#define ZLIB_MIN_ALIGNMENT_BYTES (ZLIB_MIN_ALIGNMENT_BITS / 8)
static voidpf zlib_fast_alloc(voidpf opaque, uInt items, uInt size)
{
zlib_allocator *alloc = (zlib_allocator *)opaque;
uintptr_t paddr = 0;
uint32_t *ptr;
int i;
size = (size * items + 0x3ff) & ~0x3ff;
for (i = 0; i < MAX_ZLIB_ALLOCS; i++)
{
ptr = alloc->allocptr[i];
if (ptr && size == *ptr)
{
*ptr |= 1;
return (voidpf)(alloc->allocptr2[i]);
}
}
ptr = (uint32_t *)malloc(size + sizeof(uint32_t) + ZLIB_MIN_ALIGNMENT_BYTES);
if (!ptr)
return NULL;
for (i = 0; i < MAX_ZLIB_ALLOCS; i++)
if (!alloc->allocptr[i])
{
alloc->allocptr[i] = ptr;
paddr = (((uintptr_t)ptr) + sizeof(uint32_t) + (ZLIB_MIN_ALIGNMENT_BYTES-1)) & (~(ZLIB_MIN_ALIGNMENT_BYTES-1));
alloc->allocptr2[i] = (uint32_t*)paddr;
break;
}
*ptr = size | 1;
return (voidpf)paddr;
}
static void zlib_fast_free(voidpf opaque, voidpf address)
{
zlib_allocator *alloc = (zlib_allocator *)opaque;
uint32_t *ptr = (uint32_t *)address;
int i;
for (i = 0; i < MAX_ZLIB_ALLOCS; i++)
if (ptr == alloc->allocptr2[i])
{
*(alloc->allocptr[i]) &= ~1;
return;
}
}
static void zlib_allocator_free(voidpf opaque)
{
zlib_allocator *alloc = (zlib_allocator *)opaque;
int i;
for (i = 0; i < MAX_ZLIB_ALLOCS; i++)
if (alloc->allocptr[i])
free(alloc->allocptr[i]);
}
static core_file *core_stdio_fopen(char const *path) {
core_file *file = malloc(sizeof(core_file));
if (!file)
return NULL;
if (!(file->argp = fopen(path, "rb"))) {
free(file);
return NULL;
}
file->fsize = core_stdio_fsize;
file->fread = core_stdio_fread;
file->fclose = core_stdio_fclose;
file->fseek = core_stdio_fseek;
return file;
}
static uint64_t core_stdio_fsize(core_file *file) {
#if defined USE_LIBRETRO_VFS
#define core_stdio_fseek_impl fseek
#define core_stdio_ftell_impl ftell
#elif defined(__WIN32__) || defined(_WIN32) || defined(WIN32) || defined(__WIN64__)
#define core_stdio_fseek_impl _fseeki64
#define core_stdio_ftell_impl _ftelli64
#elif defined(_LARGEFILE_SOURCE) && defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64
#define core_stdio_fseek_impl fseeko64
#define core_stdio_ftell_impl ftello64
#elif defined(__PS3__) && !defined(__PSL1GHT__) || defined(__SWITCH__) || defined(__vita__)
#define core_stdio_fseek_impl(x,y,z) fseek(x,(off_t)y,z)
#define core_stdio_ftell_impl(x) (off_t)ftell(x)
#else
#define core_stdio_fseek_impl fseeko
#define core_stdio_ftell_impl ftello
#endif
FILE *fp;
uint64_t p, rv;
fp = (FILE*)file->argp;
p = core_stdio_ftell_impl(fp);
core_stdio_fseek_impl(fp, 0, SEEK_END);
rv = core_stdio_ftell_impl(fp);
core_stdio_fseek_impl(fp, p, SEEK_SET);
return rv;
}
static size_t core_stdio_fread(void *ptr, size_t size, size_t nmemb, core_file *file) {
return fread(ptr, size, nmemb, (FILE*)file->argp);
}
static int core_stdio_fclose(core_file *file) {
int err = fclose((FILE*)file->argp);
if (err == 0)
free(file);
return err;
}
static int core_stdio_fclose_nonowner(core_file *file) {
free(file);
return 0;
}
static int core_stdio_fseek(core_file* file, int64_t offset, int whence) {
return core_stdio_fseek_impl((FILE*)file->argp, offset, whence);
}