Damaged Archive Repair Tool Dart Fix [TRUSTED]

No damaged archive repair tool is magic. Be aware of these limitations:

Prevention is better than recovery. Follow these rules:

For an archive that is truly damaged (e.g., partially corrupted files, encoding errors), dart fix is not enough. Use it in tandem with: damaged archive repair tool dart fix

The Damaged Archive Repair Tool (DART) is a mediocre but sometimes lifesaving utility. It’s overpriced for what it does, and its feature set hasn’t evolved much in the last 5 years. However, when you have a single, non-encrypted, mildly corrupted ZIP or RAR file, and you don’t want to touch a command line, DART will probably work.

Recommendation:

Bottom line: 3.2 stars – it works when conditions are perfect, but don’t expect miracles. Keep backups next time.

Dart Fix is a simple, command-line repair utility designed to detect, analyze, and attempt automated recovery of common issues in archive files. It supports popular formats (ZIP, TAR, GZ, and RAR via optional support) and uses a stepwise strategy: No damaged archive repair tool is magic

Dart Fix emphasizes safety: it never overwrites the original file and produces a recovery report listing which files were fully recovered, partially recovered, or unrecoverable.

Dart Fix provides a focused, safe, and practical way to attempt recovery of damaged archives: it scans, salvages, and rebuilds while preserving the original file. Use it as part of a conservative recovery workflow: copy the original, run scans and salvage, verify recovered files, and maintain good backup and verification practices to reduce future risk. Bottom line: 3

If you want, I can:

(Reminder: do not run Dart Fix on the original file — always work with a copy.)

// damaged_archive_repair_tool.dart
import 'dart:io';
import 'dart:typed_data';
import 'dart:convert';
import 'package:archive/archive.dart';
/// Main tool for detecting and repairing damaged archive files
class DamagedArchiveRepairTool 
  final String archivePath;
  late File _archiveFile;
  late List<int> _originalBytes;
RepairStatistics statistics = RepairStatistics();
DamagedArchiveRepairTool(this.archivePath) 
    _archiveFile = File(archivePath);
/// Main entry point: analyze and attempt repair
  Future<RepairResult> repair(bool aggressive = false) async 
    print('🔧 Damaged Archive Repair Tool');
    print('📁 Processing: $archivePath');
// Step 1: Check if file exists
    if (!await _archiveFile.exists()) 
      return RepairResult.failure('Archive file not found');
// Step 2: Read file bytes
    _originalBytes = await _archiveFile.readAsBytes();
    statistics.originalSize = _originalBytes.length;
    print('📦 Original size: $_formatSize(_originalBytes.length)');
// Step 3: Analyze damage
    final damageReport = await _analyzeDamage();
    print('\n🔍 Damage Analysis:');
    print(damageReport);
// Step 4: Attempt repair based on damage type
    List<int>? repairedBytes;
if (damageReport.hasCorruptCentralDirectory) 
      print('\n🛠️ Attempting central directory repair...');
      repairedBytes = await _repairCentralDirectory();
if (damageReport.hasCorruptLocalHeaders && repairedBytes == null) 
      print('\n🛠️ Attempting local header repair...');
      repairedBytes = await _repairLocalHeaders();
if (damageReport.hasTruncatedData && repairedBytes == null) 
      print('\n🛠️ Attempting truncation recovery...');
      repairedBytes = await _recoverTruncatedData();
if (aggressive && repairedBytes == null) 
      print('\n⚠️ Aggressive recovery mode...');
      repairedBytes = await _aggressiveRecovery();
// Step 5: Validate and save repair
    if (repairedBytes != null && repairedBytes.isNotEmpty) 
      final isValid = await _validateArchive(repairedBytes);
if (isValid) 
        final repairedPath = _getRepairedPath();
        await File(repairedPath).writeAsBytes(repairedBytes);
        statistics.repairedSize = repairedBytes.length;
print('\n✅ Repair successful!');
        print('📁 Repaired archive saved to: $repairedPath');
        print('📊 Statistics:\n$statistics');
