From 6be36b6bda13dd0c4f70c710673bc33f4e888f03 Mon Sep 17 00:00:00 2001 From: Kevin Bortis Date: Wed, 18 Mar 2026 23:36:09 +0100 Subject: [PATCH] Add Zstandard compression support Implements zstd as an alternative to LZMA for data block and subchannel compression using the allocated compression IDs 4 (kCompressionZstd) and 5 (kCompressionZstdCst). Adds zstd v1.5.7 as a bundled submodule with a static library build including x86_64 assembly fast path for decompression. Write path selects zstd or LZMA based on the zstd option. Subchannel blocks use zstd+CST instead of LZMA+CST when enabled. LZMA properties header is only written for LZMA-based compression types, preventing format corruption in zstd-compressed images. Activated via options string: "zstd=true;zstd_level=19". Default remains LZMA for backwards compatibility. Tested: SHA-256 verified lossless roundtrips across 9 disc systems (Dreamcast, Saturn, Mega CD, PC Engine CD, Neo Geo CD, PS1, PS2 CD, PS2 DVD) and PS1 SBI subchannel preservation with zstd+CST. --- .gitmodules | 3 ++ 3rdparty/zstd | 1 + 3rdparty/zstd.cmake | 60 +++++++++++++++++++++ CMakeLists.txt | 4 +- include/aaruformat/context.h | 2 + include/aaruformat/decls.h | 5 ++ include/aaruformat/enums.h | 4 +- include/aaruformat/structs/options.h | 2 + src/blocks/data.c | 79 ++++++++++++++++++++++++++++ src/close.c | 61 ++++++++++++++------- src/compression/zstd.c | 62 ++++++++++++++++++++++ src/create.c | 2 + src/options.c | 24 +++++++-- src/read.c | 53 +++++++++++++++++++ src/write.c | 35 +++++++++++- 15 files changed, 369 insertions(+), 28 deletions(-) create mode 160000 3rdparty/zstd create mode 100644 3rdparty/zstd.cmake create mode 100644 src/compression/zstd.c diff --git a/.gitmodules b/.gitmodules index 5f981eb..81d8b34 100644 --- a/.gitmodules +++ b/.gitmodules @@ -16,3 +16,6 @@ [submodule "3rdparty/BLAKE3"] path = 3rdparty/BLAKE3 url = https://github.com/BLAKE3-team/BLAKE3 +[submodule "3rdparty/zstd"] + path = 3rdparty/zstd + url = https://github.com/facebook/zstd.git diff --git a/3rdparty/zstd b/3rdparty/zstd new file mode 160000 index 0000000..f8745da --- /dev/null +++ b/3rdparty/zstd @@ -0,0 +1 @@ +Subproject commit f8745da6ff1ad1e7bab384bd1f9d742439278e99 diff --git a/3rdparty/zstd.cmake b/3rdparty/zstd.cmake new file mode 100644 index 0000000..f30c3c8 --- /dev/null +++ b/3rdparty/zstd.cmake @@ -0,0 +1,60 @@ +# Zstandard compression library (https://github.com/facebook/zstd) +# BSD/GPLv2 dual license + +set(ZSTD_LIB_DIR "${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/zstd/lib") + +if(NOT EXISTS "${ZSTD_LIB_DIR}/zstd.h") + message(FATAL_ERROR "Zstandard submodule not found. Run: git submodule update --init 3rdparty/zstd") +endif() + +message(STATUS "Zstandard: Building static library") + +add_library(zstd_static STATIC + ${ZSTD_LIB_DIR}/common/debug.c + ${ZSTD_LIB_DIR}/common/entropy_common.c + ${ZSTD_LIB_DIR}/common/error_private.c + ${ZSTD_LIB_DIR}/common/fse_decompress.c + ${ZSTD_LIB_DIR}/common/pool.c + ${ZSTD_LIB_DIR}/common/threading.c + ${ZSTD_LIB_DIR}/common/xxhash.c + ${ZSTD_LIB_DIR}/common/zstd_common.c + ${ZSTD_LIB_DIR}/compress/fse_compress.c + ${ZSTD_LIB_DIR}/compress/hist.c + ${ZSTD_LIB_DIR}/compress/huf_compress.c + ${ZSTD_LIB_DIR}/compress/zstd_compress.c + ${ZSTD_LIB_DIR}/compress/zstd_compress_literals.c + ${ZSTD_LIB_DIR}/compress/zstd_compress_sequences.c + ${ZSTD_LIB_DIR}/compress/zstd_compress_superblock.c + ${ZSTD_LIB_DIR}/compress/zstd_double_fast.c + ${ZSTD_LIB_DIR}/compress/zstd_fast.c + ${ZSTD_LIB_DIR}/compress/zstd_lazy.c + ${ZSTD_LIB_DIR}/compress/zstd_ldm.c + ${ZSTD_LIB_DIR}/compress/zstd_opt.c + ${ZSTD_LIB_DIR}/compress/zstd_preSplit.c + ${ZSTD_LIB_DIR}/compress/zstdmt_compress.c + ${ZSTD_LIB_DIR}/decompress/huf_decompress.c + ${ZSTD_LIB_DIR}/decompress/zstd_ddict.c + ${ZSTD_LIB_DIR}/decompress/zstd_decompress.c + ${ZSTD_LIB_DIR}/decompress/zstd_decompress_block.c +) + +# x86_64 assembly fast path for Huffman decompression +if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64") + enable_language(ASM) + target_sources(zstd_static PRIVATE ${ZSTD_LIB_DIR}/decompress/huf_decompress_amd64.S) +endif() + +target_include_directories(zstd_static PUBLIC ${ZSTD_LIB_DIR}) +set_property(TARGET zstd_static PROPERTY POSITION_INDEPENDENT_CODE TRUE) + +if(CMAKE_BUILD_TYPE STREQUAL "Release") + if(MSVC) + target_compile_options(zstd_static PRIVATE /O2) + else() + target_compile_options(zstd_static PRIVATE -O3) + endif() +endif() + +# Link to aaruformat +target_link_libraries(aaruformat zstd_static) +target_include_directories(aaruformat PRIVATE ${ZSTD_LIB_DIR}) diff --git a/CMakeLists.txt b/CMakeLists.txt index 94ce7e2..a937600 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -268,7 +268,8 @@ add_library(aaruformat src/ngcw/ngcw_junk.c src/ngcw/ngcw_junk.h src/ngcw/wii_crypto.c - src/ngcw/wii_crypto.h) + src/ngcw/wii_crypto.h + src/compression/zstd.c) # Set up include directories for the target target_include_directories(aaruformat @@ -287,6 +288,7 @@ include(3rdparty/flac.cmake) include(3rdparty/lzma.cmake) include(3rdparty/xxhash.cmake) include(3rdparty/blake3.cmake) +include(3rdparty/zstd.cmake) if(TARGET blake3) target_link_libraries(aaruformat blake3) diff --git a/include/aaruformat/context.h b/include/aaruformat/context.h index f38566a..98ecac8 100644 --- a/include/aaruformat/context.h +++ b/include/aaruformat/context.h @@ -301,6 +301,8 @@ typedef struct aaruformat_context uint32_t lzma_dict_size; ///< LZMA dictionary size (writing path). bool deduplicate; ///< Storage deduplication active (duplicates coalesce). bool compression_enabled; ///< True if block compression enabled (writing path). + bool use_zstd; ///< Use Zstandard instead of LZMA for data blocks. + int zstd_level; ///< Zstandard compression level (writing path, default 19). /* Tape-specific structures */ tapeFileHashEntry *tape_files; ///< Hash table root for tape files diff --git a/include/aaruformat/decls.h b/include/aaruformat/decls.h index 0c0abfa..86bd1dd 100644 --- a/include/aaruformat/decls.h +++ b/include/aaruformat/decls.h @@ -254,6 +254,11 @@ AARU_EXPORT int32_t AARU_CALL aaruf_lzma_encode_buffer(uint8_t *dst_buffer, size int32_t level, uint32_t dict_size, int32_t lc, int32_t lp, int32_t pb, int32_t fb, int32_t num_threads); +AARU_EXPORT size_t AARU_CALL aaruf_zstd_decode_buffer(uint8_t *dst_buffer, size_t dst_size, const uint8_t *src_buffer, + size_t src_size); +AARU_EXPORT size_t AARU_CALL aaruf_zstd_encode_buffer(uint8_t *dst_buffer, size_t dst_size, const uint8_t *src_buffer, + size_t src_size, int level); + AARU_EXPORT void AARU_CALL aaruf_md5_init(md5_ctx *ctx); AARU_EXPORT void AARU_CALL aaruf_md5_update(md5_ctx *ctx, const void *data, unsigned long size); AARU_EXPORT void AARU_CALL aaruf_md5_final(md5_ctx *ctx, unsigned char *result); diff --git a/include/aaruformat/enums.h b/include/aaruformat/enums.h index 3b148cb..1a0b188 100644 --- a/include/aaruformat/enums.h +++ b/include/aaruformat/enums.h @@ -34,8 +34,8 @@ typedef enum kCompressionLzma = 1, ///< LZMA compression. kCompressionFlac = 2, ///< FLAC compression. kCompressionLzmaCst = 3, ///< LZMA applied to Claunia Subchannel Transform processed data. - kCompressionZstd = 4, ///< Zstandard compression (reserved for future implementation). - kCompressionZstdCst = 5 ///< Zstandard applied to Claunia Subchannel Transform processed data (reserved). + kCompressionZstd = 4, ///< Zstandard compression. + kCompressionZstdCst = 5 ///< Zstandard applied to Claunia Subchannel Transform processed data. } CompressionType; /** diff --git a/include/aaruformat/structs/options.h b/include/aaruformat/structs/options.h index faff506..ca9ee27 100644 --- a/include/aaruformat/structs/options.h +++ b/include/aaruformat/structs/options.h @@ -227,6 +227,8 @@ typedef struct bool sha256; ///< Generate SHA-256 checksum (ChecksumAlgorithm::Sha256) when finalizing image. bool blake3; ///< Generate BLAKE3 checksum if supported (not stored if algorithm unavailable). bool spamsum; ///< Generate SpamSum fuzzy hash (ChecksumAlgorithm::SpamSum) if enabled. + bool zstd; ///< Use Zstandard instead of LZMA for data blocks. Default: false. + int zstd_level; ///< Zstandard compression level (1-22). Default: 19. } aaru_options; #endif // LIBAARUFORMAT_OPTIONS_H diff --git a/src/blocks/data.c b/src/blocks/data.c index cda3304..9e53edd 100644 --- a/src/blocks/data.c +++ b/src/blocks/data.c @@ -281,6 +281,85 @@ int32_t process_data_block(aaruformat_context *ctx, IndexEntry *entry) free(cmp_data); } + else if(block_header.compression == kCompressionZstd || block_header.compression == kCompressionZstdCst) + { + if(block_header.compression == kCompressionZstdCst && block_header.type != kDataTypeCdSubchannel) + { + TRACE("Invalid compression type %u for block with data type %u, continuing...", block_header.compression, + block_header.type); + TRACE("Exiting process_data_block() = AARUF_STATUS_OK"); + return AARUF_STATUS_OK; + } + + cmp_data = (block_header.cmpLength == 0) ? NULL : (uint8_t *)malloc(block_header.cmpLength); + if(block_header.cmpLength != 0 && cmp_data == NULL) + { + TRACE("Cannot allocate memory for compressed block, continuing..."); + TRACE("Exiting process_data_block() = AARUF_STATUS_OK"); + return AARUF_STATUS_OK; + } + + if(block_header.length != 0) + { + data = (uint8_t *)malloc(block_header.length); + if(data == NULL) + { + TRACE("Cannot allocate memory for block, continuing..."); + free(cmp_data); + TRACE("Exiting process_data_block() = AARUF_STATUS_OK"); + return AARUF_STATUS_OK; + } + } + else + data = NULL; + + if(block_header.cmpLength != 0) + { + read_bytes = fread(cmp_data, 1, block_header.cmpLength, ctx->imageStream); + if(read_bytes != block_header.cmpLength) + { + TRACE("Could not read compressed block, continuing..."); + free(cmp_data); + free(data); + TRACE("Exiting process_data_block() = AARUF_STATUS_OK"); + return AARUF_STATUS_OK; + } + } + + if(block_header.length != 0) + { + size_t decoded = aaruf_zstd_decode_buffer(data, block_header.length, cmp_data, block_header.cmpLength); + + if(decoded != block_header.length) + { + TRACE("Error decompressing zstd block, expected %" PRIu32 " bytes but got %zu bytes, continuing...", + block_header.length, decoded); + free(cmp_data); + free(data); + TRACE("Exiting process_data_block() = AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK"); + return AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK; + } + } + + free(cmp_data); + + if(block_header.compression == kCompressionZstdCst && block_header.length != 0) + { + cst_data = (uint8_t *)malloc(block_header.length); + if(cst_data == NULL) + { + TRACE("Cannot allocate memory for CST untransform, continuing..."); + free(data); + TRACE("Exiting process_data_block() = AARUF_STATUS_OK"); + return AARUF_STATUS_OK; + } + + aaruf_cst_untransform(data, cst_data, block_header.length); + free(data); + data = cst_data; + cst_data = NULL; + } + } else if(block_header.compression == kCompressionNone) { if(block_header.length != 0) diff --git a/src/close.c b/src/close.c index 938ece9..e765658 100644 --- a/src/close.c +++ b/src/close.c @@ -1698,23 +1698,35 @@ static void write_sector_subchannel(aaruformat_context *ctx) if(dst_buffer == NULL) { - TRACE("Failed to allocate memory for LZMA output"); + TRACE("Failed to allocate memory for compressed output"); free(cst_buffer); return; } aaruf_cst_transform(ctx->sector_subchannel, cst_buffer, subchannel_block.length); - size_t dst_size = subchannel_block.length; - size_t props_size = LZMA_PROPERTIES_LENGTH; - aaruf_lzma_encode_buffer(dst_buffer, &dst_size, cst_buffer, subchannel_block.length, lzma_properties, - &props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273, 8); + size_t dst_size; + + if(ctx->use_zstd) + { + dst_size = aaruf_zstd_encode_buffer(dst_buffer, subchannel_block.length, cst_buffer, + subchannel_block.length, ctx->zstd_level); + if(dst_size == 0) dst_size = subchannel_block.length; /* compression failed, fall through to none */ + } + else + { + dst_size = subchannel_block.length; + size_t props_size = LZMA_PROPERTIES_LENGTH; + + aaruf_lzma_encode_buffer(dst_buffer, &dst_size, cst_buffer, subchannel_block.length, lzma_properties, + &props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273, 8); + } free(cst_buffer); if(dst_size < subchannel_block.length) { - subchannel_block.compression = kCompressionLzmaCst; + subchannel_block.compression = ctx->use_zstd ? kCompressionZstdCst : kCompressionLzmaCst; subchannel_block.cmpLength = (uint32_t)dst_size; buffer = dst_buffer; owns_buffer = true; @@ -1749,28 +1761,39 @@ static void write_sector_subchannel(aaruformat_context *ctx) TRACE("Incorrect media type, not writing sector subchannel block"); return; // Incorrect media type } - subchannel_block.cmpLength = subchannel_block.length; - subchannel_block.compression = kCompressionLzma; + subchannel_block.cmpLength = subchannel_block.length; uint8_t *dst_buffer = malloc(subchannel_block.length); if(dst_buffer == NULL) { - TRACE("Failed to allocate memory for LZMA output"); + TRACE("Failed to allocate memory for compressed output"); return; } - size_t dst_size = subchannel_block.length; - size_t props_size = LZMA_PROPERTIES_LENGTH; + size_t dst_size; - aaruf_lzma_encode_buffer(dst_buffer, &dst_size, ctx->sector_subchannel, subchannel_block.length, - lzma_properties, &props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273, 8); + if(ctx->use_zstd) + { + dst_size = aaruf_zstd_encode_buffer(dst_buffer, subchannel_block.length, ctx->sector_subchannel, + subchannel_block.length, ctx->zstd_level); + if(dst_size == 0) dst_size = subchannel_block.length; /* compression failed, fall through to none */ + } + else + { + dst_size = subchannel_block.length; + size_t props_size = LZMA_PROPERTIES_LENGTH; + + aaruf_lzma_encode_buffer(dst_buffer, &dst_size, ctx->sector_subchannel, subchannel_block.length, + lzma_properties, &props_size, 9, ctx->lzma_dict_size, 4, 0, 2, 273, 8); + } if(dst_size < subchannel_block.length) { - subchannel_block.cmpLength = (uint32_t)dst_size; - buffer = dst_buffer; - owns_buffer = true; + subchannel_block.compression = ctx->use_zstd ? kCompressionZstd : kCompressionLzma; + subchannel_block.cmpLength = (uint32_t)dst_size; + buffer = dst_buffer; + owns_buffer = true; } else { @@ -1793,12 +1816,14 @@ static void write_sector_subchannel(aaruformat_context *ctx) subchannel_block.cmpCrc64 = aaruf_crc64_data(buffer, subchannel_block.cmpLength); const size_t length_to_write = subchannel_block.cmpLength; - if(subchannel_block.compression != kCompressionNone) subchannel_block.cmpLength += LZMA_PROPERTIES_LENGTH; + const bool has_lzma_props = subchannel_block.compression == kCompressionLzma || + subchannel_block.compression == kCompressionLzmaCst; + if(has_lzma_props) subchannel_block.cmpLength += LZMA_PROPERTIES_LENGTH; // Write header if(fwrite(&subchannel_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1) { - if(subchannel_block.compression != kCompressionNone) + if(has_lzma_props) fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream); // Write data diff --git a/src/compression/zstd.c b/src/compression/zstd.c new file mode 100644 index 0000000..5c5a4eb --- /dev/null +++ b/src/compression/zstd.c @@ -0,0 +1,62 @@ +/* + * This file is part of the Aaru Data Preservation Suite. + * Copyright (c) 2019-2026 Natalia Portillo. + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of the + * License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include +#include + +#include +#include + +/** + * @brief Decodes a Zstandard-compressed buffer. + * + * @param dst_buffer Pointer to the destination buffer. + * @param dst_size Size of the destination buffer. + * @param src_buffer Pointer to the source (compressed) buffer. + * @param src_size Size of the source buffer. + * @return Number of decompressed bytes, or 0 on error. + */ +AARU_EXPORT size_t AARU_CALL aaruf_zstd_decode_buffer(uint8_t *dst_buffer, size_t dst_size, const uint8_t *src_buffer, + size_t src_size) +{ + size_t result = ZSTD_decompress(dst_buffer, dst_size, src_buffer, src_size); + + if(ZSTD_isError(result)) return 0; + + return result; +} + +/** + * @brief Encodes a buffer using Zstandard compression. + * + * @param dst_buffer Pointer to the destination buffer. + * @param dst_size Size of the destination buffer. + * @param src_buffer Pointer to the source (uncompressed) buffer. + * @param src_size Size of the source buffer. + * @param level Compression level (1-22). + * @return Number of compressed bytes, or 0 on error. + */ +AARU_EXPORT size_t AARU_CALL aaruf_zstd_encode_buffer(uint8_t *dst_buffer, size_t dst_size, const uint8_t *src_buffer, + size_t src_size, int level) +{ + size_t result = ZSTD_compress(dst_buffer, dst_size, src_buffer, src_size, level); + + if(ZSTD_isError(result)) return 0; + + return result; +} diff --git a/src/create.c b/src/create.c index e07edc1..6a423dd 100644 --- a/src/create.c +++ b/src/create.c @@ -526,6 +526,8 @@ AARU_EXPORT void AARU_CALL *aaruf_create(const char *filepath, const uint32_t me ctx->compression_enabled = parsed_options.compress; ctx->lzma_dict_size = parsed_options.dictionary; ctx->deduplicate = parsed_options.deduplicate; + ctx->use_zstd = parsed_options.zstd; + ctx->zstd_level = parsed_options.zstd_level; if(ctx->deduplicate) ctx->sector_hash_map = create_map(ctx->user_data_ddt_header.blocks * 25 / 100); // 25% of total sectors diff --git a/src/options.c b/src/options.c index a8d9310..dc88bee 100644 --- a/src/options.c +++ b/src/options.c @@ -50,14 +50,18 @@ aaru_options parse_options(const char *options, bool *table_shift_found) .sha1 = false, .sha256 = false, .blake3 = false, - .spamsum = false}; + .spamsum = false, + .zstd = false, + .zstd_level = 19}; if(options == NULL) { TRACE("Exiting parse_options() = {compress: %d, deduplicate: %d, dictionary: %u, table_shift: %d, " - "data_shift: %u, block_alignment: %u, md5: %d, sha1: %d, sha256: %d, blake3: %d, spamsum: %d}", + "data_shift: %u, block_alignment: %u, md5: %d, sha1: %d, sha256: %d, blake3: %d, spamsum: %d, " + "zstd: %d, zstd_level: %d}", parsed.compress, parsed.deduplicate, parsed.dictionary, parsed.table_shift, parsed.data_shift, - parsed.block_alignment, parsed.md5, parsed.sha1, parsed.sha256, parsed.blake3, parsed.spamsum); + parsed.block_alignment, parsed.md5, parsed.sha1, parsed.sha256, parsed.blake3, parsed.spamsum, + parsed.zstd, parsed.zstd_level); return parsed; } @@ -134,13 +138,23 @@ aaru_options parse_options(const char *options, bool *table_shift_found) parsed.blake3 = bval; else if(strncmp(key, "spamsum", 7) == 0) parsed.spamsum = bval; + else if(strncmp(key, "zstd_level", 10) == 0) + { + parsed.zstd_level = (int)strtol(value, NULL, 10); + if(parsed.zstd_level < 1) parsed.zstd_level = 1; + if(parsed.zstd_level > 22) parsed.zstd_level = 22; + } + else if(strncmp(key, "zstd", 4) == 0) + parsed.zstd = bval; } token = strtok_r(NULL, ";", &saveptr); } TRACE("Exiting parse_options() = {compress: %d, deduplicate: %d, dictionary: %u, table_shift: %d, " - "data_shift: %u, block_alignment: %u, md5: %d, sha1: %d, sha256: %d, blake3: %d, spamsum: %d}", + "data_shift: %u, block_alignment: %u, md5: %d, sha1: %d, sha256: %d, blake3: %d, spamsum: %d, " + "zstd: %d, zstd_level: %d}", parsed.compress, parsed.deduplicate, parsed.dictionary, parsed.table_shift, parsed.data_shift, - parsed.block_alignment, parsed.md5, parsed.sha1, parsed.sha256, parsed.blake3, parsed.spamsum); + parsed.block_alignment, parsed.md5, parsed.sha1, parsed.sha256, parsed.blake3, parsed.spamsum, + parsed.zstd, parsed.zstd_level); return parsed; } \ No newline at end of file diff --git a/src/read.c b/src/read.c index b6ce86a..5e26786 100644 --- a/src/read.c +++ b/src/read.c @@ -894,6 +894,59 @@ AARU_EXPORT int32_t AARU_CALL aaruf_read_sector(void *context, const uint64_t se free(cmp_data); + break; + case kCompressionZstd: + if(block_header->cmpLength == 0 || block_header->length == 0) + { + FATAL("Invalid zstd block lengths (cmpLength=%u, length=%u)", block_header->cmpLength, + block_header->length); + TRACE("Exiting aaruf_read_sector() = AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK"); + return AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK; + } + + TRACE("Allocating memory for block of size %zu bytes", block_header->length); + block = (uint8_t *)malloc(block_header->length); + if(block == NULL) + { + FATAL("Not enough memory for block"); + TRACE("Exiting aaruf_read_sector() = AARUF_ERROR_NOT_ENOUGH_MEMORY"); + return AARUF_ERROR_NOT_ENOUGH_MEMORY; + } + + TRACE("Allocating memory for compressed data of size %zu bytes", block_header->cmpLength); + cmp_data = malloc(block_header->cmpLength); + if(cmp_data == NULL) + { + FATAL("Not enough memory for compressed data"); + free(block); + TRACE("Exiting aaruf_read_sector() = AARUF_ERROR_NOT_ENOUGH_MEMORY"); + return AARUF_ERROR_NOT_ENOUGH_MEMORY; + } + + fseek(ctx->imageStream, (long)(block_offset + sizeof(BlockHeader)), SEEK_SET); + + read_bytes = fread(cmp_data, 1, block_header->cmpLength, ctx->imageStream); + if(read_bytes != block_header->cmpLength) + { + FATAL("Could not read compressed block"); + free(cmp_data); + free(block); + TRACE("Exiting aaruf_read_sector() = AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK"); + return AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK; + } + + read_bytes = aaruf_zstd_decode_buffer(block, block_header->length, cmp_data, block_header->cmpLength); + if(read_bytes != block_header->length) + { + FATAL("Error decompressing zstd block, expected %u bytes got %zu", block_header->length, read_bytes); + free(cmp_data); + free(block); + TRACE("Exiting aaruf_read_sector() = AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK"); + return AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK; + } + + free(cmp_data); + break; case kCompressionFlac: TRACE("Allocating memory for compressed data of size %zu bytes", block_header->cmpLength); diff --git a/src/write.c b/src/write.c index e8ace9b..477479b 100644 --- a/src/write.c +++ b/src/write.c @@ -371,7 +371,7 @@ AARU_EXPORT int32_t AARU_CALL aaruf_write_sector(void *context, uint64_t sector_ if(ctx->current_track_type == kTrackTypeAudio) ctx->current_block_header.compression = kCompressionFlac; else - ctx->current_block_header.compression = kCompressionLzma; + ctx->current_block_header.compression = ctx->use_zstd ? kCompressionZstd : kCompressionLzma; } else ctx->current_block_header.compression = kCompressionNone; @@ -380,7 +380,7 @@ AARU_EXPORT int32_t AARU_CALL aaruf_write_sector(void *context, uint64_t sector_ { ctx->current_track_type = kTrackTypeData; if(ctx->compression_enabled) - ctx->current_block_header.compression = kCompressionLzma; + ctx->current_block_header.compression = ctx->use_zstd ? kCompressionZstd : kCompressionLzma; else ctx->current_block_header.compression = kCompressionNone; } @@ -1576,6 +1576,37 @@ int32_t aaruf_close_current_block(aaruformat_context *ctx) free(cmp_buffer); } + break; + case kCompressionZstd: + cmp_buffer = malloc(ctx->current_block_header.length * 2); + if(cmp_buffer == NULL) + { + FATAL("Could not allocate buffer for compressed data"); + return AARUF_ERROR_NOT_ENOUGH_MEMORY; + } + + size_t zstd_dst_size = + aaruf_zstd_encode_buffer(cmp_buffer, ctx->current_block_header.length * 2, ctx->writing_buffer, + ctx->current_block_header.length, ctx->zstd_level); + + if(zstd_dst_size == 0) + { + ctx->current_block_header.compression = kCompressionNone; + free(cmp_buffer); + cmp_buffer = NULL; + } + else + { + ctx->current_block_header.cmpLength = (uint32_t)zstd_dst_size; + + if(ctx->current_block_header.cmpLength >= ctx->current_block_header.length) + { + ctx->current_block_header.compression = kCompressionNone; + free(cmp_buffer); + cmp_buffer = NULL; + } + } + break; default: FATAL("Invalid compression type");