using System; using System.IO; using SabreTools.Data.Models.TAR; using SabreTools.Numerics.Extensions; namespace SabreTools.Wrappers { public partial class TapeArchive : IExtractable { /// public bool Extract(string outputDirectory, bool includeDebug) { // Ensure there are entries to extract if (Entries.Length == 0) return false; try { // Loop through and extract the data for (int i = 0; i < Entries.Length; i++) { var entry = Entries[i]; // Handle special entries var header = entry.Header; switch (header.TypeFlag) { // Skipped types case TypeFlag.LNKTYPE: case TypeFlag.SYMTYPE: case TypeFlag.CHRTYPE: case TypeFlag.BLKTYPE: case TypeFlag.FIFOTYPE: case TypeFlag.XHDTYPE: case TypeFlag.XGLTYPE: if (includeDebug) Console.WriteLine($"Unsupported entry type: {header.TypeFlag}"); continue; // Skipped vendor types case TypeFlag.VendorSpecificA: case TypeFlag.VendorSpecificB: case TypeFlag.VendorSpecificC: case TypeFlag.VendorSpecificD: case TypeFlag.VendorSpecificE: case TypeFlag.VendorSpecificF: case TypeFlag.VendorSpecificG: case TypeFlag.VendorSpecificH: case TypeFlag.VendorSpecificI: case TypeFlag.VendorSpecificJ: case TypeFlag.VendorSpecificK: case TypeFlag.VendorSpecificL: case TypeFlag.VendorSpecificM: case TypeFlag.VendorSpecificN: case TypeFlag.VendorSpecificO: case TypeFlag.VendorSpecificP: case TypeFlag.VendorSpecificQ: case TypeFlag.VendorSpecificR: case TypeFlag.VendorSpecificS: case TypeFlag.VendorSpecificT: case TypeFlag.VendorSpecificU: case TypeFlag.VendorSpecificV: case TypeFlag.VendorSpecificW: case TypeFlag.VendorSpecificX: case TypeFlag.VendorSpecificY: case TypeFlag.VendorSpecificZ: if (includeDebug) Console.WriteLine($"Unsupported vendor entry type: {header.TypeFlag}"); continue; // Directories case TypeFlag.DIRTYPE: string? entryDirectory = header.FileName?.TrimEnd('\0'); if (entryDirectory is null) { if (includeDebug) Console.Error.WriteLine($"Entry {i} reported as directory, but no path found! Skipping..."); continue; } // Ensure directory separators are consistent entryDirectory = Path.Combine(outputDirectory, entryDirectory); entryDirectory = entryDirectory.TrimStart(['\\', '/']); if (Path.DirectorySeparatorChar == '\\') entryDirectory = entryDirectory.Replace('/', '\\'); else if (Path.DirectorySeparatorChar == '/') entryDirectory = entryDirectory.Replace('\\', '/'); // Create the directory Directory.CreateDirectory(entryDirectory); continue; // File types have no special handling case TypeFlag.REGTYPE: case TypeFlag.AREGTYPE: case TypeFlag.CONTTYPE: default: break; } // Get the file size string sizeOctalString = header.Size!.TrimEnd('\0'); if (sizeOctalString.Length == 0) { if (includeDebug) Console.WriteLine($"Entry {i} has an invalid size, skipping..."); continue; } int entrySize = Convert.ToInt32(sizeOctalString, 8); // Setup the temporary buffer byte[] dataBytes = new byte[entrySize]; int dataBytesPtr = 0; // Loop through and copy the bytes to the array for writing int blockNumber = 0; while (entrySize > 0) { // Exit early if block number is invalid if (blockNumber >= entry.Blocks.Length) { if (includeDebug) Console.Error.WriteLine($"Invalid block number {i + 1} of {entry.Blocks.Length}, file may be incomplete!"); break; } // Exit early if the block has no data var block = entry.Blocks[blockNumber++]; if (block.Data is null || block.Data.Length != 512) { if (includeDebug) Console.Error.WriteLine($"Invalid data for block number {i + 1}, file may be incomplete!"); break; } int nextBytes = Math.Min(512, entrySize); entrySize -= nextBytes; Array.Copy(block.Data, 0, dataBytes, dataBytesPtr, nextBytes); dataBytesPtr += nextBytes; } // Ensure directory separators are consistent string filename = header.FileName?.TrimEnd('\0') ?? $"entry_{i}"; filename = filename.TrimStart(['\\', '/']); if (Path.DirectorySeparatorChar == '\\') filename = filename.Replace('/', '\\'); else if (Path.DirectorySeparatorChar == '/') filename = filename.Replace('\\', '/'); // Ensure the full output directory exists filename = Path.Combine(outputDirectory, filename); var directoryName = Path.GetDirectoryName(filename); if (directoryName is not null && !Directory.Exists(directoryName)) Directory.CreateDirectory(directoryName); // Write the file using var fs = File.Open(filename, FileMode.Create, FileAccess.Write, FileShare.None); fs.Write(dataBytes); fs.Flush(); } return true; } catch (Exception ex) { if (includeDebug) Console.Error.WriteLine(ex); return false; } } } }