return RepairResult.success(repairedPath, statistics);
       else 
        print('\n❌ Repair produced invalid archive');
        return RepairResult.failure('Repair validation failed');
print('\n❌ Could not repair archive automatically');
    return RepairResult.failure('No repair method succeeded');
/// Analyze archive to identify damage patterns
  Future<DamageReport> _analyzeDamage() async 
    final report = DamageReport();
// Check for ZIP signature patterns
    const localHeaderSig = [0x50, 0x4B, 0x03, 0x04];
    const centralDirSig = [0x50, 0x4B, 0x01, 0x02];
    const endOfCentralDirSig = [0x50, 0x4B, 0x05, 0x06];
// Find signatures
    final localHeaders = _findSignatureIndices(localHeaderSig);
    final centralDirs = _findSignatureIndices(centralDirSig);
    final endCentralDirs = _findSignatureIndices(endOfCentralDirSig);
report.localHeaderCount = localHeaders.length;
    report.centralDirectoryCount = centralDirs.length;
    report.endCentralDirectoryCount = endCentralDirs.length;
// Check for truncation
    if (endCentralDirs.isEmpty) 
      report.hasTruncatedData = true;
      report.truncationPoint = _findLastValidStructure();
// Check for corrupt central directory
    if (centralDirs.isNotEmpty && endCentralDirs.isEmpty) 
      report.hasCorruptCentralDirectory = true;
// Check for corrupt local headers
    int corruptHeaders = 0;
    for (final offset in localHeaders) 
      if (!_isValidLocalHeader(offset)) 
        corruptHeaders++;
report.corruptLocalHeaderCount = corruptHeaders;
    report.hasCorruptLocalHeaders = corruptHeaders > 0;
// Check for data corruption
    report.hasDataCorruption = await _detectDataCorruption();
return report;
/// Repair corrupt central directory
  Future<List<int>?> _repairCentralDirectory() async 
    try 
      print('   Rebuilding central directory from local headers...');
final localHeaders = await _extractLocalHeaders();
      if (localHeaders.isEmpty) return null;
final rebuiltCentralDir = _rebuildCentralDirectory(localHeaders);
      final repairedArchive = _replaceCentralDirectory(rebuiltCentralDir);
statistics.repairMethods.add('Central directory rebuild');
      return repairedArchive;
     catch (e) 
      print('   Central directory repair failed: $e');
      return null;
/// Repair corrupt local file headers
  Future<List<int>?> _repairLocalHeaders() async 
    try 
      print('   Attempting to repair individual local headers...');
final repairedBytes = List<int>.from(_originalBytes);
      int repairs = 0;
// Scan for header patterns and fix common corruptions
      for (int i = 0; i < repairedBytes.length - 4; i++)  repairedBytes[i+2] == 0x01))
// Fix version and flag bytes if needed
          if (repairedBytes[i+3] != 0x04) 
            repairedBytes[i+3] = 0x04;
            repairs++;
if (repairs > 0) 
        statistics.repairMethods.add('Local header repair ($repairs fixes)');
        return repairedBytes;
return null;
     catch (e) 
      print('   Local header repair failed: $e');
      return null;
/// Recover data from truncated archive
  Future<List<int>?> _recoverTruncatedData() async 
    try 
      print('   Searching for recoverable data in truncated file...');
// Try to find complete entries even if central directory is missing
      final entries = <ArchiveEntry>[];
      int offset = 0;
while (offset < _originalBytes.length - 30) 
        final entry = _tryParseEntryAt(offset);
        if (entry != null) 
          entries.add(entry);
          offset += 30 + entry.compressedSize;
         else 
          offset++;
if (entries.isNotEmpty) 
        print('   Recovered $entries.length entries');
        final newArchive = _createArchiveFromEntries(entries);
        statistics.repairMethods.add('Truncation recovery ($entries.length entries)');
        return newArchive;
