/*
 * 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 <http://www.gnu.org/licenses/>.
 */

#include <stdlib.h>
#include <string.h>

#include <aaruformat.h>

#include "internal.h"
#include "log.h"
#include "ngcw/lfg.h"
#include "ngcw/ngcw_junk.h"
#include "ngcw/wii_crypto.h"
#include "ps3/ps3_crypto.h"
#include "ps3/ps3_encryption_map.h"
#include "wiiu/wiiu_crypto.h"

/**
 * @brief Reconstruct junk in a Wii group's user data area before re-encryption.
 *
 * The LFG stream for Wii operates in a virtual "user data stream" where each
 * group contributes WII_GROUP_DATA_SIZE (0x7C00) bytes, NOT the full group size.
 * stream_pos = group_idx * WII_GROUP_DATA_SIZE + offset_within_user_data
 * advance = stream_pos % WII_GROUP_SIZE (the 0x8000-aligned block boundary)
 *
 * This matches OBMAFS's reconstruct_group() logic.
 */
static void wii_reconstruct_group_junk(aaruformat_context *ctx, uint64_t phys_group, uint8_t *group_cache)
{
    if(ctx->ngcw_junk_entries == NULL || ctx->ngcw_junk_entry_count == 0) return;

    if(ctx->wii_partition_regions == NULL) return;

    /* Find which partition this group belongs to */
    const WiiPartitionRegion *regions = (const WiiPartitionRegion *)ctx->wii_partition_regions;
    int                       in_part = -1;

    for(uint32_t p = 0; p < ctx->wii_partition_region_count; p++)
    {
        if(phys_group >= regions[p].start_sector && phys_group < regions[p].end_sector)
        {
            in_part = (int)p;
            break;
        }
    }

    if(in_part < 0) return; /* Outside partition — no Wii junk reconstruction needed */

    uint64_t group_idx      = phys_group - regions[in_part].start_sector;
    uint64_t group_disc_off = phys_group * WII_GROUP_SIZE;

    for(uint64_t off = 0; off < WII_GROUP_DATA_SIZE; off += WII_SECTOR_SIZE)
    {
        uint64_t disc_off = group_disc_off + WII_GROUP_HASH_SIZE + off;

        /* Look up this user data sector in the junk map */
        const NgcwJunkEntry *entries = (const NgcwJunkEntry *)ctx->ngcw_junk_entries;
        int                  lo      = 0;
        int                  hi      = (int)ctx->ngcw_junk_entry_count - 1;
        int                  found   = -1;

        while(lo <= hi)
        {
            int      mid       = lo + (hi - lo) / 2;
            uint64_t entry_end = entries[mid].offset + entries[mid].length;

            if(disc_off >= entry_end)
                lo = mid + 1;
            else if(disc_off < entries[mid].offset)
                hi = mid - 1;
            else
            {
                found = mid;
                break;
            }
        }

        if(found < 0) continue; /* Not junk — leave stored data */

        /* Junk found — regenerate using OBMAFS stream formula */
        struct ngc_lfg_ctx lfg;
        uint32_t           seed_copy[NGC_LFG_SEED_SIZE];
        memcpy(seed_copy, entries[found].seed, sizeof(seed_copy));
        ngc_lfg_set_seed(&lfg, seed_copy);

        uint64_t stream_pos = group_idx * WII_GROUP_DATA_SIZE + off;
        size_t   advance    = (size_t)(stream_pos % WII_GROUP_SIZE);

        if(advance > 0)
        {
            uint8_t discard[4096];
            size_t  adv = advance;

            while(adv > 0)
            {
                size_t step = adv > sizeof(discard) ? sizeof(discard) : adv;
                ngc_lfg_get_bytes(&lfg, discard, step);
                adv -= step;
            }
        }

        size_t chunk = WII_GROUP_DATA_SIZE - (size_t)off;

        if(chunk > WII_SECTOR_SIZE) chunk = WII_SECTOR_SIZE;

        ngc_lfg_get_bytes(&lfg, group_cache + WII_GROUP_HASH_SIZE + off, chunk);
    }
}

/**
 * @brief Reads a media tag from the AaruFormat image.
 *
 * Reads the specified media tag from the image and stores it in the provided buffer.
 * Media tags contain metadata information about the storage medium such as disc
 * information, lead-in/lead-out data, manufacturer-specific information, or other
 * medium-specific metadata. This function uses a hash table lookup for efficient
 * tag retrieval and supports buffer size querying when data pointer is NULL.
 *
 * @param context Pointer to the aaruformat context.
 * @param data Pointer to the buffer to store the tag data. Can be NULL to query tag length.
 * @param tag Tag identifier to read (specific to media type and format).
 * @param length Pointer to the length of the buffer on input; updated with actual tag length on output.
 *
 * @return Returns one of the following status codes:
 * @retval AARUF_STATUS_OK (0) Successfully read the media tag. This is returned when:
 *         - The context is valid and properly initialized
 *         - The requested media tag exists in the image's media tag hash table
 *         - The provided buffer is large enough to contain the tag data
 *         - The tag data is successfully copied to the output buffer
 *         - The length parameter is updated with the actual tag data length
 *
 * @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_ERROR_MEDIA_TAG_NOT_PRESENT (-11) The requested media tag does not exist. This occurs when:
 *         - The tag identifier is not found in the image's media tag hash table
 *         - The image was created without the requested metadata
 *         - The tag identifier is not supported for this media type
 *         - The length parameter is set to 0 when this error is returned
 *
 * @retval AARUF_ERROR_BUFFER_TOO_SMALL (-10) The provided buffer is insufficient. This occurs when:
 *         - The data parameter is NULL (used for length querying)
 *         - The buffer length (*length) is smaller than the required tag data length
 *         - The length parameter is updated with the required size for retry
 *
 * @note Buffer Size Querying:
 *       - Pass data as NULL to query the required buffer size without reading data
 *       - The length parameter will be updated with the required size
 *       - This allows proper buffer allocation before the actual read operation
 *
 * @note Media Tag Types:
 *       - Tags are media-type specific (optical disc, floppy disk, hard disk, etc.)
 *       - Common tags include TOC data, lead-in/out, manufacturer data, defect lists
 *       - Tag availability depends on what was preserved during the imaging process
 *
 * @note Hash Table Lookup:
 *       - Uses efficient O(1) hash table lookup for tag retrieval
 *       - Tag identifiers are integer values specific to the media format
 *       - Hash table is populated during image opening from indexed metadata blocks
 *
 * @warning The function performs a direct memory copy operation. Ensure the output
 *          buffer has sufficient space to prevent buffer overflows.
 *
 * @warning Media tag data is stored as-is from the original medium. No format
 *          conversion or validation is performed on the tag content.
 */
AARU_EXPORT int32_t AARU_CALL aaruf_read_media_tag(void *context, uint8_t *data, const int32_t tag, uint32_t *length)
{
    const uint32_t initial_length = length == NULL ? 0U : *length;

    TRACE("Entering aaruf_read_media_tag(%p, %p, %d, %u)", context, data, tag, initial_length);

    mediaTagEntry *item;

    if(context == NULL)
    {
        FATAL("Invalid context");
        TRACE("Exiting aaruf_read_media_tag() = AARUF_ERROR_NOT_AARUFORMAT");
        return AARUF_ERROR_NOT_AARUFORMAT;
    }

    if(length == NULL)
    {
        FATAL("Invalid length pointer");
        TRACE("Exiting aaruf_read_media_tag() = AARUF_ERROR_INCORRECT_DATA_SIZE");
        return AARUF_ERROR_INCORRECT_DATA_SIZE;
    }

    const aaruformat_context *ctx = context;

    // Not a libaaruformat context
    if(ctx->magic != AARU_MAGIC)
    {
        FATAL("Invalid context");
        TRACE("Exiting aaruf_read_media_tag() = AARUF_ERROR_NOT_AARUFORMAT");
        return AARUF_ERROR_NOT_AARUFORMAT;
    }

    TRACE("Finding media tag %d", tag);
    HASH_FIND_INT(ctx->mediaTags, &tag, item);

    if(item == NULL)
    {
        TRACE("Media tag not found");
        *length = 0;

        TRACE("Exiting aaruf_read_media_tag() = AARUF_ERROR_MEDIA_TAG_NOT_PRESENT");
        return AARUF_ERROR_MEDIA_TAG_NOT_PRESENT;
    }

    if(data == NULL || *length < item->length)
    {
        TRACE("Buffer too small for media tag %d, required %u bytes", tag, item->length);
        *length = item->length;

        TRACE("Exiting aaruf_read_media_tag() = AARUF_ERROR_BUFFER_TOO_SMALL");
        return AARUF_ERROR_BUFFER_TOO_SMALL;
    }

    *length = item->length;
    memcpy(data, item->data, item->length);

    TRACE("Media tag %d read successfully, length %u", tag, *length);
    TRACE("Exiting aaruf_read_media_tag() = AARUF_STATUS_OK");
    return AARUF_STATUS_OK;
}

/**
 * @brief Reads a sector from the AaruFormat image.
 *
 * Reads user data from the specified sector address in the image. This function
 * reads only the user data portion of the sector, without any additional metadata
 * or ECC/EDC information. It handles block-based deduplication, compression
 * (LZMA/FLAC), and caching for optimal performance. The function supports both
 * DDT v1 and v2 formats for sector-to-block mapping.
 *
 * @param context Pointer to the aaruformat context.
 * @param sector_address The logical sector address to read from.
 * @param negative Indicates if the sector address is negative.
 * @param data Pointer to buffer where sector data will be stored. Can be NULL to query length.
 * @param length Pointer to variable containing buffer size on input, actual data length on output.
 * @param sector_status Pointer to variable that will receive the sector status flags. Must not be NULL.
 *                      The status indicates the condition of the sector data, such as whether it contains
 *                      errors, was not dumped, or has other quality indicators from the imaging process.
 *
 * @return Returns one of the following status codes:
 * @retval AARUF_STATUS_OK (0) Successfully read the sector data. This is returned when:
 *         - The sector data is successfully retrieved from cache or decompressed from storage
 *         - Block header and data are successfully read and processed
 *         - Decompression (if needed) completes successfully
 *         - The sector data is copied to the output buffer
 *         - The length parameter is updated with the actual sector size
 *
 * @retval AARUF_STATUS_SECTOR_NOT_DUMPED (1) The sector was not dumped during imaging. This occurs when:
 *         - The DDT entry indicates the sector status is SectorStatusNotDumped
 *         - The original imaging process skipped this sector due to read errors
 *         - The length parameter is set to the expected sector size for buffer allocation
 *
 * @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_ERROR_SECTOR_OUT_OF_BOUNDS (-5) The sector address exceeds image bounds. This occurs when:
 *         - sector_address is greater than or equal to ctx->imageInfo.Sectors
 *         - Attempting to read beyond the logical extent of the imaged medium
 *
 * @retval AARUF_ERROR_BUFFER_TOO_SMALL (-10) The provided buffer is insufficient. This occurs when:
 *         - The data parameter is NULL (used for length querying)
 *         - The buffer length (*length) is smaller than the block's sector size
 *         - The length parameter is updated with the required size for retry
 *
 * @retval AARUF_ERROR_NOT_ENOUGH_MEMORY (-9) Memory allocation failed. This occurs when:
 *         - Cannot allocate memory for block header (if not cached)
 *         - Cannot allocate memory for uncompressed block data
 *         - Cannot allocate memory for compressed data buffer (LZMA/FLAC)
 *         - Cannot allocate memory for decompressed block buffer
 *
 * @retval AARUF_ERROR_CANNOT_READ_HEADER (-6) Block header reading failed. This occurs when:
 *         - Cannot read the complete BlockHeader structure from the image stream
 *         - File I/O errors prevent reading header data at the calculated block offset
 *         - Image file corruption or truncation
 *
 * @retval AARUF_ERROR_CANNOT_READ_BLOCK (-7) Block data reading failed. This occurs when:
 *         - Cannot read uncompressed block data from the image stream
 *         - Cannot read LZMA properties from compressed blocks
 *         - Cannot read compressed data from LZMA or FLAC blocks
 *         - File I/O errors during block data access
 *
 * @retval AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK (-17) Decompression failed. This occurs when:
 *         - LZMA decoder returns a non-zero error code during decompression
 *         - FLAC decoder fails to decompress audio data properly
 *         - Decompressed data size doesn't match the expected block length
 *         - Compression algorithm encounters corrupted or invalid compressed data
 *
 * @retval AARUF_ERROR_UNSUPPORTED_COMPRESSION (-8) Unsupported compression algorithm. This occurs when:
 *         - The block header specifies a compression type not supported by this library
 *         - Future compression algorithms not implemented in this version
 *
 * @retval Other error codes may be propagated from DDT decoding functions:
 *         - From decode_ddt_entry_v1(): AARUF_ERROR_NOT_AARUFORMAT (-1)
 *         - From decode_ddt_entry_v2(): AARUF_ERROR_NOT_AARUFORMAT (-1), AARUF_ERROR_CANNOT_READ_BLOCK (-7),
 *           AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK (-17), AARUF_ERROR_INVALID_BLOCK_CRC (-18)
 *
 * @note DDT Processing:
 *       - Automatically selects DDT v1 or v2 decoding based on ctx->ddtVersion
 *       - DDT decoding provides sector offset within block and block file offset
 *       - Handles deduplication where multiple sectors may reference the same block
 *
 * @note Caching Mechanism:
 *       - Block headers are cached for performance (ctx->blockHeaderCache)
 *       - Decompressed block data is cached to avoid repeated decompression (ctx->blockCache)
 *       - Cache hits eliminate file I/O and decompression overhead
 *       - Cache sizes are determined during context initialization
 *
 * @note Compression Support:
 *       - None: Direct reading of uncompressed block data
 *       - LZMA: Industry-standard compression with properties header
 *       - FLAC: Audio-optimized compression for CD audio blocks
 *       - Each compression type has specific decompression requirements and error conditions
 *
 * @note Buffer Size Querying:
 *       - Pass data as NULL to query the required buffer size without reading data
 *       - The length parameter will be updated with the block's sector size
 *       - Useful for dynamic buffer allocation before the actual read operation
 *
 * @warning This function reads only user data. For complete sector data including
 *          metadata and ECC/EDC information, use aaruf_read_sector_long().
 *
 * @warning Memory allocated for caching is managed by the context. Do not free
 *          cached block data as it may be reused by subsequent operations.
 *
 * @warning Sector addresses are zero-based. The maximum valid address is
 *          ctx->imageInfo.Sectors - 1.
 */
