diff -pruN 0.0~git20240929.aaca599+dfsg-1/.github/workflows/bsd.yml 0.0~git20250608.8bba774+dfsg-1/.github/workflows/bsd.yml
--- 0.0~git20240929.aaca599+dfsg-1/.github/workflows/bsd.yml	2024-09-29 20:16:09.000000000 +0000
+++ 0.0~git20250608.8bba774+dfsg-1/.github/workflows/bsd.yml	2025-06-08 18:58:18.000000000 +0000
@@ -11,19 +11,19 @@ jobs:
         architecture: [ arm64, x86-64 ]
         include:
           - operating_system: freebsd
-            version: '14.0'
-            pkginstall: sudo pkg install -y cmake git ninja pkgconf
+            version: '14.2'
+            pkginstall: sudo pkg update && sudo pkg install -y cmake git ninja pkgconf
           - operating_system: netbsd
-            version: '10.0'
-            pkginstall: sudo pkgin update && sudo pkgin -y install cmake gcc12 git ninja-build pkgconf && export PATH=/usr/pkg/gcc12/bin:$PATH
+            version: '10.1'
+            pkginstall: sudo pkgin update && sudo pkgin -y install clang cmake git ninja-build pkgconf
           - operating_system: openbsd
-            version: '7.5'
-            pkginstall: sudo pkg_add cmake git ninja pkgconf
+            version: '7.7'
+            pkginstall: sudo pkg_add -u && sudo pkg_add cmake git ninja pkgconf
 
     steps:
       - uses: actions/checkout@v4
 
-      - uses: cross-platform-actions/action@v0.24.0
+      - uses: cross-platform-actions/action@v0.28.0
         with:
           operating_system: ${{ matrix.operating_system }}
           architecture: ${{ matrix.architecture }}
diff -pruN 0.0~git20240929.aaca599+dfsg-1/CMakeLists.txt 0.0~git20250608.8bba774+dfsg-1/CMakeLists.txt
--- 0.0~git20240929.aaca599+dfsg-1/CMakeLists.txt	2024-09-29 20:16:09.000000000 +0000
+++ 0.0~git20250608.8bba774+dfsg-1/CMakeLists.txt	2025-06-08 18:58:18.000000000 +0000
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.9)
+cmake_minimum_required(VERSION 3.10)
 
 project(chdr VERSION 0.2 LANGUAGES C)
 
@@ -7,6 +7,7 @@ if(CMAKE_PROJECT_NAME STREQUAL "chdr")
 endif()
 option(INSTALL_STATIC_LIBS "Install static libraries" OFF)
 option(WITH_SYSTEM_ZLIB "Use system provided zlib library" OFF)
+option(WITH_SYSTEM_ZSTD "Use system provided zstd library" OFF)
 
 option(BUILD_LTO "Compile libchdr with link-time optimization if supported" OFF)
 if(BUILD_LTO)
@@ -17,6 +18,14 @@ if(BUILD_LTO)
   endif()
 endif()
 
+option(BUILD_FUZZER "Build instrumented binary for fuzzing with libfuzzer, requires clang")
+if(BUILD_FUZZER)
+  # Override CFLAGS early for instrumentation. Disable shared libs for instrumentation.
+  set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address,fuzzer-no-link")
+  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address,fuzzer-no-link")
+  set(BUILD_SHARED_LIBS OFF)
+endif()
+
 include(GNUInstallDirs)
 
 #--------------------------------------------------
@@ -25,27 +34,42 @@ include(GNUInstallDirs)
 
 
 # lzma
-add_subdirectory(deps/lzma-24.05 EXCLUDE_FROM_ALL)
-  list(APPEND CHDR_LIBS lzma)
-  list(APPEND CHDR_INCLUDES lzma)
+if(NOT TARGET lzma)
+  add_subdirectory(deps/lzma-24.05 EXCLUDE_FROM_ALL)
+endif()
+list(APPEND CHDR_LIBS lzma)
+list(APPEND CHDR_INCLUDES lzma)
 
 # zlib
 if (WITH_SYSTEM_ZLIB)
   find_package(ZLIB REQUIRED)
   list(APPEND PLATFORM_LIBS ZLIB::ZLIB)
 else()
-  option(ZLIB_BUILD_EXAMPLES "Enable Zlib Examples" OFF)
-  add_subdirectory(deps/zlib-1.3.1 EXCLUDE_FROM_ALL)
-  set_target_properties(zlibstatic PROPERTIES POSITION_INDEPENDENT_CODE ON)
+  if(NOT TARGET zlibstatic)
+    option(ZLIB_BUILD_EXAMPLES "Enable Zlib Examples" OFF)
+    add_subdirectory(deps/zlib-1.3.1 EXCLUDE_FROM_ALL)
+    set_target_properties(zlibstatic PROPERTIES POSITION_INDEPENDENT_CODE ON)
+  endif()
   list(APPEND CHDR_LIBS zlibstatic)
 endif()
 
 # zstd