return null;
     catch (e) 
      print('   Truncation recovery failed: $e');
      return null;
/// Aggressive recovery - try to extract any readable data
  Future<List<int>?> _aggressiveRecovery() async 
    print('   Scanning for any readable data patterns...');
final recoveredData = <int>[];
    final Set<int> validBytes = 0x50, 0x4B, 0x03, 0x04, 0x50, 0x4B, 0x01, 0x02;
for (int i = 0; i < _originalBytes.length; i++)
if (recoveredData.length > _originalBytes.length * 0.5) 
      statistics.repairMethods.add('Aggressive byte-level recovery');
      return recoveredData;
return null;
/// Validate if repaired archive is readable
  Future<bool> _validateArchive(List<int> bytes) async 
    try 
      final archive = ZipDecoder().decodeBytes(bytes);
      // Try to read first few entries to verify integrity
      for (var i = 0; i < archive.files.length && i < 5; i++) 
        final file = archive.files[i];
        if (file.isFile) 
          final content = file.content;
          if (content.isEmpty && file.size > 0) return false;
statistics.validated = true;
      return true;
     catch (e) 
      print('   Validation failed: $e');
      return false;
/// Helper: Find all occurrences of a byte signature
  List<int> _findSignatureIndices(List<int> signature) 
    final indices = <int>[];
    for (int i = 0; i <= _originalBytes.length - signature.length; i++) 
      bool match = true;
      for (int j = 0; j < signature.length; j++) 
        if (_originalBytes[i + j] != signature[j]) 
          match = false;
          break;
if (match) indices.add(i);
return indices;
/// Helper: Check if local header at offset is valid
  bool _isValidLocalHeader(int offset)
/// Helper: Find last valid structure before truncation
  int _findLastValidStructure() 
    // Find last valid local file header
    const sig = [0x50, 0x4B, 0x03, 0x04];
    for (int i = _originalBytes.length - 4; i >= 0; i--) 
      bool match = true;
      for (int j = 0; j < 4; j++) 
        if (_originalBytes[i + j] != sig[j]) 
          match = false;
          break;
if (match) return i;
return _originalBytes.length;
/// Helper: Detect data corruption using checksums
  Future<bool> _detectDataCorruption() async 
    int crcErrors = 0;
    int offset = 0;
while (offset < _originalBytes.length - 30) 
      if (_originalBytes[offset] == 0x50 && 
          _originalBytes[offset+1] == 0x4B && 
          _originalBytes[offset+2] == 0x03 && 
          _originalBytes[offset+3] == 0x04)
