Add function to serialize DVD long sector auxiliary data blocks

This commit is contained in:
2025-10-06 05:06:17 +01:00
parent 365f4f534a
commit 7173b6b5d0

View File

@@ -1170,6 +1170,305 @@ static void write_sector_subchannel(const aaruformatContext *ctx)
}
}
/**
* @brief Serialize DVD long sector auxiliary data blocks to the image file.
*
* This function writes four separate data blocks containing DVD-specific auxiliary information
* extracted from "long" DVD sectors. DVD long sectors contain additional fields beyond the 2048
* bytes of user data, including sector identification, error detection, and copy protection
* information. When writing DVD images with long sector support, these auxiliary fields are
* stored separately from the main user data to optimize storage and enable selective access.
*
* The function is only invoked if all four auxiliary buffers have been populated during image
* creation (sector_id, sector_ied, sector_cpr_mai, sector_edc). If any buffer is NULL, the
* function returns immediately without writing anything, allowing DVD images without long sector
* data to be created normally.
*
* **Four auxiliary data blocks written:**
*
* 1. **DVD Sector ID Block (DvdSectorId)**: 4 bytes per sector
* - Contains the sector ID field from DVD long sectors
* - Used for sector identification and addressing validation
*
* 2. **DVD Sector IED Block (DvdSectorIed)**: 2 bytes per sector
* - Contains the IED (ID Error Detection) field from DVD long sectors
* - Used for detecting errors in the sector ID field
*
* 3. **DVD Sector CPR/MAI Block (DvdSectorCprMai)**: 6 bytes per sector
* - Contains the CPR_MAI (Copyright Management Information) field
* - Used for copy protection and media authentication information
*
* 4. **DVD Sector EDC Block (DvdSectorEdc)**: 4 bytes per sector
* - Contains the EDC (Error Detection Code) field from DVD long sectors
* - Used for detecting errors in the sector data
*
* **Block structure for each auxiliary block:**
* Each block consists of:
* 1. BlockHeader containing identifier (DataBlock), type (DvdSectorId/IED/CprMai/Edc),
* compression (None), lengths, and CRC64 checksums
* 2. Raw auxiliary data: concatenated fields from all sectors (including negative, normal,
* and overflow sectors)
*
* The total number of sectors includes negative sectors (for lead-in), normal image sectors,
* and overflow sectors (for lead-out), calculated as:
* total_sectors = negative + Sectors + overflow
*
* **Write sequence for each block:**
* 1. Seek to end of file
* 2. Align file position to block boundary (using blockAlignmentShift)
* 3. Construct BlockHeader with appropriate type and calculated length
* 4. Calculate CRC64 over the auxiliary data buffer
* 5. Write BlockHeader (sizeof(BlockHeader) bytes)
* 6. Write auxiliary data buffer (length bytes)
* 7. On success, add IndexEntry to ctx->indexEntries
*
* **Alignment and file positioning:**
* Before writing each block, the file position is moved to EOF and then aligned forward to the
* next boundary satisfying (position & alignment_mask) == 0, where alignment_mask is derived
* from ctx->userDataDdtHeader.blockAlignmentShift. This ensures all blocks begin on properly
* aligned offsets for efficient I/O and compliance with the Aaru format specification.
*
* **Index registration:**
* After successfully writing each block's header and data, an IndexEntry is appended to
* ctx->indexEntries with:
* - blockType = DataBlock
* - dataType = DvdSectorId, DvdSectorIed, DvdSectorCprMai, or DvdSectorEdc
* - offset = the aligned file position where the BlockHeader was written
*
* **Error handling:**
* Write errors (fwrite returning < 1) are silently ignored for individual blocks; no index entry
* is added if a write fails, but iteration continues to attempt writing remaining blocks.
* Diagnostic TRACE logs report success or failure for each block. The function does not propagate
* error codes; higher-level close logic must validate overall integrity if needed.
*
* **No-op conditions:**
* - ctx->sector_id is NULL OR
* - ctx->sector_ied is NULL OR
* - ctx->sector_cpr_mai is NULL OR
* - ctx->sector_edc is NULL
*
* @param ctx Pointer to an initialized aaruformatContext in write mode. Must not be NULL.
* ctx->sector_id contains the ID fields from all DVD long sectors (may be NULL).
* ctx->sector_ied contains the IED fields from all DVD long sectors (may be NULL).
* ctx->sector_cpr_mai contains the CPR/MAI fields from all DVD long sectors (may be NULL).
* ctx->sector_edc contains the EDC fields from all DVD long sectors (may be NULL).
* ctx->imageStream must be open and writable. ctx->indexEntries must be initialized
* (utarray) to accept new index entries.
*
* @note DVD Long Sector Format:
* - Standard DVD sectors contain 2048 bytes of user data
* - Long DVD sectors include additional fields for error detection and copy protection
* - Total long sector size varies by DVD format (typically 2064-2076 bytes)
* - These auxiliary fields are critical for forensic imaging and copy-protected media
*
* @note Sector Coverage:
* - Negative sectors: Lead-in area before sector 0 (if present)
* - Normal sectors: Main data area (sectors 0 to Sectors-1)
* - Overflow sectors: Lead-out area after the main data (if present)
* - All three areas are included in the auxiliary data blocks
*
* @note Field Sizes:
* - ID field: 4 bytes per sector (sector identification)
* - IED field: 2 bytes per sector (ID error detection)
* - CPR/MAI field: 6 bytes per sector (copyright management)
* - EDC field: 4 bytes per sector (error detection code)
* - Total auxiliary data: 16 bytes per sector across all four blocks
*
* @note Memory Management:
* - The function does not allocate or free any memory
* - Auxiliary data buffers are managed by the caller
* - Buffers are written directly without modification
* - Memory is freed later during context cleanup (aaruf_close)
*
* @note Use Cases:
* - Forensic imaging of DVD media requiring complete sector data
* - Preservation of copy-protected DVD content
* - Analysis of DVD error detection and correction information
* - Validation of DVD sector structure and integrity
* - Research into DVD copy protection mechanisms
*
* @note Order in Close Sequence:
* - DVD long sector blocks are typically written after user data but before metadata
* - The exact position in the file depends on what other blocks precede them
* - Index entries ensure blocks can be located during subsequent opens
* - All four blocks are written consecutively if present
*
* @warning The auxiliary data buffers must contain data for ALL sectors (negative + normal +
* overflow). Partial buffers or mismatched sizes will cause incorrect data to be
* written or buffer overruns.
*
* @warning No compression is applied to DVD auxiliary data blocks. They are stored as raw
* binary data, which may result in larger image files for DVD media.
*
* @warning If any of the four auxiliary buffers is NULL, the entire function is skipped.
* This is an all-or-nothing operation - either all four blocks are written or none.
*
* @see aaruf_write_sector_long() for writing individual DVD long sectors that populate these buffers.
* @see BlockHeader for the block header structure definition.
*
* @internal
*/
void write_dvd_long_sector_blocks(aaruformatContext *ctx)
{
if(ctx->sector_id == NULL || ctx->sector_ied == NULL || ctx->sector_cpr_mai == NULL || ctx->sector_edc == NULL)
return;
uint64_t total_sectors = ctx->userDataDdtHeader.negative + ctx->imageInfo.Sectors + ctx->userDataDdtHeader.overflow;
// Write DVD sector ID block
fseek(ctx->imageStream, 0, SEEK_END);
long id_position = ftell(ctx->imageStream);
const uint64_t alignment_mask = (1ULL << ctx->userDataDdtHeader.blockAlignmentShift) - 1;
if(id_position & alignment_mask)
{
const uint64_t aligned_position = id_position + alignment_mask & ~alignment_mask;
fseek(ctx->imageStream, aligned_position, SEEK_SET);
id_position = aligned_position;
}
TRACE("Writing DVD sector ID block at position %ld", id_position);
BlockHeader id_block = {0};
id_block.identifier = DataBlock;
id_block.type = DvdSectorId;
id_block.compression = None;
id_block.length = (uint32_t)total_sectors * 4;
id_block.cmpLength = id_block.length;
// Calculate CRC64
id_block.crc64 = aaruf_crc64_data(ctx->sector_id, id_block.length);
id_block.cmpCrc64 = id_block.crc64;
// Write header
if(fwrite(&id_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
{
// Write data
const size_t written_bytes = fwrite(ctx->sector_id, id_block.length, 1, ctx->imageStream);
if(written_bytes == 1)
{
TRACE("Successfully wrote DVD sector ID block (%" PRIu64 " bytes)", id_block.length);
// Add ID block to index
TRACE("Adding DVD sector ID block to index");
IndexEntry id_index_entry;
id_index_entry.blockType = DataBlock;
id_index_entry.dataType = DvdSectorId;
id_index_entry.offset = id_position;
utarray_push_back(ctx->indexEntries, &id_index_entry);
TRACE("Added DVD sector ID block index entry at offset %" PRIu64, id_position);
}
}
// Write DVD sector IED block
fseek(ctx->imageStream, 0, SEEK_END);
long ied_position = ftell(ctx->imageStream);
if(ied_position & alignment_mask)
{
const uint64_t aligned_position = ied_position + alignment_mask & ~alignment_mask;
fseek(ctx->imageStream, aligned_position, SEEK_SET);
ied_position = aligned_position;
}
TRACE("Writing DVD sector IED block at position %ld", ied_position);
BlockHeader ied_block = {0};
ied_block.identifier = DataBlock;
ied_block.type = DvdSectorIed;
ied_block.compression = None;
ied_block.length = (uint32_t)total_sectors * 2;
ied_block.cmpLength = ied_block.length;
// Calculate CRC64
ied_block.crc64 = aaruf_crc64_data(ctx->sector_ied, ied_block.length);
ied_block.cmpCrc64 = ied_block.crc64;
// Write header
if(fwrite(&ied_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
{
// Write data
const size_t written_bytes = fwrite(ctx->sector_ied, ied_block.length, 1, ctx->imageStream);
if(written_bytes == 1)
{
TRACE("Successfully wrote DVD sector IED block (%" PRIu64 " bytes)", ied_block.length);
// Add IED block to index
TRACE("Adding DVD sector IED block to index");
IndexEntry ied_index_entry;
ied_index_entry.blockType = DataBlock;
ied_index_entry.dataType = DvdSectorIed;
ied_index_entry.offset = ied_position;
utarray_push_back(ctx->indexEntries, &ied_index_entry);
TRACE("Added DVD sector IED block index entry at offset %" PRIu64, ied_position);
}
}
// Write DVD sector CPR/MAI block
fseek(ctx->imageStream, 0, SEEK_END);
long cpr_mai_position = ftell(ctx->imageStream);
if(cpr_mai_position & alignment_mask)
{
const uint64_t aligned_position = cpr_mai_position + alignment_mask & ~alignment_mask;
fseek(ctx->imageStream, aligned_position, SEEK_SET);
cpr_mai_position = aligned_position;
}
TRACE("Writing DVD sector CPR/MAI block at position %ld", cpr_mai_position);
BlockHeader cpr_mai_block = {0};
cpr_mai_block.identifier = DataBlock;
cpr_mai_block.type = DvdSectorCprMai;
cpr_mai_block.compression = None;
cpr_mai_block.length = (uint32_t)total_sectors * 6;
cpr_mai_block.cmpLength = cpr_mai_block.length;
// Calculate CRC64
cpr_mai_block.crc64 = aaruf_crc64_data(ctx->sector_cpr_mai, cpr_mai_block.length);
cpr_mai_block.cmpCrc64 = cpr_mai_block.crc64;
// Write header
if(fwrite(&cpr_mai_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
{
// Write data
const size_t written_bytes = fwrite(ctx->sector_cpr_mai, cpr_mai_block.length, 1, ctx->imageStream);
if(written_bytes == 1)
{
TRACE("Successfully wrote DVD sector CPR/MAI block (%" PRIu64 " bytes)", cpr_mai_block.length);
// Add CPR/MAI block to index
TRACE("Adding DVD sector CPR/MAI block to index");
IndexEntry cpr_mai_index_entry;
cpr_mai_index_entry.blockType = DataBlock;
cpr_mai_index_entry.dataType = DvdSectorCprMai;
cpr_mai_index_entry.offset = cpr_mai_position;
utarray_push_back(ctx->indexEntries, &cpr_mai_index_entry);
TRACE("Added DVD sector CPR/MAI block index entry at offset %" PRIu64, cpr_mai_position);
}
}
// Write DVD sector EDC block
fseek(ctx->imageStream, 0, SEEK_END);
long edc_position = ftell(ctx->imageStream);
if(edc_position & alignment_mask)
{
const uint64_t aligned_position = edc_position + alignment_mask & ~alignment_mask;
fseek(ctx->imageStream, aligned_position, SEEK_SET);
edc_position = aligned_position;
}
TRACE("Writing DVD sector EDC block at position %ld", edc_position);
BlockHeader edc_block = {0};
edc_block.identifier = DataBlock;
edc_block.type = DvdSectorEdc;
edc_block.compression = None;
edc_block.length = (uint32_t)total_sectors * 4;
edc_block.cmpLength = edc_block.length;
// Calculate CRC64
edc_block.crc64 = aaruf_crc64_data(ctx->sector_edc, edc_block.length);
edc_block.cmpCrc64 = edc_block.crc64;
// Write header
if(fwrite(&edc_block, sizeof(BlockHeader), 1, ctx->imageStream) == 1)
{
// Write data
const size_t written_bytes = fwrite(ctx->sector_edc, edc_block.length, 1, ctx->imageStream);
if(written_bytes == 1)
{
TRACE("Successfully wrote DVD sector EDC block (%" PRIu64 " bytes)", edc_block.length);
// Add EDC block to index
TRACE("Adding DVD sector EDC block to index");
IndexEntry edc_index_entry;
edc_index_entry.blockType = DataBlock;
edc_index_entry.dataType = DvdSectorEdc;
edc_index_entry.offset = edc_position;
utarray_push_back(ctx->indexEntries, &edc_index_entry);
TRACE("Added DVD sector EDC block index entry at offset %" PRIu64, edc_position);
}
}
}
/**
* @brief Serialize all accumulated media tags to the image file.
*
@@ -2409,6 +2708,9 @@ int aaruf_close(void *context)
// Write sector subchannel data block
write_sector_subchannel(ctx);
// Write DVD long sector data blocks
write_dvd_long_sector_blocks(ctx);
// Write media tags data blocks
write_media_tags(ctx);
@@ -2536,6 +2838,11 @@ int aaruf_close(void *context)
free(ctx->checksums.spamsum);
ctx->checksums.spamsum = NULL;
free(ctx->sector_id);
free(ctx->sector_ied);
free(ctx->sector_cpr_mai);
free(ctx->sector_edc);
// TODO: Free caches
free(context);