-option(ZSTD_BUILD_SHARED "BUILD SHARED LIBRARIES" OFF)
-option(ZSTD_BUILD_PROGRAMS "BUILD PROGRAMS" OFF)
-option(ZSTD_LEGACY_SUPPORT "LEGACY SUPPORT" OFF)
-add_subdirectory(deps/zstd-1.5.6/build/cmake EXCLUDE_FROM_ALL)
-list(APPEND CHDR_LIBS libzstd_static)
+if (WITH_SYSTEM_ZSTD)
+  find_package(zstd REQUIRED)
+  if(TARGET zstd::libzstd_shared)
+    list(APPEND PLATFORM_LIBS zstd::libzstd_shared)
+  else()
+    list(APPEND PLATFORM_LIBS zstd::libzstd_static)
+  endif()
+else()
+  if(NOT TARGET libzstd_static)
+    option(ZSTD_BUILD_SHARED "BUILD SHARED LIBRARIES" OFF)
+    option(ZSTD_BUILD_PROGRAMS "BUILD PROGRAMS" OFF)
+    option(ZSTD_LEGACY_SUPPORT "LEGACY SUPPORT" OFF)
+    add_subdirectory(deps/zstd-1.5.6/build/cmake EXCLUDE_FROM_ALL)
+  endif()
+  list(APPEND CHDR_LIBS libzstd_static)
+endif()
 #--------------------------------------------------
 # chdr
 #--------------------------------------------------