AARU_EXPORT int32_t AARU_CALL aaruf_read_sector(void *context, const uint64_t sector_address, bool negative,
                                                uint8_t *data, uint32_t *length, uint8_t *sector_status)
{
    const uint32_t initial_length = length == NULL ? 0U : *length;

    TRACE("Entering aaruf_read_sector(%p, %" PRIu64 ", %d, %p, %u)", context, sector_address, negative, data,
          initial_length);

    aaruformat_context *ctx          = NULL;
    uint64_t            offset       = 0;
    uint64_t            block_offset = 0;
    BlockHeader        *block_header = NULL;
    uint8_t            *block        = NULL;
    size_t              read_bytes   = 0;
    uint8_t             lzma_properties[LZMA_PROPERTIES_LENGTH];
    size_t              lzma_size = 0;
    uint8_t            *cmp_data  = NULL;
    int                 error_no  = 0;
    *sector_status                = SectorStatusNotDumped;

    if(context == NULL)
    {
        FATAL("Invalid context");

        TRACE("Exiting aaruf_read_sector() = AARUF_ERROR_NOT_AARUFORMAT");
        return AARUF_ERROR_NOT_AARUFORMAT;
    }

    ctx = context;

    if(length == NULL)
    {
        FATAL("Invalid length pointer");

        TRACE("Exiting aaruf_read_sector() = AARUF_ERROR_INCORRECT_DATA_SIZE");
        return AARUF_ERROR_INCORRECT_DATA_SIZE;
    }

    // Not a libaaruformat context
    if(ctx->magic != AARU_MAGIC)
    {
        FATAL("Invalid context");

        TRACE("Exiting aaruf_read_sector() = AARUF_ERROR_NOT_AARUFORMAT");
        return AARUF_ERROR_NOT_AARUFORMAT;
    }

    if(negative && sector_address > ctx->user_data_ddt_header.negative)
    {
        FATAL("Sector address out of bounds");

        TRACE("Exiting aaruf_read_sector() = AARUF_ERROR_SECTOR_OUT_OF_BOUNDS");
        return AARUF_ERROR_SECTOR_OUT_OF_BOUNDS;
    }

    if(!negative && sector_address > ctx->image_info.Sectors + ctx->user_data_ddt_header.overflow - 1)
    {
        FATAL("Sector address out of bounds");

        TRACE("Exiting aaruf_read_sector() = AARUF_ERROR_SECTOR_OUT_OF_BOUNDS");
        return AARUF_ERROR_SECTOR_OUT_OF_BOUNDS;
    }

    if(ctx->ddt_version == 1)
    {
        if(negative)
        {
            FATAL("Negative sector addresses not supported in this image");
            return AARUF_ERROR_SECTOR_OUT_OF_BOUNDS;
        }

        error_no = decode_ddt_entry_v1(ctx, sector_address, &offset, &block_offset, sector_status);
    }
    else if(ctx->ddt_version == 2)
        error_no = decode_ddt_entry_v2(ctx, sector_address, negative, &offset, &block_offset, sector_status);

    if(error_no != AARUF_STATUS_OK)
    {
        FATAL("Error %d decoding DDT entry", error_no);

        TRACE("Exiting aaruf_read_sector() = %d", error_no);
        return error_no;
    }

    // Partially written image... as we can't know the real sector size just assume it's common :/
    if(*sector_status == SectorStatusNotDumped)
    {
        *length = ctx->image_info.SectorSize;

        TRACE("Exiting aaruf_read_sector() = AARUF_STATUS_SECTOR_NOT_DUMPED");
        return AARUF_STATUS_SECTOR_NOT_DUMPED;
    }

    // NGCW: SectorStatusGenerable — regenerate junk from seed, no block data exists
    if(*sector_status == SectorStatusGenerable && (ctx->header.mediaType == GOD || ctx->header.mediaType == WOD))
    {
        if(!ctx->ngcw_junk_initialized)
        {
            ngcw_junk_lazy_init(ctx);
            ctx->ngcw_junk_initialized = true;
        }

        *length = ctx->image_info.SectorSize;

        if(data == NULL)
        {
            TRACE("Exiting aaruf_read_sector() = AARUF_ERROR_BUFFER_TOO_SMALL (generable, NULL data)");
            return AARUF_ERROR_BUFFER_TOO_SMALL;
        }

        if(ctx->ngcw_junk_entries != NULL)
        {
            uint64_t disc_offset = sector_address * (uint64_t)ctx->image_info.SectorSize;

            if(ngcw_regenerate_junk_sector((const NgcwJunkEntry *)ctx->ngcw_junk_entries, ctx->ngcw_junk_entry_count,
                                           disc_offset, data, *length) == 0)
            {
                if(ctx->header.mediaType == GOD)
                {
                    *sector_status = SectorStatusDumped;
                    TRACE("Exiting aaruf_read_sector() = AARUF_STATUS_OK (GOD generable)");
                    return AARUF_STATUS_OK;
                }

                // WOD: regenerated junk needs Wii re-encryption.
                // We have the plaintext in the data buffer — do re-encryption now and return.
                if(!ctx->wii_encryption_initialized)
                {
                    wii_lazy_init(ctx);
                    ctx->wii_encryption_initialized = true;
                }

                if(ctx->wii_partition_regions != NULL && ctx->wii_encrypted_group_cache != NULL &&
                   !ctx->wii_building_crypto_block)
                {
                    const uint8_t *part_key = wii_get_sector_key((const WiiPartitionRegion *)ctx->wii_partition_regions,
                                                                 ctx->wii_partition_region_count, sector_address);

                    if(part_key != NULL)
                    {
                        uint64_t phys_group  = sector_address / WII_LOGICAL_PER_GROUP;
                        uint32_t slice_index = (uint32_t)(sector_address % WII_LOGICAL_PER_GROUP);

                        if(!ctx->wii_cache_valid || ctx->wii_cached_physical_group != phys_group)
                        {
                            uint64_t first_logical = phys_group * WII_LOGICAL_PER_GROUP;

                            ctx->wii_building_crypto_block = true;

                            for(uint32_t s = 0; s < WII_LOGICAL_PER_GROUP; s++)
                            {
                                uint64_t logical = first_logical + s;

                                if(logical == sector_address)
                                {
                                    memcpy(ctx->wii_encrypted_group_cache + s * (*length), data, *length);
                                }
                                else
                                {
                                    uint32_t s_len    = *length;
                                    uint8_t  s_status = 0;
                                    int32_t  s_ret    = aaruf_read_sector(context, logical, false,
                                                                          ctx->wii_encrypted_group_cache + s * s_len,
                                                                          &s_len, &s_status);

                                    if(s_ret != AARUF_STATUS_OK)
                                        memset(ctx->wii_encrypted_group_cache + s * (*length), 0, *length);
                                }
                            }

                            ctx->wii_building_crypto_block = false;

                            // Reconstruct junk in user data area
                            wii_reconstruct_group_junk(ctx, phys_group, ctx->wii_encrypted_group_cache);

                            wii_encrypt_group(part_key, ctx->wii_encrypted_group_cache,
                                              ctx->wii_encrypted_group_cache + WII_GROUP_HASH_SIZE,
                                              ctx->wii_encrypted_group_cache);
                            ctx->wii_cached_physical_group = phys_group;
                            ctx->wii_cache_valid           = true;
                        }

                        memcpy(data, ctx->wii_encrypted_group_cache + slice_index * (*length), *length);
                    }
                }

                *sector_status = SectorStatusDumped;
                TRACE("Exiting aaruf_read_sector() = AARUF_STATUS_OK (WOD generable re-encrypted)");
                return AARUF_STATUS_OK;
            }
            else
            {
                // Not found in junk map — zero-fill
                memset(data, 0, *length);
                *sector_status = SectorStatusDumped;
                TRACE("Exiting aaruf_read_sector() = AARUF_STATUS_OK (generable not in map)");
                return AARUF_STATUS_OK;
            }
        }
        else
        {
            // No junk map loaded — zero-fill
            memset(data, 0, *length);
            *sector_status = SectorStatusDumped;
            TRACE("Exiting aaruf_read_sector() = AARUF_STATUS_OK (no junk map)");
            return AARUF_STATUS_OK;
        }
    }

    // Check if block header is cached
    TRACE("Checking if block header is cached");
    block_header = find_in_cache_uint64(&ctx->block_header_cache, block_offset);

    // Read block header
    if(block_header == NULL)
    {
        TRACE("Allocating memory for block header");
        block_header = malloc(sizeof(BlockHeader));
        if(block_header == NULL)
        {
            FATAL("Not enough memory for block header");

            TRACE("Exiting aaruf_read_sector() = AARUF_ERROR_NOT_ENOUGH_MEMORY");
            return AARUF_ERROR_NOT_ENOUGH_MEMORY;
        }

        TRACE("Reading block header");
        if(fseek(ctx->imageStream, block_offset, SEEK_SET) != 0)
        {
            FATAL("Could not seek to block header");
            free(block_header);

            TRACE("Exiting aaruf_read_sector() = AARUF_ERROR_CANNOT_READ_HEADER");
            return AARUF_ERROR_CANNOT_READ_HEADER;
        }

        read_bytes = fread(block_header, 1, sizeof(BlockHeader), ctx->imageStream);

        if(read_bytes != sizeof(BlockHeader))
        {
            FATAL("Error reading block header");
            free(block_header);

            TRACE("Exiting aaruf_read_sector() = AARUF_ERROR_CANNOT_READ_HEADER");
            return AARUF_ERROR_CANNOT_READ_HEADER;
        }

        TRACE("Adding block header to cache");
        add_to_cache_uint64(&ctx->block_header_cache, block_offset, block_header);
    }
    else if(fseek(ctx->imageStream, block_offset + sizeof(BlockHeader), SEEK_SET) != 0)
    {
        FATAL("Could not seek past cached block header");

        TRACE("Exiting aaruf_read_sector() = AARUF_ERROR_CANNOT_READ_HEADER");
        return AARUF_ERROR_CANNOT_READ_HEADER;
    }

    if(data == NULL || *length < block_header->sectorSize)
    {
        TRACE("Buffer too small for sector, required %u bytes", block_header->sectorSize);
        *length = block_header->sectorSize;

        TRACE("Exiting aaruf_read_sector() = AARUF_ERROR_BUFFER_TOO_SMALL");
        return AARUF_ERROR_BUFFER_TOO_SMALL;
    }

    // Check if block is cached
    TRACE("Checking if block is cached");
    block = find_in_cache_uint64(&ctx->block_cache, block_offset);

    if(block != NULL)
    {
        TRACE("Getting data from cache");
        memcpy(data, block + offset * block_header->sectorSize, block_header->sectorSize);
        *length = block_header->sectorSize;

        // PS3 re-encryption: if stored decrypted, re-encrypt for caller
        if(*sector_status == SectorStatusUnencrypted &&
           (ctx->header.mediaType == PS3DVD || ctx->header.mediaType == PS3BD))
        {
            if(!ctx->ps3_encryption_initialized)
            {
                ps3_lazy_init(ctx);
                ctx->ps3_encryption_initialized = true;
            }

            if(ctx->ps3_disc_key != NULL && ctx->ps3_plaintext_regions != NULL &&
               ps3_is_sector_encrypted((const Ps3PlaintextRegion *)ctx->ps3_plaintext_regions,
                                       ctx->ps3_plaintext_region_count, sector_address))
            {
                ps3_encrypt_sector(ctx->ps3_disc_key, sector_address, data, *length);
            }

            *sector_status = SectorStatusDumped;
        }

        // Wii U re-encryption: if stored decrypted, re-encrypt for caller
        if(*sector_status == SectorStatusUnencrypted && ctx->header.mediaType == WUOD &&
           !ctx->wiiu_building_crypto_block)
        {
            if(!ctx->wiiu_encryption_initialized)
            {
                wiiu_lazy_init(ctx);
                ctx->wiiu_encryption_initialized = true;
            }

            if(ctx->wiiu_partition_regions != NULL && ctx->wiiu_encrypted_block_cache != NULL)
            {
                const uint8_t *part_key = wiiu_get_sector_key((const WiiuPartitionRegion *)ctx->wiiu_partition_regions,
                                                              ctx->wiiu_partition_region_count, sector_address);

                if(part_key != NULL)
                {
                    uint64_t phys_sector = sector_address / WIIU_LOGICAL_PER_PHYSICAL;
                    uint32_t slice_index = (uint32_t)(sector_address % WIIU_LOGICAL_PER_PHYSICAL);

                    if(!ctx->wiiu_cache_valid || ctx->wiiu_cached_physical_sector != phys_sector)
                    {
                        uint64_t first_logical = phys_sector * WIIU_LOGICAL_PER_PHYSICAL;

                        // Read all 16 logical sectors via aaruf_read_sector with recursion guard
                        ctx->wiiu_building_crypto_block = true;

                        for(uint32_t s = 0; s < WIIU_LOGICAL_PER_PHYSICAL; s++)
                        {
                            uint64_t logical = first_logical + s;

                            if(logical == sector_address)
                            {
                                // Current sector is already in data buffer
                                memcpy(ctx->wiiu_encrypted_block_cache + s * (*length), data, *length);
                            }
                            else
                            {
                                uint32_t s_len    = *length;
                                uint8_t  s_status = 0;
                                int32_t  s_ret =
                                    aaruf_read_sector(context, logical, false,
                                                      ctx->wiiu_encrypted_block_cache + s * s_len, &s_len, &s_status);

                                if(s_ret != AARUF_STATUS_OK)
                                    memset(ctx->wiiu_encrypted_block_cache + s * (*length), 0, *length);
                            }
                        }

                        ctx->wiiu_building_crypto_block = false;

                        wiiu_encrypt_physical_sector(part_key, ctx->wiiu_encrypted_block_cache,
                                                     WIIU_CRYPTO_SECTOR_SIZE);
                        ctx->wiiu_cached_physical_sector = phys_sector;
                        ctx->wiiu_cache_valid            = true;
                    }

                    memcpy(data, ctx->wiiu_encrypted_block_cache + slice_index * (*length), *length);
                }
            }

            *sector_status = SectorStatusDumped;
        }

        // Wii re-encryption: if stored decrypted, re-encrypt for caller
        if(*sector_status == SectorStatusUnencrypted && ctx->header.mediaType == WOD && !ctx->wii_building_crypto_block)
        {
            if(!ctx->wii_encryption_initialized)
            {
                wii_lazy_init(ctx);
                ctx->wii_encryption_initialized = true;
            }

            if(!ctx->ngcw_junk_initialized)
            {
                ngcw_junk_lazy_init(ctx);
                ctx->ngcw_junk_initialized = true;
            }

            if(ctx->wii_partition_regions != NULL && ctx->wii_encrypted_group_cache != NULL)
            {
                const uint8_t *part_key = wii_get_sector_key((const WiiPartitionRegion *)ctx->wii_partition_regions,
                                                             ctx->wii_partition_region_count, sector_address);

                if(part_key != NULL)
                {
                    uint64_t phys_group  = sector_address / WII_LOGICAL_PER_GROUP;
                    uint32_t slice_index = (uint32_t)(sector_address % WII_LOGICAL_PER_GROUP);

                    if(!ctx->wii_cache_valid || ctx->wii_cached_physical_group != phys_group)
                    {
                        uint64_t first_logical = phys_group * WII_LOGICAL_PER_GROUP;

                        ctx->wii_building_crypto_block = true;

                        for(uint32_t s = 0; s < WII_LOGICAL_PER_GROUP; s++)
                        {
                            uint64_t logical = first_logical + s;

                            if(logical == sector_address)
                            {
                                memcpy(ctx->wii_encrypted_group_cache + s * (*length), data, *length);
                            }
                            else
                            {
                                uint32_t s_len    = *length;
                                uint8_t  s_status = 0;
                                int32_t  s_ret =
                                    aaruf_read_sector(context, logical, false,
                                                      ctx->wii_encrypted_group_cache + s * s_len, &s_len, &s_status);

                                if(s_ret != AARUF_STATUS_OK)
                                    memset(ctx->wii_encrypted_group_cache + s * (*length), 0, *length);
                            }
                        }

                        ctx->wii_building_crypto_block = false;

                        // Reconstruct junk in user data area
                        wii_reconstruct_group_junk(ctx, phys_group, ctx->wii_encrypted_group_cache);

                        wii_encrypt_group(part_key, ctx->wii_encrypted_group_cache,
                                          ctx->wii_encrypted_group_cache + WII_GROUP_HASH_SIZE,
                                          ctx->wii_encrypted_group_cache);
                        ctx->wii_cached_physical_group = phys_group;
                        ctx->wii_cache_valid           = true;
                    }

                    memcpy(data, ctx->wii_encrypted_group_cache + slice_index * (*length), *length);
                }
            }

            *sector_status = SectorStatusDumped;
        }

        TRACE("Exiting aaruf_read_sector() = AARUF_STATUS_OK");
        return AARUF_STATUS_OK;
    }

    // Decompress block
    switch(block_header->compression)
    {
        case kCompressionNone:
            TRACE("Allocating memory for block");
            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("Reading block into memory");
            read_bytes = fread(block, 1, block_header->length, ctx->imageStream);

            if(read_bytes != block_header->length)
            {
                FATAL("Could not read block");
                free(block);

                TRACE("Exiting aaruf_read_sector() = AARUF_ERROR_CANNOT_READ_BLOCK");
                return AARUF_ERROR_CANNOT_READ_BLOCK;
            }

            break;
        case kCompressionLzma:
            if(block_header->cmpLength <= LZMA_PROPERTIES_LENGTH || block_header->length == 0)
            {
                FATAL("Invalid LZMA 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;
            }

            lzma_size = block_header->cmpLength - LZMA_PROPERTIES_LENGTH;
            TRACE("Allocating memory for compressed data of size %zu bytes", lzma_size);
            cmp_data = malloc(lzma_size);

            if(cmp_data == NULL)
            {
                FATAL("Cannot allocate memory for block...");

                TRACE("Exiting aaruf_read_sector() = AARUF_ERROR_NOT_ENOUGH_MEMORY");
                return AARUF_ERROR_NOT_ENOUGH_MEMORY;
            }

            TRACE("Allocating memory for block of size %zu bytes", block_header->length);
            block = malloc(block_header->length);
            if(block == NULL)
            {
                FATAL("Cannot allocate memory for block...");
                free(cmp_data);

                TRACE("Exiting aaruf_read_sector() = AARUF_ERROR_NOT_ENOUGH_MEMORY");
                return AARUF_ERROR_NOT_ENOUGH_MEMORY;
            }

            read_bytes = fread(lzma_properties, 1, LZMA_PROPERTIES_LENGTH, ctx->imageStream);

            if(read_bytes != LZMA_PROPERTIES_LENGTH)
            {
                FATAL("Could not read LZMA properties...");
                free(block);
                free(cmp_data);

                TRACE("Exiting aaruf_read_sector() = AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK");
                return AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK;
            }

            read_bytes = fread(cmp_data, 1, lzma_size, ctx->imageStream);
            if(read_bytes != lzma_size)
            {
                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;
            }

            TRACE("Decompressing block of size %zu bytes", block_header->length);
            read_bytes = block_header->length;
            error_no   = aaruf_lzma_decode_buffer(block, &read_bytes, cmp_data, &lzma_size, lzma_properties,
                                                  LZMA_PROPERTIES_LENGTH);

            if(error_no != 0)
            {
                FATAL("Got error %d from LZMA...", error_no);
                free(cmp_data);
                free(block);

                TRACE("Exiting aaruf_read_sector() = AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK");
                return AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK;
            }

            if(read_bytes != block_header->length)
            {
                FATAL("Error decompressing block, should be {0} bytes but got {1} 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 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);
            cmp_data = malloc(block_header->cmpLength);

            if(cmp_data == NULL)
            {
                FATAL("Cannot allocate memory for block...");

                TRACE("Exiting aaruf_read_sector() = AARUF_ERROR_NOT_ENOUGH_MEMORY");
                return AARUF_ERROR_NOT_ENOUGH_MEMORY;
            }

            TRACE("Allocating memory for block of size %zu bytes", block_header->length);
            block = malloc(block_header->length);
            if(block == NULL)
            {
                FATAL("Cannot allocate memory for block...");
                free(cmp_data);

                TRACE("Exiting aaruf_read_sector() = AARUF_ERROR_NOT_ENOUGH_MEMORY");
                return AARUF_ERROR_NOT_ENOUGH_MEMORY;
            }

            TRACE("Reading compressed data into memory");
            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;
            }

            TRACE("Decompressing block of size %zu bytes", block_header->length);
            read_bytes =
                aaruf_flac_decode_redbook_buffer(block, block_header->length, cmp_data, block_header->cmpLength);

            if(read_bytes != block_header->length)
            {
                FATAL("Error decompressing block, should be {0} bytes but got {1} 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;
        default:
            FATAL("Unsupported compression %d", block_header->compression);
            TRACE("Exiting aaruf_read_sector() = AARUF_ERROR_UNSUPPORTED_COMPRESSION");
            return AARUF_ERROR_UNSUPPORTED_COMPRESSION;
    }

    // Add block to cache
    TRACE("Adding block to cache");
    add_to_cache_uint64(&ctx->block_cache, block_offset, block);

    memcpy(data, block + offset * block_header->sectorSize, block_header->sectorSize);
    *length = block_header->sectorSize;

    // PS3 re-encryption: if stored decrypted, re-encrypt for caller
    if(*sector_status == SectorStatusUnencrypted && (ctx->header.mediaType == PS3DVD || ctx->header.mediaType == PS3BD))
    {
        if(!ctx->ps3_encryption_initialized)
        {
            ps3_lazy_init(ctx);
            ctx->ps3_encryption_initialized = true;
        }

        if(ctx->ps3_disc_key != NULL && ctx->ps3_plaintext_regions != NULL &&
           ps3_is_sector_encrypted((const Ps3PlaintextRegion *)ctx->ps3_plaintext_regions,
                                   ctx->ps3_plaintext_region_count, sector_address))
        {
            ps3_encrypt_sector(ctx->ps3_disc_key, sector_address, data, *length);
        }

        *sector_status = SectorStatusDumped;
    }

    // Wii U re-encryption: if stored decrypted, re-encrypt for caller
    if(*sector_status == SectorStatusUnencrypted && ctx->header.mediaType == WUOD && !ctx->wiiu_building_crypto_block)
    {
        if(!ctx->wiiu_encryption_initialized)
        {
            wiiu_lazy_init(ctx);
            ctx->wiiu_encryption_initialized = true;
        }

        if(ctx->wiiu_partition_regions != NULL && ctx->wiiu_encrypted_block_cache != NULL)
        {
            const uint8_t *part_key = wiiu_get_sector_key((const WiiuPartitionRegion *)ctx->wiiu_partition_regions,
                                                          ctx->wiiu_partition_region_count, sector_address);

            if(part_key != NULL)
            {
                uint64_t phys_sector = sector_address / WIIU_LOGICAL_PER_PHYSICAL;
                uint32_t slice_index = (uint32_t)(sector_address % WIIU_LOGICAL_PER_PHYSICAL);

                if(!ctx->wiiu_cache_valid || ctx->wiiu_cached_physical_sector != phys_sector)
                {
                    uint64_t first_logical = phys_sector * WIIU_LOGICAL_PER_PHYSICAL;

                    // Read all 16 logical sectors via aaruf_read_sector with recursion guard
                    ctx->wiiu_building_crypto_block = true;

                    for(uint32_t s = 0; s < WIIU_LOGICAL_PER_PHYSICAL; s++)
                    {
                        uint64_t logical = first_logical + s;

                        if(logical == sector_address)
                        {
                            // Current sector is already in data buffer
                            memcpy(ctx->wiiu_encrypted_block_cache + s * (*length), data, *length);
                        }
                        else
                        {
                            uint32_t s_len    = *length;
                            uint8_t  s_status = 0;
                            int32_t  s_ret =
                                aaruf_read_sector(context, logical, false, ctx->wiiu_encrypted_block_cache + s * s_len,
                                                  &s_len, &s_status);

                            if(s_ret != AARUF_STATUS_OK)
                                memset(ctx->wiiu_encrypted_block_cache + s * (*length), 0, *length);
                        }
                    }

                    ctx->wiiu_building_crypto_block = false;

                    wiiu_encrypt_physical_sector(part_key, ctx->wiiu_encrypted_block_cache, WIIU_CRYPTO_SECTOR_SIZE);
                    ctx->wiiu_cached_physical_sector = phys_sector;
                    ctx->wiiu_cache_valid            = true;
                }

                memcpy(data, ctx->wiiu_encrypted_block_cache + slice_index * (*length), *length);
            }
        }

        *sector_status = SectorStatusDumped;
    }

    // Wii re-encryption: if stored decrypted, re-encrypt for caller
    if(*sector_status == SectorStatusUnencrypted && ctx->header.mediaType == WOD && !ctx->wii_building_crypto_block)
    {
        if(!ctx->wii_encryption_initialized)
        {
            wii_lazy_init(ctx);
            ctx->wii_encryption_initialized = true;
        }

        if(!ctx->ngcw_junk_initialized)
        {
            ngcw_junk_lazy_init(ctx);
            ctx->ngcw_junk_initialized = true;
        }

        if(ctx->wii_partition_regions != NULL && ctx->wii_encrypted_group_cache != NULL)
        {
            const uint8_t *part_key = wii_get_sector_key((const WiiPartitionRegion *)ctx->wii_partition_regions,
                                                         ctx->wii_partition_region_count, sector_address);

            if(part_key != NULL)
            {
                uint64_t phys_group  = sector_address / WII_LOGICAL_PER_GROUP;
                uint32_t slice_index = (uint32_t)(sector_address % WII_LOGICAL_PER_GROUP);

                if(!ctx->wii_cache_valid || ctx->wii_cached_physical_group != phys_group)
                {
                    uint64_t first_logical = phys_group * WII_LOGICAL_PER_GROUP;

                    ctx->wii_building_crypto_block = true;

                    for(uint32_t s = 0; s < WII_LOGICAL_PER_GROUP; s++)
                    {
                        uint64_t logical = first_logical + s;

                        if(logical == sector_address)
                        {
                            memcpy(ctx->wii_encrypted_group_cache + s * (*length), data, *length);
                        }
                        else
                        {
                            uint32_t s_len    = *length;
                            uint8_t  s_status = 0;
                            int32_t  s_ret    = aaruf_read_sector(
                                context, logical, false, ctx->wii_encrypted_group_cache + s * s_len, &s_len, &s_status);

                            if(s_ret != AARUF_STATUS_OK)
                                memset(ctx->wii_encrypted_group_cache + s * (*length), 0, *length);
                        }
                    }

                    ctx->wii_building_crypto_block = false;

                    // Reconstruct junk in user data area
                    wii_reconstruct_group_junk(ctx, phys_group, ctx->wii_encrypted_group_cache);

                    wii_encrypt_group(part_key, ctx->wii_encrypted_group_cache,
                                      ctx->wii_encrypted_group_cache + WII_GROUP_HASH_SIZE,
                                      ctx->wii_encrypted_group_cache);
                    ctx->wii_cached_physical_group = phys_group;
                    ctx->wii_cache_valid           = true;
                }

                memcpy(data, ctx->wii_encrypted_group_cache + slice_index * (*length), *length);
            }
        }

        *sector_status = SectorStatusDumped;
    }

    TRACE("Exiting aaruf_read_sector() = AARUF_STATUS_OK");
    return AARUF_STATUS_OK;
}

/**
 * @brief Reads a sector from a specific track in the AaruFormat image.
 *
 * Reads user data from the specified sector address within a particular track.
 * This function is specifically designed for optical disc images where sectors
 * are organized by tracks. The sector address is relative to the start of the track.
 * It validates the media type, locates the specified track by sequence number,
 * calculates the absolute sector address, and delegates to aaruf_read_sector().
 *
 * @param context Pointer to the aaruformat context.
 * @param data Pointer to buffer where sector data will be stored.
 * @param sector_address The sector address relative to the track start.
 * @param length Pointer to variable containing buffer size on input, actual data length on output.
 * @param track The track sequence number to read from.
 * @param sector_status Pointer to variable that will receive the sector status flags. Must not be NULL.
 *                      The status indicates the condition of the sector data, such as whether it contains
 *                      errors, was not dumped, or has other quality indicators from the imaging process.
 *
 * @return Returns one of the following status codes:
 * @retval AARUF_STATUS_OK (0) Successfully read the sector data from the specified track. This is returned when:
 *         - The context is valid and the media type is OpticalDisc
 *         - The specified track sequence number exists in the data tracks array
 *         - The underlying aaruf_read_sector() call succeeds
 *         - The sector data is successfully retrieved and copied to the output buffer
 *
 * @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_ERROR_INCORRECT_MEDIA_TYPE (-5) The media is not an optical disc. This occurs when:
 *         - ctx->imageInfo.XmlMediaType is not OpticalDisc
 *         - This function is only applicable to CD, DVD, BD, and other optical disc formats
 *
 * @retval AARUF_ERROR_TRACK_NOT_FOUND (-13) The specified track does not exist. This occurs when:
 *         - No track in ctx->dataTracks[] has a sequence number matching the requested track
 *         - The track may not contain data or may not have been imaged
 *         - Only data tracks are searched; audio-only tracks are not included
 *
 * @retval All other error codes from aaruf_read_sector() may be returned:
 *         - AARUF_STATUS_SECTOR_NOT_DUMPED (1) - Sector was not dumped during imaging
 *         - AARUF_ERROR_SECTOR_OUT_OF_BOUNDS (-5) - Calculated absolute sector address exceeds image bounds
 *         - AARUF_ERROR_BUFFER_TOO_SMALL (-10) - Data buffer is NULL or insufficient size
 *         - AARUF_ERROR_NOT_ENOUGH_MEMORY (-9) - Memory allocation fails during sector reading
 *         - AARUF_ERROR_CANNOT_READ_HEADER (-6) - Block header cannot be read from image stream
 *         - AARUF_ERROR_CANNOT_READ_BLOCK (-7) - Block data cannot be read from image stream
 *         - AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK (-17) - LZMA or FLAC decompression fails
 *         - AARUF_ERROR_UNSUPPORTED_COMPRESSION (-8) - Block uses unsupported compression
 *
 * @note Track-Relative Addressing:
 *       - The sector_address parameter is relative to the start of the specified track
 *       - The function calculates the absolute sector address as: track.start + sector_address
 *       - Track boundaries are defined by track.start and track.end values
 *
 * @note Data Track Filtering:
 *       - Only tracks in the ctx->dataTracks[] array are searched
 *       - Audio-only tracks without data content are not included in this search
 *       - The track sequence number is the logical track number from the disc TOC
 *
 * @note Function Delegation:
 *       - This function is a wrapper that performs track validation and address calculation
 *       - The actual sector reading is performed by aaruf_read_sector()
 *       - All caching, decompression, and DDT processing occurs in the underlying function
 *
 * @warning This function is only applicable to optical disc media types.
 *          Attempting to use it with block media will result in AARUF_ERROR_INCORRECT_MEDIA_TYPE.
 *
 * @warning The sector_address is relative to the track start, not the disc start.
 *          Ensure correct address calculation when working with multi-track discs.
 *
 * @warning Track sequence numbers may not be contiguous. Always verify track
 *          existence before assuming a track number is valid.
 */
AARU_EXPORT int32_t AARU_CALL aaruf_read_track_sector(void *context, uint8_t *data, const uint64_t sector_address,
                                                      uint32_t *length, const uint8_t track, uint8_t *sector_status)
{
    const uint32_t initial_length = length == NULL ? 0U : *length;

    TRACE("Entering aaruf_read_track_sector(%p, %p, %" PRIu64 ", %u, %d)", context, data, sector_address,
          initial_length, track);

    if(context == NULL)
    {
        FATAL("Invalid context");

        TRACE("Exiting aaruf_read_track_sector() = AARUF_ERROR_NOT_AARUFORMAT");
        return AARUF_ERROR_NOT_AARUFORMAT;
    }

    aaruformat_context *ctx = context;

    if(length == NULL)
    {
        FATAL("Invalid length pointer");

        TRACE("Exiting aaruf_read_track_sector() = AARUF_ERROR_INCORRECT_DATA_SIZE");
        return AARUF_ERROR_INCORRECT_DATA_SIZE;
    }

    // Not a libaaruformat context
    if(ctx->magic != AARU_MAGIC)
    {
        FATAL("Invalid context");

        TRACE("Exiting aaruf_read_track_sector() = AARUF_ERROR_NOT_AARUFORMAT");
        return AARUF_ERROR_NOT_AARUFORMAT;
    }

    if(ctx->image_info.MetadataMediaType != OpticalDisc)
    {
        FATAL("Incorrect media type %d, expected OpticalDisc", ctx->image_info.MetadataMediaType);

        TRACE("Exiting aaruf_read_track_sector() = AARUF_ERROR_INCORRECT_MEDIA_TYPE");
        return AARUF_ERROR_INCORRECT_MEDIA_TYPE;
    }

    for(int i = 0; i < ctx->number_of_data_tracks; i++)
        if(ctx->data_tracks[i].sequence == track)
            return aaruf_read_sector(context, ctx->data_tracks[i].start + sector_address, false, data, length,
                                     sector_status);

    TRACE("Track %d not found", track);
    TRACE("Exiting aaruf_read_track_sector() = AARUF_ERROR_TRACK_NOT_FOUND");
    return AARUF_ERROR_TRACK_NOT_FOUND;
}

/**
 * @brief Reads a complete sector with all metadata from the AaruFormat image.
 *
 * Reads the complete sector data including user data, ECC/EDC, subchannel data,
 * and other metadata depending on the media type. For optical discs, this returns
 * a full 2352-byte sector with sync, header, user data, and ECC/EDC. For block
 * media with tags, this includes both the user data and tag information. The function
 * handles complex sector reconstruction including ECC correction and format-specific
 * metadata assembly.
 *
 * @param context Pointer to the aaruformat context.
 * @param sector_address The logical sector address to read from.
 * @param negative Indicates if the sector address is negative.
 * @param data Pointer to buffer where complete sector data will be stored. Can be NULL to query length.
 * @param length Pointer to variable containing buffer size on input, actual data length on output.
 * @param sector_status Pointer to variable that will receive the sector status flags. Must not be NULL.
 *                      The status indicates the condition of the sector data, such as whether it contains
 *                      errors, was not dumped, or has other quality indicators from the imaging process.
 *
 * @return Returns one of the following status codes:
 * @retval AARUF_STATUS_OK (0) Successfully read the complete sector with metadata. This is returned when:
 *         - The sector data is successfully retrieved and reconstructed with all metadata
 *         - For optical discs: sync, header, user data, and ECC/EDC are assembled into 2352 bytes
 *         - For block media: user data and tags are combined according to media type
 *         - ECC reconstruction (if applicable) completes successfully
 *         - All required metadata (prefix/suffix, subheaders, etc.) is available and applied
 *
 * @retval AARUF_STATUS_SECTOR_NOT_DUMPED (1) The sector or metadata was not dumped. This can occur when:
 *         - The underlying sector data was not dumped during imaging
 *         - CD prefix or suffix metadata indicates NotDumped status for the sector
 *         - ECC reconstruction cannot be performed due to missing correction data
 *
 * @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_ERROR_INCORRECT_MEDIA_TYPE (-5) The media type doesn't support long sector reading. This occurs when:
 *         - ctx->imageInfo.XmlMediaType is not OpticalDisc or BlockMedia
 *         - For BlockMedia: the specific media type is not supported (not Apple, Priam, etc.)
 *         - Media types that don't have extended sector formats
 *
 * @retval AARUF_ERROR_BUFFER_TOO_SMALL (-10) The buffer is insufficient for complete sector data. This occurs when:
 *         - The data parameter is NULL (used for length querying)
 *         - For optical discs: buffer length is less than 2352 bytes
 *         - For block media: buffer length is less than (user data size + tag size)
 *         - The length parameter is updated with the required size for retry
 *
 * @retval AARUF_ERROR_NOT_ENOUGH_MEMORY (-9) Memory allocation failed. This occurs when:
 *         - Cannot allocate memory for bare user data during optical disc processing
 *         - Cannot allocate memory for user data during block media processing
 *         - Memory allocation fails in underlying aaruf_read_sector() calls
 *
 * @retval AARUF_ERROR_TRACK_NOT_FOUND (-13) Cannot locate the sector's track. This occurs when:
 *         - For optical discs: the sector address doesn't fall within any data track boundaries
 *         - No track contains the specified sector address (address not in any track.start to track.end range)
 *         - The track list is empty or corrupted
 *
 * @retval AARUF_ERROR_INVALID_TRACK_FORMAT (-14) The track has an unsupported format. This occurs when:
 *         - The track type is not recognized (not Audio, Data, CdMode1, CdMode2*, etc.)
 *         - Internal track format validation fails
 *
 * @retval AARUF_ERROR_REACHED_UNREACHABLE_CODE (-15) Internal logic error. This occurs when:
 *         - Required metadata structures (sectorPrefix, sectorSuffix, etc.) are unexpectedly NULL
 *         - Control flow reaches states that should be impossible with valid image data
 *         - Indicates potential image corruption or library bug
 *
 * @retval All error codes from aaruf_read_sector() may be propagated:
 *         - AARUF_ERROR_SECTOR_OUT_OF_BOUNDS (-5) - Calculated sector address exceeds image bounds
 *         - AARUF_ERROR_CANNOT_READ_HEADER (-6) - Block header cannot be read
 *         - AARUF_ERROR_CANNOT_READ_BLOCK (-7) - Block data cannot be read
 *         - AARUF_ERROR_CANNOT_DECOMPRESS_BLOCK (-17) - Decompression fails
 *         - AARUF_ERROR_UNSUPPORTED_COMPRESSION (-8) - Compression algorithm not supported
 *
 * @note Optical Disc Sector Reconstruction:
 *       - Creates full 2352-byte sectors from separate user data, sync, header, and ECC/EDC components
 *       - Supports different CD modes: Mode 1, Mode 2 Form 1, Mode 2 Form 2, Mode 2 Formless
 *       - Handles ECC correction using stored correction data or reconstructed ECC
 *       - Uses prefix/suffix DDTs to determine correction strategy for each sector
 *
 * @note Block Media Tag Assembly:
 *       - Combines user data (typically 512 bytes) with media-specific tags
 *       - Tag sizes vary by media type: Apple (12-20 bytes), Priam (24 bytes)
 *       - Tags contain manufacturer-specific information like spare sector mapping
 *
 * @note ECC Reconstruction Modes:
 *       - Correct: Reconstructs ECC/EDC from user data (no stored correction data needed)
 *       - NotDumped: Indicates the metadata portion was not successfully read during imaging
 *       - Stored: Uses pre-computed correction data stored separately in the image
 *
 * @note Buffer Size Requirements:
 *       - Optical discs: Always 2352 bytes (full raw sector)
 *       - Block media: User data size + tag size (varies by media type)
 *       - Pass data as NULL to query the exact required buffer size
 *
 * @warning For optical discs, this function requires track information to be available.
 *          Images without track data may not support long sector reading.
 *
 * @warning The function performs complex sector reconstruction. Corrupted metadata
 *          or missing correction data may result in incorrect sector assembly.
 *
 * @warning Not all AaruFormat images contain the metadata necessary for long sector
 *          reading. Some images may only support basic sector reading via aaruf_read_sector().
 */
AARU_EXPORT int32_t AARU_CALL aaruf_read_sector_long(void *context, const uint64_t sector_address, bool negative,
                                                     uint8_t *data, uint32_t *length, uint8_t *sector_status)
{
    const uint32_t initial_length = length == NULL ? 0U : *length;

    TRACE("Entering aaruf_read_sector_long(%p, %" PRIu64 ", %d, %p, %u)", context, sector_address, data,
          initial_length);

    const aaruformat_context *ctx         = NULL;
    uint32_t                  bare_length = 0;
    uint32_t                  tag_length  = 0;
    uint8_t                  *bare_data   = NULL;
    int32_t                   res         = 0;
    int32_t                   query_status;
    TrackEntry                trk;
    int                       i         = 0;
    bool                      trk_found = false;

    if(context == NULL)
    {
        FATAL("Invalid context");

        TRACE("Exiting aaruf_read_sector_long() = AARUF_ERROR_NOT_AARUFORMAT");
        return AARUF_ERROR_NOT_AARUFORMAT;
    }

    ctx = context;

    if(length == NULL)
    {
        FATAL("Invalid length pointer");

        TRACE("Exiting aaruf_read_sector_long() = AARUF_ERROR_INCORRECT_DATA_SIZE");
        return AARUF_ERROR_INCORRECT_DATA_SIZE;
    }

    // Not a libaaruformat context
    if(ctx->magic != AARU_MAGIC)
    {
        FATAL("Invalid context");

        TRACE("Exiting aaruf_read_sector_long() = AARUF_ERROR_NOT_AARUFORMAT");
        return AARUF_ERROR_NOT_AARUFORMAT;
    }

    if(negative && sector_address > ctx->user_data_ddt_header.negative)
    {
        FATAL("Sector address out of bounds");

        TRACE("Exiting aaruf_read_sector_long() = AARUF_ERROR_SECTOR_OUT_OF_BOUNDS");
        return AARUF_ERROR_SECTOR_OUT_OF_BOUNDS;
    }

    if(!negative && sector_address > ctx->image_info.Sectors + ctx->user_data_ddt_header.overflow - 1)
    {
        FATAL("Sector address out of bounds");

        TRACE("Exiting aaruf_read_sector_long() = AARUF_ERROR_SECTOR_OUT_OF_BOUNDS");
        return AARUF_ERROR_SECTOR_OUT_OF_BOUNDS;
    }

    uint64_t corrected_sector_address = sector_address;

    // Calculate positive or negative sector
    if(negative)
        corrected_sector_address = ctx->user_data_ddt_header.negative - sector_address;
    else
        corrected_sector_address += ctx->user_data_ddt_header.negative;

    switch(ctx->image_info.MetadataMediaType)
    {
        case OpticalDisc:
            if(ctx->image_info.MediaType == DVDROM || ctx->image_info.MediaType == PS2DVD ||
               ctx->image_info.MediaType == SACD || ctx->image_info.MediaType == PS3DVD ||
               ctx->image_info.MediaType == DVDR || ctx->image_info.MediaType == DVDRW ||
               ctx->image_info.MediaType == DVDPR || ctx->image_info.MediaType == DVDPRW ||
               ctx->image_info.MediaType == DVDPRWDL || ctx->image_info.MediaType == DVDRDL ||
               ctx->image_info.MediaType == DVDPRDL || ctx->image_info.MediaType == DVDRAM ||
               ctx->image_info.MediaType == DVDRWDL || ctx->image_info.MediaType == DVDDownload ||
               ctx->image_info.MediaType == Nuon)
            {
                if(ctx->sector_id == NULL || ctx->sector_ied == NULL || ctx->sector_cpr_mai == NULL ||
                   ctx->sector_edc == NULL)
                    return aaruf_read_sector(context, sector_address, negative, data, length, sector_status);

                if(*length < 2064 || data == NULL)
                {
                    *length = 2064;
                    FATAL("Buffer too small for sector, required %u bytes", *length);

                    TRACE("Exiting aaruf_read_sector_long() = AARUF_ERROR_BUFFER_TOO_SMALL");
                    return AARUF_ERROR_BUFFER_TOO_SMALL;
                }

                bare_length  = 0;
                query_status = aaruf_read_sector(context, sector_address, negative, NULL, &bare_length, sector_status);

                if(query_status != AARUF_ERROR_BUFFER_TOO_SMALL && query_status != AARUF_STATUS_OK)
                {
                    TRACE("Exiting aaruf_read_sector_long() = %d", query_status);
                    return query_status;
                }

                if(bare_length == 0)
                {
                    FATAL("Invalid bare sector length (0)");

                    TRACE("Exiting aaruf_read_sector_long() = AARUF_ERROR_INCORRECT_DATA_SIZE");
                    return AARUF_ERROR_INCORRECT_DATA_SIZE;
                }

                TRACE("Allocating memory for bare data");
                bare_data = (uint8_t *)malloc(bare_length);

                if(bare_data == NULL)
                {
                    FATAL("Could not allocate memory for bare data");

                    TRACE("Exiting aaruf_read_sector_long() = AARUF_ERROR_NOT_ENOUGH_MEMORY");
                    return AARUF_ERROR_NOT_ENOUGH_MEMORY;
                }

                res = aaruf_read_sector(context, sector_address, negative, bare_data, &bare_length, sector_status);

                if(res != AARUF_STATUS_OK)
                {
                    *length = 2064;
                    free(bare_data);

                    TRACE("Exiting aaruf_read_sector_long() = %d", res);
                    return res;
                }

                memcpy(data, ctx->sector_id + corrected_sector_address * 4, 4);
                memcpy(data + 4, ctx->sector_ied + corrected_sector_address * 2, 2);
                memcpy(data + 6, ctx->sector_cpr_mai + corrected_sector_address * 6, 6);
                memcpy(data + 12, bare_data, 2048);
                memcpy(data + 2060, ctx->sector_edc + corrected_sector_address * 4, 4);

                *length = 2064;

                free(bare_data);
                return AARUF_STATUS_OK;
            }

            if(ctx->image_info.MediaType == GOD || ctx->image_info.MediaType == WOD)
             {
                 if(ctx->sector_id == NULL || ctx->sector_ied == NULL || ctx->sector_cpr_mai == NULL ||
                    ctx->sector_edc == NULL)
                     return aaruf_read_sector(context, sector_address, negative, data, length, sector_status);
 
                 if(*length < 2064 || data == NULL)
                 {
                     *length = 2064;
                     FATAL("Buffer too small for sector, required %u bytes", *length);
 
                     TRACE("Exiting aaruf_read_sector_long() = AARUF_ERROR_BUFFER_TOO_SMALL");
                     return AARUF_ERROR_BUFFER_TOO_SMALL;
                 }
 
                 bare_length  = 0;
                 query_status = aaruf_read_sector(context, sector_address, negative, NULL, &bare_length, sector_status);
 
                 if(query_status != AARUF_ERROR_BUFFER_TOO_SMALL && query_status != AARUF_STATUS_OK)
                 {
                     TRACE("Exiting aaruf_read_sector_long() = %d", query_status);
                     return query_status;
                 }
 
                 if(bare_length == 0)
                 {
                     FATAL("Invalid bare sector length (0)");
 
                     TRACE("Exiting aaruf_read_sector_long() = AARUF_ERROR_INCORRECT_DATA_SIZE");
                     return AARUF_ERROR_INCORRECT_DATA_SIZE;
                 }
 
                 TRACE("Allocating memory for bare data");
                 bare_data = (uint8_t *)malloc(bare_length);
 
                 if(bare_data == NULL)
                 {
                     FATAL("Could not allocate memory for bare data");
 
                     TRACE("Exiting aaruf_read_sector_long() = AARUF_ERROR_NOT_ENOUGH_MEMORY");
                     return AARUF_ERROR_NOT_ENOUGH_MEMORY;
                 }
 
                 res = aaruf_read_sector(context, sector_address, negative, bare_data, &bare_length, sector_status);
 
                 if(res != AARUF_STATUS_OK)
                 {
                     *length = 2064;
                     free(bare_data);
 
                     TRACE("Exiting aaruf_read_sector_long() = %d", res);
                     return res;
                 }
 
                 memcpy(data, ctx->sector_id + corrected_sector_address * 4, 4);
                 memcpy(data + 4, ctx->sector_ied + corrected_sector_address * 2, 2);
                 memcpy(data + 6, bare_data, 2048);
                 memcpy(data + 2054, ctx->sector_cpr_mai + corrected_sector_address * 6, 6);
                 memcpy(data + 2060, ctx->sector_edc + corrected_sector_address * 4, 4);
 
                 *length = 2064;
 
                 free(bare_data);
                 return AARUF_STATUS_OK;
             }

            if(*length < 2352 || data == NULL)
            {
                *length = 2352;
                FATAL("Buffer too small for sector, required %u bytes", *length);

                TRACE("Exiting aaruf_read_sector_long() = AARUF_ERROR_BUFFER_TOO_SMALL");
                return AARUF_ERROR_BUFFER_TOO_SMALL;
            }

            if((ctx->sector_suffix == NULL || ctx->sector_prefix == NULL) &&
               (ctx->sector_suffix_ddt == NULL || ctx->sector_prefix_ddt == NULL) &&
               (ctx->sector_suffix_ddt2 == NULL || ctx->sector_prefix_ddt2 == NULL))
                return aaruf_read_sector(context, sector_address, negative, data, length, sector_status);

            bare_length  = 0;
            query_status = aaruf_read_sector(context, sector_address, negative, NULL, &bare_length, sector_status);

            if(query_status != AARUF_ERROR_BUFFER_TOO_SMALL && query_status != AARUF_STATUS_OK)
            {
                TRACE("Exiting aaruf_read_sector_long() = %d", query_status);
                return query_status;
            }

            if(bare_length == 0)
            {
                FATAL("Invalid bare sector length (0)");

                TRACE("Exiting aaruf_read_sector_long() = AARUF_ERROR_INCORRECT_DATA_SIZE");
                return AARUF_ERROR_INCORRECT_DATA_SIZE;
            }

            TRACE("Allocating memory for bare data");
            bare_data = (uint8_t *)malloc(bare_length);

            if(bare_data == NULL)
            {
                FATAL("Could not allocate memory for bare data");

                TRACE("Exiting aaruf_read_sector_long() = AARUF_ERROR_NOT_ENOUGH_MEMORY");
                return AARUF_ERROR_NOT_ENOUGH_MEMORY;
            }

            res = aaruf_read_sector(context, sector_address, negative, bare_data, &bare_length, sector_status);

            if(res != AARUF_STATUS_OK)
            {
                free(bare_data);

                TRACE("Exiting aaruf_read_sector_long() = %d", res);
                return res;
            }

            trk_found = false;

            for(i = 0; i < ctx->number_of_data_tracks; i++)
                if(sector_address >= ctx->data_tracks[i].start && sector_address <= ctx->data_tracks[i].end)
                {
                    trk_found = true;
                    trk       = ctx->data_tracks[i];
                    break;
                }

            if(!trk_found)
            {
                FATAL("Track not found");
                free(bare_data);

                TRACE("Exiting aaruf_read_sector_long() = AARUF_ERROR_TRACK_NOT_FOUND");
                return AARUF_ERROR_TRACK_NOT_FOUND;
            }

            switch(trk.type)
            {
                case kTrackTypeAudio:
                case kTrackTypeData:
                    memcpy(data, bare_data, bare_length);
                    *length = bare_length;
                    free(bare_data);
                    return res;
                case kTrackTypeCdMode1:
                    memcpy(data + 16, bare_data, 2048);

                    if(ctx->sector_prefix_ddt2 != NULL)
                    {
                        const uint64_t prefix_ddt_entry = ctx->sector_prefix_ddt2[corrected_sector_address];
                        const uint32_t prefix_status    = prefix_ddt_entry >> 60;
                        const uint64_t prefix_index     = prefix_ddt_entry & 0x0FFFFFFFFFFFFFFF;

                        if(prefix_status == SectorStatusMode1Correct)
                        {
                            aaruf_ecc_cd_reconstruct_prefix(data, trk.type, sector_address);
                            res = AARUF_STATUS_OK;
                        }
                        else if(prefix_status == SectorStatusNotDumped)
                            res = AARUF_STATUS_SECTOR_NOT_DUMPED;
                        else
                            memcpy(data, ctx->sector_prefix + prefix_index * 16, 16);
                    }
                    else if(ctx->sector_prefix != NULL)
                        memcpy(data, ctx->sector_prefix + corrected_sector_address * 16, 16);
                    else if(ctx->sector_prefix_ddt != NULL)
                    {
                        if((ctx->sector_prefix_ddt[corrected_sector_address] & CD_XFIX_MASK) == Correct)
                        {
                            aaruf_ecc_cd_reconstruct_prefix(data, trk.type, sector_address);
                            res = AARUF_STATUS_OK;
                        }
                        else if((ctx->sector_prefix_ddt[corrected_sector_address] & CD_XFIX_MASK) == NotDumped)
                            res = AARUF_STATUS_SECTOR_NOT_DUMPED;
                        else
                            memcpy(data,
                                   ctx->sector_prefix_corrected +
                                       ((ctx->sector_prefix_ddt[corrected_sector_address] & CD_DFIX_MASK) - 1) * 16,
                                   16);
                    }
                    else
                    {
                        TRACE("Exiting aaruf_read_sector_long() = AARUF_ERROR_REACHED_UNREACHABLE_CODE");
                        free(bare_data);
                        return AARUF_ERROR_REACHED_UNREACHABLE_CODE;
                    }

                    if(res != AARUF_STATUS_OK)
                    {
                        *length = 2352;
                        free(bare_data);
                        return res;
                    }

                    if(ctx->sector_suffix_ddt2 != NULL)
                    {
                        const uint64_t suffix_ddt_entry = ctx->sector_suffix_ddt2[corrected_sector_address];
                        const uint64_t suffix_status    = suffix_ddt_entry >> 60;
                        const uint64_t suffix_index     = suffix_ddt_entry & 0x0FFFFFFFFFFFFFFF;

                        if(suffix_status == SectorStatusMode1Correct)
                        {
                            aaruf_ecc_cd_reconstruct(ctx->ecc_cd_context, data, trk.type);
                            res = AARUF_STATUS_OK;
                        }
                        else if(suffix_status == SectorStatusNotDumped)
                            res = AARUF_STATUS_SECTOR_NOT_DUMPED;
                        else
                            memcpy(data + 2064, ctx->sector_suffix + suffix_index * 288, 288);
                    }
                    else if(ctx->sector_suffix != NULL)
                        memcpy(data + 2064, ctx->sector_suffix + corrected_sector_address * 288, 288);
                    else if(ctx->sector_suffix_ddt != NULL)
                    {
                        if((ctx->sector_suffix_ddt[corrected_sector_address] & CD_XFIX_MASK) == Correct)
                        {
                            aaruf_ecc_cd_reconstruct(ctx->ecc_cd_context, data, trk.type);
                            res = AARUF_STATUS_OK;
                        }
                        else if((ctx->sector_suffix_ddt[corrected_sector_address] & CD_XFIX_MASK) == NotDumped)
                            res = AARUF_STATUS_SECTOR_NOT_DUMPED;
                        else
                            memcpy(data + 2064,
                                   ctx->sector_suffix_corrected +
                                       ((ctx->sector_suffix_ddt[corrected_sector_address] & CD_DFIX_MASK) - 1) * 288,
                                   288);
                    }
                    else
                    {
                        TRACE("Exiting aaruf_read_sector_long() = AARUF_ERROR_REACHED_UNREACHABLE_CODE");
                        free(bare_data);
                        return AARUF_ERROR_REACHED_UNREACHABLE_CODE;
                    }

                    *length = 2352;
                    free(bare_data);
                    return res;
                case kTrackTypeCdMode2Formless:
                case kTrackTypeCdMode2Form1:
                case kTrackTypeCdMode2Form2:
                    if(ctx->sector_prefix_ddt2 != NULL)
                    {
                        const uint64_t prefix_ddt_entry = ctx->sector_prefix_ddt2[corrected_sector_address];
                        const uint64_t prefix_status    = prefix_ddt_entry >> 60;
                        const uint64_t prefix_index     = prefix_ddt_entry & 0x0FFFFFFFFFFFFFFF;

                        if(prefix_status == SectorStatusMode2Form1Ok || prefix_status == SectorStatusMode2Form2Ok)
                        {
                            aaruf_ecc_cd_reconstruct_prefix(data, trk.type, sector_address);
                            res = AARUF_STATUS_OK;
                        }
                        else if(prefix_status == SectorStatusNotDumped)
                            res = AARUF_STATUS_SECTOR_NOT_DUMPED;
                        else
                            memcpy(data, ctx->sector_prefix + prefix_index * 16, 16);
                    }
                    else if(ctx->sector_prefix != NULL)
                        memcpy(data, ctx->sector_prefix + corrected_sector_address * 16, 16);
                    else if(ctx->sector_prefix_ddt != NULL)
                    {
                        if((ctx->sector_prefix_ddt[corrected_sector_address] & CD_XFIX_MASK) == Correct)
                        {
                            aaruf_ecc_cd_reconstruct_prefix(data, trk.type, sector_address);
                            res = AARUF_STATUS_OK;
                        }
                        else if((ctx->sector_prefix_ddt[corrected_sector_address] & CD_XFIX_MASK) == NotDumped)
                            res = AARUF_STATUS_SECTOR_NOT_DUMPED;
                        else
                            memcpy(data,
                                   ctx->sector_prefix_corrected +
                                       ((ctx->sector_prefix_ddt[corrected_sector_address] & CD_DFIX_MASK) - 1) * 16,
                                   16);
                    }
                    else
                    {
                        TRACE("Exiting aaruf_read_sector_long() = AARUF_ERROR_REACHED_UNREACHABLE_CODE");
                        free(bare_data);
                        return AARUF_ERROR_REACHED_UNREACHABLE_CODE;
                    }

                    if(res != AARUF_STATUS_OK)
                    {
                        *length = 2352;
                        free(bare_data);
                        return res;
                    }

                    if(ctx->mode2_subheaders != NULL && ctx->sector_suffix_ddt2 != NULL)
                    {
                        memcpy(data + 16, ctx->mode2_subheaders + corrected_sector_address * 8, 8);
                        const uint64_t suffix_ddt_entry = ctx->sector_suffix_ddt2[corrected_sector_address];
                        const uint64_t suffix_status    = suffix_ddt_entry >> 60;
                        const uint64_t suffix_index     = suffix_ddt_entry & 0x0FFFFFFFFFFFFFFF;

                        if(suffix_status == SectorStatusMode2Form1Ok)
                        {
                            memcpy(data + 24, bare_data, 2048);
                            aaruf_ecc_cd_reconstruct(ctx->ecc_cd_context, data, kTrackTypeCdMode2Form1);
                        }
                        else if(suffix_status == SectorStatusMode2Form2Ok ||
                                suffix_status == SectorStatusMode2Form2NoCrc)
                        {
                            memcpy(data + 24, bare_data, 2324);
                            if(suffix_status == SectorStatusMode2Form2Ok)
                                aaruf_ecc_cd_reconstruct(ctx->ecc_cd_context, data, kTrackTypeCdMode2Form2);
                            else
                                memset(data + 2348, 0, 4);
                        }
                        else if(suffix_status == SectorStatusNotDumped)
                            res = AARUF_STATUS_SECTOR_NOT_DUMPED;
                        else
                        {
                            // Mode 2 where ECC/EDC failed — restore user data + stored suffix.
                            // Check subheader (already copied to data+16) to determine form.
                            if(data[0x12] & 0x20)
                            {
                                // Form 2: 2324 bytes user data, 4 bytes EDC suffix
                                memcpy(data + 24, bare_data, 2324);
                                memcpy(data + 2348, ctx->sector_suffix + suffix_index * 288, 4);
                            }
                            else
                            {
                                // Form 1: 2048 bytes user data, 280 bytes EDC+ECC suffix
                                memcpy(data + 24, bare_data, 2048);
                                memcpy(data + 2072, ctx->sector_suffix + suffix_index * 288, 280);
                            }
                        }
                    }
                    else if(ctx->mode2_subheaders != NULL && ctx->sector_suffix_ddt != NULL)
                    {
                        memcpy(data + 16, ctx->mode2_subheaders + corrected_sector_address * 8, 8);

                        if((ctx->sector_suffix_ddt[corrected_sector_address] & CD_XFIX_MASK) == Mode2Form1Ok)
                        {
                            memcpy(data + 24, bare_data, 2048);
                            aaruf_ecc_cd_reconstruct(ctx->ecc_cd_context, data, kTrackTypeCdMode2Form1);
                        }
                        else if((ctx->sector_suffix_ddt[corrected_sector_address] & CD_XFIX_MASK) == Mode2Form2Ok ||
                                (ctx->sector_suffix_ddt[corrected_sector_address] & CD_XFIX_MASK) == Mode2Form2NoCrc)
                        {
                            memcpy(data + 24, bare_data, 2324);
                            if((ctx->sector_suffix_ddt[corrected_sector_address] & CD_XFIX_MASK) == Mode2Form2Ok)
                                aaruf_ecc_cd_reconstruct(ctx->ecc_cd_context, data, kTrackTypeCdMode2Form2);
                            else
                                memset(data + 2348, 0, 4);
                        }
                        else if((ctx->sector_suffix_ddt[corrected_sector_address] & CD_XFIX_MASK) == NotDumped)
                            res = AARUF_STATUS_SECTOR_NOT_DUMPED;
                        else
                        {
                            // Mode 2 where ECC/EDC failed — restore user data + stored suffix.
                            const uint32_t sfx_index =
                                (ctx->sector_suffix_ddt[corrected_sector_address] & CD_DFIX_MASK) - 1;
                            if(data[0x12] & 0x20)
                            {
                                memcpy(data + 24, bare_data, 2324);
                                memcpy(data + 2348, ctx->sector_suffix_corrected + sfx_index * 288, 4);
                            }
                            else
                            {
                                memcpy(data + 24, bare_data, 2048);
                                memcpy(data + 2072, ctx->sector_suffix_corrected + sfx_index * 288, 280);
                            }
                        }
                    }
                    else if(ctx->mode2_subheaders != NULL)
                    {
                        memcpy(data + 16, ctx->mode2_subheaders + corrected_sector_address * 8, 8);
                        memcpy(data + 24, bare_data, 2328);
                    }
                    else
                        memcpy(data + 16, bare_data, 2336);

                    *length = 2352;
                    free(bare_data);
                    return res;
                default:
                    FATAL("Invalid track type %d", trk.type);
                    free(bare_data);

                    TRACE("Exiting aaruf_read_sector_long() = AARUF_ERROR_INVALID_TRACK_FORMAT");
                    return AARUF_ERROR_INVALID_TRACK_FORMAT;
            }
        case BlockMedia:
            switch(ctx->image_info.MediaType)
            {
                case AppleFileWare:
                case AppleProfile:
                case AppleSonySS:
                case AppleSonyDS:
                case AppleWidget:
                case PriamDataTower:
                    if(ctx->sector_subchannel == NULL)
                        return aaruf_read_sector(context, sector_address, negative, data, length, sector_status);

                    switch(ctx->image_info.MediaType)
                    {
                        case AppleFileWare:
                        case AppleProfile:
                        case AppleWidget:
                            tag_length = 20;
                            break;
                        case AppleSonySS:
                        case AppleSonyDS:
                            tag_length = 12;
                            break;
                        case PriamDataTower:
                            tag_length = 24;
                            break;
                        default:
                            FATAL("Unsupported media type %d", ctx->image_info.MediaType);

                            TRACE("Exiting aaruf_read_sector_long() = AARUF_ERROR_INCORRECT_MEDIA_TYPE");
                            return AARUF_ERROR_INCORRECT_MEDIA_TYPE;
                    }

                    bare_length = 512;

                    if(*length < tag_length + bare_length || data == NULL)
                    {
                        *length = tag_length + bare_length;
                        FATAL("Buffer too small for sector, required %u bytes", *length);

                        TRACE("Exiting aaruf_read_sector_long() = AARUF_ERROR_BUFFER_TOO_SMALL");
                        return AARUF_ERROR_BUFFER_TOO_SMALL;
                    }

                    TRACE("Allocating memory for bare data of size %u bytes", bare_length);
                    bare_data = malloc(bare_length);

                    if(bare_data == NULL)
                    {
                        FATAL("Could not allocate memory for bare data");

                        TRACE("Exiting aaruf_read_sector_long() = AARUF_ERROR_NOT_ENOUGH_MEMORY");
                        return AARUF_ERROR_NOT_ENOUGH_MEMORY;
                    }

                    res = aaruf_read_sector(context, sector_address, negative, bare_data, &bare_length, sector_status);

                    if(res != AARUF_STATUS_OK)
                    {
                        free(bare_data);

                        TRACE("Exiting aaruf_read_sector_long() = %d", res);
                        return res;
                    }

                    if(bare_length != 512)
                    {
                        FATAL("Bare data length is %u, expected 512", bare_length);
                        free(bare_data);

                        TRACE("Exiting aaruf_read_sector_long() = AARUF_ERROR_INCORRECT_DATA_SIZE");
                        return AARUF_ERROR_INCORRECT_DATA_SIZE;
                    }

                    memcpy(data + bare_length, ctx->sector_subchannel + corrected_sector_address * tag_length,
                           tag_length);
                    memcpy(data, bare_data, bare_length);
                    *length = tag_length + bare_length;

                    free(bare_data);

                    TRACE("Exiting aaruf_read_sector_long() = AARUF_STATUS_OK");
                    return AARUF_STATUS_OK;
                default:
                    FATAL("Incorrect media type %d for long sector reading", ctx->image_info.MediaType);

                    TRACE("Exiting aaruf_read_sector_long() = AARUF_ERROR_INCORRECT_MEDIA_TYPE");
                    return AARUF_ERROR_INCORRECT_MEDIA_TYPE;
            }
        default:
            FATAL("Incorrect media type %d for long sector reading", ctx->image_info.MediaType);

            TRACE("Exiting aaruf_read_sector_long() = AARUF_ERROR_INCORRECT_MEDIA_TYPE");
            return AARUF_ERROR_INCORRECT_MEDIA_TYPE;
    }
}

/**
 * @brief Reads a specific sector tag from the AaruFormat image.
 *
 * Reads sector-level metadata tags such as subchannel data, track information,
 * DVD sector metadata, or Apple/Priam proprietary tags from the specified sector.
 * Sector tags are ancillary data stored separately from the main user data and
 * provide format-specific metadata like track flags, ISRC codes, DVD encryption
 * information, or ECC/EDC data. This function validates tag type against media
 * type and ensures the tag data is present in the image before returning it.
 *
 * @param context Pointer to the aaruformat context.
 * @param sector_address The logical sector address to read the tag from.
 * @param negative Indicates if the sector address is negative (pre-track area).
 * @param buffer Pointer to buffer where tag data will be stored. Can be NULL to query tag length.
 * @param length Pointer to variable containing buffer size on input, actual tag length on output.
 * @param tag Tag identifier specifying which sector tag to read (see SectorTagType enum).
 *
 * @return Returns one of the following status codes:
 * @retval AARUF_STATUS_OK (0) Successfully read the sector tag. This is returned when:
 *         - The context is valid and properly initialized
 *         - The requested tag type exists and is applicable to the media type
 *         - The tag data is present in the image for the specified sector
 *         - The provided buffer is large enough to contain the tag data
 *         - The tag data is successfully copied to the output buffer
 *         - The length parameter is updated with the actual tag data length
 *
 * @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_ERROR_SECTOR_OUT_OF_BOUNDS (-5) The sector address exceeds image bounds. This occurs when:
 *         - negative is true and sector_address > ctx->userDataDdtHeader.negative - 1
 *         - negative is false and sector_address > ctx->imageInfo.Sectors + ctx->userDataDdtHeader.overflow - 1
 *         - Attempting to read beyond the logical extent of the imaged medium
 *
 * @retval AARUF_ERROR_INCORRECT_MEDIA_TYPE (-12) Tag type incompatible with media type. This occurs when:
 *         - Optical disc tags (CdTrackFlags, CdTrackIsrc, CdSectorSubchannel, DvdSector*) requested from BlockMedia
 *         - Block media tags (AppleSonyTag, AppleProfileTag, PriamDataTowerTag) requested from OpticalDisc
 *         - Tag type is specific to a media format not present in the current image
 *
 * @retval AARUF_ERROR_INCORRECT_DATA_SIZE (-26) The provided buffer has incorrect size. This occurs when:
 *         - The buffer parameter is NULL (used for length querying - NOT an error, updates length and returns this)
 *         - The buffer length (*length) doesn't match the required tag data length
 *         - The length parameter is updated with the required exact size for retry
 *
 * @retval AARUF_ERROR_SECTOR_TAG_NOT_PRESENT (-16) The requested tag is not stored in the image. This occurs when:
 *         - The tag data was not captured during the imaging process
 *         - The storage pointer for the tag is NULL (e.g., ctx->sector_subchannel is NULL)
 *         - The imaging hardware/software did not support reading this tag type
 *         - The tag is optional and was not available on the source medium
 *
 * @retval AARUF_ERROR_TRACK_NOT_FOUND (-13) Track information not found for the sector. This occurs when:
 *         - Requesting CdTrackFlags or CdTrackIsrc for a sector not within any track boundaries
 *         - The sector address doesn't fall within any track's start/end range in ctx->trackEntries[]
 *         - Track metadata was not properly initialized or is corrupted
 *
 * @retval AARUF_ERROR_INVALID_TAG (-27) The tag identifier is not recognized or supported. This occurs when:
 *         - The tag parameter value doesn't match any known SectorTagType enum value
 *         - Future/unsupported tag types not implemented in this library version
 *
 * @note Supported Tag Types and Sizes:
 *       Optical Disc Tags (OpticalDisc media only):
 *       - CdTrackFlags (11): 1 byte - Track control flags (audio/data, copy permitted, pre-emphasis)
 *       - CdTrackIsrc (9): 12 bytes - International Standard Recording Code (no null terminator)
 *       - CdSectorSubchannel (71): 96 bytes - Raw P-W subchannel data
 *       - DvdSectorCprMai (81): 6 bytes - DVD Copyright Management Information (CPR_MAI)
 *       - DvdSectorInformation (16): 1 byte - DVD sector information byte from ID field
 *       - DvdSectorNumber (17): 3 bytes - DVD sector number from ID field
 *       - DvdSectorIed (84): 2 bytes - DVD ID Error Detection code
 *       - DvdSectorEdc (85): 4 bytes - DVD Error Detection Code
 *       - DvdDiscKeyDecrypted (80): 5 bytes - Decrypted DVD title key for the sector
 *
 *       Block Media Tags (BlockMedia only):
 *       - AppleSonyTag (73): 12 bytes - Apple Sony format 12-byte sector tag
 *       - AppleProfileTag (72): 20 bytes - Apple Profile format 20-byte sector tag
 *       - PriamDataTowerTag (74): 24 bytes - Priam DataTower format 24-byte sector tag
 *
 * @note Sector Address Correction:
 *       - The function automatically adjusts sector addresses based on the negative parameter
 *       - Negative sectors are adjusted: corrected = sector_address - ctx->userDataDdtHeader.negative
 *       - Positive sectors are adjusted: corrected = sector_address + ctx->userDataDdtHeader.negative
 *       - Corrected addresses are used for indexing into tag data arrays
 *
 * @note Track-Based Tags (CdTrackFlags, CdTrackIsrc):
 *       - These tags are per-track, not per-sector
 *       - Function searches ctx->trackEntries[] for track containing the sector
 *       - All sectors within a track return the same track-level metadata
 *       - Returns AARUF_ERROR_TRACK_NOT_FOUND if sector is outside all tracks
 *
 * @note DVD Sector Tags:
 *       - DVD sector tags are extracted from the DVD ID field or associated metadata
 *       - DvdSectorInformation and DvdSectorNumber are derived from ctx->sector_id array
 *       - Multiple tags may share the same underlying storage (e.g., ID field breakdown)
 *       - Presence depends on the DVD reading capabilities during imaging
 *
 * @note Buffer Size Querying:
 *       - Pass buffer as NULL to query the required buffer size without reading data
 *       - The length parameter will be updated with the exact required size
 *       - Returns AARUF_ERROR_INCORRECT_DATA_SIZE (not a fatal error in this context)
 *       - Allows proper buffer allocation before the actual read operation
 *
 * @note Tag Data Storage:
 *       - Tag data is stored in dedicated context arrays (ctx->sector_subchannel, ctx->sector_cpr_mai, etc.)
 *       - Each tag type has a specific array with fixed-size entries per sector
 *       - NULL storage pointer indicates tag was not captured/available
 *       - Tag data is indexed using the corrected sector address
 *
 * @warning The buffer must be exactly the required size for each tag type.
 *          Unlike aaruf_read_sector(), this function enforces strict size matching.
 *
 * @warning Tag availability is hardware and media dependent. Always check for
 *          AARUF_ERROR_SECTOR_TAG_NOT_PRESENT and handle gracefully.
 *
 * @warning Track-based tags (CdTrackFlags, CdTrackIsrc) return the same value
 *          for all sectors within a track. Do not assume per-sector uniqueness.
 *
 * @warning Some tags contain binary data without string termination (e.g., ISRC).
 *          Do not treat tag buffers as null-terminated strings without validation.
 */
AARU_EXPORT int32_t AARU_CALL aaruf_read_sector_tag(const void *context, const uint64_t sector_address,
                                                    const bool negative, uint8_t *buffer, uint32_t *length,
                                                    const int32_t tag)
{
    const uint32_t initial_length = length == NULL ? 0U : *length;

    TRACE("Entering aaruf_read_sector_tag(%p, %" PRIu64 ", %d, %p, %u, %d)", context, sector_address, negative, buffer,
          initial_length, tag);

    const aaruformat_context *ctx = NULL;

    if(context == NULL)
    {
        FATAL("Invalid context");

        TRACE("Exiting aaruf_read_sector_tag() = AARUF_ERROR_NOT_AARUFORMAT");
        return AARUF_ERROR_NOT_AARUFORMAT;
    }

    ctx = context;

    if(length == NULL)
    {
        FATAL("Invalid length pointer");

        TRACE("Exiting aaruf_read_sector_tag() = AARUF_ERROR_INCORRECT_DATA_SIZE");
        return AARUF_ERROR_INCORRECT_DATA_SIZE;
    }

    // Not a libaaruformat context
    if(ctx->magic != AARU_MAGIC)
    {
        FATAL("Invalid context");

        TRACE("Exiting aaruf_read_sector_tag() = AARUF_ERROR_NOT_AARUFORMAT");
        return AARUF_ERROR_NOT_AARUFORMAT;
    }

    if(negative && sector_address > ctx->user_data_ddt_header.negative)
    {
        FATAL("Sector address out of bounds");

        TRACE("Exiting aaruf_read_sector_tag() = AARUF_ERROR_SECTOR_OUT_OF_BOUNDS");
        return AARUF_ERROR_SECTOR_OUT_OF_BOUNDS;
    }

    if(!negative && sector_address > ctx->image_info.Sectors + ctx->user_data_ddt_header.overflow - 1)
    {
        FATAL("Sector address out of bounds");

        TRACE("Exiting aaruf_read_sector_tag() = AARUF_ERROR_SECTOR_OUT_OF_BOUNDS");
        return AARUF_ERROR_SECTOR_OUT_OF_BOUNDS;
    }

    uint64_t corrected_sector_address = sector_address;

    // Calculate positive or negative sector
    if(negative)
        corrected_sector_address = ctx->user_data_ddt_header.negative - sector_address;
    else
        corrected_sector_address += ctx->user_data_ddt_header.negative;

    switch(tag)
    {
        case kSectorTagCdTrackFlags:
            if(ctx->image_info.MetadataMediaType != OpticalDisc)
            {
                FATAL("Invalid media type for tag");
                TRACE("Exiting aaruf_read_sector_tag() = AARUF_ERROR_INCORRECT_MEDIA_TYPE");
                return AARUF_ERROR_INCORRECT_MEDIA_TYPE;
            }

            if(*length != 1 || buffer == NULL)
            {
                *length = 1;
                FATAL("Incorrect tag size");
                TRACE("Exiting aaruf_read_sector_tag() = AARUF_ERROR_INCORRECT_DATA_SIZE");
                return AARUF_ERROR_INCORRECT_DATA_SIZE;
            }

            for(int i = 0; i < ctx->tracks_header.entries; i++)
                if(sector_address >= ctx->track_entries[i].start && sector_address <= ctx->track_entries[i].end)
                {
                    buffer[0] = ctx->track_entries[i].flags;
                    TRACE("Exiting aaruf_read_sector_tag() = AARUF_STATUS_OK");
                    return AARUF_STATUS_OK;
                }

            FATAL("Track not found");
            return AARUF_ERROR_TRACK_NOT_FOUND;
        case kSectorTagCdTrackIsrc:
            if(ctx->image_info.MetadataMediaType != OpticalDisc)
            {
                FATAL("Invalid media type for tag");
                TRACE("Exiting aaruf_read_sector_tag() = AARUF_ERROR_INCORRECT_MEDIA_TYPE");
                return AARUF_ERROR_INCORRECT_MEDIA_TYPE;
            }

            if(*length != 12 || buffer == NULL)
            {
                *length = 12;
                FATAL("Incorrect tag size");
                TRACE("Exiting aaruf_read_sector_tag() = AARUF_ERROR_INCORRECT_DATA_SIZE");
                return AARUF_ERROR_INCORRECT_DATA_SIZE;
            }

            for(int i = 0; i < ctx->tracks_header.entries; i++)
                if(sector_address >= ctx->track_entries[i].start && sector_address <= ctx->track_entries[i].end)
                {
                    memcpy(buffer, ctx->track_entries[i].isrc, 12);
                    TRACE("Exiting aaruf_read_sector_tag() = AARUF_STATUS_OK");
                    return AARUF_STATUS_OK;
                }

            FATAL("Track not found");
            return AARUF_ERROR_TRACK_NOT_FOUND;
        case kSectorTagCdSubchannel:
            if(ctx->image_info.MetadataMediaType != OpticalDisc)
            {
                FATAL("Invalid media type for tag");
                TRACE("Exiting aaruf_read_sector_tag() = AARUF_ERROR_INCORRECT_MEDIA_TYPE");
                return AARUF_ERROR_INCORRECT_MEDIA_TYPE;
            }

            if(*length != 96 || buffer == NULL)
            {
                *length = 96;
                FATAL("Incorrect tag size");
                TRACE("Exiting aaruf_read_sector_tag() = AARUF_ERROR_INCORRECT_DATA_SIZE");
                return AARUF_ERROR_INCORRECT_DATA_SIZE;
            }

            if(ctx->sector_subchannel == NULL)
            {
                FATAL("Sector tag not found");
                TRACE("Exiting aaruf_read_sector_tag() = AARUF_ERROR_SECTOR_TAG_NOT_PRESENT");
                return AARUF_ERROR_SECTOR_TAG_NOT_PRESENT;
            }

            memcpy(buffer, ctx->sector_subchannel + corrected_sector_address * 96, 96);
            TRACE("Exiting aaruf_read_sector_tag() = AARUF_STATUS_OK");
            return AARUF_STATUS_OK;
        case kSectorTagDvdCmi:
            if(ctx->image_info.MetadataMediaType != OpticalDisc)
            {
                FATAL("Invalid media type for tag");
                TRACE("Exiting aaruf_read_sector_tag() = AARUF_ERROR_INCORRECT_MEDIA_TYPE");
                return AARUF_ERROR_INCORRECT_MEDIA_TYPE;
            }

            if(*length != 1 || buffer == NULL)
            {
                *length = 1;
                FATAL("Incorrect tag size");
                TRACE("Exiting aaruf_read_sector_tag() = AARUF_ERROR_INCORRECT_DATA_SIZE");
                return AARUF_ERROR_INCORRECT_DATA_SIZE;
            }

            if(ctx->sector_cpr_mai == NULL)
            {
                FATAL("Sector tag not found");
                TRACE("Exiting aaruf_read_sector_tag() = AARUF_ERROR_SECTOR_TAG_NOT_PRESENT");
                return AARUF_ERROR_SECTOR_TAG_NOT_PRESENT;
            }

            memcpy(buffer, ctx->sector_cpr_mai + corrected_sector_address * 6, 1);
            TRACE("Exiting aaruf_read_sector_tag() = AARUF_STATUS_OK");
            return AARUF_STATUS_OK;
        case kSectorTagDvdSectorInformation:
            if(ctx->image_info.MetadataMediaType != OpticalDisc)
            {
                FATAL("Invalid media type for tag");
                TRACE("Exiting aaruf_read_sector_tag() = AARUF_ERROR_INCORRECT_MEDIA_TYPE");
                return AARUF_ERROR_INCORRECT_MEDIA_TYPE;
            }

            if(*length != 1 || buffer == NULL)
            {
                *length = 1;
                FATAL("Incorrect tag size");
                TRACE("Exiting aaruf_read_sector_tag() = AARUF_ERROR_INCORRECT_DATA_SIZE");
                return AARUF_ERROR_INCORRECT_DATA_SIZE;
            }

            if(ctx->sector_id == NULL)
            {
                FATAL("Sector tag not found");
                TRACE("Exiting aaruf_read_sector_tag() = AARUF_ERROR_SECTOR_TAG_NOT_PRESENT");
                return AARUF_ERROR_SECTOR_TAG_NOT_PRESENT;
            }

            memcpy(buffer, ctx->sector_id + corrected_sector_address * 4, 1);
            TRACE("Exiting aaruf_read_sector_tag() = AARUF_STATUS_OK");
            return AARUF_STATUS_OK;
        case kSectorTagDvdSectorNumber:
            if(ctx->image_info.MetadataMediaType != OpticalDisc)
            {
                FATAL("Invalid media type for tag");
                TRACE("Exiting aaruf_read_sector_tag() = AARUF_ERROR_INCORRECT_MEDIA_TYPE");
                return AARUF_ERROR_INCORRECT_MEDIA_TYPE;
            }

            if(*length != 3 || buffer == NULL)
            {
                *length = 3;
                FATAL("Incorrect tag size");
                TRACE("Exiting aaruf_read_sector_tag() = AARUF_ERROR_INCORRECT_DATA_SIZE");
                return AARUF_ERROR_INCORRECT_DATA_SIZE;
            }

            if(ctx->sector_id == NULL)
            {
                FATAL("Sector tag not found");
                TRACE("Exiting aaruf_read_sector_tag() = AARUF_ERROR_SECTOR_TAG_NOT_PRESENT");
                return AARUF_ERROR_SECTOR_TAG_NOT_PRESENT;
            }

            memcpy(buffer, ctx->sector_id + corrected_sector_address * 4 + 1, 3);
            TRACE("Exiting aaruf_read_sector_tag() = AARUF_STATUS_OK");
            return AARUF_STATUS_OK;
        case kSectorTagDvdSectorIed:
            if(ctx->image_info.MetadataMediaType != OpticalDisc)
            {
                FATAL("Invalid media type for tag");
                TRACE("Exiting aaruf_read_sector_tag() = AARUF_ERROR_INCORRECT_MEDIA_TYPE");
                return AARUF_ERROR_INCORRECT_MEDIA_TYPE;
            }

            if(*length != 2 || buffer == NULL)
            {
                *length = 2;
                FATAL("Incorrect tag size");
                TRACE("Exiting aaruf_read_sector_tag() = AARUF_ERROR_INCORRECT_DATA_SIZE");
                return AARUF_ERROR_INCORRECT_DATA_SIZE;
            }

            if(ctx->sector_ied == NULL)
            {
                FATAL("Sector tag not found");
                TRACE("Exiting aaruf_read_sector_tag() = AARUF_ERROR_SECTOR_TAG_NOT_PRESENT");
                return AARUF_ERROR_SECTOR_TAG_NOT_PRESENT;
            }

            memcpy(buffer, ctx->sector_ied + corrected_sector_address * 2, 2);
            TRACE("Exiting aaruf_read_sector_tag() = AARUF_STATUS_OK");
            return AARUF_STATUS_OK;
        case kSectorTagDvdSectorEdc:
            if(ctx->image_info.MetadataMediaType != OpticalDisc)
            {
                FATAL("Invalid media type for tag");
                TRACE("Exiting aaruf_read_sector_tag() = AARUF_ERROR_INCORRECT_MEDIA_TYPE");
                return AARUF_ERROR_INCORRECT_MEDIA_TYPE;
            }

            if(*length != 4 || buffer == NULL)
            {
                *length = 4;
                FATAL("Incorrect tag size");
                TRACE("Exiting aaruf_read_sector_tag() = AARUF_ERROR_INCORRECT_DATA_SIZE");
                return AARUF_ERROR_INCORRECT_DATA_SIZE;
            }

            if(ctx->sector_edc == NULL)
            {
                FATAL("Sector tag not found");
                TRACE("Exiting aaruf_read_sector_tag() = AARUF_ERROR_SECTOR_TAG_NOT_PRESENT");
                return AARUF_ERROR_SECTOR_TAG_NOT_PRESENT;
            }

            memcpy(buffer, ctx->sector_edc + corrected_sector_address * 4, 4);
            TRACE("Exiting aaruf_read_sector_tag() = AARUF_STATUS_OK");
            return AARUF_STATUS_OK;
        case kSectorTagDvdTitleKeyDecrypted:
            if(ctx->image_info.MetadataMediaType != OpticalDisc)
            {
                FATAL("Invalid media type for tag");
                TRACE("Exiting aaruf_read_sector_tag() = AARUF_ERROR_INCORRECT_MEDIA_TYPE");
                return AARUF_ERROR_INCORRECT_MEDIA_TYPE;
            }

            if(*length != 5 || buffer == NULL)
            {
                *length = 5;
                FATAL("Incorrect tag size");
                TRACE("Exiting aaruf_read_sector_tag() = AARUF_ERROR_INCORRECT_DATA_SIZE");
                return AARUF_ERROR_INCORRECT_DATA_SIZE;
            }

            if(ctx->sector_decrypted_title_key == NULL)
            {
                FATAL("Sector tag not found");
                TRACE("Exiting aaruf_read_sector_tag() = AARUF_ERROR_SECTOR_TAG_NOT_PRESENT");
                return AARUF_ERROR_SECTOR_TAG_NOT_PRESENT;
            }

            memcpy(buffer, ctx->sector_decrypted_title_key + corrected_sector_address * 5, 5);
            TRACE("Exiting aaruf_read_sector_tag() = AARUF_STATUS_OK");
            return AARUF_STATUS_OK;
        case kSectorTagAppleSony:
            if(ctx->image_info.MetadataMediaType != BlockMedia)
            {
                FATAL("Invalid media type for tag");
                TRACE("Exiting aaruf_read_sector_tag() = AARUF_ERROR_INCORRECT_MEDIA_TYPE");
                return AARUF_ERROR_INCORRECT_MEDIA_TYPE;
            }

            if(*length != 12 || buffer == NULL)
            {
                *length = 12;
                FATAL("Incorrect tag size");
                TRACE("Exiting aaruf_read_sector_tag() = AARUF_ERROR_INCORRECT_DATA_SIZE");
                return AARUF_ERROR_INCORRECT_DATA_SIZE;
            }

            if(ctx->sector_subchannel == NULL)
            {
                FATAL("Sector tag not found");
                TRACE("Exiting aaruf_read_sector_tag() = AARUF_ERROR_SECTOR_TAG_NOT_PRESENT");
                return AARUF_ERROR_SECTOR_TAG_NOT_PRESENT;
            }

            memcpy(buffer, ctx->sector_subchannel + corrected_sector_address * 12, 12);
            TRACE("Exiting aaruf_read_sector_tag() = AARUF_STATUS_OK");
            return AARUF_STATUS_OK;
        case kSectorTagAppleProfile:
            if(ctx->image_info.MetadataMediaType != BlockMedia)
            {
                FATAL("Invalid media type for tag");
                TRACE("Exiting aaruf_read_sector_tag() = AARUF_ERROR_INCORRECT_MEDIA_TYPE");
                return AARUF_ERROR_INCORRECT_MEDIA_TYPE;
            }

            if(*length != 20 || buffer == NULL)
            {
                *length = 20;
                FATAL("Incorrect tag size");
                TRACE("Exiting aaruf_read_sector_tag() = AARUF_ERROR_INCORRECT_DATA_SIZE");
                return AARUF_ERROR_INCORRECT_DATA_SIZE;
            }

            if(ctx->sector_subchannel == NULL)
            {
                FATAL("Sector tag not found");
                TRACE("Exiting aaruf_read_sector_tag() = AARUF_ERROR_SECTOR_TAG_NOT_PRESENT");
                return AARUF_ERROR_SECTOR_TAG_NOT_PRESENT;
            }

            memcpy(buffer, ctx->sector_subchannel + corrected_sector_address * 20, 20);
            TRACE("Exiting aaruf_read_sector_tag() = AARUF_STATUS_OK");
            return AARUF_STATUS_OK;
        case kSectorTagPriamDataTower:
            if(ctx->image_info.MetadataMediaType != BlockMedia)
            {
                FATAL("Invalid media type for tag");
                TRACE("Exiting aaruf_read_sector_tag() = AARUF_ERROR_INCORRECT_MEDIA_TYPE");
                return AARUF_ERROR_INCORRECT_MEDIA_TYPE;
            }

            if(*length != 24 || buffer == NULL)
            {
                *length = 24;
                FATAL("Incorrect tag size");
                TRACE("Exiting aaruf_read_sector_tag() = AARUF_ERROR_INCORRECT_DATA_SIZE");
                return AARUF_ERROR_INCORRECT_DATA_SIZE;
            }

            if(ctx->sector_subchannel == NULL)
            {
                FATAL("Sector tag not found");
                TRACE("Exiting aaruf_read_sector_tag() = AARUF_ERROR_SECTOR_TAG_NOT_PRESENT");
                return AARUF_ERROR_SECTOR_TAG_NOT_PRESENT;
            }

            memcpy(buffer, ctx->sector_subchannel + corrected_sector_address * 24, 24);
            TRACE("Exiting aaruf_read_sector_tag() = AARUF_STATUS_OK");
            return AARUF_STATUS_OK;
        default:
            TRACE("Do not know how to read sector tag %d", tag);
            return AARUF_ERROR_INVALID_TAG;
    }
}
