diff --git a/include/libchdr/chd.h b/include/libchdr/chd.h index c3330b9..cc82711 100644 --- a/include/libchdr/chd.h +++ b/include/libchdr/chd.h @@ -417,7 +417,10 @@ CHD_EXPORT chd_error chd_read_header(const char *filename, chd_header *header); /* read one hunk from the CHD file */ CHD_EXPORT chd_error chd_read(chd_file *chd, uint32_t hunknum, void *buffer); - +/* read one hunk from the CHD file without touching it's state so this method is thread-safe + * compressed_buffer must be at least 'hunkbytes'-bytes sized. + * 'fpread' file callback is required for this method, otherwise 'CHDERR_NOT_SUPPORTED' will be returned. */ +CHD_EXPORT chd_error chd_read_threadsafe(chd_file *chd, uint32_t hunknum, void *buffer, void *compressed_buffer); /* ----- metadata management ----- */ diff --git a/include/libchdr/coretypes.h b/include/libchdr/coretypes.h index 11692d7..724ef0f 100644 --- a/include/libchdr/coretypes.h +++ b/include/libchdr/coretypes.h @@ -32,6 +32,9 @@ typedef struct chd_core_file_callbacks { // fseek clone int (*fseek)(void*, int64_t, int); + + // pread clone + size_t (*fpread)(void*, void*, size_t, int64_t); } core_file_callbacks; typedef struct chd_core_file_callbacks_and_argp { @@ -72,4 +75,12 @@ static CHDR_INLINE uint64_t core_fsize(const core_file_callbacks_and_argp *fp) return fp->callbacks->fsize(fp->argp); } +static CHDR_INLINE int core_has_fpread(const core_file_callbacks_and_argp *fp) { + return fp->callbacks->fpread != NULL; +} + +static CHDR_INLINE size_t core_fpread(const core_file_callbacks_and_argp *fp, void *ptr, size_t len, int64_t offset) { + return fp->callbacks->fpread(fp->argp, ptr, len, offset); +} + #endif diff --git a/src/libchdr_chd.c b/src/libchdr_chd.c index 0ed25e4..13d23ca 100644 --- a/src/libchdr_chd.c +++ b/src/libchdr_chd.c @@ -52,6 +52,20 @@ # endif #endif +/* Includes used to implement "fpread" on POSIX and Windows */ +#if defined(__WIN32__) || defined(_WIN32) || defined(WIN32) || defined(__WIN64__) +# ifndef _PREAD_WINDOWS +# define _PREAD_WINDOWS 1 +# endif +#include +#include +#elif defined (__unix__) || (defined (__APPLE__) && defined (__MACH__)) +# ifndef _PREAD_UNISTD +# define _PREAD_UNISTD 1 +# endif +#include +#endif + #include #include #include @@ -351,6 +365,8 @@ struct _chd_file #endif }; +/* definition of helper that reads N bytes from file by given offset */ +typedef int (buffer_filler) (chd_file *chd, uint64_t position, void *buffer, size_t total_bytes); /*************************************************************************** GLOBAL VARIABLES @@ -370,6 +386,7 @@ static size_t core_stdio_fread(void *ptr, size_t size, size_t nmemb, void *file) static int core_stdio_fclose(void *file); static int core_stdio_fclose_nonowner(void *file); /* alternate fclose used by chd_open_file */ static int core_stdio_fseek(void* file, int64_t offset, int whence); +static size_t core_stdio_fpread(void *file, void *ptr, size_t len, int64_t offset); /* Legacy core_file wrappers */ static uint64_t core_legacy_fsize(void *file); @@ -381,7 +398,7 @@ static int core_legacy_fseek(void* file, int64_t offset, int whence); static chd_error header_read(chd_file *chd); /* internal hunk read/write */ -static chd_error hunk_read_into_memory(chd_file *chd, uint32_t hunknum, uint8_t *dest); +static chd_error hunk_read_into_memory(chd_file *chd, uint32_t hunknum, uint8_t *dest, uint8_t *compressed_dest, buffer_filler *reader); /* internal map access */ static chd_error map_read(chd_file *chd); @@ -574,6 +591,21 @@ static CHDR_INLINE int seek_and_read(chd_file *chd, uint64_t position, void *buf return TRUE; } + +/*------------------------------------------------- + read_at - read data from file at + specified position using 'pread' +-------------------------------------------------*/ +static CHDR_INLINE int read_at(chd_file *chd, uint64_t position, void *buffer, size_t total_bytes) +{ + if (!core_has_fpread(&chd->file)) + return FALSE; + if (core_fpread(&chd->file, buffer, total_bytes, position) != total_bytes) + return FALSE; + + return TRUE; +} + /*------------------------------------------------- get_bigendian_uint64_t - fetch a uint64_t from the data stream in bigendian order @@ -1633,21 +1665,32 @@ static const core_file_callbacks core_stdio = { core_stdio_fsize, core_stdio_fread, core_stdio_fclose, - core_stdio_fseek + core_stdio_fseek, +#if defined (_PREAD_UNISTD) || defined (_PREAD_WINDOWS) + core_stdio_fpread, +#else + NULL, +#endif }; static const core_file_callbacks core_stdio_nonowner = { core_stdio_fsize, core_stdio_fread, core_stdio_fclose_nonowner, - core_stdio_fseek + core_stdio_fseek, +#if defined (_PREAD_UNISTD) || defined (_PREAD_WINDOWS) + core_stdio_fpread, +#else + NULL, +#endif }; static const core_file_callbacks core_legacy = { core_legacy_fsize, core_legacy_fread, core_legacy_fclose, - core_legacy_fseek + core_legacy_fseek, + NULL, }; /*------------------------------------------------- @@ -2278,9 +2321,33 @@ CHD_EXPORT chd_error chd_read(chd_file *chd, uint32_t hunknum, void *buffer) return CHDERR_HUNK_OUT_OF_RANGE; /* perform the read */ - return hunk_read_into_memory(chd, hunknum, (uint8_t *)buffer); + return hunk_read_into_memory(chd, hunknum, (uint8_t *)buffer, chd->compressed, seek_and_read); +} + +/*------------------------------------------------- + chd_read_threadsafe - read a single hunk from the CHD + file without touching it's state +-------------------------------------------------*/ + +CHD_EXPORT chd_error chd_read_threadsafe(chd_file *chd, uint32_t hunknum, void *buffer, void *compressed_buffer) +{ + /* punt if NULL or invalid */ + if (chd == NULL || chd->cookie != COOKIE_VALUE) + return CHDERR_INVALID_PARAMETER; + + /* unsupported if no 'fpread' provided */ + if (!core_has_fpread(&chd->file)) + return CHDERR_NOT_SUPPORTED; + + /* if we're past the end, fail */ + if (hunknum >= chd->header.totalhunks) + return CHDERR_HUNK_OUT_OF_RANGE; + + /* perform the read */ + return hunk_read_into_memory(chd, hunknum, (uint8_t *)buffer, compressed_buffer, read_at); } + /*************************************************************************** METADATA MANAGEMENT ***************************************************************************/ @@ -2583,7 +2650,7 @@ static chd_error header_read(chd_file *chd) hunk -------------------------------------------------*/ -static uint8_t* hunk_read_compressed(chd_file *chd, uint64_t offset, size_t size) +static uint8_t* hunk_read_compressed(chd_file *chd, uint64_t offset, size_t size, uint8_t *compressed_dest, buffer_filler *reader) { if (chd->file_cache != NULL) { @@ -2604,19 +2671,20 @@ static uint8_t* hunk_read_compressed(chd_file *chd, uint64_t offset, size_t size * compressed_capacity). Monotonic: only grows, never shrinks, so a * realloc failure here leaves the existing buffer/capacity intact * and this call simply fails - the chd_file stays consistent. */ - if (size > chd->compressed_capacity) + if (compressed_dest == chd->compressed && size > chd->compressed_capacity) { uint8_t *grown = (uint8_t*)realloc(chd->compressed, size); if (grown == NULL) return NULL; chd->compressed = grown; chd->compressed_capacity = (uint32_t)size; + compressed_dest = chd->compressed; } #endif - if (!seek_and_read(chd, offset, chd->compressed, size)) + if (!reader(chd, offset, compressed_dest, size)) return NULL; - return chd->compressed; + return compressed_dest; } } @@ -2625,7 +2693,7 @@ static uint8_t* hunk_read_compressed(chd_file *chd, uint64_t offset, size_t size hunk -------------------------------------------------*/ -static chd_error hunk_read_uncompressed(chd_file *chd, uint64_t offset, size_t size, uint8_t *dest) +static chd_error hunk_read_uncompressed(chd_file *chd, uint64_t offset, size_t size, uint8_t *dest, buffer_filler *reader) { if (chd->file_cache != NULL) { @@ -2636,7 +2704,7 @@ static chd_error hunk_read_uncompressed(chd_file *chd, uint64_t offset, size_t s } else { - if (!seek_and_read(chd, offset, dest, size)) + if (!reader(chd, offset, dest, size)) return CHDERR_READ_ERROR; } return CHDERR_NONE; @@ -2647,7 +2715,7 @@ static chd_error hunk_read_uncompressed(chd_file *chd, uint64_t offset, size_t s memory at the given location -------------------------------------------------*/ -static chd_error hunk_read_into_memory(chd_file *chd, uint32_t hunknum, uint8_t *dest) +static chd_error hunk_read_into_memory(chd_file *chd, uint32_t hunknum, uint8_t *dest, uint8_t *compressed_dest, buffer_filler *reader) { chd_error err; @@ -2684,7 +2752,7 @@ static chd_error hunk_read_into_memory(chd_file *chd, uint32_t hunknum, uint8_t void *codec = NULL; /* read it into the decompression buffer */ - compressed_bytes = hunk_read_compressed(chd, entry->offset, entry->length); + compressed_bytes = hunk_read_compressed(chd, entry->offset, entry->length, compressed_dest, reader); if (compressed_bytes == NULL) return CHDERR_READ_ERROR; @@ -2702,7 +2770,7 @@ static chd_error hunk_read_into_memory(chd_file *chd, uint32_t hunknum, uint8_t /* uncompressed data */ case V34_MAP_ENTRY_TYPE_UNCOMPRESSED: - err = hunk_read_uncompressed(chd, entry->offset, chd->header.hunkbytes, dest); + err = hunk_read_uncompressed(chd, entry->offset, chd->header.hunkbytes, dest, reader); if (err != CHDERR_NONE) return err; break; @@ -2716,11 +2784,11 @@ static chd_error hunk_read_into_memory(chd_file *chd, uint32_t hunknum, uint8_t /* self-referenced data */ case V34_MAP_ENTRY_TYPE_SELF_HUNK: - return hunk_read_into_memory(chd, entry->offset, dest); + return hunk_read_into_memory(chd, entry->offset, dest, compressed_dest, reader); /* parent-referenced data */ case V34_MAP_ENTRY_TYPE_PARENT_HUNK: - err = hunk_read_into_memory(chd->parent, entry->offset, dest); + err = hunk_read_into_memory(chd->parent, entry->offset, dest, compressed_dest, reader); if (err != CHDERR_NONE) return err; break; @@ -2757,7 +2825,7 @@ static chd_error hunk_read_into_memory(chd_file *chd, uint32_t hunknum, uint8_t else if (m_parent_missing) throw CHDERR_REQUIRES_PARENT; */ } else if (chd->parent) { - err = hunk_read_into_memory(chd->parent, hunknum, dest); + err = hunk_read_into_memory(chd->parent, hunknum, dest, compressed_dest, reader); if (err != CHDERR_NONE) return err; } else { @@ -2780,7 +2848,7 @@ static chd_error hunk_read_into_memory(chd_file *chd, uint32_t hunknum, uint8_t case COMPRESSION_TYPE_1: case COMPRESSION_TYPE_2: case COMPRESSION_TYPE_3: - compressed_bytes = hunk_read_compressed(chd, blockoffs, blocklen); + compressed_bytes = hunk_read_compressed(chd, blockoffs, blocklen, compressed_dest, reader); if (compressed_bytes == NULL) return CHDERR_READ_ERROR; switch (chd->codecintf[rawmap[0]]->compression) @@ -2841,7 +2909,7 @@ static chd_error hunk_read_into_memory(chd_file *chd, uint32_t hunknum, uint8_t return CHDERR_NONE; case COMPRESSION_NONE: - err = hunk_read_uncompressed(chd, blockoffs, blocklen, dest); + err = hunk_read_uncompressed(chd, blockoffs, blocklen, dest, reader); if (err != CHDERR_NONE) return err; #if VERIFY_BLOCK_CRC @@ -2851,7 +2919,7 @@ static chd_error hunk_read_into_memory(chd_file *chd, uint32_t hunknum, uint8_t return CHDERR_NONE; case COMPRESSION_SELF: - return hunk_read_into_memory(chd, blockoffs, dest); + return hunk_read_into_memory(chd, blockoffs, dest, compressed_dest, reader); case COMPRESSION_PARENT: { @@ -2863,20 +2931,20 @@ static chd_error hunk_read_into_memory(chd_file *chd, uint32_t hunknum, uint8_t /* blockoffs is aligned to units_in_hunk */ if (blockoffs % units_in_hunk == 0) { - return hunk_read_into_memory(chd->parent, blockoffs / units_in_hunk, dest); + return hunk_read_into_memory(chd->parent, blockoffs / units_in_hunk, dest, compressed_dest, reader); /* blockoffs is not aligned to units_in_hunk */ } else { uint32_t unit_in_hunk = blockoffs % units_in_hunk; uint8_t *buf = (uint8_t*)malloc(chd->header.hunkbytes); /* Read first half of hunk which contains blockoffs */ - err = hunk_read_into_memory(chd->parent, blockoffs / units_in_hunk, buf); + err = hunk_read_into_memory(chd->parent, blockoffs / units_in_hunk, buf, compressed_dest, reader); if (err != CHDERR_NONE) { free(buf); return err; } memcpy(dest, buf + (size_t)unit_in_hunk * chd->header.unitbytes, (size_t)(units_in_hunk - unit_in_hunk) * chd->header.unitbytes); /* Read second half of hunk which contains blockoffs */ - err = hunk_read_into_memory(chd->parent, (blockoffs / units_in_hunk) + 1, buf); + err = hunk_read_into_memory(chd->parent, (blockoffs / units_in_hunk) + 1, buf, compressed_dest, reader); if (err != CHDERR_NONE) { free(buf); return err; @@ -3132,6 +3200,39 @@ static int core_stdio_fseek(void* file, int64_t offset, int whence) { return core_stdio_fseek_impl((FILE*)file, offset, whence); } +/*------------------------------------------------- + core_stdio_fpread - core_file wrapper over pread ready for FILE* +-------------------------------------------------*/ +static size_t core_stdio_fpread(void *file, void *ptr, size_t len, int64_t offset) { +#if defined _PREAD_UNISTD + return pread(fileno((FILE*)file), ptr, len, offset); +#elif defined _PREAD_WINDOWS + /* Windows does not have "pread" function + * but we can implement it with ReadFile and OVERLAPPED + * Reference: https://github.com/aleitner/windows_pread */ + long unsigned int read_bytes = 0; + + OVERLAPPED overlapped; + memset(&overlapped, 0, sizeof(OVERLAPPED)); + + overlapped.OffsetHigh = (uint32_t)((offset & 0xFFFFFFFF00000000LL) >> 32); + overlapped.Offset = (uint32_t)(offset & 0xFFFFFFFFLL); + + HANDLE fhandle = (HANDLE)_get_osfhandle(fileno((FILE*)file)); + SetLastError(0); + int RF = ReadFile(fhandle, ptr, len, &read_bytes, &overlapped); + + /* For some reason it errors when it hits end of file so we don't want to check that */ + if ((RF == 0) && GetLastError() != ERROR_HANDLE_EOF) + return (size_t)-1; + + return (size_t)read_bytes; +#else + return (size_t)-1; +#endif +} + + /*------------------------------------------------- core_legacy_fsize - legacy core_file wrapper -------------------------------------------------*/