From c8de84871d8987caeb237bcddd7519b7fc770e79 Mon Sep 17 00:00:00 2001 From: Natalia Portillo Date: Tue, 1 Sep 2026 14:56:21 +0100 Subject: [PATCH] Add repair-cd-arena tool command Recovers CD sector prefix/suffix arenas in a v2 image whose resumed dump sessions left the final deduplication table referencing slots past the shrunken data block. Scans the file for every prefix/suffix generation, rebuilds a fresh compact arena and DDT2 for the final table by sourcing each custom slot from the generation that held it, and rewrites the index. Supports --dry-run to report recoverable/lost slots without writing. --- tool/CMakeLists.txt | 1 + tool/aaruformattool.h | 1 + tool/commands.c | 21 ++ tool/commands.h | 1 + tool/repair_cd_arena.c | 566 +++++++++++++++++++++++++++++++++++++++++ tool/usage.c | 13 + tool/usage.h | 1 + 7 files changed, 604 insertions(+) create mode 100644 tool/repair_cd_arena.c diff --git a/tool/CMakeLists.txt b/tool/CMakeLists.txt index d463343..b330582 100644 --- a/tool/CMakeLists.txt +++ b/tool/CMakeLists.txt @@ -33,6 +33,7 @@ add_executable(aaruformattool cli_compare.c convert.c upgrade_ddt_to_alpha21.c + repair_cd_arena.c inject_media_tag.c ps3/ird.c ps3/ird.h diff --git a/tool/aaruformattool.h b/tool/aaruformattool.h index fc37786..118c8e9 100644 --- a/tool/aaruformattool.h +++ b/tool/aaruformattool.h @@ -73,6 +73,7 @@ int cli_compare(const char *path1, const char *path2, bool use_long); int convert_ec(const char *input_path, const char *output_path, bool use_long, uint16_t ec_k, uint16_t ec_m); int verify_recover(const char *path); int upgrade_ddt_to_alpha21(const char *path); +int repair_cd_arena(const char *path, bool dry_run); int inject_media_tag(const char *tag_type, const char *media_tag_file, const char *image_file); int convert_ps3(const char *input_path, const char *output_path, const char *disc_key_hex, const char *data1_key_hex, const char *ird_path); diff --git a/tool/commands.c b/tool/commands.c index 28b5644..c7fcba7 100644 --- a/tool/commands.c +++ b/tool/commands.c @@ -213,6 +213,26 @@ int cmd_upgrade_ddt_to_alpha21(int argc, char *argv[]) return result; } +int cmd_repair_cd_arena(int argc, char *argv[]) +{ + struct arg_lit *dry_run = arg_lit0(NULL, "dry-run", "Only report recoverable/lost slots, write nothing"); + struct arg_str *filename = arg_str1(NULL, NULL, "", "Image to repair"); + struct arg_end *end = arg_end(10); + void *argtable[] = {dry_run, filename, end}; + + if(arg_parse(argc, argv, argtable) > 0) + { + arg_print_errors(stderr, end, "repair-cd-arena"); + usage_repair_cd_arena(); + arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); + return -1; + } + + const int result = repair_cd_arena(filename->sval[0], dry_run->count > 0); + arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0])); + return result; +} + int cmd_inject_media_tag(int argc, char *argv[]) { struct arg_str *tag_type = arg_str1(NULL, NULL, "", "Media tag type to inject"); @@ -318,6 +338,7 @@ Command commands[] = { { "cli-compare", cmd_cli_compare}, { "convert", cmd_convert}, {"upgrade-ddt-to-alpha21", cmd_upgrade_ddt_to_alpha21}, + { "repair-cd-arena", cmd_repair_cd_arena}, { "inject-media-tag", cmd_inject_media_tag}, { "convert-ps3", cmd_convert_ps3}, { "convert-wiiu", cmd_convert_wiiu}, diff --git a/tool/commands.h b/tool/commands.h index 8c18765..4ec362f 100644 --- a/tool/commands.h +++ b/tool/commands.h @@ -44,6 +44,7 @@ int cmd_compare(int argc, char *argv[]); int cmd_cli_compare(int argc, char *argv[]); int cmd_convert(int argc, char *argv[]); int cmd_upgrade_ddt_to_alpha21(int argc, char *argv[]); +int cmd_repair_cd_arena(int argc, char *argv[]); int cmd_inject_media_tag(int argc, char *argv[]); int cmd_convert_ps3(int argc, char *argv[]); int cmd_convert_ngcw(int argc, char *argv[]); diff --git a/tool/repair_cd_arena.c b/tool/repair_cd_arena.c new file mode 100644 index 0000000..1c664df --- /dev/null +++ b/tool/repair_cd_arena.c @@ -0,0 +1,566 @@ +/* + * This file is part of the Aaru Data Preservation Suite. + * Copyright (c) 2019-2026 Natalia Portillo. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/* + * repair-cd-arena + * + * Recovers CD sector prefix/suffix arenas in AaruFormat v2 images damaged by the resume bug in which a + * resumed dump session restarted the custom prefix/suffix arena at offset 0. That left the final + * deduplication table (DDT2) referencing slot indexes that point past the (shrunken) final data block, + * so errored sectors read back as garbage. + * + * Such an image still physically contains every dump session's prefix/suffix data block and DDT2 (the old + * generations are merely orphaned, not overwritten). This tool scans the file for all generations, and for + * every custom entry in the final DDT2 finds the generation whose DDT2 held that exact (status,index) value + * and whose data block actually contains the slot, then rebuilds a fresh compact arena + DDT2 with + * renumbered indexes and rewrites the index to point at them. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "aaruformattool.h" +#include "usage.h" + +#define PREFIX_SLOT 16u +#define SUFFIX_SLOT 288u + +/// A decoded prefix/suffix generation: one data block and its matching DDT2, both for the same data type. +typedef struct +{ + uint16_t data_type; // 69 (prefix) or 70 (suffix) + uint64_t block_offset; // file offset of the DataBlock (for ordering: session order == file order) + uint8_t *block; // decoded arena bytes + uint64_t block_length; // arena length in bytes + uint64_t ddt_offset; // file offset of the DDT2 + uint64_t *ddt; // decoded DDT2 entries (one uint64_t per internal sector) + uint64_t ddt_entries; // number of DDT2 entries +} Generation; + +static uint32_t status_of(const uint64_t entry) { return (uint32_t)(entry >> 60); } +static uint64_t index_of(const uint64_t entry) { return entry & 0x0FFFFFFFFFFFFFFFULL; } + +/// True when the entry references a stored slot (custom bytes), false for the inline/regenerable statuses. +static bool is_custom(const uint64_t entry) +{ + switch(status_of(entry)) + { + case SectorStatusNotDumped: + case SectorStatusMode1Correct: + case SectorStatusMode2Form1Ok: + case SectorStatusMode2Form2Ok: + case SectorStatusMode2Form2NoCrc: return false; + default: return true; + } +} + +/// Decodes a DataBlock/DDT2 payload (compression none/lzma/zstd) into a freshly allocated buffer whose CRC64 +/// matches @p crc64. The compressed payload starts at @p payload in the in-memory image, @p cmp_length long. +static uint8_t *decode_payload(const uint8_t *payload, const uint16_t compression, const uint64_t cmp_length, + const uint64_t length, const uint64_t crc64) +{ + uint8_t *out = NULL; + + if(compression == kCompressionNone) + { + out = malloc(length); + if(out != NULL) memcpy(out, payload, length); + } + else if(compression == kCompressionLzma) + { + if(cmp_length < LZMA_PROPERTIES_LENGTH) return NULL; + out = malloc(length); + if(out != NULL) + { + size_t out_size = length; + size_t src_size = cmp_length - LZMA_PROPERTIES_LENGTH; + if(aaruf_lzma_decode_buffer(out, &out_size, payload + LZMA_PROPERTIES_LENGTH, &src_size, + payload, LZMA_PROPERTIES_LENGTH) != 0 || + out_size != length) + { + free(out); + out = NULL; + } + } + } + else if(compression == kCompressionZstd) + { + out = malloc(length); + if(out != NULL && aaruf_zstd_decode_buffer(out, length, payload, cmp_length) != length) + { + free(out); + out = NULL; + } + } + + if(out != NULL && aaruf_crc64_data(out, (uint32_t)length) != crc64) + { + free(out); + return NULL; + } + + return out; +} + +/// Scans the in-memory image for prefix/suffix DataBlock and DDT2 blocks, decoding and CRC-validating each, +/// and pairs each DDT2 with the nearest preceding data block of the same type into a Generation. +static Generation *scan_generations(const uint8_t *img, const uint64_t file_size, size_t *out_count) +{ + // Collect decoded data blocks and DDT2s separately, then pair by (type, order). + typedef struct + { + uint16_t type; + uint64_t offset; + uint8_t *data; + uint64_t length; + } Decoded; + + Decoded *blocks = NULL, *ddts = NULL; + size_t nblocks = 0, nddts = 0, capb = 0, capd = 0; + + for(uint64_t pos = 0; pos + sizeof(BlockHeader) <= file_size; pos++) + { + uint32_t ident; + memcpy(&ident, img + pos, sizeof(ident)); + + if(ident == DataBlock) + { + BlockHeader bh; + memcpy(&bh, img + pos, sizeof(bh)); + if(bh.type != kDataTypeCdSectorPrefix && bh.type != kDataTypeCdSectorSuffix) continue; + if(bh.length == 0 || bh.length > file_size) continue; + + uint64_t payload = pos + sizeof(BlockHeader); + if(bh.compression == kCompressionLzma) payload += LZMA_PROPERTIES_LENGTH; + if(payload + bh.cmpLength > file_size) continue; + uint8_t *data = decode_payload(img + payload, bh.compression, bh.cmpLength, bh.length, bh.crc64); + if(data == NULL) continue; + + if(nblocks == capb) + { + capb = capb ? capb * 2 : 8; + blocks = realloc(blocks, capb * sizeof(Decoded)); + } + blocks[nblocks++] = (Decoded){bh.type, pos, data, bh.length}; + } + else if(ident == DeDuplicationTable2) + { + DdtHeader2 dh; + if(pos + sizeof(DdtHeader2) > file_size) continue; + memcpy(&dh, img + pos, sizeof(dh)); + if(dh.type != kDataTypeCdSectorPrefix && dh.type != kDataTypeCdSectorSuffix) continue; + if(dh.length == 0 || dh.length > file_size || (dh.length % sizeof(uint64_t)) != 0) continue; + + uint64_t payload = pos + sizeof(DdtHeader2); + if(dh.compression == kCompressionLzma) payload += LZMA_PROPERTIES_LENGTH; + if(payload + dh.cmpLength > file_size) continue; + uint8_t *data = decode_payload(img + payload, dh.compression, dh.cmpLength, dh.length, dh.crc64); + if(data == NULL) continue; + + if(nddts == capd) + { + capd = capd ? capd * 2 : 8; + ddts = realloc(ddts, capd * sizeof(Decoded)); + } + ddts[nddts++] = (Decoded){dh.type, pos, data, dh.length}; + } + } + + // Pair every DDT2 with the closest preceding data block of the same type (they are written together). + Generation *gens = calloc(nddts, sizeof(Generation)); + size_t ngens = 0; + + for(size_t i = 0; i < nddts; i++) + { + Decoded *best = NULL; + for(size_t j = 0; j < nblocks; j++) + if(blocks[j].type == ddts[i].type && blocks[j].offset < ddts[i].offset && + (best == NULL || blocks[j].offset > best->offset)) + best = &blocks[j]; + + if(best == NULL) continue; + + gens[ngens].data_type = ddts[i].type; + gens[ngens].block_offset = best->offset; + gens[ngens].block = best->data; + gens[ngens].block_length = best->length; + gens[ngens].ddt_offset = ddts[i].offset; + gens[ngens].ddt = (uint64_t *)ddts[i].data; + gens[ngens].ddt_entries = ddts[i].length / sizeof(uint64_t); + ngens++; + } + + free(blocks); + free(ddts); + *out_count = ngens; + return gens; +} + +/// Locates the slot bytes for one custom entry by walking generations for a matching (status,index) whose +/// block holds the slot. Returns a pointer into a generation's block, or NULL if unrecoverable. +static const uint8_t *find_slot(const Generation *gens, const size_t ngens, const uint16_t type, + const uint64_t sector, const uint64_t entry, const uint32_t slot_size) +{ + const uint64_t idx = index_of(entry); + + for(size_t g = 0; g < ngens; g++) + { + if(gens[g].data_type != type) continue; + if(sector >= gens[g].ddt_entries) continue; + if(gens[g].ddt[sector] != entry) continue; + if((idx + 1) * slot_size > gens[g].block_length) continue; + return gens[g].block + idx * slot_size; + } + + return NULL; +} + +/// Rebuilds one arena (prefix or suffix) from the final DDT2. On success returns the new compact arena and +/// fills @p new_ddt (caller-provided, entries long) with renumbered entries; reports recovered/lost counts. +static uint8_t *rebuild_arena(const Generation *gens, const size_t ngens, const uint16_t type, + const uint64_t *final_ddt, const uint64_t entries, const uint32_t slot_size, + uint64_t *new_ddt, uint64_t *out_length, uint64_t *recovered, uint64_t *lost) +{ + uint64_t customs = 0; + for(uint64_t s = 0; s < entries; s++) + if(is_custom(final_ddt[s])) customs++; + + uint8_t *arena = customs ? calloc(customs, slot_size) : NULL; + uint64_t next_slot = 0; + *recovered = 0; + *lost = 0; + + for(uint64_t s = 0; s < entries; s++) + { + if(!is_custom(final_ddt[s])) + { + new_ddt[s] = final_ddt[s]; + continue; + } + + const uint8_t *slot = find_slot(gens, ngens, type, s, final_ddt[s], slot_size); + if(slot == NULL) + { + // Unrecoverable: mark NotDumped so the sector reads back as a gap instead of garbage. + new_ddt[s] = (uint64_t)SectorStatusNotDumped << 60; + (*lost)++; + continue; + } + + memcpy(arena + next_slot * slot_size, slot, slot_size); + new_ddt[s] = ((uint64_t)status_of(final_ddt[s]) << 60) | next_slot; + next_slot++; + (*recovered)++; + } + + *out_length = next_slot * slot_size; + return arena; +} + +/// Appends a DataBlock (uncompressed) at an aligned EOF and returns its file offset (via @p out_offset). +static int append_data_block(FILE *fp, const uint16_t type, const uint8_t *data, const uint64_t length, + const uint8_t alignment_shift, uint64_t *out_offset) +{ + fseek(fp, 0, SEEK_END); + uint64_t pos = (uint64_t)ftell(fp); + const uint64_t mask = (1ULL << alignment_shift) - 1; + pos = (pos + mask) & ~mask; + fseek(fp, (long)pos, SEEK_SET); + + BlockHeader bh = {0}; + bh.identifier = DataBlock; + bh.type = type; + bh.compression = kCompressionNone; + bh.sectorSize = type == kDataTypeCdSectorPrefix ? PREFIX_SLOT : SUFFIX_SLOT; + bh.cmpLength = (uint32_t)length; + bh.length = (uint32_t)length; + bh.crc64 = length ? aaruf_crc64_data(data, (uint32_t)length) : 0; + bh.cmpCrc64 = bh.crc64; + + if(fwrite(&bh, sizeof(bh), 1, fp) != 1) return EIO; + if(length && fwrite(data, length, 1, fp) != 1) return EIO; + + *out_offset = pos; + return 0; +} + +/// Appends a DDT2 (uncompressed) at an aligned EOF, cloning @p templ's geometry, and returns its offset. +static int append_ddt2(FILE *fp, const DdtHeader2 *templ, const uint16_t type, const uint64_t *ddt, + const uint64_t entries, uint64_t *out_offset) +{ + fseek(fp, 0, SEEK_END); + uint64_t pos = (uint64_t)ftell(fp); + const uint64_t mask = (1ULL << templ->blockAlignmentShift) - 1; + pos = (pos + mask) & ~mask; + fseek(fp, (long)pos, SEEK_SET); + + const uint64_t length = entries * sizeof(uint64_t); + + DdtHeader2 dh = *templ; + dh.identifier = DeDuplicationTable2; + dh.type = type; + dh.compression = kCompressionNone; + dh.entries = entries; + dh.cmpLength = length; + dh.length = length; + dh.crc64 = aaruf_crc64_data((const uint8_t *)ddt, (uint32_t)length); + dh.cmpCrc64 = dh.crc64; + + if(fwrite(&dh, sizeof(dh), 1, fp) != 1) return EIO; + if(length && fwrite(ddt, length, 1, fp) != 1) return EIO; + + *out_offset = pos; + return 0; +} + +int repair_cd_arena(const char *path, const bool dry_run) +{ + print_banner(); + printf("repair-cd-arena on '%s'%s\n\n", path, dry_run ? " (dry run)" : ""); + + FILE *fp = fopen(path, dry_run ? "rb" : "r+b"); + if(fp == NULL) + { + printf("ERROR: cannot open '%s': %s\n", path, strerror(errno)); + return errno; + } + + AaruHeaderV2 header; + if(fread(&header, 1, sizeof(header), fp) != sizeof(header)) + { + printf("ERROR: cannot read header\n"); + fclose(fp); + return EIO; + } + if(header.identifier != AARU_MAGIC || header.imageMajorVersion != AARUF_VERSION_V2) + { + printf("ERROR: not an AaruFormat v2 image\n"); + fclose(fp); + return EINVAL; + } + + fseek(fp, 0, SEEK_END); + const uint64_t file_size = (uint64_t)ftell(fp); + + // Load the whole image into memory for fast scanning/decoding. + uint8_t *img = malloc(file_size); + if(img == NULL) + { + printf("ERROR: cannot allocate %llu bytes for image\n", (unsigned long long)file_size); + fclose(fp); + return ENOMEM; + } + fseek(fp, 0, SEEK_SET); + if(fread(img, 1, file_size, fp) != file_size) + { + printf("ERROR: cannot read image into memory\n"); + free(img); + fclose(fp); + return EIO; + } + + // --- Read the current index (IndexBlock3 chain) --- + size_t index_count = 0, index_cap = 0; + IndexEntry *index = NULL; + uint64_t idx_off = header.indexOffset; + while(idx_off) + { + IndexHeader3 ih; + if(fseek(fp, (long)idx_off, SEEK_SET) != 0 || fread(&ih, 1, sizeof(ih), fp) != sizeof(ih) || + ih.identifier != IndexBlock3) + { + printf("ERROR: cannot read index at %llu\n", (unsigned long long)idx_off); + free(index); + fclose(fp); + return EIO; + } + for(uint64_t i = 0; i < ih.entries; i++) + { + IndexEntry e; + if(fread(&e, 1, sizeof(e), fp) != sizeof(e)) break; + if(index_count == index_cap) + { + index_cap = index_cap ? index_cap * 2 : 32; + index = realloc(index, index_cap * sizeof(IndexEntry)); + } + index[index_count++] = e; + } + idx_off = ih.previous; + } + + // --- Locate the final prefix/suffix DDT2 the index references --- + uint64_t final_prefix_ddt_off = 0, final_suffix_ddt_off = 0; + for(size_t i = 0; i < index_count; i++) + if(index[i].blockType == DeDuplicationTable2) + { + if(index[i].dataType == kDataTypeCdSectorPrefix) final_prefix_ddt_off = index[i].offset; + else if(index[i].dataType == kDataTypeCdSectorSuffix) + final_suffix_ddt_off = index[i].offset; + } + + if(final_prefix_ddt_off == 0 && final_suffix_ddt_off == 0) + { + printf("Image has no CD sector prefix/suffix DDT2; nothing to repair.\n"); + free(index); + fclose(fp); + return 0; + } + + // --- Scan all generations --- + size_t ngens = 0; + Generation *gens = scan_generations(img, file_size, &ngens); + printf("Found %zu prefix/suffix generations.\n", ngens); + + int rc = 0; + + // Process both arenas. + struct + { + uint16_t type; + uint64_t final_ddt_off; + uint32_t slot; + uint64_t new_block_off; + uint64_t new_ddt_off; + } jobs[2] = { + {kDataTypeCdSectorPrefix, final_prefix_ddt_off, PREFIX_SLOT, 0, 0}, + {kDataTypeCdSectorSuffix, final_suffix_ddt_off, SUFFIX_SLOT, 0, 0}, + }; + + for(int j = 0; j < 2; j++) + { + if(jobs[j].final_ddt_off == 0) continue; + + // Find the final DDT2 among the scanned generations (by offset) and its header template. + const Generation *final_gen = NULL; + for(size_t g = 0; g < ngens; g++) + if(gens[g].data_type == jobs[j].type && gens[g].ddt_offset == jobs[j].final_ddt_off) + final_gen = &gens[g]; + + if(final_gen == NULL) + { + printf("WARNING: final %s DDT2 could not be decoded; skipping.\n", + jobs[j].type == kDataTypeCdSectorPrefix ? "prefix" : "suffix"); + continue; + } + + DdtHeader2 templ; + if(fseek(fp, (long)jobs[j].final_ddt_off, SEEK_SET) != 0 || + fread(&templ, 1, sizeof(templ), fp) != sizeof(templ)) + { + rc = EIO; + break; + } + + const uint64_t entries = final_gen->ddt_entries; + uint64_t *new_ddt = malloc(entries * sizeof(uint64_t)); + uint64_t reclen = 0, recovered = 0, lost = 0; + uint8_t *arena = + rebuild_arena(gens, ngens, jobs[j].type, final_gen->ddt, entries, jobs[j].slot, new_ddt, &reclen, + &recovered, &lost); + + printf(" %s: %llu custom slots recovered, %llu unrecoverable (marked not-dumped).\n", + jobs[j].type == kDataTypeCdSectorPrefix ? "prefix" : "suffix", + (unsigned long long)recovered, (unsigned long long)lost); + + if(!dry_run) + { + if(append_data_block(fp, jobs[j].type, arena, reclen, templ.blockAlignmentShift, + &jobs[j].new_block_off) != 0 || + append_ddt2(fp, &templ, jobs[j].type, new_ddt, entries, &jobs[j].new_ddt_off) != 0) + rc = EIO; + } + + free(arena); + free(new_ddt); + if(rc) break; + } + + // --- Rewrite the index pointing at the new blocks, and update the header --- + if(!dry_run && rc == 0) + { + for(size_t i = 0; i < index_count; i++) + { + if(index[i].blockType == DataBlock && index[i].dataType == kDataTypeCdSectorPrefix && jobs[0].new_block_off) + index[i].offset = jobs[0].new_block_off; + else if(index[i].blockType == DeDuplicationTable2 && index[i].dataType == kDataTypeCdSectorPrefix && + jobs[0].new_ddt_off) + index[i].offset = jobs[0].new_ddt_off; + else if(index[i].blockType == DataBlock && index[i].dataType == kDataTypeCdSectorSuffix && + jobs[1].new_block_off) + index[i].offset = jobs[1].new_block_off; + else if(index[i].blockType == DeDuplicationTable2 && index[i].dataType == kDataTypeCdSectorSuffix && + jobs[1].new_ddt_off) + index[i].offset = jobs[1].new_ddt_off; + } + + fseek(fp, 0, SEEK_END); + uint64_t new_index = (uint64_t)ftell(fp); + const uint64_t mask = (1ULL << header.blockAlignmentShift) - 1; + new_index = (new_index + mask) & ~mask; + fseek(fp, (long)new_index, SEEK_SET); + + IndexHeader3 ih = {0}; + ih.identifier = IndexBlock3; + ih.entries = index_count; + ih.previous = 0; // single self-contained index segment + ih.crc64 = aaruf_crc64_data((const uint8_t *)index, (uint32_t)(index_count * sizeof(IndexEntry))); + + if(fwrite(&ih, sizeof(ih), 1, fp) != 1 || fwrite(index, sizeof(IndexEntry), index_count, fp) != index_count) + rc = EIO; + else + { + header.indexOffset = new_index; + fseek(fp, 0, SEEK_SET); + if(fwrite(&header, sizeof(header), 1, fp) != 1) rc = EIO; + } + + if(rc == 0) + { + fflush(fp); + printf("\nRepair written. New index at %llu.\n", (unsigned long long)new_index); + } + } + else if(dry_run) + printf("\nDry run only; no changes written.\n"); + + for(size_t g = 0; g < ngens; g++) + { + free(gens[g].block); + free(gens[g].ddt); + } + free(gens); + free(index); + free(img); + fclose(fp); + + if(rc) printf("ERROR: repair failed (%d)\n", rc); + return rc; +} diff --git a/tool/usage.c b/tool/usage.c index 8f707b6..798af36 100644 --- a/tool/usage.c +++ b/tool/usage.c @@ -46,6 +46,7 @@ void usage() printf(" inject-media-tag Injects a media tag into an AaruFormat image.\n"); printf(" read Reads a sector and prints it out on screen.\n"); printf(" read_long Reads a sector with all its prefixes and suffixes.\n"); + printf(" repair-cd-arena Repairs CD sector prefix/suffix arenas damaged by the resume bug.\n"); printf(" upgrade-ddt-to-alpha21 Upgrades a DDT image to alpha21 format.\n"); printf(" verify Verifies the integrity of blocks in an image.\n"); printf(" verify_sectors Verifies the integrity of all sectors in an image.\n\n"); @@ -162,6 +163,18 @@ void usage_upgrade_ddt_to_alpha21() printf(" Path to the DDT image file to upgrade.\n"); } +void usage_repair_cd_arena() +{ + printf("\nUsage:\n"); + printf(" aaruformattool repair-cd-arena [--dry-run] \n\n"); + printf("Repairs CD sector prefix/suffix arenas in a v2 image whose resumed dump sessions left the\n"); + printf("final deduplication table referencing slots past the shrunken data block (garbage reads on\n"); + printf("errored sectors). Recovers the slots from the orphaned dump generations still in the file.\n"); + printf("Arguments:\n"); + printf(" --dry-run Only report how many slots are recoverable; write nothing.\n"); + printf(" Path to the AaruFormat image to repair (modified in place).\n"); +} + void usage_inject_media_tag() { printf("\nUsage:\n"); diff --git a/tool/usage.h b/tool/usage.h index fafbe9c..11f412f 100644 --- a/tool/usage.h +++ b/tool/usage.h @@ -32,6 +32,7 @@ void usage_compare(); void usage_cli_compare(); void usage_convert(); void usage_upgrade_ddt_to_alpha21(); +void usage_repair_cd_arena(); void usage_inject_media_tag(); void usage_convert_ps3(); void usage_convert_wiiu();