diff -pruN 0.0~git20240929.aaca599+dfsg-1/debian/changelog 0.0~git20250608.8bba774+dfsg-1/debian/changelog
--- 0.0~git20240929.aaca599+dfsg-1/debian/changelog	2024-10-23 07:29:41.000000000 +0000
+++ 0.0~git20250608.8bba774+dfsg-1/debian/changelog	2025-09-27 08:28:54.000000000 +0000
@@ -1,3 +1,9 @@
+libchdr (0.0~git20250608.8bba774+dfsg-1) unstable; urgency=medium
+
+  * New upstream version 0.0~git20250608.8bba774+dfsg
+
+ -- Sébastien Noel <sebastien@twolife.be>  Sat, 27 Sep 2025 10:28:54 +0200
+
 libchdr (0.0~git20240929.aaca599+dfsg-1) unstable; urgency=medium
 
   * New upstream version 0.0~git20240929.aaca599+dfsg (Closes: #1084991)
diff -pruN 0.0~git20240929.aaca599+dfsg-1/debian/libchdr0.symbols 0.0~git20250608.8bba774+dfsg-1/debian/libchdr0.symbols
--- 0.0~git20240929.aaca599+dfsg-1/debian/libchdr0.symbols	2024-10-23 07:19:36.000000000 +0000
+++ 0.0~git20250608.8bba774+dfsg-1/debian/libchdr0.symbols	2025-09-27 08:28:54.000000000 +0000
@@ -13,3 +13,5 @@ libchdr.so.0 libchdr0 #MINVER#
  chd_precache@Base 0
  chd_read@Base 0
  chd_read_header@Base 0
+ chd_read_header_core_file@Base 0.0~git20250608
+ chd_read_header_file@Base 0.0~git20250608
diff -pruN 0.0~git20240929.aaca599+dfsg-1/debian/patches/series 0.0~git20250608.8bba774+dfsg-1/debian/patches/series
--- 0.0~git20240929.aaca599+dfsg-1/debian/patches/series	2024-10-23 07:20:09.000000000 +0000
+++ 0.0~git20250608.8bba774+dfsg-1/debian/patches/series	1970-01-01 00:00:00.000000000 +0000
@@ -1 +0,0 @@
-use_system_zstd.patch
diff -pruN 0.0~git20240929.aaca599+dfsg-1/debian/patches/use_system_zstd.patch 0.0~git20250608.8bba774+dfsg-1/debian/patches/use_system_zstd.patch
--- 0.0~git20240929.aaca599+dfsg-1/debian/patches/use_system_zstd.patch	2024-10-23 07:29:26.000000000 +0000
+++ 0.0~git20250608.8bba774+dfsg-1/debian/patches/use_system_zstd.patch	1970-01-01 00:00:00.000000000 +0000
@@ -1,37 +0,0 @@
-From: Alexandre Detiste <tchet@debian.org>
-Subject: devendor ZSTD
-Forwarded: https://github.com/rtissera/libchdr/pull/131
-
---- a/CMakeLists.txt
-+++ b/CMakeLists.txt
-@@ -7,6 +7,7 @@
- endif()
- option(INSTALL_STATIC_LIBS "Install static libraries" OFF)
- option(WITH_SYSTEM_ZLIB "Use system provided zlib library" OFF)
-+option(WITH_SYSTEM_ZSTD "Use system provided zstd library" OFF)
- 
- option(BUILD_LTO "Compile libchdr with link-time optimization if supported" OFF)
- if(BUILD_LTO)
-@@ -41,11 +42,17 @@
- endif()
- 
- # zstd
--option(ZSTD_BUILD_SHARED "BUILD SHARED LIBRARIES" OFF)
--option(ZSTD_BUILD_PROGRAMS "BUILD PROGRAMS" OFF)
--option(ZSTD_LEGACY_SUPPORT "LEGACY SUPPORT" OFF)
--add_subdirectory(deps/zstd-1.5.6/build/cmake EXCLUDE_FROM_ALL)
--list(APPEND CHDR_LIBS libzstd_static)
-+if (WITH_SYSTEM_ZSTD)
-+  find_path(ZSTD_INCLUDE_DIR NAMES zstd.h)
-+  find_library(ZSTD_LIBRARIES NAMES zstd HINTS ${ZSTD_ROOT_DIR}/lib)
-+  list(APPEND CHDR_LIBS zstd)
-+else()
-+  option(ZSTD_BUILD_SHARED "BUILD SHARED LIBRARIES" OFF)
-+  option(ZSTD_BUILD_PROGRAMS "BUILD PROGRAMS" OFF)
-+  option(ZSTD_LEGACY_SUPPORT "LEGACY SUPPORT" OFF)
-+  add_subdirectory(deps/zstd-1.5.6/build/cmake EXCLUDE_FROM_ALL)
-+  list(APPEND CHDR_LIBS libzstd_static)
-+endif()
- #--------------------------------------------------
- # chdr
- #--------------------------------------------------
diff -pruN 0.0~git20240929.aaca599+dfsg-1/debian/watch 0.0~git20250608.8bba774+dfsg-1/debian/watch
--- 0.0~git20240929.aaca599+dfsg-1/debian/watch	2024-10-23 07:19:36.000000000 +0000
+++ 0.0~git20250608.8bba774+dfsg-1/debian/watch	2025-09-27 08:27:37.000000000 +0000
@@ -1,3 +1,6 @@
-version=4
-opts=dversionmangle=auto,repacksuffix=+dfsg,mode=git \
-https://github.com/rtissera/libchdr.git HEAD
+Version: 5
+Mode: git
+Source: https://github.com/rtissera/libchdr.git
+Matching-Pattern: HEAD
+Dversionmangle: auto
+Repacksuffix: +dfsg
diff -pruN 0.0~git20240929.aaca599+dfsg-1/deps/lzma-24.05/src/CpuArch.c 0.0~git20250608.8bba774+dfsg-1/deps/lzma-24.05/src/CpuArch.c
--- 0.0~git20240929.aaca599+dfsg-1/deps/lzma-24.05/src/CpuArch.c	2024-09-29 20:16:09.000000000 +0000
+++ 0.0~git20250608.8bba774+dfsg-1/deps/lzma-24.05/src/CpuArch.c	2025-06-08 18:58:18.000000000 +0000
@@ -781,6 +781,8 @@ BoolInt CPU_IsSupported_AES (void) { ret
 
 #if defined(__GLIBC__) && (__GLIBC__ * 100 + __GLIBC_MINOR__ >= 216)
   #define Z7_GETAUXV_AVAILABLE
+#elif defined(__ANDROID__) && __ANDROID_API__ < 18
+  // getauxval not available
 #else
 // #pragma message("=== is not NEW GLIBC === ")
   #if defined __has_include
@@ -799,7 +801,11 @@ BoolInt CPU_IsSupported_AES (void) { ret
 
 #ifdef USE_HWCAP
 
-#if defined(__FreeBSD__)
+#if defined(__FreeBSD__) || defined(__OpenBSD__)
+  #include <sys/param.h>
+#endif
+
+#if (defined(__FreeBSD__) && __FreeBSD_version >= 1200000) || (defined(__OpenBSD__) && OpenBSD >= 202409)
 static unsigned long MY_getauxval(int aux)
 {
   unsigned long val;
diff -pruN 0.0~git20240929.aaca599+dfsg-1/include/libchdr/chd.h 0.0~git20250608.8bba774+dfsg-1/include/libchdr/chd.h
--- 0.0~git20240929.aaca599+dfsg-1/include/libchdr/chd.h	2024-09-29 20:16:09.000000000 +0000
+++ 0.0~git20250608.8bba774+dfsg-1/include/libchdr/chd.h	2025-06-08 18:58:18.000000000 +0000
@@ -399,6 +399,8 @@ CHD_EXPORT const char *chd_error_string(
 CHD_EXPORT const chd_header *chd_get_header(chd_file *chd);
 
 /* read CHD header data from file into the pointed struct */
+CHD_EXPORT chd_error chd_read_header_core_file(core_file *file, chd_header *header);
+CHD_EXPORT chd_error chd_read_header_file(FILE *file, chd_header *header);
 CHD_EXPORT chd_error chd_read_header(const char *filename, chd_header *header);
 
 
diff -pruN 0.0~git20240929.aaca599+dfsg-1/include/libchdr/huffman.h 0.0~git20250608.8bba774+dfsg-1/include/libchdr/huffman.h
--- 0.0~git20240929.aaca599+dfsg-1/include/libchdr/huffman.h	2024-09-29 20:16:09.000000000 +0000
+++ 0.0~git20250608.8bba774+dfsg-1/include/libchdr/huffman.h	2025-06-08 18:58:18.000000000 +0000
@@ -85,6 +85,6 @@ int huffman_build_tree(struct huffman_de
 enum huffman_error huffman_assign_canonical_codes(struct huffman_decoder* decoder);
 enum huffman_error huffman_compute_tree_from_histo(struct huffman_decoder* decoder);
 
-void huffman_build_lookup_table(struct huffman_decoder* decoder);
+enum huffman_error huffman_build_lookup_table(struct huffman_decoder* decoder);
 
 #endif
diff -pruN 0.0~git20240929.aaca599+dfsg-1/src/libchdr_chd.c 0.0~git20250608.8bba774+dfsg-1/src/libchdr_chd.c
--- 0.0~git20240929.aaca599+dfsg-1/src/libchdr_chd.c	2024-09-29 20:16:09.000000000 +0000
+++ 0.0~git20250608.8bba774+dfsg-1/src/libchdr_chd.c	2025-06-08 18:58:18.000000000 +0000
@@ -41,6 +41,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <limits.h>
 #include <time.h>
 
 #include <libchdr/chd.h>
@@ -88,6 +89,11 @@
 
 #define CHD_V1_SECTOR_SIZE			512			/* size of a "sector" in the V1 header */
 
+#define CHD_MAX_HUNK_SIZE				(128 * 1024 * 1024) /* hunk size probably shouldn't be more than 128MB */
+
+/* we're currently only using this for CD/DVDs, if we end up with more than 10GB data, it's probably invalid */
+#define CHD_MAX_FILE_SIZE				(10ULL * 1024 * 1024 * 1024)
+
 #define COOKIE_VALUE				0xbaadf00d
 #define MAX_ZLIB_ALLOCS				64
 
@@ -298,6 +304,7 @@ struct _chd_file
 	uint32_t					cookie;			/* cookie, should equal COOKIE_VALUE */
 
 	core_file *				file;			/* handle to the open core file */
+	uint64_t				file_size;		/* size of the core file */
 	chd_header				header;			/* header, extracted from file */
 
 	chd_file *				parent;			/* pointer to parent file, or NULL */
@@ -712,22 +719,39 @@ static chd_error cdlz_codec_decompress(v
 {
 	uint32_t framenum;
 	cdlz_codec_data* cdlz = (cdlz_codec_data*)codec;
+	chd_error decomp_err;
+	uint32_t complen_base;
 
 	/* determine header bytes */
-	uint32_t frames = destlen / CD_FRAME_SIZE;
-	uint32_t complen_bytes = (destlen < 65536) ? 2 : 3;
-	uint32_t ecc_bytes = (frames + 7) / 8;
-	uint32_t header_bytes = ecc_bytes + complen_bytes;
+	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;
+
+	/* input may be truncated, double-check */
+	if (complen < (ecc_bytes + 2))
+		return CHDERR_DECOMPRESSION_ERROR;
 
 	/* extract compressed length of base */
-	uint32_t complen_base = (src[ecc_bytes + 0] << 8) | src[ecc_bytes + 1];
+	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;
 
 	/* reset and decode */
-	lzma_codec_decompress(&cdlz->base_decompressor, &src[header_bytes], complen_base, &cdlz->buffer[0], frames * CD_MAX_SECTOR_DATA);
-#ifdef WANT_SUBCODE
-	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);
+	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
 
 	/* reassemble the data */
@@ -795,22 +819,39 @@ static chd_error cdzl_codec_decompress(v
 {
 	uint32_t framenum;
 	cdzl_codec_data* cdzl = (cdzl_codec_data*)codec;
+	chd_error decomp_err;
+	uint32_t complen_base;
 
 	/* determine header bytes */
-	uint32_t frames = destlen / CD_FRAME_SIZE;
-	uint32_t complen_bytes = (destlen < 65536) ? 2 : 3;
-	uint32_t ecc_bytes = (frames + 7) / 8;
-	uint32_t header_bytes = ecc_bytes + complen_bytes;
+	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;
+
+	/* input may be truncated, double-check */
+	if (complen < (ecc_bytes + 2))
+		return CHDERR_DECOMPRESSION_ERROR;
 
 	/* extract compressed length of base */
-	uint32_t complen_base = (src[ecc_bytes + 0] << 8) | src[ecc_bytes + 1];
+	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;
 
 	/* reset and decode */
-	zlib_codec_decompress(&cdzl->base_decompressor, &src[header_bytes], complen_base, &cdzl->buffer[0], frames * CD_MAX_SECTOR_DATA);
-#ifdef WANT_SUBCODE
-	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);
+	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
 
 	/* reassemble the data */
@@ -1155,22 +1196,39 @@ static chd_error cdzs_codec_decompress(v
 {
 	uint32_t framenum;
 	cdzs_codec_data* cdzs = (cdzs_codec_data*)codec;
+	chd_error decomp_err;
+	uint32_t complen_base;
 
 	/* determine header bytes */
-	uint32_t frames = destlen / CD_FRAME_SIZE;
-	uint32_t complen_bytes = (destlen < 65536) ? 2 : 3;
-	uint32_t ecc_bytes = (frames + 7) / 8;
-	uint32_t header_bytes = ecc_bytes + complen_bytes;
+	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;
+
+	/* input may be truncated, double-check */
+	if (complen < (ecc_bytes + 2))
+		return CHDERR_DECOMPRESSION_ERROR;
 
 	/* extract compressed length of base */
-	uint32_t complen_base = (src[ecc_bytes + 0] << 8) | src[ecc_bytes + 1];
+	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;
 
 	/* reset and decode */
-	zstd_codec_decompress(&cdzs->base_decompressor, &src[header_bytes], complen_base, &cdzs->buffer[0], frames * CD_MAX_SECTOR_DATA);
-#ifdef WANT_SUBCODE
-	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);
+	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
 
 	/* reassemble the data */
@@ -1491,6 +1549,11 @@ static inline void map_assemble(uint8_t
 -------------------------------------------------*/
 static inline int map_size_v5(chd_header* header)
 {
+	// Avoid overflow due to corrupted data.
+	const uint32_t max_hunkcount = (UINT32_MAX / header->mapentrybytes);
+	if (header->hunkcount > max_hunkcount)
+		return -1;
+
 	return header->hunkcount * header->mapentrybytes;
 }
 
@@ -1575,11 +1638,16 @@ static chd_error decompress_v5_map(chd_f
 	uint8_t rawbuf[16];
 	struct huffman_decoder* decoder;
 	enum huffman_error err;
-	uint64_t curoffset;	
+	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;
@@ -1599,6 +1667,8 @@ static chd_error decompress_v5_map(chd_f
 	parentbits = rawbuf[14];
 
 	/* now read the map */
+	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;
@@ -1638,7 +1708,16 @@ static chd_error decompress_v5_map(chd_f
 			rawmap[0] = lastcomp, repcount--;
 		else
 		{
-			uint8_t val = huffman_decode_one(decoder, bitbuf);
+			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)
@@ -1788,6 +1867,9 @@ CHD_EXPORT chd_error chd_open_core_file(
 	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);
 
 	/* now attempt to read the header */
 	err = header_read(newchd, &newchd->header);
@@ -1888,7 +1970,8 @@ CHD_EXPORT chd_error chd_open_core_file(
 	}
 	else
 	{
-		int decompnum;
+		int decompnum, needsinit;
+
 		/* verify the compression types and initialize the codecs */
 		for (decompnum = 0; decompnum < ARRAY_LENGTH(newchd->header.compression); decompnum++)
 		{
@@ -1905,8 +1988,21 @@ CHD_EXPORT chd_error chd_open_core_file(
 			if (newchd->codecintf[decompnum] == NULL && newchd->header.compression[decompnum] != 0)
 				EARLY_EXIT(err = CHDERR_UNSUPPORTED_FORMAT);
 
+			/* ensure we don't try to initialize the same codec twice */
+			/* this is "normal" for chds where the user overrides the codecs, it'll have none repeated */
+			needsinit = (newchd->codecintf[decompnum]->init != NULL);
+			for (i = 0; i < decompnum; i++)
+			{
+				if (newchd->codecintf[decompnum] == newchd->codecintf[i])
+				{
+					/* already initialized */
+					needsinit = 0;
+					break;
+				}
+      }
+
 			/* initialize the codec */
-			if (newchd->codecintf[decompnum]->init != NULL)
+			if (needsinit)
 			{
 				void* codec = NULL;
 				switch (newchd->header.compression[decompnum])
@@ -1976,19 +2072,15 @@ cleanup:
 CHD_EXPORT chd_error chd_precache(chd_file *chd)
 {
 	int64_t count;
-	uint64_t size;
 
 	if (chd->file_cache == NULL)
 	{
-		size = core_fsize(chd->file);
-		if ((int64_t)size <= 0)
-			return CHDERR_INVALID_DATA;
-		chd->file_cache = malloc(size);
+		chd->file_cache = malloc(chd->file_size);
 		if (chd->file_cache == NULL)
 			return CHDERR_OUT_OF_MEMORY;
 		core_fseek(chd->file, 0, SEEK_SET);
-		count = core_fread(chd->file, chd->file_cache, size);
-		if (count != size)
+		count = core_fread(chd->file, chd->file_cache, chd->file_size);
+		if (count != chd->file_size)
 		{
 			free(chd->file_cache);
 			chd->file_cache = NULL;
@@ -2066,10 +2158,24 @@ CHD_EXPORT void chd_close(chd_file *chd)
 		for (i = 0 ; i < ARRAY_LENGTH(chd->codecintf); i++)
 		{
 			void* codec = NULL;
+			int j, needsfree;
 
 			if (chd->codecintf[i] == NULL)
 				continue;
 
+			/* only free each codec at max once */
+			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:
@@ -2226,34 +2332,78 @@ CHD_EXPORT const chd_header *chd_get_hea
     chd_read_header - read CHD header data
 	from file into the pointed struct
 -------------------------------------------------*/
-CHD_EXPORT chd_error chd_read_header(const char *filename, chd_header *header)
+
+CHD_EXPORT chd_error chd_read_header_core_file(core_file *file, chd_header *header)
 {
 	chd_error err = CHDERR_NONE;
 	chd_file chd;
 
-	/* punt if NULL */
-	if (filename == NULL || header == NULL)
-		EARLY_EXIT(err = CHDERR_INVALID_PARAMETER);
+	/* verify parameters */
+	if (file == NULL || header == NULL)
+		return CHDERR_INVALID_PARAMETER;
 
-	/* open the file */
-	chd.file = core_stdio_fopen(filename);
-	if (chd.file == NULL)
-		EARLY_EXIT(err = CHDERR_FILE_NOT_FOUND);
+	chd.file = file;
 
 	/* attempt to read the header */
 	err = header_read(&chd, header);
 	if (err != CHDERR_NONE)
-		EARLY_EXIT(err);
+		return err;
 
 	/* validate the header */
-	err = header_validate(header);
-	if (err != CHDERR_NONE)
-		EARLY_EXIT(err);
+	return header_validate(header);
+}
 
-cleanup:
-	if (chd.file != NULL)
-		core_fclose(chd.file);
+/*-------------------------------------------------
+    chd_read_header - read CHD header data
+	from file into the pointed struct
+-------------------------------------------------*/
+
+CHD_EXPORT chd_error chd_read_header_file(FILE *file, chd_header *header)
+{
+	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_read_header_core_file(stream, header);
+	core_fclose(stream);
+	return err;
+}
+
+/*-------------------------------------------------
+    chd_read_header - read CHD header data
+	from file into the pointed struct
+-------------------------------------------------*/
+
+CHD_EXPORT chd_error chd_read_header(const char *filename, chd_header *header)
+{
+	chd_error err;
+	core_file *file = NULL;
 
+	if (filename == NULL)
+	{
+		err = CHDERR_INVALID_PARAMETER;
+		goto cleanup;
+	}
+
+	/* open the file */
+	file = core_stdio_fopen(filename);
+	if (file == 0)
+	{
+		err = CHDERR_FILE_NOT_FOUND;
+		goto cleanup;
+	}
+
+	err = chd_read_header_core_file(file, header);
+
+	cleanup:
+	if (file != NULL)
+		core_fclose(file);
 	return err;
 }
 
@@ -2306,7 +2456,7 @@ CHD_EXPORT chd_error chd_get_metadata(ch
 			uint32_t faux_length;
 
 			/* fill in the faux metadata */
-			sprintf(faux_metadata, HARD_DISK_METADATA_FORMAT, chd->header.obsolete_cylinders, chd->header.obsolete_heads, chd->header.obsolete_sectors, chd->header.hunkbytes / chd->header.obsolete_hunksize);
+			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;
 
 			/* copy the metadata itself */
@@ -2428,6 +2578,10 @@ static chd_error header_validate(const c
 			return CHDERR_INVALID_PARAMETER;
 	}
 
+	/* some basic size checks to prevent huge mallocs */
+	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;
 }
 
@@ -2621,10 +2775,17 @@ static uint8_t* hunk_read_compressed(chd
 #endif
 	if (chd->file_cache != NULL)
 	{
-		return chd->file_cache + offset;
+		if ((offset + size) > chd->file_size || (offset + size) < offset)
+			return NULL;
+		else
+			return chd->file_cache + offset;
 	}
 	else
 	{
+		/* make sure it isn't larger than the compressed buffer */
+		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)
@@ -2647,6 +2808,9 @@ static chd_error hunk_read_uncompressed(
 #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
@@ -2989,7 +3153,7 @@ static chd_error map_read(chd_file *chd)
 	}
 
 	/* verify the length */
-	if (maxoffset > core_fsize(chd->file))
+	if (maxoffset > chd->file_size)
 	{
 		err = CHDERR_INVALID_FILE;
 		goto cleanup;
@@ -3024,7 +3188,8 @@ static chd_error metadata_find_entry(chd
 		uint32_t	count;
 
 		/* read the raw header */
-		core_fseek(chd->file, metaentry->offset, SEEK_SET);
+		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;
diff -pruN 0.0~git20240929.aaca599+dfsg-1/src/libchdr_huffman.c 0.0~git20250608.8bba774+dfsg-1/src/libchdr_huffman.c
--- 0.0~git20240929.aaca599+dfsg-1/src/libchdr_huffman.c	2024-09-29 20:16:09.000000000 +0000
+++ 0.0~git20250608.8bba774+dfsg-1/src/libchdr_huffman.c	2025-06-08 18:58:18.000000000 +0000
@@ -230,7 +230,9 @@ enum huffman_error huffman_import_tree_r
 		return error;
 
 	/* build the lookup table */
-	huffman_build_lookup_table(decoder);
+	error = huffman_build_lookup_table(decoder);
+	if (error != HUFFERR_NONE)
+		return error;
 
 	/* determine final input length and report errors */
 	return bitstream_overflow(bitbuf) ? HUFFERR_INPUT_BUFFER_TOO_SMALL : HUFFERR_NONE;
@@ -271,8 +273,16 @@ enum huffman_error huffman_import_tree_h
 	/* then regenerate the tree */
 	error = huffman_assign_canonical_codes(smallhuff);
 	if (error != HUFFERR_NONE)
+	{
+		delete_huffman_decoder(smallhuff);
+		return error;
+	}
+	error = huffman_build_lookup_table(smallhuff);
+	if (error != HUFFERR_NONE)
+	{
+		delete_huffman_decoder(smallhuff);
 		return error;
-	huffman_build_lookup_table(smallhuff);
+	}
 
 	/* determine the maximum length of an RLE count */
 	temp = decoder->numcodes - 9;
@@ -308,7 +318,9 @@ enum huffman_error huffman_import_tree_h
 		return error;
 
 	/* build the lookup table */
-	huffman_build_lookup_table(decoder);
+	error = huffman_build_lookup_table(decoder);
+	if (error != HUFFERR_NONE)
+		return error;
 
 	/* determine final input length and report errors */
 	return bitstream_overflow(bitbuf) ? HUFFERR_INPUT_BUFFER_TOO_SMALL : HUFFERR_NONE;
@@ -523,8 +535,9 @@ enum huffman_error huffman_assign_canoni
  *-------------------------------------------------
  */
 
-void huffman_build_lookup_table(struct huffman_decoder* decoder)
+enum huffman_error huffman_build_lookup_table(struct huffman_decoder* decoder)
 {
+	const lookup_value* lookupend = &decoder->lookup[(1u << decoder->maxbits)];
 	uint32_t curcode;
 	/* iterate over all codes */
 	for (curcode = 0; curcode < decoder->numcodes; curcode++)
@@ -533,9 +546,10 @@ void huffman_build_lookup_table(struct h
 		struct node_t* node = &decoder->huffnode[curcode];
 		if (node->numbits > 0)
 		{
-         int shift;
-         lookup_value *dest;
-         lookup_value *destend;
+			int shift;
+			lookup_value *dest;
+			lookup_value *destend;
+
 			/* set up the entry */
 			lookup_value value = MAKE_LOOKUP(curcode, node->numbits);
 
@@ -543,8 +557,12 @@ void huffman_build_lookup_table(struct h
 			shift = decoder->maxbits - node->numbits;
 			dest = &decoder->lookup[node->bits << shift];
 			destend = &decoder->lookup[((node->bits + 1) << shift) - 1];
+			if (dest >= lookupend || destend >= lookupend || destend < dest)
+				return HUFFERR_INTERNAL_INCONSISTENCY;
 			while (dest <= destend)
 				*dest++ = value;
 		}
 	}
+
+	return HUFFERR_NONE;
 }
diff -pruN 0.0~git20240929.aaca599+dfsg-1/tests/CMakeLists.txt 0.0~git20250608.8bba774+dfsg-1/tests/CMakeLists.txt
--- 0.0~git20240929.aaca599+dfsg-1/tests/CMakeLists.txt	2024-09-29 20:16:09.000000000 +0000
+++ 0.0~git20250608.8bba774+dfsg-1/tests/CMakeLists.txt	2025-06-08 18:58:18.000000000 +0000
@@ -1,2 +1,12 @@
 add_executable(chdr-benchmark benchmark.c)
 target_link_libraries(chdr-benchmark PRIVATE chdr-static)
+
+# fuzzing
+if(BUILD_FUZZER)
+  add_executable(chdr-fuzz fuzz.c)
+  target_link_options(chdr-fuzz PRIVATE "-fsanitize=address,fuzzer")
+  target_link_libraries(chdr-fuzz PRIVATE chdr-static)
+  add_custom_target(fuzz
+    COMMAND "$<TARGET_FILE:chdr-fuzz>" "-max_len=131072"
+    DEPENDS chdr-fuzz)
+endif()
diff -pruN 0.0~git20240929.aaca599+dfsg-1/tests/fuzz.c 0.0~git20250608.8bba774+dfsg-1/tests/fuzz.c
--- 0.0~git20240929.aaca599+dfsg-1/tests/fuzz.c	1970-01-01 00:00:00.000000000 +0000
+++ 0.0~git20250608.8bba774+dfsg-1/tests/fuzz.c	2025-06-08 18:58:18.000000000 +0000
@@ -0,0 +1,80 @@
+#include <libchdr/chd.h>
+#include <libchdr/coretypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+typedef struct {
+  const uint8_t *buffer;
+  size_t buffer_size;
+  size_t buffer_pos;
+} membuf;
+
+static uint64_t membuf_fsize(struct chd_core_file *cf) {
+  return ((membuf *)cf->argp)->buffer_size;
+}
+
+static size_t membuf_fread(void *buf, size_t size, size_t count,
+                           struct chd_core_file *cf) {
+  membuf *mb = (membuf *)cf->argp;
+  if ((UINT32_MAX / size) < count)
+    return 0;
+  size_t copy = size * count;
+  size_t remain = mb->buffer_size - mb->buffer_pos;
+  if (remain < copy)
+    copy = remain;
+  memcpy(buf, &mb->buffer[mb->buffer_pos], copy);
+  mb->buffer_pos += copy;
+  return copy;
+}
+
+static int membuf_fclose(struct chd_core_file *cf) { return 0; }
+
+static int membuf_fseek(struct chd_core_file *cf, int64_t pos, int origin) {
+  membuf *mb = (membuf *)cf->argp;
+  if (origin == SEEK_SET) {
+    if (pos < 0 || (size_t)pos > mb->buffer_size)
+      return -1;
+    mb->buffer_pos = (size_t)pos;
+    return 0;
+  } else if (origin == SEEK_CUR) {
+    if (pos < 0 && (size_t)-pos > mb->buffer_pos)
+      return -1;
+    else if ((mb->buffer_pos + (size_t)pos) > mb->buffer_size)
+      return -1;
+    mb->buffer_pos =
+        (pos < 0) ? (mb->buffer_pos - (size_t)-pos) : (mb->buffer_pos + pos);
+    return 0;
+  } else if (origin == SEEK_END) {
+    mb->buffer_pos = mb->buffer_size;
+    return 0;
+  } else {
+    return -1;
+  }
+}
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+  unsigned int i;
+  unsigned int totalbytes;
+  void *buffer;
+  membuf mb = {data, size, 0u};
+  struct chd_core_file cf = {&mb, membuf_fsize, membuf_fread, membuf_fclose,
+                             membuf_fseek};
+  chd_file *file;
+  const chd_header *header;
+  chd_error err = chd_open_core_file(&cf, CHD_OPEN_READ, NULL, &file);
+  if (err != CHDERR_NONE)
+    return 0;
+
+  header = chd_get_header(file);
+  totalbytes = header->hunkbytes * header->totalhunks;
+  buffer = malloc(header->hunkbytes);
+  for (i = 0; i < header->totalhunks; i++) {
+    err = chd_read(file, i, buffer);
+    if (err != CHDERR_NONE)
+      continue;
+  }
+  free(buffer);
+  chd_close(file);
+  return 0;
+}
