Merge branch 'devel' of github.com:aaru-dps/libaaruformat into devel

This commit is contained in:
2026-04-12 14:49:24 +01:00
4 changed files with 247 additions and 1 deletions

View File

@@ -208,7 +208,7 @@ typedef struct aaruformat_context
uint8_t *sector_id; ///< DVD sector ID (4 bytes) if present.
uint8_t *sector_ied; ///< DVD sector IED (2 bytes) if present.
uint8_t *sector_cpr_mai; ///< DVD sector CPR_MAI (6 bytes) if present.
uint8_t *sector_edc; ///< DVD sector EDC (4 bytes) if present.
uint8_t *sector_edc; ///< DVD or Blu-ray sector EDC (4 bytes) if present (see DvdSectorEdc / BdSectorEdc data types).
uint8_t *sector_decrypted_title_key; ///< DVD decrypted title key (5 bytes) if present.
/* Metadata & geometry */
@@ -331,6 +331,7 @@ typedef struct aaruformat_context
bool dirty_sector_suffix_ddt; ///< True if sector suffix DDT should be written during close
bool dirty_sector_subchannel_block; ///< True if subchannel block should be written during close
bool dirty_dvd_long_sector_blocks; ///< True if DVD long sector blocks should be written during close
bool dirty_bd_sector_edc_block; ///< True if Blu-ray sector EDC data block should be written during close
bool dirty_dvd_title_key_decrypted_block; ///< True if decrypted title key block should be written during close
bool dirty_media_tags; ///< True if media tags should be written during close
bool dirty_tape_ddt; ///< True if tape DDT should be written during close

View File

