2025-08-13 16:17:45 +01:00
|
|
|
|
/*
|
|
|
|
|
|
* This file is part of the Aaru Data Preservation Suite.
|
|
|
|
|
|
* Copyright (c) 2019-2025 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 <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
*/
|
|
|
|
|
|
#include <errno.h>
|
|
|
|
|
|
#include <stdbool.h>
|
|
|
|
|
|
#include <stdint.h>
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
|
|
|
|
#include "aaruformat.h"
|
|
|
|
|
|
#include "internal.h"
|
2025-08-14 00:38:28 +01:00
|
|
|
|
#include "log.h"
|
2025-09-30 20:10:40 +01:00
|
|
|
|
#include "xxhash.h"
|
2025-08-13 16:17:45 +01:00
|
|
|
|
|
2025-09-30 13:08:45 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief Writes a sector to the AaruFormat image.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Writes the given data to the specified sector address in the image, with the given status and length.
|
2025-09-30 16:03:34 +01:00
|
|
|
|
* This function handles buffering data into blocks, automatically closing blocks when necessary (sector
|
|
|
|
|
|
* size changes or block size limits are reached), and managing the deduplication table (DDT) entries.
|
2025-09-30 13:08:45 +01:00
|
|
|
|
*
|
|
|
|
|
|
* @param context Pointer to the aaruformat context.
|
2025-09-30 15:11:27 +01:00
|
|
|
|
* @param sector_address Logical sector address to write.
|
2025-10-02 17:07:17 +01:00
|
|
|
|
* @param negative Indicates if the sector address is negative.
|
2025-09-30 13:08:45 +01:00
|
|
|
|
* @param data Pointer to the data buffer to write.
|
2025-09-30 15:11:27 +01:00
|
|
|
|
* @param sector_status Status of the sector to write.
|
2025-09-30 13:08:45 +01:00
|
|
|
|
* @param length Length of the data buffer.
|
2025-09-30 16:03:34 +01:00
|
|
|
|
*
|
|
|
|
|
|
* @return Returns one of the following status codes:
|
|
|
|
|
|
* @retval AARUF_STATUS_OK (0) Successfully wrote the sector data. This is returned when:
|
|
|
|
|
|
* - The sector data is successfully copied to the writing buffer
|
|
|
|
|
|
* - The DDT entry is successfully updated for the sector address
|
|
|
|
|
|
* - Block management operations complete successfully
|
|
|
|
|
|
* - Buffer positions and offsets are properly updated
|
|
|
|
|
|
*
|
|
|
|
|
|
* @retval AARUF_ERROR_NOT_AARUFORMAT (-1) The context is invalid. This occurs when:
|
|
|
|
|
|
* - The context parameter is NULL
|
|
|
|
|
|
* - The context magic number doesn't match AARU_MAGIC (invalid context type)
|
|
|
|
|
|
*
|
|
|
|
|
|
* @retval AARUF_READ_ONLY (-22) Attempting to write to a read-only image. This occurs when:
|
|
|
|
|
|
* - The context's isWriting flag is false
|
|
|
|
|
|
* - The image was opened in read-only mode
|
|
|
|
|
|
*
|
|
|
|
|
|
* @retval AARUF_ERROR_NOT_ENOUGH_MEMORY (-9) Memory allocation failed. This occurs when:
|
|
|
|
|
|
* - Failed to allocate memory for the writing buffer when creating a new block
|
|
|
|
|
|
* - The system is out of available memory for buffer allocation
|
|
|
|
|
|
*
|
|
|
|
|
|
* @retval AARUF_ERROR_CANNOT_WRITE_BLOCK_HEADER (-23) Failed to write block header to the image file.
|
|
|
|
|
|
* This can occur during automatic block closure when:
|
|
|
|
|
|
* - The fwrite() call for the block header fails
|
|
|
|
|
|
* - Disk space is insufficient or file system errors occur
|
|
|
|
|
|
*
|
|
|
|
|
|
* @retval AARUF_ERROR_CANNOT_WRITE_BLOCK_DATA (-24) Failed to write block data to the image file.
|
|
|
|
|
|
* This can occur during automatic block closure when:
|
|
|
|
|
|
* - The fwrite() call for the block data fails
|
|
|
|
|
|
* - Disk space is insufficient or file system errors occur
|
|
|
|
|
|
*
|
2025-09-30 18:47:38 +01:00
|
|
|
|
* @retval AARUF_ERROR_CANNOT_SET_DDT_ENTRY (-25) Failed to update the deduplication table (DDT) entry.
|
|
|
|
|
|
* This occurs when:
|
|
|
|
|
|
* - The DDT entry for the specified sector address could not be set or updated
|
|
|
|
|
|
* - Internal DDT management functions return failure
|
|
|
|
|
|
* - DDT table corruption or memory issues prevent entry updates
|
|
|
|
|
|
*
|
2025-09-30 16:03:34 +01:00
|
|
|
|
* @note Block Management:
|
|
|
|
|
|
* - The function automatically closes the current block when sector size changes
|
|
|
|
|
|
* - Blocks are also closed when they reach the maximum size (determined by dataShift)
|
|
|
|
|
|
* - New blocks are created automatically when needed with appropriate headers
|
|
|
|
|
|
*
|
|
|
|
|
|
* @note Memory Management:
|
|
|
|
|
|
* - Writing buffers are allocated on-demand when creating new blocks
|
|
|
|
|
|
* - Buffer memory is freed when blocks are closed
|
|
|
|
|
|
* - Buffer size is calculated based on sector size and data shift parameters
|
|
|
|
|
|
*
|
|
|
|
|
|
* @note DDT Updates:
|
|
|
|
|
|
* - Each written sector updates the corresponding DDT entry
|
|
|
|
|
|
* - DDT entries track block offset, position, and sector status
|
|
|
|
|
|
* - Uses DDT version 2 format for entries
|
|
|
|
|
|
*
|
|
|
|
|
|
* @warning The function may trigger automatic block closure, which can result in disk I/O
|
|
|
|
|
|
* operations and potential write errors even for seemingly simple sector writes.
|
2025-09-30 13:08:45 +01:00
|
|
|
|
*/
|
2025-10-02 17:07:17 +01:00
|
|
|
|
int32_t aaruf_write_sector(void *context, uint64_t sector_address, bool negative, const uint8_t *data,
|
|
|
|
|
|
uint8_t sector_status, uint32_t length)
|
2025-08-13 16:17:45 +01:00
|
|
|
|
{
|
2025-10-02 17:07:17 +01:00
|
|
|
|
TRACE("Entering aaruf_write_sector(%p, %" PRIu64 ", %d, %p, %u, %u)", context, sector_address, negative, data,
|
|
|
|
|
|
sector_status, length);
|
2025-08-14 00:38:28 +01:00
|
|
|
|
|
2025-08-13 16:17:45 +01:00
|
|
|
|
// Check context is correct AaruFormat context
|
2025-08-14 00:38:28 +01:00
|
|
|
|
if(context == NULL)
|
|
|
|
|
|
{
|
|
|
|
|
|
FATAL("Invalid context");
|
|
|
|
|
|
|
|
|
|
|
|
TRACE("Exiting aaruf_write_sector() = AARUF_ERROR_NOT_AARUFORMAT");
|
|
|
|
|
|
return AARUF_ERROR_NOT_AARUFORMAT;
|
|
|
|
|
|
}
|
2025-08-13 16:17:45 +01:00
|
|
|
|
|
|
|
|
|
|
aaruformatContext *ctx = context;
|
|
|
|
|
|
|
|
|
|
|
|
// Not a libaaruformat context
|
2025-08-14 00:38:28 +01:00
|
|
|
|
if(ctx->magic != AARU_MAGIC)
|
|
|
|
|
|
{
|
|
|
|
|
|
FATAL("Invalid context");
|
|
|
|
|
|
|
|
|
|
|
|
TRACE("Exiting aaruf_write_sector() = AARUF_ERROR_NOT_AARUFORMAT");
|
|
|
|
|
|
return AARUF_ERROR_NOT_AARUFORMAT;
|
|
|
|
|
|
}
|
2025-08-13 16:17:45 +01:00
|
|
|
|
|
|
|
|
|
|
// Check we are writing
|
2025-08-14 00:38:28 +01:00
|
|
|
|
if(!ctx->isWriting)
|
|
|
|
|
|
{
|
|
|
|
|
|
FATAL("Trying to write a read-only image");
|
|
|
|
|
|
|
|
|
|
|
|
TRACE("Exiting aaruf_write_sector() = AARUF_READ_ONLY");
|
|
|
|
|
|
return AARUF_READ_ONLY;
|
|
|
|
|
|
}
|
2025-08-13 16:17:45 +01:00
|
|
|
|
|
2025-10-02 17:29:13 +01:00
|
|
|
|
if(negative && sector_address > ctx->userDataDdtHeader.negative - 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
FATAL("Sector address out of bounds");
|
|
|
|
|
|
|
|
|
|
|
|
TRACE("Exiting aaruf_write_sector() = AARUF_ERROR_SECTOR_OUT_OF_BOUNDS");
|
|
|
|
|
|
return AARUF_ERROR_SECTOR_OUT_OF_BOUNDS;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(!negative && sector_address > ctx->imageInfo.Sectors + ctx->userDataDdtHeader.overflow - 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
FATAL("Sector address out of bounds");
|
|
|
|
|
|
|
|
|
|
|
|
TRACE("Exiting aaruf_write_sector() = AARUF_ERROR_SECTOR_OUT_OF_BOUNDS");
|
|
|
|
|
|
return AARUF_ERROR_SECTOR_OUT_OF_BOUNDS;
|
|
|
|
|
|
}
|
2025-08-13 16:17:45 +01:00
|
|
|
|
|
2025-10-03 00:17:16 +01:00
|
|
|
|
if(!ctx->rewinded)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(sector_address <= ctx->last_written_block)
|
|
|
|
|
|
{
|
|
|
|
|
|
TRACE("Rewinded");
|
|
|
|
|
|
ctx->rewinded = true;
|
2025-10-03 00:57:14 +01:00
|
|
|
|
|
|
|
|
|
|
// Disable MD5 calculation
|
|
|
|
|
|
if(ctx->calculating_md5) ctx->calculating_md5 = false;
|
2025-10-03 01:49:44 +01:00
|
|
|
|
// Disable SHA1 calculation
|
|
|
|
|
|
if(ctx->calculating_sha1) ctx->calculating_sha1 = false;
|
2025-10-03 02:03:39 +01:00
|
|
|
|
// Disable SHA256 calculation
|
|
|
|
|
|
if(ctx->calculating_sha256) ctx->calculating_sha256 = false;
|
2025-10-03 02:17:47 +01:00
|
|
|
|
// Disable SpamSum calculation
|
|
|
|
|
|
if(ctx->calculating_spamsum) ctx->calculating_spamsum = false;
|
2025-10-03 04:01:30 +01:00
|
|
|
|
// Disable BLAKE3 calculation
|
|
|
|
|
|
if(ctx->calculating_blake3) ctx->calculating_blake3 = false;
|
2025-10-03 00:17:16 +01:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
ctx->last_written_block = sector_address;
|
|
|
|
|
|
}
|
2025-08-13 16:17:45 +01:00
|
|
|
|
|
2025-10-03 00:57:14 +01:00
|
|
|
|
// Calculate MD5 on-the-fly if requested and sector is within user sectors (not negative or overflow)
|
2025-10-03 17:46:40 +01:00
|
|
|
|
if(ctx->calculating_md5 && !negative && sector_address <= ctx->imageInfo.Sectors && !ctx->writingLong)
|
2025-10-03 00:57:14 +01:00
|
|
|
|
aaruf_md5_update(&ctx->md5_context, data, length);
|
2025-10-03 01:49:44 +01:00
|
|
|
|
// Calculate SHA1 on-the-fly if requested and sector is within user sectors (not negative or overflow)
|
2025-10-03 17:46:40 +01:00
|
|
|
|
if(ctx->calculating_sha1 && !negative && sector_address <= ctx->imageInfo.Sectors && !ctx->writingLong)
|
2025-10-03 01:49:44 +01:00
|
|
|
|
aaruf_sha1_update(&ctx->sha1_context, data, length);
|
2025-10-03 02:03:39 +01:00
|
|
|
|
// Calculate SHA256 on-the-fly if requested and sector is within user sectors (not negative or overflow)
|
2025-10-03 17:46:40 +01:00
|
|
|
|
if(ctx->calculating_sha256 && !negative && sector_address <= ctx->imageInfo.Sectors && !ctx->writingLong)
|
2025-10-03 02:03:39 +01:00
|
|
|
|
aaruf_sha256_update(&ctx->sha256_context, data, length);
|
2025-10-03 02:17:47 +01:00
|
|
|
|
// Calculate SpamSum on-the-fly if requested and sector is within user sectors (not negative or overflow)
|
2025-10-03 17:46:40 +01:00
|
|
|
|
if(ctx->calculating_sha256 && !negative && sector_address <= ctx->imageInfo.Sectors && !ctx->writingLong)
|
2025-10-03 04:01:30 +01:00
|
|
|
|
aaruf_spamsum_update(ctx->spamsum_context, data, length);
|
|
|
|
|
|
// Calculate BLAKE3 on-the-fly if requested and sector is within user sectors (not negative or overflow)
|
2025-10-03 17:46:40 +01:00
|
|
|
|
if(ctx->calculating_blake3 && !negative && sector_address <= ctx->imageInfo.Sectors && !ctx->writingLong)
|
2025-10-03 04:01:30 +01:00
|
|
|
|
blake3_hasher_update(ctx->blake3_context, data, length);
|
2025-10-03 00:57:14 +01:00
|
|
|
|
|
2025-08-13 16:17:45 +01:00
|
|
|
|
// Close current block first
|
|
|
|
|
|
if(ctx->writingBuffer != NULL &&
|
|
|
|
|
|
// When sector size changes
|
|
|
|
|
|
(ctx->currentBlockHeader.sectorSize != length || ctx->currentBlockOffset == 1 << ctx->userDataDdtHeader.dataShift
|
|
|
|
|
|
// TODO: Implement compression
|
|
|
|
|
|
))
|
|
|
|
|
|
{
|
2025-08-14 00:38:28 +01:00
|
|
|
|
TRACE("Closing current block before writing new data");
|
2025-08-13 16:17:45 +01:00
|
|
|
|
int error = aaruf_close_current_block(ctx);
|
|
|
|
|
|
|
2025-08-14 00:38:28 +01:00
|
|
|
|
if(error != AARUF_STATUS_OK)
|
|
|
|
|
|
{
|
|
|
|
|
|
FATAL("Error closing current block: %d", error);
|
|
|
|
|
|
|
|
|
|
|
|
TRACE("Exiting aaruf_write_sector() = %d", error);
|
|
|
|
|
|
return error;
|
|
|
|
|
|
}
|
2025-08-13 16:17:45 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-30 20:10:40 +01:00
|
|
|
|
uint64_t ddt_entry = 0;
|
2025-10-01 02:54:51 +01:00
|
|
|
|
bool ddt_ok;
|
2025-09-30 20:10:40 +01:00
|
|
|
|
|
|
|
|
|
|
if(ctx->deduplicate)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Calculate 64-bit XXH3 hash of the sector
|
|
|
|
|
|
TRACE("Hashing sector data for deduplication");
|
|
|
|
|
|
uint64_t hash = XXH3_64bits(data, length);
|
|
|
|
|
|
|
|
|
|
|
|
// Check if the hash is already in the map
|
|
|
|
|
|
bool existing = lookup_map(ctx->sectorHashMap, hash, &ddt_entry);
|
|
|
|
|
|
TRACE("Block does %s exist in deduplication map", existing ? "already" : "not yet");
|
|
|
|
|
|
|
2025-10-02 17:07:17 +01:00
|
|
|
|
ddt_ok = set_ddt_entry_v2(ctx, sector_address, negative, ctx->currentBlockOffset, ctx->nextBlockPosition,
|
|
|
|
|
|
sector_status, &ddt_entry);
|
2025-09-30 20:45:30 +01:00
|
|
|
|
if(!ddt_ok)
|
|
|
|
|
|
{
|
|
|
|
|
|
TRACE("Exiting aaruf_write_sector() = AARUF_ERROR_CANNOT_SET_DDT_ENTRY");
|
|
|
|
|
|
return AARUF_ERROR_CANNOT_SET_DDT_ENTRY;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(existing)
|
|
|
|
|
|
{
|
|
|
|
|
|
TRACE("Sector exists, so not writing to image");
|
|
|
|
|
|
TRACE("Exiting aaruf_write_sector() = AARUF_STATUS_OK");
|
|
|
|
|
|
return AARUF_STATUS_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TRACE("Inserting sector hash into deduplication map, proceeding to write into image as normal");
|
|
|
|
|
|
insert_map(ctx->sectorHashMap, hash, ddt_entry);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
2025-10-02 17:07:17 +01:00
|
|
|
|
ddt_ok = set_ddt_entry_v2(ctx, sector_address, negative, ctx->currentBlockOffset, ctx->nextBlockPosition,
|
|
|
|
|
|
sector_status, &ddt_entry);
|
2025-09-30 18:47:38 +01:00
|
|
|
|
|
|
|
|
|
|
if(!ddt_ok)
|
|
|
|
|
|
{
|
|
|
|
|
|
TRACE("Exiting aaruf_write_sector() = AARUF_ERROR_CANNOT_SET_DDT_ENTRY");
|
|
|
|
|
|
return AARUF_ERROR_CANNOT_SET_DDT_ENTRY;
|
|
|
|
|
|
}
|
2025-09-28 16:13:56 +01:00
|
|
|
|
|
2025-08-13 16:17:45 +01:00
|
|
|
|
// No block set
|
|
|
|
|
|
if(ctx->writingBufferPosition == 0)
|
|
|
|
|
|
{
|
2025-08-14 00:38:28 +01:00
|
|
|
|
TRACE("Creating new writing block");
|
2025-08-13 16:17:45 +01:00
|
|
|
|
ctx->currentBlockHeader.identifier = DataBlock;
|
|
|
|
|
|
ctx->currentBlockHeader.type = UserData;
|
|
|
|
|
|
ctx->currentBlockHeader.compression = None; // TODO: Compression
|
|
|
|
|
|
ctx->currentBlockHeader.sectorSize = length;
|
|
|
|
|
|
|
2025-10-03 15:25:01 +01:00
|
|
|
|
// We need to save the track type for later compression
|
|
|
|
|
|
if(ctx->imageInfo.XmlMediaType == OpticalDisc && ctx->trackEntries != NULL)
|
|
|
|
|
|
{
|
|
|
|
|
|
const TrackEntry *track = NULL;
|
|
|
|
|
|
for(int i = 0; i < ctx->tracksHeader.entries; i++)
|
|
|
|
|
|
if(sector_address >= ctx->trackEntries[i].start && sector_address <= ctx->trackEntries[i].end)
|
|
|
|
|
|
{
|
|
|
|
|
|
track = &ctx->trackEntries[i];
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(track != NULL)
|
|
|
|
|
|
{
|
|
|
|
|
|
ctx->currentTrackType = track->type;
|
|
|
|
|
|
|
|
|
|
|
|
if(track->sequence == 0 && track->start == 0 && track->end == 0) ctx->currentTrackType = Data;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
ctx->currentTrackType = Data;
|
|
|
|
|
|
|
|
|
|
|
|
if(ctx->currentTrackType == Audio &&
|
|
|
|
|
|
// JaguarCD stores data in audio tracks. FLAC is too inefficient, we need to use LZMA as data.
|
|
|
|
|
|
(ctx->imageInfo.MediaType == JaguarCD && track->session > 1 ||
|
|
|
|
|
|
// VideoNow stores video in audio tracks, and LZMA works better too.
|
|
|
|
|
|
ctx->imageInfo.MediaType == VideoNow || ctx->imageInfo.MediaType == VideoNowColor ||
|
|
|
|
|
|
ctx->imageInfo.MediaType == VideoNowXp))
|
|
|
|
|
|
ctx->currentTrackType = Data;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
ctx->currentTrackType = Data;
|
|
|
|
|
|
|
2025-09-30 15:11:27 +01:00
|
|
|
|
uint32_t max_buffer_size = (1 << ctx->userDataDdtHeader.dataShift) * ctx->currentBlockHeader.sectorSize;
|
|
|
|
|
|
TRACE("Setting max buffer size to %u bytes", max_buffer_size);
|
2025-08-14 00:38:28 +01:00
|
|
|
|
|
|
|
|
|
|
TRACE("Allocating memory for writing buffer");
|
2025-09-30 15:11:27 +01:00
|
|
|
|
ctx->writingBuffer = (uint8_t *)calloc(1, max_buffer_size);
|
2025-08-14 00:38:28 +01:00
|
|
|
|
if(ctx->writingBuffer == NULL)
|
|
|
|
|
|
{
|
|
|
|
|
|
FATAL("Could not allocate memory");
|
|
|
|
|
|
|
|
|
|
|
|
TRACE("Exiting aaruf_write_sector() = AARUF_ERROR_NOT_ENOUGH_MEMORY");
|
|
|
|
|
|
return AARUF_ERROR_NOT_ENOUGH_MEMORY;
|
|
|
|
|
|
}
|
2025-08-13 16:17:45 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-14 00:38:28 +01:00
|
|
|
|
TRACE("Copying data to writing buffer at position %zu", ctx->writingBufferPosition);
|
2025-09-29 14:47:09 +01:00
|
|
|
|
memcpy(ctx->writingBuffer + ctx->writingBufferPosition, data, length);
|
2025-08-14 00:38:28 +01:00
|
|
|
|
TRACE("Advancing writing buffer position to %zu", ctx->writingBufferPosition + length);
|
2025-08-13 16:17:45 +01:00
|
|
|
|
ctx->writingBufferPosition += length;
|
2025-08-14 00:38:28 +01:00
|
|
|
|
TRACE("Advancing current block offset to %zu", ctx->currentBlockOffset + 1);
|
2025-08-13 16:17:45 +01:00
|
|
|
|
ctx->currentBlockOffset++;
|
|
|
|
|
|
|
2025-08-14 00:38:28 +01:00
|
|
|
|
TRACE("Exiting aaruf_write_sector() = AARUF_STATUS_OK");
|
2025-08-13 16:17:45 +01:00
|
|
|
|
return AARUF_STATUS_OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-03 17:46:40 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief Writes a full ("long") raw sector (2352 bytes) from optical media, splitting and validating structure.
|
|
|
|
|
|
*
|
|
|
|
|
|
* This function is specialized for raw CD sector ingestion when the caller provides the complete 2352-byte
|
|
|
|
|
|
* (or derived) raw sector including synchronization pattern, header (prefix), user data area and suffix
|
|
|
|
|
|
* (EDC/ECC / parity depending on the mode). It supports:
|
|
|
|
|
|
* - Audio (2352 bytes of PCM) and Data raw sectors which are simply forwarded to aaruf_write_sector().
|
|
|
|
|
|
* - CD Mode 1 sectors (sync + header + 2048 user bytes + EDC + ECC P + ECC Q).
|
|
|
|
|
|
* - CD Mode 2 (Form 1, Form 2 and Formless) sectors, handling sub-headers, EDC, and ECC as appropriate.
|
|
|
|
|
|
*
|
|
|
|
|
|
* For each sector, the function:
|
|
|
|
|
|
* 1. Locates the track definition covering the provided logical sector (LBA) and derives its type.
|
|
|
|
|
|
* 2. Validates input length (must be exactly 2352 for optical raw sectors at present).
|
|
|
|
|
|
* 3. Performs rewind detection: if sectors are written out of strictly increasing order, ongoing hash
|
|
|
|
|
|
* calculations (MD5, SHA1, SHA256, SpamSum, BLAKE3) are disabled to prevent incorrect streaming digests.
|
|
|
|
|
|
* 4. Optionally updates hash digests if still enabled and the sector lies within the user range (i.e. not
|
|
|
|
|
|
* negative / overflow) – this is performed on the full raw content for long sectors.
|
|
|
|
|
|
* 5. Splits the sector into prefix, user data, and suffix portions depending on mode and validates:
|
|
|
|
|
|
* - Prefix (sync + header) timing/address fields (MM:SS:FF → LBA) and mode byte.
|
|
|
|
|
|
* - For Mode 1: checks prefix conformity and ECC / EDC correctness via helper routines.
|
|
|
|
|
|
* - For Mode 2: distinguishes Form 1 vs Form 2 (bit flags), validates ECC (Form 1) and EDC (both forms),
|
|
|
|
|
|
* and extracts sub-header (8 bytes) for separate storage.
|
|
|
|
|
|
* 6. Stores anomalous (non-conforming or errored) prefix/suffix fragments into dynamically growing buffers
|
|
|
|
|
|
* (sectorPrefixBuffer / sectorSuffixBuffer) and records miniature DDT entries (sectorPrefixDdtMini /
|
|
|
|
|
|
* sectorSuffixDdtMini) with offsets and status bits. Correct standard patterns are recorded without
|
|
|
|
|
|
* copying (status code only) to save space.
|
|
|
|
|
|
* 7. Writes only the user data portion (2048 for Mode 1 & Mode 2 Form 1, 2324 for Mode 2 Form 2, 2352 for
|
|
|
|
|
|
* audio or for already treated Data) to the standard user data path by delegating to aaruf_write_sector(),
|
|
|
|
|
|
* passing an appropriate derived sector status (e.g. SectorStatusMode1Correct, SectorStatusMode2Form1Ok,
|
|
|
|
|
|
* SectorStatusErrored, etc.).
|
|
|
|
|
|
*
|
|
|
|
|
|
* Deduplication: Long sector handling itself does not directly hash for deduplication; dedupe is applied when
|
|
|
|
|
|
* aaruf_write_sector() is invoked for the extracted user data segment.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Memory allocation strategy:
|
|
|
|
|
|
* - Mini DDT arrays (prefix/suffix) are lazily allocated on first need sized for the total addressable sector
|
|
|
|
|
|
* span (negative + user + overflow).
|
|
|
|
|
|
* - Prefix (16-byte units) and suffix buffers (288-byte units; or smaller copies for Form 2 EDC) grow by
|
|
|
|
|
|
* doubling when capacity would be exceeded.
|
|
|
|
|
|
* - Mode 2 sub-header storage (8 bytes per sector) is also lazily allocated.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Address normalization: Internally a corrected index (corrected_sector_address) is computed by offsetting the
|
|
|
|
|
|
* raw logical sector number with the negative-region size to provide a linear index across negative, user and
|
|
|
|
|
|
* overflow regions.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Sector status encoding (high nibble of 16-bit mini DDT entries):
|
|
|
|
|
|
* - SectorStatusMode1Correct / SectorStatusMode2Form1Ok / SectorStatusMode2Form2Ok / ... mark validated content.
|
|
|
|
|
|
* - SectorStatusErrored marks stored anomalous fragments whose data was copied into side buffers.
|
|
|
|
|
|
* - SectorStatusNotDumped marks all-zero (empty) raw sectors treated as not dumped.
|
|
|
|
|
|
* - Additional specific codes differentiate Mode 2 Form 2 without CRC vs with correct CRC, etc.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Rewind side effects: Once a rewind is detected (writing an LBA <= previously written), all on-the-fly hash
|
|
|
|
|
|
* computations are disabled permanently for the session in order to maintain integrity guarantees of the
|
|
|
|
|
|
* produced digest values.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Limitations / TODO:
|
|
|
|
|
|
* - BlockMedia (non-optical) handling is currently unimplemented and returns AARUF_ERROR_INCORRECT_MEDIA_TYPE.
|
|
|
|
|
|
* - Only 2352-byte raw sectors are accepted; other raw lengths (e.g. 2448 with subchannel) are not handled here.
|
|
|
|
|
|
* - Compression for block writing is not yet implemented (mirrors limitations of aaruf_write_sector()).
|
|
|
|
|
|
*
|
|
|
|
|
|
* Thread safety: This function is not thread-safe. The context carries mutable shared buffers and state.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Error handling model: On encountering an error (allocation failure, bounds, incorrect size, media type) the
|
|
|
|
|
|
* function logs a fatal message via FATAL() and returns immediately with an appropriate negative error code.
|
|
|
|
|
|
* Partial allocations made earlier in the same call are not rolled back beyond what standard free-on-close does.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Preconditions:
|
|
|
|
|
|
* - context is a valid aaruformatContext with magic == AARU_MAGIC.
|
|
|
|
|
|
* - Image opened for writing (isWriting == true).
|
|
|
|
|
|
* - sector_address within allowed negative / user / overflow ranges depending on 'negative'.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Postconditions on success:
|
|
|
|
|
|
* - User data DDT updated (indirectly via aaruf_write_sector()).
|
|
|
|
|
|
* - For Mode 1 / Mode 2 sectors: prefix/suffix / sub-header metadata structures updated accordingly.
|
|
|
|
|
|
* - User data portion appended (buffered) into current block (or new block created) via aaruf_write_sector().
|
|
|
|
|
|
* - Hash contexts updated unless rewind occurred.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param context Pointer to the aaruformat context.
|
|
|
|
|
|
* @param sector_address Logical Block Address (LBA) for the raw sector (0-based user LBA,
|
|
|
|
|
|
* can include negative region when 'negative' is true).
|
|
|
|
|
|
* @param negative true if sector_address refers to the negative (pre-gap) region; false for user/overflow.
|
|
|
|
|
|
* @param data Pointer to 2352-byte raw sector buffer (sync+header+userdata+EDC/ECC) or audio PCM.
|
|
|
|
|
|
* @param sector_status Initial sector status hint provided by caller (may be overridden for derived writing call).
|
|
|
|
|
|
* @param length Length in bytes of the provided raw buffer. Must be exactly 2352 for optical sectors.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @return One of:
|
|
|
|
|
|
* @retval AARUF_STATUS_OK Sector processed and (user data portion) queued/written successfully.
|
|
|
|
|
|
* @retval AARUF_ERROR_NOT_AARUFORMAT Invalid or NULL context / magic mismatch.
|
|
|
|
|
|
* @retval AARUF_READ_ONLY Image opened read-only.
|
|
|
|
|
|
* @retval AARUF_ERROR_SECTOR_OUT_OF_BOUNDS sector_address outside negative/user/overflow bounds.
|
|
|
|
|
|
* @retval AARUF_ERROR_INCORRECT_DATA_SIZE length != 2352 for optical disc long sector ingestion.
|
|
|
|
|
|
* @retval AARUF_ERROR_NOT_ENOUGH_MEMORY Failed to allocate or grow any required buffer (mini DDT,
|
|
|
|
|
|
* prefix, suffix, sub-headers).
|
|
|
|
|
|
* @retval AARUF_ERROR_INCORRECT_MEDIA_TYPE Media type unsupported for long sector writes (non OpticalDisc).
|
|
|
|
|
|
* @retval AARUF_ERROR_CANNOT_SET_DDT_ENTRY Propagated from underlying aaruf_write_sector() when DDT update fails.
|
|
|
|
|
|
* @retval AARUF_ERROR_CANNOT_WRITE_BLOCK_HEADER Propagated from block flush inside aaruf_write_sector().
|
|
|
|
|
|
* @retval AARUF_ERROR_CANNOT_WRITE_BLOCK_DATA Propagated from block flush inside aaruf_write_sector().
|
|
|
|
|
|
*
|
|
|
|
|
|
* @note The function returns immediately after delegating to aaruf_write_sector() for user data; any status
|
|
|
|
|
|
* described there may propagate. That function performs deduplication and block finalization.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @warning Do not mix calls to aaruf_write_sector() and aaruf_write_sector_long() with out-of-order addresses
|
|
|
|
|
|
* if you rely on calculated digests; rewind will disable digest updates irrevocably.
|
|
|
|
|
|
*/
|
|
|
|
|
|
int32_t aaruf_write_sector_long(void *context, uint64_t sector_address, bool negative, const uint8_t *data,
|
|
|
|
|
|
uint8_t sector_status, uint32_t length)
|
|
|
|
|
|
{
|
|
|
|
|
|
TRACE("Entering aaruf_write_sector_long(%p, %" PRIu64 ", %d, %p, %u, %u)", context, sector_address, negative, data,
|
|
|
|
|
|
sector_status, length);
|
|
|
|
|
|
|
|
|
|
|
|
// Check context is correct AaruFormat context
|
|
|
|
|
|
if(context == NULL)
|
|
|
|
|
|
{
|
|
|
|
|
|
FATAL("Invalid context");
|
|
|
|
|
|
|
|
|
|
|
|
TRACE("Exiting aaruf_write_sector() = AARUF_ERROR_NOT_AARUFORMAT");
|
|
|
|
|
|
return AARUF_ERROR_NOT_AARUFORMAT;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
aaruformatContext *ctx = context;
|
|
|
|
|
|
|
|
|
|
|
|
// Not a libaaruformat context
|
|
|
|
|
|
if(ctx->magic != AARU_MAGIC)
|
|
|
|
|
|
{
|
|
|
|
|
|
FATAL("Invalid context");
|
|
|
|
|
|
|
|
|
|
|
|
TRACE("Exiting aaruf_write_sector() = AARUF_ERROR_NOT_AARUFORMAT");
|
|
|
|
|
|
return AARUF_ERROR_NOT_AARUFORMAT;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Check we are writing
|
|
|
|
|
|
if(!ctx->isWriting)
|
|
|
|
|
|
{
|
|
|
|
|
|
FATAL("Trying to write a read-only image");
|
|
|
|
|
|
|
|
|
|
|
|
TRACE("Exiting aaruf_write_sector() = AARUF_READ_ONLY");
|
|
|
|
|
|
return AARUF_READ_ONLY;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(negative && sector_address > ctx->userDataDdtHeader.negative - 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
FATAL("Sector address out of bounds");
|
|
|
|
|
|
|
|
|
|
|
|
TRACE("Exiting aaruf_write_sector() = AARUF_ERROR_SECTOR_OUT_OF_BOUNDS");
|
|
|
|
|
|
return AARUF_ERROR_SECTOR_OUT_OF_BOUNDS;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(!negative && sector_address > ctx->imageInfo.Sectors + ctx->userDataDdtHeader.overflow - 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
FATAL("Sector address out of bounds");
|
|
|
|
|
|
|
|
|
|
|
|
TRACE("Exiting aaruf_write_sector() = AARUF_ERROR_SECTOR_OUT_OF_BOUNDS");
|
|
|
|
|
|
return AARUF_ERROR_SECTOR_OUT_OF_BOUNDS;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
switch(ctx->imageInfo.XmlMediaType)
|
|
|
|
|
|
{
|
|
|
|
|
|
case OpticalDisc:
|
|
|
|
|
|
TrackEntry track = {0};
|
|
|
|
|
|
|
|
|
|
|
|
for(int i = 0; i < ctx->tracksHeader.entries; i++)
|
|
|
|
|
|
if(sector_address >= ctx->trackEntries[i].start && sector_address <= ctx->trackEntries[i].end)
|
|
|
|
|
|
{
|
|
|
|
|
|
track = ctx->trackEntries[i];
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(track.sequence == 0 && track.start == 0 && track.end == 0) track.type = Data;
|
|
|
|
|
|
|
|
|
|
|
|
if(length != 2352)
|
|
|
|
|
|
{
|
|
|
|
|
|
FATAL("Incorrect sector size");
|
|
|
|
|
|
TRACE("Exiting aaruf_write_sector() = AARUF_ERROR_INCORRECT_DATA_SIZE");
|
|
|
|
|
|
return AARUF_ERROR_INCORRECT_DATA_SIZE;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ctx->writingLong = true;
|
|
|
|
|
|
|
|
|
|
|
|
if(!ctx->rewinded)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(sector_address <= ctx->last_written_block)
|
|
|
|
|
|
{
|
|
|
|
|
|
TRACE("Rewinded");
|
|
|
|
|
|
ctx->rewinded = true;
|
|
|
|
|
|
|
|
|
|
|
|
// Disable MD5 calculation
|
|
|
|
|
|
if(ctx->calculating_md5) ctx->calculating_md5 = false;
|
|
|
|
|
|
// Disable SHA1 calculation
|
|
|
|
|
|
if(ctx->calculating_sha1) ctx->calculating_sha1 = false;
|
|
|
|
|
|
// Disable SHA256 calculation
|
|
|
|
|
|
if(ctx->calculating_sha256) ctx->calculating_sha256 = false;
|
|
|
|
|
|
// Disable SpamSum calculation
|
|
|
|
|
|
if(ctx->calculating_spamsum) ctx->calculating_spamsum = false;
|
|
|
|
|
|
// Disable BLAKE3 calculation
|
|
|
|
|
|
if(ctx->calculating_blake3) ctx->calculating_blake3 = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
ctx->last_written_block = sector_address;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Calculate MD5 on-the-fly if requested and sector is within user sectors (not negative or overflow)
|
|
|
|
|
|
if(ctx->calculating_md5 && !negative && sector_address <= ctx->imageInfo.Sectors)
|
|
|
|
|
|
aaruf_md5_update(&ctx->md5_context, data, length);
|
|
|
|
|
|
// Calculate SHA1 on-the-fly if requested and sector is within user sectors (not negative or overflow)
|
|
|
|
|
|
if(ctx->calculating_sha1 && !negative && sector_address <= ctx->imageInfo.Sectors)
|
|
|
|
|
|
aaruf_sha1_update(&ctx->sha1_context, data, length);
|
|
|
|
|
|
// Calculate SHA256 on-the-fly if requested and sector is within user sectors (not negative or overflow)
|
|
|
|
|
|
if(ctx->calculating_sha256 && !negative && sector_address <= ctx->imageInfo.Sectors)
|
|
|
|
|
|
aaruf_sha256_update(&ctx->sha256_context, data, length);
|
|
|
|
|
|
// Calculate SpamSum on-the-fly if requested and sector is within user sectors (not negative or overflow)
|
|
|
|
|
|
if(ctx->calculating_sha256 && !negative && sector_address <= ctx->imageInfo.Sectors)
|
|
|
|
|
|
aaruf_spamsum_update(ctx->spamsum_context, data, length);
|
|
|
|
|
|
// Calculate BLAKE3 on-the-fly if requested and sector is within user sectors (not negative or overflow)
|
|
|
|
|
|
if(ctx->calculating_blake3 && !negative && sector_address <= ctx->imageInfo.Sectors)
|
|
|
|
|
|
blake3_hasher_update(ctx->blake3_context, data, length);
|
|
|
|
|
|
|
|
|
|
|
|
bool prefix_correct;
|
|
|
|
|
|
|
|
|
|
|
|
uint64_t corrected_sector_address = sector_address;
|
|
|
|
|
|
|
|
|
|
|
|
// Calculate positive or negative sector
|
|
|
|
|
|
if(negative)
|
|
|
|
|
|
corrected_sector_address -= ctx->userDataDdtHeader.negative;
|
|
|
|
|
|
else
|
|
|
|
|
|
corrected_sector_address += ctx->userDataDdtHeader.negative;
|
|
|
|
|
|
|
|
|
|
|
|
// Split raw cd sector data in prefix (sync, header), user data and suffix (edc, ecc p, ecc q)
|
|
|
|
|
|
switch(track.type)
|
|
|
|
|
|
{
|
|
|
|
|
|
case Audio:
|
|
|
|
|
|
case Data:
|
|
|
|
|
|
return aaruf_write_sector(context, sector_address, negative, data, sector_status, length);
|
|
|
|
|
|
case CdMode1:
|
|
|
|
|
|
|
|
|
|
|
|
// If we do not have a DDT V2 for sector prefix, create one
|
|
|
|
|
|
if(ctx->sectorPrefixDdtMini == NULL)
|
|
|
|
|
|
{
|
|
|
|
|
|
ctx->sectorPrefixDdtMini =
|
|
|
|
|
|
calloc(1, sizeof(uint16_t) * (ctx->userDataDdtHeader.negative + ctx->imageInfo.Sectors +
|
|
|
|
|
|
ctx->userDataDdtHeader.overflow));
|
|
|
|
|
|
|
|
|
|
|
|
if(ctx->sectorPrefixDdtMini == NULL)
|
|
|
|
|
|
{
|
|
|
|
|
|
FATAL("Could not allocate memory for CD sector prefix DDT");
|
|
|
|
|
|
|
|
|
|
|
|
TRACE("Exiting aaruf_write_sector() = AARUF_ERROR_NOT_ENOUGH_MEMORY");
|
|
|
|
|
|
return AARUF_ERROR_NOT_ENOUGH_MEMORY;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// If we do not have a DDT V2 for sector suffix, create one
|
|
|
|
|
|
if(ctx->sectorSuffixDdtMini == NULL)
|
|
|
|
|
|
{
|
|
|
|
|
|
ctx->sectorSuffixDdtMini =
|
|
|
|
|
|
calloc(1, sizeof(uint16_t) * (ctx->userDataDdtHeader.negative + ctx->imageInfo.Sectors +
|
|
|
|
|
|
ctx->userDataDdtHeader.overflow));
|
|
|
|
|
|
|
|
|
|
|
|
if(ctx->sectorSuffixDdtMini == NULL)
|
|
|
|
|
|
{
|
|
|
|
|
|
FATAL("Could not allocate memory for CD sector prefix DDT");
|
|
|
|
|
|
|
|
|
|
|
|
TRACE("Exiting aaruf_write_sector() = AARUF_ERROR_NOT_ENOUGH_MEMORY");
|
|
|
|
|
|
return AARUF_ERROR_NOT_ENOUGH_MEMORY;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-03 19:53:16 +01:00
|
|
|
|
if(ctx->sector_prefix == NULL)
|
2025-10-03 17:46:40 +01:00
|
|
|
|
{
|
2025-10-03 19:53:16 +01:00
|
|
|
|
ctx->sector_prefix_length = 16 * (ctx->userDataDdtHeader.negative + ctx->imageInfo.Sectors +
|
|
|
|
|
|
ctx->userDataDdtHeader.overflow);
|
|
|
|
|
|
ctx->sector_prefix = malloc(ctx->sector_prefix_length);
|
2025-10-03 17:46:40 +01:00
|
|
|
|
|
2025-10-03 19:53:16 +01:00
|
|
|
|
if(ctx->sector_prefix == NULL)
|
2025-10-03 17:46:40 +01:00
|
|
|
|
{
|
|
|
|
|
|
FATAL("Could not allocate memory for CD sector prefix buffer");
|
|
|
|
|
|
|
|
|
|
|
|
TRACE("Exiting aaruf_write_sector() = AARUF_ERROR_NOT_ENOUGH_MEMORY");
|
|
|
|
|
|
return AARUF_ERROR_NOT_ENOUGH_MEMORY;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(ctx->sectorSuffixBuffer == NULL)
|
|
|
|
|
|
{
|
|
|
|
|
|
ctx->sectorSuffixBufferLength =
|
|
|
|
|
|
288 * (ctx->userDataDdtHeader.negative + ctx->imageInfo.Sectors +
|
|
|
|
|
|
ctx->userDataDdtHeader.overflow);
|
|
|
|
|
|
ctx->sectorSuffixBuffer = malloc(ctx->sectorSuffixBufferLength);
|
|
|
|
|
|
|
|
|
|
|
|
if(ctx->sectorSuffixBuffer == NULL)
|
|
|
|
|
|
{
|
|
|
|
|
|
FATAL("Could not allocate memory for CD sector suffix buffer");
|
|
|
|
|
|
|
|
|
|
|
|
TRACE("Exiting aaruf_write_sector() = AARUF_ERROR_NOT_ENOUGH_MEMORY");
|
|
|
|
|
|
return AARUF_ERROR_NOT_ENOUGH_MEMORY;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool empty = true;
|
|
|
|
|
|
|
|
|
|
|
|
for(int i = 0; i < length; i++)
|
|
|
|
|
|
if(data[i] != 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
empty = false;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(empty)
|
|
|
|
|
|
{
|
|
|
|
|
|
ctx->sectorPrefixDdtMini[corrected_sector_address] = SectorStatusNotDumped;
|
|
|
|
|
|
ctx->sectorSuffixDdtMini[corrected_sector_address] = SectorStatusNotDumped;
|
|
|
|
|
|
return aaruf_write_sector(context, sector_address, negative, data + 16, SectorStatusNotDumped,
|
|
|
|
|
|
2048);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
prefix_correct = true;
|
|
|
|
|
|
|
|
|
|
|
|
if(data[0x00] != 0x00 || data[0x01] != 0xFF || data[0x02] != 0xFF || data[0x03] != 0xFF ||
|
|
|
|
|
|
data[0x04] != 0xFF || data[0x05] != 0xFF || data[0x06] != 0xFF || data[0x07] != 0xFF ||
|
|
|
|
|
|
data[0x08] != 0xFF || data[0x09] != 0xFF || data[0x0A] != 0xFF || data[0x0B] != 0x00 ||
|
|
|
|
|
|
data[0x0F] != 0x01)
|
|
|
|
|
|
prefix_correct = false;
|
|
|
|
|
|
|
|
|
|
|
|
if(prefix_correct)
|
|
|
|
|
|
{
|
|
|
|
|
|
const int minute = (data[0x0C] >> 4) * 10 + (data[0x0C] & 0x0F);
|
|
|
|
|
|
const int second = (data[0x0D] >> 4) * 10 + (data[0x0D] & 0x0F);
|
|
|
|
|
|
const int frame = (data[0x0E] >> 4) * 10 + (data[0x0E] & 0x0F);
|
|
|
|
|
|
const int stored_lba = minute * 60 * 75 + second * 75 + frame - 150;
|
|
|
|
|
|
prefix_correct = stored_lba == sector_address;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(prefix_correct)
|
|
|
|
|
|
ctx->sectorPrefixDdtMini[corrected_sector_address] = SectorStatusMode1Correct << 12;
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// Copy CD prefix from data buffer to prefix buffer
|
2025-10-03 19:53:16 +01:00
|
|
|
|
memcpy(ctx->sector_prefix + ctx->sector_prefix_offset, data, 16);
|
|
|
|
|
|
ctx->sectorPrefixDdtMini[corrected_sector_address] = (uint16_t)(ctx->sector_prefix_offset / 16);
|
2025-10-03 17:46:40 +01:00
|
|
|
|
ctx->sectorPrefixDdtMini[corrected_sector_address] |= SectorStatusErrored << 12;
|
2025-10-03 19:53:16 +01:00
|
|
|
|
ctx->sector_prefix_offset += 16;
|
2025-10-03 17:46:40 +01:00
|
|
|
|
|
|
|
|
|
|
// Grow prefix buffer if needed
|
2025-10-03 19:53:16 +01:00
|
|
|
|
if(ctx->sector_prefix_offset >= ctx->sector_prefix_length)
|
2025-10-03 17:46:40 +01:00
|
|
|
|
{
|
2025-10-03 19:53:16 +01:00
|
|
|
|
ctx->sector_prefix_length *= 2;
|
|
|
|
|
|
ctx->sector_prefix = realloc(ctx->sector_prefix, ctx->sector_prefix_length);
|
2025-10-03 17:46:40 +01:00
|
|
|
|
|
2025-10-03 19:53:16 +01:00
|
|
|
|
if(ctx->sector_prefix == NULL)
|
2025-10-03 17:46:40 +01:00
|
|
|
|
{
|
|
|
|
|
|
FATAL("Could not allocate memory for CD sector prefix buffer");
|
|
|
|
|
|
|
|
|
|
|
|
TRACE("Exiting aaruf_write_sector() = AARUF_ERROR_NOT_ENOUGH_MEMORY");
|
|
|
|
|
|
return AARUF_ERROR_NOT_ENOUGH_MEMORY;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const bool suffix_correct = aaruf_ecc_cd_is_suffix_correct(context, data);
|
|
|
|
|
|
|
|
|
|
|
|
if(suffix_correct)
|
|
|
|
|
|
ctx->sectorSuffixDdtMini[corrected_sector_address] = SectorStatusMode1Correct << 12;
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// Copy CD suffix from data buffer to suffix buffer
|
|
|
|
|
|
memcpy(ctx->sectorSuffixBuffer + ctx->sectorSuffixBufferOffset, data + 2064, 288);
|
|
|
|
|
|
ctx->sectorSuffixDdtMini[corrected_sector_address] =
|
|
|
|
|
|
(uint16_t)(ctx->sectorSuffixBufferOffset / 288);
|
|
|
|
|
|
ctx->sectorSuffixDdtMini[corrected_sector_address] |= SectorStatusErrored << 12;
|
|
|
|
|
|
ctx->sectorSuffixBufferOffset += 288;
|
|
|
|
|
|
|
|
|
|
|
|
// Grow suffix buffer if needed
|
|
|
|
|
|
if(ctx->sectorSuffixBufferOffset >= ctx->sectorSuffixBufferLength)
|
|
|
|
|
|
{
|
|
|
|
|
|
ctx->sectorSuffixBufferLength *= 2;
|
|
|
|
|
|
ctx->sectorSuffixBuffer = realloc(ctx->sectorSuffixBuffer, ctx->sectorSuffixBufferLength);
|
|
|
|
|
|
|
|
|
|
|
|
if(ctx->sectorSuffixBuffer == NULL)
|
|
|
|
|
|
{
|
|
|
|
|
|
FATAL("Could not allocate memory for CD sector suffix buffer");
|
|
|
|
|
|
|
|
|
|
|
|
TRACE("Exiting aaruf_write_sector() = AARUF_ERROR_NOT_ENOUGH_MEMORY");
|
|
|
|
|
|
return AARUF_ERROR_NOT_ENOUGH_MEMORY;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return aaruf_write_sector(context, sector_address, negative, data + 16, SectorStatusMode1Correct,
|
|
|
|
|
|
2048);
|
|
|
|
|
|
case CdMode2Form1:
|
|
|
|
|
|
case CdMode2Form2:
|
|
|
|
|
|
case CdMode2Formless:
|
|
|
|
|
|
// If we do not have a DDT V2 for sector prefix, create one
|
|
|
|
|
|
if(ctx->sectorPrefixDdtMini == NULL)
|
|
|
|
|
|
{
|
|
|
|
|
|
ctx->sectorPrefixDdtMini =
|
|
|
|
|
|
calloc(1, sizeof(uint16_t) * (ctx->userDataDdtHeader.negative + ctx->imageInfo.Sectors +
|
|
|
|
|
|
ctx->userDataDdtHeader.overflow));
|
|
|
|
|
|
|
|
|
|
|
|
if(ctx->sectorPrefixDdtMini == NULL)
|
|
|
|
|
|
{
|
|
|
|
|
|
FATAL("Could not allocate memory for CD sector prefix DDT");
|
|
|
|
|
|
|
|
|
|
|
|
TRACE("Exiting aaruf_write_sector() = AARUF_ERROR_NOT_ENOUGH_MEMORY");
|
|
|
|
|
|
return AARUF_ERROR_NOT_ENOUGH_MEMORY;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// If we do not have a DDT V2 for sector suffix, create one
|
|
|
|
|
|
if(ctx->sectorSuffixDdtMini == NULL)
|
|
|
|
|
|
{
|
|
|
|
|
|
ctx->sectorSuffixDdtMini =
|
|
|
|
|
|
calloc(1, sizeof(uint16_t) * (ctx->userDataDdtHeader.negative + ctx->imageInfo.Sectors +
|
|
|
|
|
|
ctx->userDataDdtHeader.overflow));
|
|
|
|
|
|
|
|
|
|
|
|
if(ctx->sectorSuffixDdtMini == NULL)
|
|
|
|
|
|
{
|
|
|
|
|
|
FATAL("Could not allocate memory for CD sector prefix DDT");
|
|
|
|
|
|
|
|
|
|
|
|
TRACE("Exiting aaruf_write_sector() = AARUF_ERROR_NOT_ENOUGH_MEMORY");
|
|
|
|
|
|
return AARUF_ERROR_NOT_ENOUGH_MEMORY;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-03 19:53:16 +01:00
|
|
|
|
if(ctx->sector_prefix == NULL)
|
2025-10-03 17:46:40 +01:00
|
|
|
|
{
|
2025-10-03 19:53:16 +01:00
|
|
|
|
ctx->sector_prefix_length = 16 * (ctx->userDataDdtHeader.negative + ctx->imageInfo.Sectors +
|
|
|
|
|
|
ctx->userDataDdtHeader.overflow);
|
|
|
|
|
|
ctx->sector_prefix = malloc(ctx->sector_prefix_length);
|
2025-10-03 17:46:40 +01:00
|
|
|
|
|
2025-10-03 19:53:16 +01:00
|
|
|
|
if(ctx->sector_prefix == NULL)
|
2025-10-03 17:46:40 +01:00
|
|
|
|
{
|
|
|
|
|
|
FATAL("Could not allocate memory for CD sector prefix buffer");
|
|
|
|
|
|
|
|
|
|
|
|
TRACE("Exiting aaruf_write_sector() = AARUF_ERROR_NOT_ENOUGH_MEMORY");
|
|
|
|
|
|
return AARUF_ERROR_NOT_ENOUGH_MEMORY;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(ctx->sectorSuffixBuffer == NULL)
|
|
|
|
|
|
{
|
|
|
|
|
|
ctx->sectorSuffixBufferLength =
|
|
|
|
|
|
288 * (ctx->userDataDdtHeader.negative + ctx->imageInfo.Sectors +
|
|
|
|
|
|
ctx->userDataDdtHeader.overflow);
|
|
|
|
|
|
ctx->sectorSuffixBuffer = malloc(ctx->sectorSuffixBufferLength);
|
|
|
|
|
|
|
|
|
|
|
|
if(ctx->sectorSuffixBuffer == NULL)
|
|
|
|
|
|
{
|
|
|
|
|
|
FATAL("Could not allocate memory for CD sector suffix buffer");
|
|
|
|
|
|
|
|
|
|
|
|
TRACE("Exiting aaruf_write_sector() = AARUF_ERROR_NOT_ENOUGH_MEMORY");
|
|
|
|
|
|
return AARUF_ERROR_NOT_ENOUGH_MEMORY;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
empty = true;
|
|
|
|
|
|
|
|
|
|
|
|
for(int i = 0; i < length; i++)
|
|
|
|
|
|
if(data[i] != 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
empty = false;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(empty)
|
|
|
|
|
|
{
|
|
|
|
|
|
ctx->sectorPrefixDdtMini[corrected_sector_address] = SectorStatusNotDumped;
|
|
|
|
|
|
ctx->sectorSuffixDdtMini[corrected_sector_address] = SectorStatusNotDumped;
|
|
|
|
|
|
return aaruf_write_sector(context, sector_address, negative, data + 16, SectorStatusNotDumped,
|
|
|
|
|
|
2328);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const bool form2 = (data[18] & 0x20) == 0x20 || (data[22] & 0x20) == 0x20;
|
|
|
|
|
|
|
|
|
|
|
|
prefix_correct = true;
|
|
|
|
|
|
|
|
|
|
|
|
if(data[0x00] != 0x00 || data[0x01] != 0xFF || data[0x02] != 0xFF || data[0x03] != 0xFF ||
|
|
|
|
|
|
data[0x04] != 0xFF || data[0x05] != 0xFF || data[0x06] != 0xFF || data[0x07] != 0xFF ||
|
|
|
|
|
|
data[0x08] != 0xFF || data[0x09] != 0xFF || data[0x0A] != 0xFF || data[0x0B] != 0x00 ||
|
|
|
|
|
|
data[0x0F] != 0x02)
|
|
|
|
|
|
prefix_correct = false;
|
|
|
|
|
|
|
|
|
|
|
|
if(prefix_correct)
|
|
|
|
|
|
{
|
|
|
|
|
|
const int minute = (data[0x0C] >> 4) * 10 + (data[0x0C] & 0x0F);
|
|
|
|
|
|
const int second = (data[0x0D] >> 4) * 10 + (data[0x0D] & 0x0F);
|
|
|
|
|
|
const int frame = (data[0x0E] >> 4) * 10 + (data[0x0E] & 0x0F);
|
|
|
|
|
|
const int stored_lba = minute * 60 * 75 + second * 75 + frame - 150;
|
|
|
|
|
|
prefix_correct = stored_lba == sector_address;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(prefix_correct)
|
|
|
|
|
|
ctx->sectorPrefixDdtMini[corrected_sector_address] =
|
|
|
|
|
|
(form2 ? SectorStatusMode2Form2Ok : SectorStatusMode2Form1Ok) << 12;
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// Copy CD prefix from data buffer to prefix buffer
|
2025-10-03 19:53:16 +01:00
|
|
|
|
memcpy(ctx->sector_prefix + ctx->sector_prefix_offset, data, 16);
|
|
|
|
|
|
ctx->sectorPrefixDdtMini[corrected_sector_address] = (uint16_t)(ctx->sector_prefix_offset / 16);
|
2025-10-03 17:46:40 +01:00
|
|
|
|
ctx->sectorPrefixDdtMini[corrected_sector_address] |= SectorStatusErrored << 12;
|
2025-10-03 19:53:16 +01:00
|
|
|
|
ctx->sector_prefix_offset += 16;
|
2025-10-03 17:46:40 +01:00
|
|
|
|
|
|
|
|
|
|
// Grow prefix buffer if needed
|
2025-10-03 19:53:16 +01:00
|
|
|
|
if(ctx->sector_prefix_offset >= ctx->sector_prefix_length)
|
2025-10-03 17:46:40 +01:00
|
|
|
|
{
|
2025-10-03 19:53:16 +01:00
|
|
|
|
ctx->sector_prefix_length *= 2;
|
|
|
|
|
|
ctx->sector_prefix = realloc(ctx->sector_prefix, ctx->sector_prefix_length);
|
2025-10-03 17:46:40 +01:00
|
|
|
|
|
2025-10-03 19:53:16 +01:00
|
|
|
|
if(ctx->sector_prefix == NULL)
|
2025-10-03 17:46:40 +01:00
|
|
|
|
{
|
|
|
|
|
|
FATAL("Could not allocate memory for CD sector prefix buffer");
|
|
|
|
|
|
|
|
|
|
|
|
TRACE("Exiting aaruf_write_sector() = AARUF_ERROR_NOT_ENOUGH_MEMORY");
|
|
|
|
|
|
return AARUF_ERROR_NOT_ENOUGH_MEMORY;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(ctx->mode2_subheaders == NULL)
|
|
|
|
|
|
{
|
|
|
|
|
|
ctx->mode2_subheaders =
|
|
|
|
|
|
calloc(1, 8 * (ctx->userDataDdtHeader.negative + ctx->imageInfo.Sectors +
|
|
|
|
|
|
ctx->userDataDdtHeader.overflow));
|
|
|
|
|
|
|
|
|
|
|
|
if(ctx->mode2_subheaders == NULL)
|
|
|
|
|
|
{
|
|
|
|
|
|
FATAL("Could not allocate memory for CD mode 2 subheader buffer");
|
|
|
|
|
|
|
|
|
|
|
|
TRACE("Exiting aaruf_write_sector() = AARUF_ERROR_NOT_ENOUGH_MEMORY");
|
|
|
|
|
|
return AARUF_ERROR_NOT_ENOUGH_MEMORY;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(form2)
|
|
|
|
|
|
{
|
|
|
|
|
|
const uint32_t computed_edc = aaruf_edc_cd_compute(context, 0, data, 0x91C, 0x10);
|
|
|
|
|
|
const uint32_t edc = *(data + 0x92C);
|
|
|
|
|
|
const bool correct_edc = computed_edc == edc;
|
|
|
|
|
|
|
|
|
|
|
|
if(correct_edc)
|
|
|
|
|
|
ctx->sectorSuffixDdtMini[corrected_sector_address] = SectorStatusMode2Form2Ok << 12;
|
|
|
|
|
|
else if(edc == 0)
|
|
|
|
|
|
ctx->sectorSuffixDdtMini[corrected_sector_address] = SectorStatusMode2Form2NoCrc << 12;
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// Copy CD suffix from data buffer to suffix buffer
|
|
|
|
|
|
memcpy(ctx->sectorSuffixBuffer + ctx->sectorSuffixBufferOffset, data + 2348, 4);
|
|
|
|
|
|
ctx->sectorSuffixDdtMini[corrected_sector_address] =
|
|
|
|
|
|
(uint16_t)(ctx->sectorSuffixBufferOffset / 288);
|
|
|
|
|
|
ctx->sectorSuffixDdtMini[corrected_sector_address] |= SectorStatusErrored << 12;
|
|
|
|
|
|
ctx->sectorSuffixBufferOffset += 288;
|
|
|
|
|
|
|
|
|
|
|
|
// Grow suffix buffer if needed
|
|
|
|
|
|
if(ctx->sectorSuffixBufferOffset >= ctx->sectorSuffixBufferLength)
|
|
|
|
|
|
{
|
|
|
|
|
|
ctx->sectorSuffixBufferLength *= 2;
|
|
|
|
|
|
ctx->sectorSuffixBuffer =
|
|
|
|
|
|
realloc(ctx->sectorSuffixBuffer, ctx->sectorSuffixBufferLength);
|
|
|
|
|
|
|
|
|
|
|
|
if(ctx->sectorSuffixBuffer == NULL)
|
|
|
|
|
|
{
|
|
|
|
|
|
FATAL("Could not allocate memory for CD sector suffix buffer");
|
|
|
|
|
|
|
|
|
|
|
|
TRACE("Exiting aaruf_write_sector() = AARUF_ERROR_NOT_ENOUGH_MEMORY");
|
|
|
|
|
|
return AARUF_ERROR_NOT_ENOUGH_MEMORY;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Copy subheader from data buffer to subheader buffer
|
|
|
|
|
|
memcpy(ctx->mode2_subheaders + corrected_sector_address * 8, data + 0x10, 8);
|
|
|
|
|
|
return aaruf_write_sector(context, sector_address, negative, data + 24,
|
|
|
|
|
|
edc == 0 ? SectorStatusMode2Form2NoCrc
|
|
|
|
|
|
: correct_edc ? SectorStatusMode2Form2Ok
|
|
|
|
|
|
: SectorStatusErrored,
|
|
|
|
|
|
2324);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const bool correct_ecc = aaruf_ecc_cd_is_suffix_correct_mode2(context, data);
|
|
|
|
|
|
const uint32_t computed_edc = aaruf_edc_cd_compute(context, 0, data, 0x808, 0x10);
|
|
|
|
|
|
const uint32_t edc = *(data + 0x818);
|
|
|
|
|
|
const bool correct_edc = computed_edc == edc;
|
|
|
|
|
|
|
|
|
|
|
|
if(correct_ecc && correct_edc)
|
|
|
|
|
|
ctx->sectorSuffixDdtMini[corrected_sector_address] = SectorStatusMode2Form1Ok << 12;
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// Copy CD suffix from data buffer to suffix buffer
|
|
|
|
|
|
memcpy(ctx->sectorSuffixBuffer + ctx->sectorSuffixBufferOffset, data + 2072, 280);
|
|
|
|
|
|
ctx->sectorSuffixDdtMini[corrected_sector_address] =
|
|
|
|
|
|
(uint16_t)(ctx->sectorSuffixBufferOffset / 288);
|
|
|
|
|
|
ctx->sectorSuffixDdtMini[corrected_sector_address] |= SectorStatusErrored << 12;
|
|
|
|
|
|
ctx->sectorSuffixBufferOffset += 288;
|
|
|
|
|
|
|
|
|
|
|
|
// Grow suffix buffer if needed
|
|
|
|
|
|
if(ctx->sectorSuffixBufferOffset >= ctx->sectorSuffixBufferLength)
|
|
|
|
|
|
{
|
|
|
|
|
|
ctx->sectorSuffixBufferLength *= 2;
|
|
|
|
|
|
ctx->sectorSuffixBuffer = realloc(ctx->sectorSuffixBuffer, ctx->sectorSuffixBufferLength);
|
|
|
|
|
|
|
|
|
|
|
|
if(ctx->sectorSuffixBuffer == NULL)
|
|
|
|
|
|
{
|
|
|
|
|
|
FATAL("Could not allocate memory for CD sector suffix buffer");
|
|
|
|
|
|
|
|
|
|
|
|
TRACE("Exiting aaruf_write_sector() = AARUF_ERROR_NOT_ENOUGH_MEMORY");
|
|
|
|
|
|
return AARUF_ERROR_NOT_ENOUGH_MEMORY;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Copy subheader from data buffer to subheader buffer
|
|
|
|
|
|
memcpy(ctx->mode2_subheaders + corrected_sector_address * 8, data + 0x10, 8);
|
|
|
|
|
|
return aaruf_write_sector(
|
|
|
|
|
|
context, sector_address, negative, data + 24,
|
|
|
|
|
|
correct_edc && correct_ecc ? SectorStatusMode2Form1Ok : SectorStatusErrored, 2048);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
case BlockMedia:
|
|
|
|
|
|
// TODO: Implement
|
|
|
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
|
|
|
TRACE("Exiting aaruf_write_sector() = AARUF_ERROR_INCORRECT_MEDIA_TYPE");
|
|
|
|
|
|
return AARUF_ERROR_INCORRECT_MEDIA_TYPE;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Fallback return when media type branch does not produce a value (satisfy non-void contract)
|
|
|
|
|
|
return AARUF_ERROR_INCORRECT_MEDIA_TYPE;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-13 16:17:45 +01:00
|
|
|
|
int32_t aaruf_close_current_block(aaruformatContext *ctx)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Not a libaaruformat context
|
|
|
|
|
|
if(ctx->magic != AARU_MAGIC) return AARUF_ERROR_NOT_AARUFORMAT;
|
|
|
|
|
|
|
|
|
|
|
|
// Check we are writing
|
|
|
|
|
|
if(!ctx->isWriting) return AARUF_READ_ONLY;
|
|
|
|
|
|
|
|
|
|
|
|
ctx->currentBlockHeader.length = ctx->currentBlockOffset * ctx->currentBlockHeader.sectorSize;
|
2025-09-29 02:59:38 +01:00
|
|
|
|
|
|
|
|
|
|
TRACE("Initializing CRC64 context");
|
|
|
|
|
|
ctx->crc64Context = aaruf_crc64_init();
|
|
|
|
|
|
TRACE("Updating CRC64");
|
|
|
|
|
|
aaruf_crc64_update(ctx->crc64Context, ctx->writingBuffer, ctx->currentBlockHeader.length);
|
2025-08-13 16:17:45 +01:00
|
|
|
|
aaruf_crc64_final(ctx->crc64Context, &ctx->currentBlockHeader.crc64);
|
|
|
|
|
|
|
|
|
|
|
|
switch(ctx->currentBlockHeader.compression)
|
|
|
|
|
|
{
|
|
|
|
|
|
case None:
|
|
|
|
|
|
ctx->currentBlockHeader.cmpCrc64 = ctx->currentBlockHeader.crc64;
|
|
|
|
|
|
ctx->currentBlockHeader.cmpLength = ctx->currentBlockHeader.length;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-28 17:08:33 +01:00
|
|
|
|
// Add to index
|
|
|
|
|
|
TRACE("Adding block to index");
|
2025-09-30 15:11:27 +01:00
|
|
|
|
IndexEntry index_entry;
|
|
|
|
|
|
index_entry.blockType = DataBlock;
|
|
|
|
|
|
index_entry.dataType = UserData;
|
|
|
|
|
|
index_entry.offset = ctx->nextBlockPosition;
|
2025-09-28 17:08:33 +01:00
|
|
|
|
|
2025-09-30 15:11:27 +01:00
|
|
|
|
utarray_push_back(ctx->indexEntries, &index_entry);
|
|
|
|
|
|
TRACE("Block added to index at offset %" PRIu64, index_entry.offset);
|
2025-08-13 16:17:45 +01:00
|
|
|
|
|
|
|
|
|
|
// Write block header to file
|
|
|
|
|
|
|
2025-08-13 20:16:42 +01:00
|
|
|
|
// Move to expected block position
|
|
|
|
|
|
fseek(ctx->imageStream, ctx->nextBlockPosition, SEEK_SET);
|
2025-08-13 16:17:45 +01:00
|
|
|
|
|
|
|
|
|
|
// Write block header
|
|
|
|
|
|
if(fwrite(&ctx->currentBlockHeader, sizeof(BlockHeader), 1, ctx->imageStream) != 1)
|
|
|
|
|
|
return AARUF_ERROR_CANNOT_WRITE_BLOCK_HEADER;
|
|
|
|
|
|
|
|
|
|
|
|
// Write block data
|
|
|
|
|
|
if(fwrite(ctx->writingBuffer, ctx->currentBlockHeader.length, 1, ctx->imageStream) != 1)
|
|
|
|
|
|
return AARUF_ERROR_CANNOT_WRITE_BLOCK_DATA;
|
|
|
|
|
|
|
2025-09-28 18:43:44 +01:00
|
|
|
|
// Update nextBlockPosition to point to the next available aligned position
|
2025-09-30 15:11:27 +01:00
|
|
|
|
uint64_t block_total_size = sizeof(BlockHeader) + ctx->currentBlockHeader.cmpLength;
|
|
|
|
|
|
uint64_t alignment_mask = (1ULL << ctx->userDataDdtHeader.blockAlignmentShift) - 1;
|
2025-10-01 02:54:51 +01:00
|
|
|
|
ctx->nextBlockPosition = ctx->nextBlockPosition + block_total_size + alignment_mask & ~alignment_mask;
|
2025-09-28 18:43:44 +01:00
|
|
|
|
TRACE("Updated nextBlockPosition to %" PRIu64, ctx->nextBlockPosition);
|
|
|
|
|
|
|
2025-08-13 16:17:45 +01:00
|
|
|
|
// Clear values
|
|
|
|
|
|
free(ctx->writingBuffer);
|
|
|
|
|
|
ctx->writingBuffer = NULL;
|
|
|
|
|
|
ctx->currentBlockOffset = 0;
|
|
|
|
|
|
memset(&ctx->currentBlockHeader, 0, sizeof(BlockHeader));
|
|
|
|
|
|
aaruf_crc64_free(ctx->crc64Context);
|
|
|
|
|
|
ctx->writingBufferPosition = 0;
|
|
|
|
|
|
|
|
|
|
|
|
return AARUF_STATUS_OK;
|
|
|
|
|
|
}
|