// Read CRC from header
        if (offset + 18 < _originalBytes.length) 
          final storedCrc = _readUint32(offset + 14);
          // This is simplified - real CRC check would compute actual CRC
          if (storedCrc == 0xFFFFFFFF 
        offset += 30;
       else 
        offset++;
return crcErrors > 0;
/// Helper: Extract local headers from archive
  Future<List<Map<String, dynamic>>> _extractLocalHeaders() async 
    final headers = <Map<String, dynamic>>[];
    int offset = 0;
while (offset < _originalBytes.length - 30) 
      if (_originalBytes[offset] == 0x50 && 
          _originalBytes[offset+1] == 0x4B && 
          _originalBytes[offset+2] == 0x03 && 
          _originalBytes[offset+3] == 0x04)
headers.add(
          'offset': offset,
          'filenameLength': _readUint16(offset + 26),
          'extraLength': _readUint16(offset + 28),
          'compressedSize': _readUint32(offset + 18),
        );
offset += 30;
       else 
        offset++;
return headers;
/// Helper: Rebuild central directory from local headers
  List<int> _rebuildCentralDirectory(List<Map<String, dynamic>> headers) 
    // Simplified central directory rebuild
    final buffer = <int>[];
// Write central directory signature
    buffer.addAll([0x50, 0x4B, 0x01, 0x02]);
for (final header in headers) 
      // Write minimal central directory entry
      buffer.addAll([0x14, 0x00]); // Version made by
      buffer.addAll([0x14, 0x00]); // Version needed
      buffer.addAll([0x00, 0x00]); // General purpose bit flag
      buffer.addAll([0x08, 0x00]); // Compression method
      buffer.addAll([0x00, 0x00, 0x00, 0x00]); // Mod time
      buffer.addAll([0x00, 0x00, 0x00, 0x00]); // Mod date
      buffer.addAll([0x00, 0x00, 0x00, 0x00]); // CRC-32
      buffer.addAll(_toBytes(header['compressedSize'] as int, 4));
      buffer.addAll(_toBytes(header['compressedSize'] as int, 4));
      buffer.addAll(_toBytes(header['filenameLength'] as int, 2));
      buffer.addAll(_toBytes(header['extraLength'] as int, 2));
      buffer.addAll([0x00, 0x00]); // File comment length
      buffer.addAll([0x00, 0x00]); // Disk number start
      buffer.addAll([0x00, 0x00]); // Internal file attributes
      buffer.addAll([0x00, 0x00, 0x00, 0x00]); // External file attributes
      buffer.addAll(_toBytes(header['offset'] as int, 4));
// Placeholder for filename (would need actual name from original)
      for (int i = 0; i < header['filenameLength']; i++) 
        buffer.add(0x20); // Space as placeholder
// Write end of central directory record
    buffer.addAll([0x50, 0x4B, 0x05, 0x06]);
    buffer.addAll([0x00, 0x00]); // Disk number
    buffer.addAll([0x00, 0x00]); // Central dir disk
    buffer.addAll(_toBytes(headers.length, 2));
    buffer.addAll(_toBytes(headers.length, 2));
    buffer.addAll(_toBytes(buffer.length - 22, 4));
    buffer.addAll([0x00, 0x00, 0x00, 0x00]);
    buffer.addAll([0x00, 0x00]);
return buffer;
/// Helper: Replace central directory in archive
  List<int> _replaceCentralDirectory(List<int> newCentralDir) 
    final repaired = List<int>.from(_originalBytes);
// Find and remove old central directory
    const endSig = [0x50, 0x4B, 0x05, 0x06];
    for (int i = 0; i < repaired.length - 4; i++) 
      if (repaired[i] == endSig[0] && 
          repaired[i+1] == endSig[1] && 
          repaired[i+2] == endSig[2] && 
          repaired[i+3] == endSig[3]) 
        // Truncate at end of central directory
        repaired.length = i;
        break;
// Append new central directory
    repaired.addAll(newCentralDir);
    return repaired;
/// Helper: Parse archive entry at offset
  ArchiveEntry? _tryParseEntryAt(int offset) 
    if (offset + 30 > _originalBytes.length) return null;
try 
      final filenameLength = _readUint16(offset + 26);
      final extraLength = _readUint16(offset + 28);
      final compressedSize = _readUint32(offset + 18);
if (filenameLength > 0 && filenameLength < 256 && 
          compressedSize < 100 * 1024 * 1024) 
        // Likely a valid entry
        return ArchiveEntry('recovered_$offset', compressedSize, 
            isFile: true, 
            compression: ArchiveCompression.STORE);
catch (e) 
      // Invalid entry
return null;
/// Helper: Create new archive from recovered entries
  List<int> _createArchiveFromEntries(List<ArchiveEntry> entries) 
    final archive = Archive();
    for (final entry in entries) 
      archive.addFile(entry);
return ZipEncoder().encode(archive)!;
/// Helper: Read Uint16 from bytes
  int _readUint16(int offset)  (_originalBytes[offset + 1] << 8);
/// Helper: Read Uint32 from bytes
  int _readUint32(int offset)  
           (_originalBytes[offset + 1] << 8)
/// Helper: Convert integer to byte list
  List<int> _toBytes(int value, int length) 
    final bytes = <int>[];
    for (int i = 0; i < length; i++) 
      bytes.add((value >> (i * 8)) & 0xFF);
return bytes;
/// Helper: Format file size for display
  String _formatSize(int bytes) 
    if (bytes < 1024) return '$bytes B';
    if (bytes < 1024 * 1024) return '$(bytes / 1024).toStringAsFixed(1) KB';
    return '$(bytes / (1024 * 1024)).toStringAsFixed(1) MB';
/// Helper: Generate repaired file path
  String _getRepairedPath() 
    final file = File(archivePath);
    final dir = file.parent.path;
    final name = file.uri.pathSegments.last;
    final extIndex = name.lastIndexOf('.');
if (extIndex > 0) 
      return '$dir/$name.substring(0, extIndex)_repaired$name.substring(extIndex)';
return '$dir/$name_repaired';
/// Damage analysis report
class DamageReport 
  int localHeaderCount = 0;
  int centralDirectoryCount = 0;
  int endCentralDirectoryCount = 0;
  int corruptLocalHeaderCount = 0;
  bool hasCorruptCentralDirectory = false;
  bool hasCorruptLocalHeaders = false;
  bool hasTruncatedData = false;
  bool hasDataCorruption = false;
  int truncationPoint = 0;
@override
  String toString() 
    final buffer = StringBuffer();
    buffer.writeln('   • Local headers found: $localHeaderCount');
    buffer.writeln('   • Central directory entries: $centralDirectoryCount');
    buffer.writeln('   • End of central directory: $endCentralDirectoryCount > 0 ? 'Present' : 'Missing'');
    buffer.writeln('   • Corrupt local headers: $corruptLocalHeaderCount');
    buffer.writeln('   • Data corruption detected: $hasDataCorruption');
    buffer.writeln('   • Truncated archive: $hasTruncatedData');
    return buffer.toString();
/// Repair statistics
class RepairStatistics 
  int originalSize = 0;
  int repairedSize = 0;
  int corruptBytesFixed = 0;
  bool validated = false;
  List<String> repairMethods = [];
@override
  String toString() 
    return '''
   Original size: $_formatSize(originalSize)
   Repaired size: $_formatSize(repairedSize)
   Bytes repaired: $corruptBytesFixed
   Validation passed: $validated
   Methods applied: $repairMethods.join(', ')''';
String _formatSize(int bytes) 
    if (bytes < 1024) return '$bytes B';
    if (bytes < 1024 * 1024) return '$(bytes / 1024).toStringAsFixed(1) KB';
    return '$(bytes / (1024 * 1024)).toStringAsFixed(1) MB';
/// Result of repair operation
class RepairResult 
  final bool success;
  final String? repairedPath;
  final RepairStatistics? statistics;
  final String? error;
RepairResult._(this.success, this.repairedPath, this.statistics, this.error);
factory RepairResult.success(String path, RepairStatistics stats) 
    return RepairResult._(true, path, stats, null);
factory RepairResult.failure(String error) 
    return RepairResult._(false, null, null, error);
@override
  String toString() 
    if (success) 
      return '✅ Success: Archive repaired to $repairedPath';
return '❌ Failed: $error';
/// Command-line interface
void main(List<String> args) async 
  print('═══════════════════════════════════════════');
  print('  Damaged Archive Repair Tool v1.0');
  print('═══════════════════════════════════════════\n');
if (args.isEmpty) 
    print('Usage: dart damaged_archive_repair_tool.dart <archive_path> [--aggressive]');
    print('\nOptions:');
    print('  --aggressive    Enable aggressive recovery mode');
    print('\nExamples:');
    print('  dart repair.dart damaged.zip');
    print('  dart repair.dart corrupted.zip --aggressive');
    exit(1);
final archivePath = args[0];
  final aggressive = args.contains('--aggressive');
final tool = DamagedArchiveRepairTool(archivePath);
try 
    final result = await tool.repair(aggressive: aggressive);
    print('\n$result');
    exit(result.success ? 0 : 1);
   catch (e) 
    print('\n❌ Unexpected error: $e');
    exit(1);