@@ -2566,6 +2566,218 @@ static void write_dvd_long_sector_blocks(aaruformat_context *ctx)
if(edc_block.compression != kCompressionNone) free(buffer);
}
/**
* @brief Serialize Blu-ray long-sector EDC (4 bytes per sector) to the image file.
*
* Blu-ray user sectors are normally 2048 bytes; long BD sectors supplied to the library are
* 2052 bytes, with 4 bytes of EDC immediately following the user data. That EDC is stored in
* ctx->sector_edc (one uint32_t per logical sector) while the main user data path stores only
* 2048-byte sectors in the DDT. This function writes those EDC bytes as a single DataBlock so
* they round-trip on open and through aaruf_read_sector_long().
*
* This is separate from write_dvd_long_sector_blocks(): DVD long sectors require four auxiliary
* buffers (ID, IED, CPR/MAI, EDC) and are written only when all four are present; BD imaging
* only needs the EDC buffer. The on-disk layout of the payload matches the DVD EDC block (raw
* concatenation, 4 bytes per sector), but the block type is kDataTypeBdSectorEdc so readers
* distinguish BD from DVD auxiliary data.
*
* **Single auxiliary data block written:**
*
* 1. **Blu-ray Sector EDC Block (BdSectorEdc)**: 4 bytes per sector
* - Contains the EDC field from long BD sectors (bytes 20482051 of each long sector)
* - Used for error detection over the user payload; preserved for forensic and archival use
*
* **Block structure:**
* The block consists of:
* 1. BlockHeader with identifier DataBlock, type kDataTypeBdSectorEdc, compression (None,
* Zstd, or Lzma when enabled), uncompressed and compressed lengths, and CRC64 checksums
* 2. Optional LZMA property bytes when compression is LZMA
* 3. Raw auxiliary data: EDC fields for all logical sectors (negative, normal, overflow),
* possibly stored compressed
*
* The total number of sectors matches the user-data DDT span:
* total_sectors = negative + Sectors + overflow
*
* **Write sequence:**
* 1. Seek to end of file
* 2. Align file position to block boundary (using blockAlignmentShift)
* 3. Construct BlockHeader with type kDataTypeBdSectorEdc and length = total_sectors * 4
* 4. Calculate CRC64 over the uncompressed auxiliary buffer
* 5. Optionally compress; if compression does not shrink the payload, store uncompressed
* 6. Write BlockHeader (sizeof(BlockHeader) bytes), optional LZMA props, then payload
* 7. On success, replace any existing index entry for this block type and append a new IndexEntry
*
* **Alignment and file positioning:**
* The file position is moved to EOF then aligned forward so that
* (position & alignment_mask) == 0, with alignment_mask from ctx->user_data_ddt_header.blockAlignmentShift,
* matching other DataBlocks in the image.
*
* **Index registration:**
* After a successful write, any prior DataBlock index entry with dataType kDataTypeBdSectorEdc
* is removed, then a new entry is pushed with offset equal to the aligned position of the
* BlockHeader. ctx->dirty_index_block is set to true.
*
* **Error handling:**
* Write failures (fwrite returning other than 1) are not propagated; no index update occurs on
* failure. TRACE logs record success paths. Compression allocation failure returns early
* without writing.
*
* **No-op conditions:**
* If ctx->sector_edc is NULL, the function returns immediately without writing, allowing BD
* images created without long-sector EDC to close normally.
*
* @param ctx Pointer to an initialized aaruformatContext in write mode. Must not be NULL.
* ctx->sector_edc holds 4 * total_sectors bytes when present (may be NULL).
* ctx->imageStream must be open and writable. ctx->index_entries must be initialized
* (utarray) to accept new index entries.
*
* @note BD long sector format:
* - User data remains 2048 bytes per sector in the main data path
* - Long sectors are 2052 bytes: 2048 bytes user + 4 bytes EDC
* - EDC is stored separately under kDataTypeBdSectorEdc for parity with the format spec
*
* @note Sector coverage:
* - Negative sectors: lead-in area before sector 0 (if present)
* - Normal sectors: main data area (0 to Sectors-1)
* - Overflow sectors: lead-out or beyond user extent (if present)
* - The buffer is indexed by the same corrected sector index as DVD auxiliary data
*
* @note Memory management:
* - Temporary buffers may be allocated for compression; freed before return when used
* - ctx->sector_edc is owned by the context and freed on aaruf_close()
*
* @note Order in close sequence:
* - Invoked from aaruf_finalize_write() when dirty_bd_sector_edc_block is set, adjacent
* to write_dvd_long_sector_blocks() for optical auxiliary blocks
*
* @warning The sector_edc buffer must cover all total_sectors entries; sparse or partial buffers
* produce incorrect images.
*
* @warning When compression is enabled, behavior matches other DataBlocks (Zstd or LZMA);
* uncompressed storage is used if compression expands the payload.
*
* @see write_dvd_long_sector_blocks() for the four-block DVD auxiliary path.
* @see aaruf_write_sector() for the 2052-byte BD branch that populates sector_edc.
* @see BlockHeader for the block header structure definition.
*
* @internal
*/
static void write_bd_sector_edc_block(aaruformat_context *ctx)
{
if(ctx->sector_edc == NULL) return;
uint64_t total_sectors =
ctx->user_data_ddt_header.negative + ctx->image_info.Sectors + ctx->user_data_ddt_header.overflow;
const uint64_t alignment_mask = (1ULL << ctx->user_data_ddt_header.blockAlignmentShift) - 1;
aaruf_fseek(ctx->imageStream, 0, SEEK_END);
aaru_off_t edc_position = aaruf_ftell(ctx->imageStream);
if(edc_position & alignment_mask)
{
const uint64_t aligned_position = edc_position + alignment_mask & ~alignment_mask;
aaruf_fseek(ctx->imageStream, aligned_position, SEEK_SET);
edc_position = aligned_position;
}
TRACE("Writing BD sector EDC block at position %ld", edc_position);
BlockHeader edc_block = {0};
edc_block.identifier = DataBlock;
edc_block.type = kDataTypeBdSectorEdc;
edc_block.compression =
ctx->compression_enabled ? (ctx->use_zstd ? kCompressionZstd : kCompressionLzma) : kCompressionNone;
edc_block.length = (uint32_t)total_sectors * 4;
edc_block.crc64 = aaruf_crc64_data(ctx->sector_edc, edc_block.length);
uint8_t *buffer = NULL;
uint8_t lzma_properties[LZMA_PROPERTIES_LENGTH] = {0};
if(edc_block.compression == kCompressionNone)
{
buffer = ctx->sector_edc;
edc_block.cmpCrc64 = edc_block.crc64;
}
else
{
buffer = malloc((size_t)edc_block.length * 2);
if(buffer == NULL)
{
TRACE("Failed to allocate memory for BD sector EDC compression");
return;
}
size_t dst_size;
if(ctx->use_zstd)
{
dst_size = aaruf_zstd_encode_buffer(buffer, (size_t)edc_block.length * 2, ctx->sector_edc,
edc_block.length, ctx->zstd_level, ctx->num_threads);
if(dst_size == 0) dst_size = edc_block.length;
}
else
{
dst_size = (size_t)edc_block.length * 2 * 2;
size_t props_size = LZMA_PROPERTIES_LENGTH;
aaruf_lzma_encode_buffer(buffer, &dst_size, ctx->sector_edc, edc_block.length, lzma_properties, &props_size,
9, ctx->lzma_dict_size, 4, 0, 2, 273, LZMA_THREADS(ctx));
}
edc_block.cmpLength = (uint32_t)dst_size;
if(edc_block.cmpLength >= edc_block.length)
{
edc_block.compression = kCompressionNone;
free(buffer);
buffer = ctx->sector_edc;
}
}
if(edc_block.compression == kCompressionNone)
{
edc_block.cmpLength = edc_block.length;
edc_block.cmpCrc64 = edc_block.crc64;
}
else
{
edc_block.cmpCrc64 = aaruf_crc64_data(buffer, edc_block.cmpLength);
if(ctx->use_zstd) ctx->has_zstd_blocks = true;
}
size_t length_to_write = edc_block.cmpLength;
if(edc_block.compression == kCompressionLzma) edc_block.cmpLength += LZMA_PROPERTIES_LENGTH;
if(fwrite(&edc_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
{
if(edc_block.compression == kCompressionLzma)
fwrite(lzma_properties, LZMA_PROPERTIES_LENGTH, 1, ctx->imageStream);
const size_t written_bytes = fwrite(buffer, length_to_write, 1, ctx->imageStream);
if(written_bytes == 1)
{
TRACE("Successfully wrote BD sector EDC block (%" PRIu64 " bytes)", edc_block.cmpLength);
for(int k = utarray_len(ctx->index_entries) - 1; k >= 0; k--)
{
const IndexEntry *entry = (IndexEntry *)utarray_eltptr(ctx->index_entries, k);
if(entry && entry->blockType == DataBlock && entry->dataType == kDataTypeBdSectorEdc)
{
TRACE("Found existing BD sector EDC block index entry at position %d, removing", k);
utarray_erase(ctx->index_entries, k, 1);
}
}
IndexEntry edc_index_entry;
edc_index_entry.blockType = DataBlock;
edc_index_entry.dataType = kDataTypeBdSectorEdc;
edc_index_entry.offset = edc_position;
utarray_push_back(ctx->index_entries, &edc_index_entry);
ctx->dirty_index_block = true;
TRACE("Added BD sector EDC block index entry at offset %" PRIu64, edc_position);
}
}
if(edc_block.compression != kCompressionNone) free(buffer);
}
/**
* @brief Serialize the DVD decrypted title key data block to the image file.
*
@@ -5350,6 +5562,9 @@ int32_t aaruf_finalize_write(aaruformat_context *ctx)
// Write DVD long sector data blocks
if(ctx->dirty_dvd_long_sector_blocks) write_dvd_long_sector_blocks(ctx);
// Write Blu-ray sector EDC block (long sectors)
if(ctx->dirty_bd_sector_edc_block) write_bd_sector_edc_block(ctx);
// Write DVD decrypted title keys
if(ctx->dirty_dvd_title_key_decrypted_block) write_dvd_title_key_decrypted_block(ctx);

View File

@@ -594,6 +594,7 @@ AARU_EXPORT void *AARU_CALL aaruf_create(const char *filepath, const uint32_t me
ctx->dirty_sector_suffix_ddt = true;
ctx->dirty_sector_subchannel_block = true;
ctx->dirty_dvd_long_sector_blocks = true;
ctx->dirty_bd_sector_edc_block = true;
ctx->dirty_dvd_title_key_decrypted_block = true;
ctx->dirty_media_tags = true;
ctx->dirty_tape_ddt = true;

View File

@@ -2681,6 +2681,35 @@ AARU_EXPORT int32_t AARU_CALL aaruf_write_sector_tag(void *context, const uint64
ctx->dirty_sector_subchannel_block = true; // Mark subchannel block as dirty
TRACE("Exiting aaruf_write_sector_tag() = AARUF_STATUS_OK");
return AARUF_STATUS_OK;
case kSectorTagBdSectorEdc:
if(ctx->image_info.MetadataMediaType != BlockMedia)
{
FATAL("Invalid media type for tag");
TRACE("Exiting aaruf_write_sector_tag() = AARUF_ERROR_INCORRECT_MEDIA_TYPE");
return AARUF_ERROR_INCORRECT_MEDIA_TYPE;
}
if(length != 4)
{
FATAL("Incorrect tag size");
TRACE("Exiting aaruf_write_sector_tag() = AARUF_ERROR_INCORRECT_DATA_SIZE");
return AARUF_ERROR_INCORRECT_DATA_SIZE;
}
if(ctx->sector_edc == NULL) ctx->sector_edc = calloc(1, 4 * total_sectors);
if(ctx->sector_edc == NULL)
{
FATAL("Could not allocate memory for Blu-ray sector EDC");
TRACE("Exiting aaruf_write_sector_tag() = AARUF_ERROR_NOT_ENOUGH_MEMORY");
return AARUF_ERROR_NOT_ENOUGH_MEMORY;
}
memcpy(ctx->sector_edc + corrected_sector_address * 4, data, 4);
ctx->dirty_bd_sector_edc_block = true; // Mark BD sector EDC block as dirty
TRACE("Exiting aaruf_write_sector_tag() = AARUF_STATUS_OK");
return AARUF_STATUS_OK;
default:
TRACE("Do not know how to write sector tag %d", tag);
return AARUF_ERROR_INVALID_TAG;