From 0b596e6d33e35762c0726b3301838f281d034e23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nils=20m=C3=A5s=C3=A9n?= Date: Sat, 15 Aug 2020 15:46:51 +0200 Subject: [PATCH 1/7] Add ArchiveDiag projects --- ArchiveDiag-Worker/.gitignore | 264 ++++++++ ArchiveDiag-Worker/ArchiveDiag-Worker.csproj | 25 + ArchiveDiag-Worker/ProcessArchive.cs | 76 +++ ...ncies.archivediag-worker - Zip Deploy.json | 10 + .../Properties/serviceDependencies.json | 8 + .../Properties/serviceDependencies.local.json | 9 + ArchiveDiag-Worker/host.json | 11 + ArchiveDiag/ArchiveDiag.csproj | 25 + ArchiveDiag/ArchiveDiagRunner.cs | 630 ++++++++++++++++++ ArchiveDiag/ExtraDataType.cs | 151 +++++ ArchiveDiag/JobStatus.cs | 15 + ArchiveDiag/Program.cs | 143 ++++ ArchiveDiag/Properties/launchSettings.json | 8 + ArchiveDiag/ZipVersion.cs | 51 ++ ArchiveDiagWeb/.config/dotnet-tools.json | 12 + ArchiveDiagWeb/ArchiveDiagWeb.bak.csproj | 25 + ArchiveDiagWeb/ArchiveDiagWeb.csproj | 26 + ArchiveDiagWeb/Pages/Index.cshtml | 59 ++ ArchiveDiagWeb/Pages/Index.cshtml.cs | 99 +++ ArchiveDiagWeb/Pages/_ViewImports.cshtml | 1 + ArchiveDiagWeb/Program.cs | 26 + ArchiveDiagWeb/Properties/launchSettings.json | 33 + ...ependencies.archivediag - Web Deploy2.json | 10 + .../Properties/serviceDependencies.json | 13 + .../Properties/serviceDependencies.local.json | 10 + ArchiveDiagWeb/Startup.cs | 89 +++ ArchiveDiagWeb/appsettings.Development.json | 9 + ArchiveDiagWeb/appsettings.json | 10 + ICSharpCode.SharpZipLib.sln | 18 + 29 files changed, 1866 insertions(+) create mode 100644 ArchiveDiag-Worker/.gitignore create mode 100644 ArchiveDiag-Worker/ArchiveDiag-Worker.csproj create mode 100644 ArchiveDiag-Worker/ProcessArchive.cs create mode 100644 ArchiveDiag-Worker/Properties/serviceDependencies.archivediag-worker - Zip Deploy.json create mode 100644 ArchiveDiag-Worker/Properties/serviceDependencies.json create mode 100644 ArchiveDiag-Worker/Properties/serviceDependencies.local.json create mode 100644 ArchiveDiag-Worker/host.json create mode 100644 ArchiveDiag/ArchiveDiag.csproj create mode 100644 ArchiveDiag/ArchiveDiagRunner.cs create mode 100644 ArchiveDiag/ExtraDataType.cs create mode 100644 ArchiveDiag/JobStatus.cs create mode 100644 ArchiveDiag/Program.cs create mode 100644 ArchiveDiag/Properties/launchSettings.json create mode 100644 ArchiveDiag/ZipVersion.cs create mode 100644 ArchiveDiagWeb/.config/dotnet-tools.json create mode 100644 ArchiveDiagWeb/ArchiveDiagWeb.bak.csproj create mode 100644 ArchiveDiagWeb/ArchiveDiagWeb.csproj create mode 100644 ArchiveDiagWeb/Pages/Index.cshtml create mode 100644 ArchiveDiagWeb/Pages/Index.cshtml.cs create mode 100644 ArchiveDiagWeb/Pages/_ViewImports.cshtml create mode 100644 ArchiveDiagWeb/Program.cs create mode 100644 ArchiveDiagWeb/Properties/launchSettings.json create mode 100644 ArchiveDiagWeb/Properties/serviceDependencies.archivediag - Web Deploy2.json create mode 100644 ArchiveDiagWeb/Properties/serviceDependencies.json create mode 100644 ArchiveDiagWeb/Properties/serviceDependencies.local.json create mode 100644 ArchiveDiagWeb/Startup.cs create mode 100644 ArchiveDiagWeb/appsettings.Development.json create mode 100644 ArchiveDiagWeb/appsettings.json diff --git a/ArchiveDiag-Worker/.gitignore b/ArchiveDiag-Worker/.gitignore new file mode 100644 index 000000000..ff5b00c50 --- /dev/null +++ b/ArchiveDiag-Worker/.gitignore @@ -0,0 +1,264 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# Azure Functions localsettings file +local.settings.json + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +#*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc \ No newline at end of file diff --git a/ArchiveDiag-Worker/ArchiveDiag-Worker.csproj b/ArchiveDiag-Worker/ArchiveDiag-Worker.csproj new file mode 100644 index 000000000..ab2d7fd8d --- /dev/null +++ b/ArchiveDiag-Worker/ArchiveDiag-Worker.csproj @@ -0,0 +1,25 @@ + + + netcoreapp3.1 + v3 + ArchiveDiag_Worker + 611792f1-7d4a-46ba-85b5-873da1fe5642 + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + Never + + + \ No newline at end of file diff --git a/ArchiveDiag-Worker/ProcessArchive.cs b/ArchiveDiag-Worker/ProcessArchive.cs new file mode 100644 index 000000000..eab89db51 --- /dev/null +++ b/ArchiveDiag-Worker/ProcessArchive.cs @@ -0,0 +1,76 @@ +using System; +using System.IO; +using System.Text.Json; +using System.Threading.Tasks; +using ArchiveDiagJson; +using Azure.Storage.Blobs; +using Microsoft.Azure.WebJobs; +using Microsoft.Azure.WebJobs.Host; +using Microsoft.Extensions.Logging; +using Microsoft.WindowsAzure.Storage; +using Microsoft.WindowsAzure.Storage.Blob; +using ArchiveDiagApp = ICSharpCode.SharpZipLib.ArchiveDiag.Program; + +namespace SharpZipLib.ArchiveDiag.Worker +{ + public static class ArchiveDiagWorker + { + [FunctionName("ProcessArchive")] + public static async Task Run([BlobTrigger("uploads/{name}", Connection = "AzureWebJobsStorage")]Stream blobStream, string name, ILogger log) + { + log.LogInformation($"C# Blob trigger function Processed blob\n Name:{name} \n Size: {blobStream.Length} Bytes"); + + + // + + log.LogInformation("Creating reports storage client"); + var reports = new BlobContainerClient(Environment.GetEnvironmentVariable("AzureWebJobsStorage"), "reports"); + + var jobs = new BlobContainerClient(Environment.GetEnvironmentVariable("AzureWebJobsStorage"), "jobs"); + + var jobClient = jobs.GetBlobClient(name); + + var job = await jobClient.ExistsAsync() + ? await JsonSerializer.DeserializeAsync((await jobClient.DownloadAsync()).Value.Content) + : new JobStatus() {FileName = "Unknown", Status = ""}; + + + await using (var ms = new MemoryStream()) + { + await JsonSerializer.SerializeAsync(ms, job.WithStatus("processing")); + ms.Seek(0, SeekOrigin.Begin); + await jobClient.UploadAsync(ms, overwrite:true); + } + + var reportTempFile = new FileInfo(Path.GetTempFileName()); + + await using (var reportStream = reportTempFile.Create()) + { + log.LogInformation("Analyzing file..."); + ArchiveDiagApp.Run(blobStream, job.FileName, reportStream); + } + + await using (var reportStream = reportTempFile.OpenRead()) + { + log.LogInformation("Uploading results..."); + await reports.UploadBlobAsync($"{name}.html", reportStream); + } + + + blobStream.Close(); + + log.LogInformation("Deleting upload blob..."); + var uploads = new BlobContainerClient(Environment.GetEnvironmentVariable("AzureWebJobsStorage"), "uploads"); + var deleteResult = await uploads.DeleteBlobAsync(name); + log.LogInformation("Upload deletion result: {Status}, {Reason}", deleteResult.Status, deleteResult.ReasonPhrase); + + await using (var ms = new MemoryStream()) + { + await JsonSerializer.SerializeAsync(ms, job.WithStatus("done")); + ms.Seek(0, SeekOrigin.Begin); + await jobClient.UploadAsync(ms, overwrite: true); + } + + } + } +} diff --git a/ArchiveDiag-Worker/Properties/serviceDependencies.archivediag-worker - Zip Deploy.json b/ArchiveDiag-Worker/Properties/serviceDependencies.archivediag-worker - Zip Deploy.json new file mode 100644 index 000000000..63816d610 --- /dev/null +++ b/ArchiveDiag-Worker/Properties/serviceDependencies.archivediag-worker - Zip Deploy.json @@ -0,0 +1,10 @@ +{ + "dependencies": { + "storage1": { + "resourceId": "/subscriptions/[parameters('subscriptionId')]/resourceGroups/[parameters('resourceGroup')]/providers/Microsoft.Storage/storageAccounts/archivediagstore", + "type": "storage.azure", + "connectionId": "AzureWebJobsStorage", + "secretStore": "AzureAppSettings" + } + } +} \ No newline at end of file diff --git a/ArchiveDiag-Worker/Properties/serviceDependencies.json b/ArchiveDiag-Worker/Properties/serviceDependencies.json new file mode 100644 index 000000000..fcc92d112 --- /dev/null +++ b/ArchiveDiag-Worker/Properties/serviceDependencies.json @@ -0,0 +1,8 @@ +{ + "dependencies": { + "storage1": { + "type": "storage", + "connectionId": "AzureWebJobsStorage" + } + } +} \ No newline at end of file diff --git a/ArchiveDiag-Worker/Properties/serviceDependencies.local.json b/ArchiveDiag-Worker/Properties/serviceDependencies.local.json new file mode 100644 index 000000000..b550bbba9 --- /dev/null +++ b/ArchiveDiag-Worker/Properties/serviceDependencies.local.json @@ -0,0 +1,9 @@ +{ + "dependencies": { + "storage1": { + "resourceId": "/subscriptions/[parameters('subscriptionId')]/resourceGroups/[parameters('resourceGroup')]/providers/Microsoft.Storage/storageAccounts/archivediagstore", + "type": "storage.azure", + "connectionId": "AzureWebJobsStorage" + } + } +} \ No newline at end of file diff --git a/ArchiveDiag-Worker/host.json b/ArchiveDiag-Worker/host.json new file mode 100644 index 000000000..bb3b8dadd --- /dev/null +++ b/ArchiveDiag-Worker/host.json @@ -0,0 +1,11 @@ +{ + "version": "2.0", + "logging": { + "applicationInsights": { + "samplingExcludedTypes": "Request", + "samplingSettings": { + "isEnabled": true + } + } + } +} \ No newline at end of file diff --git a/ArchiveDiag/ArchiveDiag.csproj b/ArchiveDiag/ArchiveDiag.csproj new file mode 100644 index 000000000..dc0d30a18 --- /dev/null +++ b/ArchiveDiag/ArchiveDiag.csproj @@ -0,0 +1,25 @@ + + + + Exe + netcoreapp3.1 + 0.3.5 + + + + + + + + + + + + + + + + + + + diff --git a/ArchiveDiag/ArchiveDiagRunner.cs b/ArchiveDiag/ArchiveDiagRunner.cs new file mode 100644 index 000000000..4e143ebfa --- /dev/null +++ b/ArchiveDiag/ArchiveDiagRunner.cs @@ -0,0 +1,630 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using ConLib; +using ConLib.Console; +using ConLib.HTML; +using ICSharpCode.SharpZipLib.ArchiveDiag; +using ICSharpCode.SharpZipLib.Zip; +using static ConLib.PrettyConsole; + +namespace ArchiveDiag +{ + class ArchiveDiagRunner: IDisposable + { + private readonly HTMLWriter? html = null; + private readonly ColorFormatter? fmt = null; + private Stream archiveStream; + + public int MaxNameHex {get; set;} = 32; + public int MaxNameString {get; set;} = 128; + public int MaxCommentHex {get; set;} = 32; + public int MaxCommentString {get; set;} = 128; + public int MaxExtraDataHex {get; set;} = 64; + + public bool WarnUnknownSigns {get; set;} = false; + + public string? FileName { get; } + + public ArchiveDiagRunner(Stream archiveStream, string? fileName = null) + { + this.archiveStream = archiveStream; + FileName = fileName; + } + + public void Run() + { + Run(new ConsoleWriter()); + } + + public void Run(params ColorWriter[] writers) + { + PrettyFormatters.Clear(); + + foreach (var writer in writers) + { + PrettyFormatters.Add(new ColorFormatter(writer)); + } + + PrettyConsole.ChoreOptions.StartedFormat = "{0} {1}\n"; + PrettyConsole.ChoreOptions.NameColor = ConCol.White; + PrettyConsole.ChoreOptions.EndedFormat = "{0} {1} in "; + + var now = DateTime.UtcNow; + + WriteColor($"ArchiveDiag ", ConCol.White); + WriteVersion(); + WriteLine($"\n"); + + WriteLine($"File: {FileName ?? ""}"); + Write($"Size: {archiveStream.Length}"); + WriteColor(" byte(s)\n", ConCol.DarkGray); + Write($"Generated at: {now:yyyy-MM-dd}"); + WriteColor("T", ConCol.DarkGray); + Write($"{now:HH:mm:ss}"); + WriteColor("Z\n", ConCol.DarkGray); + + WriteLine(); + +#if DEBUG + Console.Read(); +#endif + + // Stream archiveStream = null; + DoTask("Open File", async () => + { + // archiveStream = File.OpenRead(file); + }, continueOnFail: false); + + DoChore("Parse Archive", () => + { + ParseArchive(archiveStream); + }, continueOnFail: true); + + DoTask("Rewind File Stream", async () => + { + archiveStream.Seek(0, SeekOrigin.Begin); + }, continueOnFail: false); + + + DoChore("Test Archive", () => + { + var zf = new ZipFile(archiveStream); + + var result = zf.TestArchive(true, TestStrategy.FindAllErrors, (status, message) => + { + if (status.Operation == TestOperation.EntryData) return; + var pad = "";//.PadLeft(18 - status.Operation.ToString().Length); + if (status.Entry is { } entry) + { + WriteLine($"[{status.Operation}{pad}] #{entry.ZipFileIndex} {entry.Name}"); + } + else + { + WriteLine($"[{status.Operation}{pad}]"); + } + + if (!string.IsNullOrEmpty(message)) + { + WriteLine($"[{status.Operation}{pad}] {message}"); + } + }); + + + WriteLine($"Test result {(result ? "PASSED" : "FAILED")}", result ? ConCol.Green : ConCol.Red); + }, continueOnFail: true); + } + + + private void ParseArchive(Stream transportStream) + { + + transportStream.Seek(0, SeekOrigin.Begin); + + var br = new BinaryReader(transportStream); + + int readVal; + while ((readVal = transportStream.ReadByte()) != -1) + { + if (readVal != 'P') continue; + readVal = transportStream.ReadByte(); + + if (readVal != 'K') continue; + var b1 = (byte)transportStream.ReadByte(); + var b2 = (byte)transportStream.ReadByte(); + + if (b1 > 0x08 || b2 > 0x08) + { + transportStream.Seek(-2, SeekOrigin.Current); + continue; + } + + var sign = 'P' | ('K' << 8) | (b1 << 16) | (b2 << 24); + + var signId = sign switch + { + ZipConstants.DataDescriptorSignature + => nameof(ZipConstants.DataDescriptorSignature), + ZipConstants.CentralHeaderSignature + => nameof(ZipConstants.CentralHeaderSignature), + ZipConstants.CentralHeaderDigitalSignature + => nameof(ZipConstants.CentralHeaderDigitalSignature), + ZipConstants.EndOfCentralDirectorySignature + => nameof(ZipConstants.EndOfCentralDirectorySignature), + ZipConstants.LocalHeaderSignature + => nameof(ZipConstants.LocalHeaderSignature), + ZipConstants.SpanningTempSignature + => nameof(ZipConstants.SpanningTempSignature), + ZipConstants.Zip64CentralDirLocatorSignature + => nameof(ZipConstants.Zip64CentralDirLocatorSignature), + ZipConstants.Zip64CentralFileHeaderSignature + => nameof(ZipConstants.Zip64CentralFileHeaderSignature), + _ => $"Unknown (0x{sign:x8})", + }; + + //if (warnUnknownSigns || signId.StartsWith("Unknown")) + //{ + WriteLine($"{transportStream.Position:x8} PK: {b1:x2} {b2:x2} {signId}"); + //} + + switch (sign) + { + case ZipConstants.LocalHeaderSignature: + DoChore("Parse Local File Header", () + => ParseLocalHeader(br), true); + break; + case ZipConstants.DataDescriptorSignature: + DoChore("Parse Data Descriptor", () + => ParseDataDescriptor(br), true); + break; + case ZipConstants.CentralHeaderSignature: + DoChore("Parse Central Directory Header", () + => ParseCentralHeader(br), true); + break; + case ZipConstants.Zip64CentralFileHeaderSignature: + DoChore("Parse Zip64 End Of Central Directory Record", () + => ParseZip64EndOfCentralDirectory(br), true); + break; + case ZipConstants.Zip64CentralDirLocatorSignature: + DoChore("Parse Zip64 Central Directory Locator", () + => ParseZip64CentralDirLocator(br), true); + break; + case ZipConstants.EndOfCentralDirectorySignature: + DoChore("Parse End of Central Directory Record", () + => ParseCentralDirectory(br), true); + break; + default: + if (WarnUnknownSigns) WriteLine($"{"Skipping unrecognized signature!"}\n"); + break; + } + + } + + } + + private void ParseArchiveExtraData(BinaryReader br) + { + var extraLen = br.ReadUInt16(); + var extraDataBytes = new byte[extraLen]; + br.Read(extraDataBytes); + var extraData = new ZipExtraData(extraDataBytes); + + WriteLine($"Extra data:"); + ParseExtraData(extraData); + } + + private void ParseZip64CentralDirLocator(BinaryReader br) + { + var diskNumber = br.ReadUInt32(); + var relativeOffset = br.ReadUInt64(); + var diskTotal = br.ReadUInt64(); + + WriteLine($"Zip64 End Record Disk Number: {diskNumber}"); + WriteLine($"Zip64 End Record Relative Offset: {relativeOffset} (0x{relativeOffset:x8})"); + WriteLine($"Total number of disks: {diskTotal}"); + } + + private void ParseZip64EndOfCentralDirectory(BinaryReader br) + { + var recordSize = br.ReadUInt64(); + var versionMadeBy = ZipVersion.From(br.ReadUInt16()); + var versionToExtract = ZipVersion.From(br.ReadUInt16()); + var diskNumber = br.ReadUInt32(); + var startCentralDirDisk = br.ReadUInt32(); + + var entriesForDisk = br.ReadUInt64(); + var entriesForWholeCentralDir = br.ReadUInt64(); + var centralDirSize = br.ReadUInt64(); + var offsetOfCentralDir = br.ReadUInt64(); + + var extDataSize = recordSize - 44; + + WriteLine($"Size of record: {recordSize} (0x{recordSize:x16})"); + WriteLine($"Version Made By: {versionMadeBy} (0x{versionMadeBy.OSRaw:x2})"); + WriteLine($"Version Needed: {versionToExtract} (0x{versionToExtract.OSRaw:x2})"); + WriteLine($"Disk Number: {diskNumber}"); + WriteLine($"Start Central Directory Disk: {startCentralDirDisk:x8}"); + + WriteLine($"Entries For Disk: {entriesForDisk}"); + WriteLine($"Entries For Central Directory: {entriesForWholeCentralDir}"); + WriteLine($"Central Directory Size: {centralDirSize} (0x{centralDirSize:x16})"); + WriteLine($"Central Directory Offset: {offsetOfCentralDir} (0x{offsetOfCentralDir:x16})"); + + + WriteLine($"Extensible data size: {extDataSize}"); + if (extDataSize <= 0) return; + + WriteLine($"Parsing of extensible data is not supported! Skipping.", ConCol.Yellow); + // Ignore skipping the data for now, since it is more likely to cause issues than it is to prevent them + //br.BaseStream.Seek((long)extDataSize, SeekOrigin.Current); + } + + private static DateTime DateTimeFromDosTime(uint dosTime) + { + var sec = Math.Min(59, 2 * (dosTime & 0x1f)); + var min = Math.Min(59, (dosTime >> 5) & 0x3f); + var hrs = Math.Min(23, (dosTime >> 11) & 0x1f); + var mon = Math.Max(1, Math.Min(12, ((dosTime >> 21) & 0xf))); + var year = ((dosTime >> 25) & 0x7f) + 1980; + var day = Math.Max(1, Math.Min(DateTime.DaysInMonth((int)year, (int)mon), (int)((dosTime >> 16) & 0x1f))); + return new DateTime((int)year, (int)mon, day, (int)hrs, (int)min, (int)sec); + } + + private void ParseCentralHeader(BinaryReader br) + { + var versionMadeBy = ZipVersion.From(br.ReadUInt16()); + var versionToExtract = ZipVersion.From(br.ReadUInt16()); + var bitFlags = (GeneralBitFlags)br.ReadUInt16(); + var method = (CompressionMethod)br.ReadUInt16(); + var dosTime = br.ReadUInt32(); + var dateTime = DateTimeFromDosTime(dosTime); + var crc = br.ReadUInt32(); + var compressedSize = (long)br.ReadUInt32(); + var size = (long)br.ReadUInt32(); + var nameLen = br.ReadUInt16(); + var extraLen = br.ReadUInt16(); + var commentLen = br.ReadUInt16(); + var diskStartNo = br.ReadUInt16(); // Not currently used + var internalAttributes = br.ReadUInt16(); // Not currently used + + var externalAttributes = (FileAttributes)br.ReadUInt32(); + var offset = br.ReadUInt32(); + + var nameData = new byte[nameLen]; + br.Read(nameData); + + var extraDataBytes = new byte[extraLen]; + br.Read(extraDataBytes); + var extraData = new ZipExtraData(extraDataBytes); + + var commentData = new byte[commentLen]; + br.Read(commentData); + + + WriteLine($"Version Made By: {versionMadeBy} (0x{versionMadeBy.OSRaw:x2})"); + WriteLine($"Version Needed: {versionToExtract} (0x{versionToExtract.OSRaw:x2})"); + WriteLine($"Bit flags: {bitFlags:F}"); + WriteLine($"Compression Method: {method:G}"); + WriteLine($"DOS Date: 0x{dosTime:x8}"); + WriteLine($"File Date: {dateTime:yyyy-MM-dd}"); + WriteLine($"File Time: {dateTime:HH:mm:ss}"); + WriteLine($"CRC: 0x{crc:x8}"); + WriteSize($"Compressed Size:", compressedSize); + WriteSize($"Uncompressed Size:", size); + WriteLine($"Name Length: {nameLen}"); + WriteLine($"Extra Data Length: {extraLen}"); + WriteLine($"Comment Length: {commentLen}"); + WriteLine($"Disk number start: {diskStartNo}"); + WriteLine($"Internal file attributes: {internalAttributes:x4}"); + WriteLine($"External file attributes: {externalAttributes:F}"); + WriteLine($"Relative offset of local header: {offset}"); + + Write($"Name (raw): "); WriteHexBytes(nameData, MaxNameHex); + Write($"Name: "); WriteString(nameData, MaxNameString); + + if (commentLen > 0) + { + Write($"Comment (raw): "); WriteHexBytes(commentData, MaxCommentHex); + Write($"Comment: "); WriteString(commentData, MaxCommentString); + } + + WriteLine($"Extra data:"); + ParseExtraData(extraData); + + } + + private static void WriteSize(FormattableString fs, in long size) + { + Write(fs); + + if (size == uint.MaxValue) + { + WriteLine($" {size} ({"Zip64 Indicator"})", ConCol.DarkGray, ConCol.Blue); + } + else + { + WriteLine($" {size}"); + } + } + + private void ParseExtraData(ZipExtraData extraData) + { + PushGroup("extra-data"); + var extraDataBytes = extraData.GetEntryData(); + + foreach (var (tag, range) in extraData.EnumerateTags().ToList()) + { + var length = range.End.Value - range.Start.Value; + + var knownId = Enum.IsDefined(typeof(ExtraDataType), tag); + + WriteLine($" - Type: {(knownId ? tag : ExtraDataType.Unknown):G} ({(uint)tag:x4}), Length: {length}"); + PushGroup("extra-raw"); + if (extraDataBytes.Length < range.End.Value) + { + WriteLine($" Invalid length! Skipping parse attemmpt!", ConCol.Red); + PopGroup(); // extra-raw + PopGroup(); // extra-data + return; + } + + Write($" Raw: "); + WriteHexBytes(extraDataBytes[range], MaxExtraDataHex); + + PopGroup(); + + + switch (tag) + { + case ExtraDataType.NTFS: + { + PushGroup("extra-ntfs"); + var tagNtDate = extraData.GetData(); + + WriteLine($" Created: {tagNtDate.CreateTime:yyyy-MM-dd HH:mm:ss}"); + WriteLine($" Accessed: {tagNtDate.LastAccessTime:yyyy-MM-dd HH:mm:ss}"); + WriteLine($" Modified: {tagNtDate.LastModificationTime:yyyy-MM-dd HH:mm:ss}"); + PopGroup(); + + break; + } + case ExtraDataType.UnixExtendedTime: + { + PushGroup("extra-unix-xtime"); + + var tagUnixDate = extraData.GetData(); + + WriteLine($" Created: {tagUnixDate.CreateTime:yyyy-MM-dd HH:mm:ss}"); + WriteLine($" Accessed: {tagUnixDate.AccessTime:yyyy-MM-dd HH:mm:ss}"); + WriteLine($" Modified: {tagUnixDate.ModificationTime:yyyy-MM-dd HH:mm:ss}"); + PopGroup(); + break; + } + case ExtraDataType.UnicodeName: + { + PushGroup("extra-unicode-name"); + + var nameRange = new Range(range.Start.Value + 5, range.End.Value); + var unicodeName = Encoding.UTF8.GetString(extraDataBytes[nameRange]); + File.WriteAllText("unicodetext.txt", unicodeName); + WriteLine($" Unicode name: {unicodeName}"); + PopGroup(); + break; + } + case ExtraDataType.Zip64: + { + PushGroup("extra-zip64"); + using (var br = new BinaryReader(extraData.GetStreamForTag((int)tag))) + { + var usize64 = br.ReadUInt64(); + var csize64 = br.ReadUInt64(); + WriteLine($" Compressed Size: {csize64} (0x{csize64:x16})"); + WriteLine($" Uncompressed Size: {usize64} (0x{usize64:x16})"); + } + + PopGroup(); + break; + } + } + } + + PopGroup(); + } + + private void ParseCentralDirectory(BinaryReader br) + { + var diskNumber = br.ReadUInt16(); + var startCentralDirDisk = br.ReadUInt16(); + var entriesForDisk = br.ReadUInt16(); + var entriesForWholeCentralDir = br.ReadUInt16(); + var centralDirSize = br.ReadUInt32(); + var offsetOfCentralDir = br.ReadUInt32(); + var commentSize = br.ReadUInt16(); + + byte[] commentData = new byte[commentSize]; + br.Read(commentData); + + WriteLine($"Disk Number: {diskNumber:x4}"); + WriteLine($"Start Central Directory Disk: {startCentralDirDisk:x4}"); + WriteLine($"Entries For Central Directory: {entriesForWholeCentralDir}"); + WriteLine($"Entries For Disk: {entriesForDisk}"); + WriteLine($"Central Directory Size: {centralDirSize}"); + WriteLine($"Central Directory Offset: {offsetOfCentralDir:x8}"); + WriteLine($"Comment Size: {commentSize}"); + + if (commentData.Length > 0) + { + Write($"Comment (raw): "); WriteHexBytes(commentData, MaxCommentHex); + Write($"Comment: "); WriteString(commentData, MaxCommentString); + } + + var zip64 = diskNumber == 0xffff + || startCentralDirDisk == 0xffff + || entriesForDisk == 0xffff + || entriesForWholeCentralDir == 0xffff + || centralDirSize == 0xffffffff + || offsetOfCentralDir == 0xffffffff; + + WriteLine($"Zip64 Indication: {(zip64 ? "Yes" : "No")}", zip64 ? ConCol.Green : ConCol.Red); + } + + private static void ParseDataDescriptor(BinaryReader br) + { + var crc = br.ReadInt32(); + + var pos = br.BaseStream.Position; + var csize = br.ReadInt32(); + var usize = br.ReadInt32(); + var crc32 = br.ReadUInt32(); + + br.BaseStream.Seek(-12, SeekOrigin.Current); + + var csize64 = br.ReadInt64(); + var usize64 = br.ReadInt64(); + var crc64 = br.ReadUInt32(); + + // Revert stream back to non-zip64 descriptor to not risk skipping next entry + br.BaseStream.Seek(-8, SeekOrigin.Current); + + WriteLine($"32-bit sizes:"); + PushGroup("descriptor32"); + WriteLine($"Compressed Size: {csize} (0x{csize:x8})"); + WriteLine($"Uncompressed Size: {usize} (0x{usize:x8})"); + WriteLine($"CRC: 0x{crc32:x8}"); + PopGroup(); + + WriteLine($"64-bit sizes (Zip64):"); + PushGroup("descriptor64"); + WriteLine($"Compressed Size: {csize64} (0x{csize64:x16})"); + WriteLine($"Uncompressed Size: {usize64} (0x{usize64:x16})"); + WriteLine($"CRC: 0x{crc64:x8}"); + PopGroup(); + + } + + private void ParseLocalHeader(BinaryReader br) + { + + + + var extractVersion = ZipVersion.From(br.ReadUInt16()); + var flags = (GeneralBitFlags)br.ReadUInt16(); + var method = (CompressionMethod)br.ReadUInt16(); + var dosTime = br.ReadUInt32(); + var dateTime = DateTimeFromDosTime(dosTime); + var crc = br.ReadUInt32(); + long compressedSize = br.ReadUInt32(); + long size = br.ReadUInt32(); + int storedNameLength = br.ReadUInt16(); + int extraDataLength = br.ReadUInt16(); + + var nameData = new byte[storedNameLength]; + br.Read(nameData); + + var extraDataBytes = new byte[extraDataLength]; + br.Read(extraDataBytes); + + var extraData = new ZipExtraData(extraDataBytes); + + + WriteLine($"Version Needed: {extractVersion} (0x{extractVersion.OperatingSystem:x})"); + WriteLine($"Local Flags: {flags:F}"); + WriteLine($"Compression Method: {method:G}"); + WriteLine($"DOS Date: 0x{dosTime:x8}"); + WriteLine($"File Date: {dateTime:yyyy-MM-dd}"); + WriteLine($"File Time: {dateTime:HH:mm:ss}"); + WriteLine($"CRC: 0x{crc:x8}"); + WriteSize($"Compressed Size:", compressedSize); + WriteSize($"Uncompressed Size:", size); + WriteLine($"Name Length: {storedNameLength}"); + WriteLine($"Extra Data Length: {extraDataLength}"); + WriteLine($"Name (raw): "); WriteHexBytes(nameData, MaxNameHex); + Write($"Name: "); WriteString(nameData, MaxNameString); + WriteLine($"Extra data:"); + ParseExtraData(extraData); + + } + + + // Utils + + private static void WriteHexBytes(IEnumerable data, int maxNumber, bool endLine = true) + { + int bytesWritten = 0; + foreach (var b in data) + { + Write($"{b:x2} "); + if (++bytesWritten >= maxNumber) + { + WriteEllipsis(endLine); + return; + } + } + + if (endLine) WriteLine(); + } + + + private static void WriteEllipsis(bool endLine = true) => WriteColor($" [...]{(endLine ? "\n" : "")}", ConCol.DarkGray); + + private static void WriteString(byte[] rawBytes, int maxLength, bool endLine = true) + { + var chars = Encoding.UTF8.GetChars(rawBytes); + var length = Math.Min(maxLength, chars.Length); + + var firstPrintableIndex = -1; + for (var i = 0; i < length; i++) + { + if (char.IsControl(chars[i]) || chars[i] == '�') + { + if (firstPrintableIndex != -1) + { + // Write the string up until this position + Write($"{new string(chars, firstPrintableIndex, i - firstPrintableIndex)}"); + firstPrintableIndex = -1; + } + + if (chars[i] == '�') + { + WriteColor("�", ConCol.Red); + continue; + } + + var charRaw = (short)chars[i]; + WriteColor(charRaw > 255 ? $"\\u{charRaw:x4}" : $"\\x{charRaw:x2}", ConCol.Cyan); + + } + else + { + if (firstPrintableIndex == -1) + { + firstPrintableIndex = i; + } + + } + } + + if (firstPrintableIndex != -1) + { + Write($"{new string(chars, firstPrintableIndex, length - firstPrintableIndex)}"); + } + + if (chars.Length > maxLength) + { + WriteEllipsis(endLine); + } + else if (endLine) + { + WriteLine(); + } + } + + public void Dispose() + { + html?.Dispose(); + fmt?.Dispose(); + } + } +} diff --git a/ArchiveDiag/ExtraDataType.cs b/ArchiveDiag/ExtraDataType.cs new file mode 100644 index 000000000..fe968ef9c --- /dev/null +++ b/ArchiveDiag/ExtraDataType.cs @@ -0,0 +1,151 @@ +namespace ICSharpCode.SharpZipLib.ArchiveDiag +{ + enum ExtraDataType + { + ///Zip64 extended information extra field + Zip64 = 0x0001, + + ///AV Info + AVInfo = 0x0007, + + ///Reserved for extended language encoding data (PFS) (see APPENDIX D) + ExtendedLanguageEncoding = 0x0008, + + ///OS/2 + OS2 = 0x0009, + + ///NTFS + NTFS = 0x000a, + + ///OpenVMS + OpenVMS = 0x000c, + + ///UNIX + UNIX = 0x000d, + + ///Reserved for file stream and fork descriptors + FileStreamFork = 0x000e, + + ///Patch Descriptor + Patch = 0x000f, + + ///PKCS#7 Store for X.509 Certificates + PKCS7Certs = 0x0014, + + ///X.509 Certificate ID and Signature for individual file + X509FileCert = 0x0015, + + ///X.509 Certificate ID for Central Directory + X509CentralDirCert = 0x0016, + + ///Strong Encryption Header + Strong = 0x0017, + + ///Record Management Controls + Record = 0x0018, + + ///PKCS#7 Encryption Recipient Certificate List + PKCS7RecipCerts = 0x0019, + + ///Reserved for Timestamp record + Timestamp = 0x0020, + + ///Policy Decryption Key Record + PolicyDecryptionKey = 0x0021, + + ///Smartcrypt Key Provider Record + SmartcryptKeyProvider = 0x0022, + + ///Smartcrypt Policy Key Data Record + SmartcryptPolicyKey = 0x0023, + + ///IBM S/390 (Z390), AS/400 (I400) attributes - uncompressed + IBMUncompressed = 0x0065, + + ///Reserved for IBM S/390 (Z390), AS/400 (I400) attributes - compressed + IBMCompressed = 0x0066, + + ///POSZIP 4690 (reserved) + POSZIP = 0x4690, + + /// Info-ZIP Macintosh (old, J. Lee) + InfoZipMacOld = 0x07c8, + + /// ZipIt Macintosh (first version) + ZipItMacShort = 0x2605, + + /// ZipIt Macintosh v 1.3.5 and newer (w/o full filename) + ZipItMacLong = 0x2705, + + /// Info-ZIP Macintosh (new, D. Haase's 'Mac3' field ) + InfoZipMacNew = 0x334d, + + /// Tandem NSK + TandemNSK = 0x4154, + + /// Acorn/SparkFS (David Pilling) + AcornSparkFS = 0x4341, + + /// Windows NT security descriptor (binary ACL) + WindowsACL = 0x4453, + + /// VM/CMS + VMCMS = 0x4704, + + /// MVS + MVS = 0x470f, + + /// Theos, old inofficial port + TheosOld = 0x4854, + + /// FWKCS MD5 (see below) + FWKCSMD5 = 0x4b46, + + /// OS/2 access control list (text ACL) + OS2ACL = 0x4c41, + + /// Info-ZIP OpenVMS (obsolete) + InfoZipOpenVMS = 0x4d49, + + /// Macintosh SmartZIP, by Macro Bambini + SmartZipMac = 0x4d63, + + /// Xceed original location extra field + XceedOriginalLocation = 0x4f4c, + + /// AOS/VS (binary ACL) + AOSVSACL = 0x5356, + + /// extended timestamp + UnixExtendedTime = 0x5455, + + /// Info-ZIP Unix (original; also OS/2, NT, etc.) + InfoZipUNIXOld = 0x5855, + + /// Xceed unicode extra field + XceedUnicode = 0x554e, + + /// BeOS (BeBox, PowerMac, etc.) + BeOS = 0x6542, + + /// Theos + Theos = 0x6854, + + /// ASi Unix + ASiUnix = 0x756e, + + /// Info-ZIP Unix (new) + InfoZipUNIXNew = 0x7855, + + /// SMS/QDOS + SMSQDOS = 0xfb4a, + + WinZipAES = 0x9901, + + UnicodeName = 0x7075, + UnicodeComment = 0x6375, + CustomCP = 0x5A4C, + + Unknown = 0xffff, + } +} diff --git a/ArchiveDiag/JobStatus.cs b/ArchiveDiag/JobStatus.cs new file mode 100644 index 000000000..e0bb92fed --- /dev/null +++ b/ArchiveDiag/JobStatus.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace ArchiveDiagJson +{ + public class JobStatus + { + public string FileName { get; set; } + + public string Status { get; set; } + + public JobStatus WithStatus(string status) => new JobStatus() {FileName = FileName, Status = status}; + } +} diff --git a/ArchiveDiag/Program.cs b/ArchiveDiag/Program.cs new file mode 100644 index 000000000..11ed83d06 --- /dev/null +++ b/ArchiveDiag/Program.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using ArchiveDiag; +using CommandLine; +using ConLib; +using ConLib.Console; +using ConLib.HTML; +using ICSharpCode.SharpZipLib.Core; +using ICSharpCode.SharpZipLib.Zip; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Scripting; +using Microsoft.CodeAnalysis.Scripting; +using static ConLib.PrettyConsole; + +namespace ICSharpCode.SharpZipLib.ArchiveDiag +{ + public class Program + { + public class Options + { + [Value(0, HelpText = "Input filename")] + public string Filename { get; set; } + + [Option('v', "verbose", Required = false, HelpText = "Set output to verbose messages")] + public bool Verbose { get; set; } + + [Option('q', "quiet")] + public bool Quiet { get; set; } + + [Option('h', "no-html-report")] + public bool SkipHtmlReport { get; set; } + + [Option('e', "eval", HelpText = "Run the input file as a C# script and create a report from the resulting stream")] + public bool Evaluate { get; set; } + } + + + static int Main(string[] args) + { + + Parser.Default.ParseArguments(args) + .WithParsed(o => + { + Stream inputStream; + var outputFile = $"{o.Filename}.html"; + var inputFile = Path.GetFileName(o.Filename); + + if (o.Evaluate) + { + inputFile = $"script:{inputFile}"; + try + { + using var fs = File.OpenRead(o.Filename); + using var sr = new StreamReader(fs); + + var opts = ScriptOptions.Default + .WithFilePath(o.Filename) + .WithImports( + "System", + "System.IO", + "System.Text", + "System.Collections.Generic", + "ICSharpCode.SharpZipLib", + "ICSharpCode.SharpZipLib.Core", + "ICSharpCode.SharpZipLib.Zip") + .WithReferences(typeof(ZipOutputStream).Assembly); + + var task = + CSharpScript.EvaluateAsync(sr.ReadToEnd(), opts); + if (task.Wait(TimeSpan.FromSeconds(30))) + { + inputStream = new MemoryStream(task.Result); + } + else throw new TimeoutException("Script evaluation timed out"); + } + catch (Exception x) + { + Console.WriteLine($"Failed to evaluate input script: {x}"); + return; + } + + } + else + { + inputStream = File.OpenRead(o.Filename); + + } + + using var outputStream = File.Open(outputFile, FileMode.Create); + using var htmlWriter = new HTMLWriter(outputStream); + + var runner = new ArchiveDiagRunner(inputStream, inputFile); + + runner.Run(new ConsoleWriter(), new HTMLWriter(outputStream)); + + htmlWriter.Flush(); + htmlWriter.Dispose(); + + }); + + return 0; + } + + } + + + + static class ZipExtraDataExtensions + { + public static IEnumerable<(ExtraDataType, Range)> EnumerateTags(this ZipExtraData zed) + { + var index = 0; + + var data = zed.GetEntryData(); + + while (index < data.Length - 3) + { + var tag = data[index++] + (data[index++] << 8); + var length = data[index++] + (data[index++] << 8); + yield return ((ExtraDataType)tag, new Range(index, index+length)); + index += length; + } + + } + } + + + internal static class StringExtensions + { + internal static string Ellipsis(this string source, int maxLength) + => source.Length > maxLength - 3 + ? source.Substring(0, maxLength - 3) + "..." + : source; + } + +} diff --git a/ArchiveDiag/Properties/launchSettings.json b/ArchiveDiag/Properties/launchSettings.json new file mode 100644 index 000000000..b2442d25f --- /dev/null +++ b/ArchiveDiag/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "ArchiveDiag": { + "commandName": "Project", + "commandLineArgs": "-e D:\\shared\\testScript.csx" + } + } +} \ No newline at end of file diff --git a/ArchiveDiag/ZipVersion.cs b/ArchiveDiag/ZipVersion.cs new file mode 100644 index 000000000..64deb5c5c --- /dev/null +++ b/ArchiveDiag/ZipVersion.cs @@ -0,0 +1,51 @@ +namespace ICSharpCode.SharpZipLib.ArchiveDiag +{ + struct ZipVersion + { + public enum ZipVersionOS: byte + { + MsDOS = 0, + Amiga = 1, + OpenVMS = 2, + UNIX = 3, + VMCMS = 4, + AtariST = 5, + OS2 = 6, + Macintosh = 7, + ZSystem = 8, + CPM = 9, + Windows = 10, + MVS = 11, + VSE = 12, + AcornRisc = 13, + VFAT = 14, + AlternateMVS = 15, + BeOS = 16, + Tandem = 17, + OS400 = 18, + MacOS = 19 + } + + public ZipVersionOS OperatingSystem; + public byte Major; + public byte Minor; + + public byte OSRaw => (byte) OperatingSystem; + + public ZipVersion(byte major, byte minor, ZipVersionOS os) + { + Major = major; + Minor = minor; + OperatingSystem = os; + } + + public static ZipVersion From(ushort versionBytes) + => new ZipVersion( + (byte)((versionBytes & 0x00ff) / 10), + (byte)((versionBytes & 0x00ff) % 10), + (ZipVersionOS) (versionBytes >> 8)); + + public override string ToString() + => $"v{Major}.{Minor} / {OperatingSystem:G}"; + } +} diff --git a/ArchiveDiagWeb/.config/dotnet-tools.json b/ArchiveDiagWeb/.config/dotnet-tools.json new file mode 100644 index 000000000..d6051bacf --- /dev/null +++ b/ArchiveDiagWeb/.config/dotnet-tools.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnet-ef": { + "version": "3.1.5", + "commands": [ + "dotnet-ef" + ] + } + } +} \ No newline at end of file diff --git a/ArchiveDiagWeb/ArchiveDiagWeb.bak.csproj b/ArchiveDiagWeb/ArchiveDiagWeb.bak.csproj new file mode 100644 index 000000000..1bbd0c596 --- /dev/null +++ b/ArchiveDiagWeb/ArchiveDiagWeb.bak.csproj @@ -0,0 +1,25 @@ + + + + netcoreapp3.1 + 1fbbc60f-1148-475d-8c7d-6bf0d1807e44 + + + + + + + + + + + + + + + + + + + + diff --git a/ArchiveDiagWeb/ArchiveDiagWeb.csproj b/ArchiveDiagWeb/ArchiveDiagWeb.csproj new file mode 100644 index 000000000..49b56c2f6 --- /dev/null +++ b/ArchiveDiagWeb/ArchiveDiagWeb.csproj @@ -0,0 +1,26 @@ + + + + netcoreapp3.1 + 1fbbc60f-1148-475d-8c7d-6bf0d1807e44 + + + + + + + + + + + + + + + + + + + + + diff --git a/ArchiveDiagWeb/Pages/Index.cshtml b/ArchiveDiagWeb/Pages/Index.cshtml new file mode 100644 index 000000000..9c5e23c4b --- /dev/null +++ b/ArchiveDiagWeb/Pages/Index.cshtml @@ -0,0 +1,59 @@ +@page +@model ArchiveDiagWeb.DefaultModel +@{ + ViewData["Title"] = "ArhiveDiag"; +} +
+ + +
+ +
+ +

+
+
+
diff --git a/ArchiveDiagWeb/Pages/Index.cshtml.cs b/ArchiveDiagWeb/Pages/Index.cshtml.cs
new file mode 100644
index 000000000..a24625017
--- /dev/null
+++ b/ArchiveDiagWeb/Pages/Index.cshtml.cs
@@ -0,0 +1,99 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text.Json;
+using System.Threading.Tasks;
+using System.Web;
+using ArchiveDiagJson;
+using Azure.Storage.Blobs;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using ArchiveDiag = ICSharpCode.SharpZipLib.ArchiveDiag.Program;
+
+namespace ArchiveDiagWeb
+{
+    public class DefaultModel : PageModel
+    {
+	    private IWebHostEnvironment _environment;
+	    private BlobServiceClient _blobClient;
+
+	    public DefaultModel(IWebHostEnvironment environment, BlobServiceClient blobClient)
+		{
+			_environment = environment;
+			_blobClient = blobClient;
+		}
+
+        [BindProperty]
+        public IFormFile Upload { get; set; }
+
+        public async Task OnPostAsync()
+        {
+
+	        var guk = Convert.ToBase64String(Guid.NewGuid().ToByteArray())
+		        .TrimEnd('=').Replace('+', '-').Replace('/', '_');
+
+
+			try
+			{
+				var jobsContainer = _blobClient.GetBlobContainerClient("jobs");
+				var jobClient = jobsContainer.GetBlobClient(guk);
+				await using (var ms = new MemoryStream())
+				{
+					await JsonSerializer.SerializeAsync(ms, new JobStatus()
+					{
+						FileName = Upload.FileName,
+						Status = "uploaded",
+					});
+
+					ms.Seek(0, SeekOrigin.Begin);
+
+					await jobClient.UploadAsync(ms, overwrite:true);
+				}
+			}
+			catch (Exception x)
+			{
+
+			}
+			try
+			{
+				var uploadContainer = _blobClient.GetBlobContainerClient("uploads");
+
+				await uploadContainer.UploadBlobAsync(guk, Upload.OpenReadStream());
+
+				return new JsonResult(new
+				{
+					result = "uploaded",
+					id = guk,
+				});
+			}
+			catch (Exception x)
+			{
+				return new JsonResult(new
+				{
+					result = "error",
+					error = $"{x}",
+				});
+			}
+
+			/*
+            var reports = new DirectoryInfo(Path.Combine(_environment.WebRootPath, "reports"));
+	        reports.Create();
+
+
+
+	        var file = new FileInfo(Path.Combine(reports.FullName, $"{guk}_{Upload.FileName}.html"));
+	        await using (var fileStream = file.Create())
+	        {
+		        ArchiveDiag.Run(Upload.OpenReadStream(), Upload.FileName, fileStream);
+
+	        }
+
+	        var urlFileName = HttpUtility.UrlEncode(file.Name).Replace('+', '_');
+	        Response.Redirect(Url.Content($"~/reports/{urlFileName}"));
+	        */
+        }
+	}
+}
diff --git a/ArchiveDiagWeb/Pages/_ViewImports.cshtml b/ArchiveDiagWeb/Pages/_ViewImports.cshtml
new file mode 100644
index 000000000..afa82bbf2
--- /dev/null
+++ b/ArchiveDiagWeb/Pages/_ViewImports.cshtml
@@ -0,0 +1 @@
+@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
diff --git a/ArchiveDiagWeb/Program.cs b/ArchiveDiagWeb/Program.cs
new file mode 100644
index 000000000..53fe25435
--- /dev/null
+++ b/ArchiveDiagWeb/Program.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+
+namespace ArchiveDiagWeb
+{
+    public class Program
+    {
+        public static void Main(string[] args)
+        {
+            CreateHostBuilder(args).Build().Run();
+        }
+
+        public static IHostBuilder CreateHostBuilder(string[] args) =>
+            Host.CreateDefaultBuilder(args)
+                .ConfigureWebHostDefaults(webBuilder =>
+                {
+                    webBuilder.UseStartup();
+                });
+    }
+}
diff --git a/ArchiveDiagWeb/Properties/launchSettings.json b/ArchiveDiagWeb/Properties/launchSettings.json
new file mode 100644
index 000000000..b9f0146a6
--- /dev/null
+++ b/ArchiveDiagWeb/Properties/launchSettings.json
@@ -0,0 +1,33 @@
+{
+  "iisSettings": {
+    "windowsAuthentication": false,
+    "anonymousAuthentication": true,
+    "iisExpress": {
+      "applicationUrl": "http://localhost:50728",
+      "sslPort": 44324
+    }
+  },
+  "profiles": {
+    "IIS Express": {
+      "commandName": "IISExpress",
+      "launchBrowser": true,
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development",
+        "ASPNETCORE_HOSTINGSTARTUP__KEYVAULT__CONFIGURATIONENABLED": "true",
+        "ASPNETCORE_HOSTINGSTARTUP__KEYVAULT__CONFIGURATIONVAULT": "https://archivediagwebvault.vault.azure.net/",
+        "AZURE_USERNAME": "piksel-nils@outlook.com"
+      }
+    },
+    "ArchiveDiagWeb": {
+      "commandName": "Project",
+      "launchBrowser": true,
+      "applicationUrl": "https://localhost:5001;http://localhost:5000",
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development",
+        "ASPNETCORE_HOSTINGSTARTUP__KEYVAULT__CONFIGURATIONENABLED": "true",
+        "ASPNETCORE_HOSTINGSTARTUP__KEYVAULT__CONFIGURATIONVAULT": "https://archivediagwebvault.vault.azure.net/",
+        "AZURE_USERNAME": "piksel-nils@outlook.com"
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/ArchiveDiagWeb/Properties/serviceDependencies.archivediag - Web Deploy2.json b/ArchiveDiagWeb/Properties/serviceDependencies.archivediag - Web Deploy2.json
new file mode 100644
index 000000000..7628f2af8
--- /dev/null
+++ b/ArchiveDiagWeb/Properties/serviceDependencies.archivediag - Web Deploy2.json	
@@ -0,0 +1,10 @@
+{
+  "dependencies": {
+    "storage1": {
+      "resourceId": "/subscriptions/[parameters('subscriptionId')]/resourceGroups/[parameters('resourceGroup')]/providers/Microsoft.Storage/storageAccounts/archivediagstore",
+      "type": "storage.azure",
+      "connectionId": "ArchiveDiagStorage",
+      "secretStore": "AzureAppSettings"
+    }
+  }
+}
\ No newline at end of file
diff --git a/ArchiveDiagWeb/Properties/serviceDependencies.json b/ArchiveDiagWeb/Properties/serviceDependencies.json
new file mode 100644
index 000000000..1a578f8e8
--- /dev/null
+++ b/ArchiveDiagWeb/Properties/serviceDependencies.json
@@ -0,0 +1,13 @@
+{
+  "dependencies": {
+    "secrets1": {
+      "type": "secrets",
+      "suggestion": "true",
+      "ignored": "true"
+    },
+    "storage1": {
+      "type": "storage",
+      "connectionId": "ArchiveDiagStorage"
+    }
+  }
+}
\ No newline at end of file
diff --git a/ArchiveDiagWeb/Properties/serviceDependencies.local.json b/ArchiveDiagWeb/Properties/serviceDependencies.local.json
new file mode 100644
index 000000000..d3b42a30c
--- /dev/null
+++ b/ArchiveDiagWeb/Properties/serviceDependencies.local.json
@@ -0,0 +1,10 @@
+{
+  "dependencies": {
+    "storage1": {
+      "resourceId": "/subscriptions/[parameters('subscriptionId')]/resourceGroups/[parameters('resourceGroup')]/providers/Microsoft.Storage/storageAccounts/archivediagstore",
+      "type": "storage.azure",
+      "connectionId": "ArchiveDiagStorage",
+      "secretStore": "LocalSecretsFile"
+    }
+  }
+}
\ No newline at end of file
diff --git a/ArchiveDiagWeb/Startup.cs b/ArchiveDiagWeb/Startup.cs
new file mode 100644
index 000000000..2a7f4e0f9
--- /dev/null
+++ b/ArchiveDiagWeb/Startup.cs
@@ -0,0 +1,89 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Azure;
+using Azure.Storage.Queues;
+using Azure.Storage.Blobs;
+using Azure.Core.Extensions;
+
+namespace ArchiveDiagWeb
+{
+    public class Startup
+    {
+        public Startup(IConfiguration configuration)
+        {
+            Configuration = configuration;
+        }
+        public IConfiguration Configuration { get; }
+        // This method gets called by the runtime. Use this method to add services to the container.
+        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
+        public void ConfigureServices(IServiceCollection services)
+        {
+	        services.AddRazorPages();
+            services.AddAzureClients(builder =>
+            {
+                builder.AddBlobServiceClient(Configuration["ConnectionStrings:ArchiveDiagStorage:blob"], preferMsi: true);
+                builder.AddQueueServiceClient(Configuration["ConnectionStrings:ArchiveDiagStorage:queue"], preferMsi: true);
+            });
+
+        }
+
+        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
+        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
+        {
+			if (env.IsDevelopment())
+            {
+				app.UseDeveloperExceptionPage();
+                
+            }
+
+			app.UseStaticFiles();
+
+            app.UseRouting();
+
+            app.UseEndpoints(endpoints =>
+            {
+                endpoints.MapGet("/status/{jobid}", async context =>
+                {
+	                var name = context.Request.RouteValues["jobid"];
+					await context.Response.WriteAsync("Hello World!");
+                });
+
+                endpoints.MapRazorPages();
+			});
+        }
+
+    }
+    internal static class StartupExtensions
+    {
+        public static IAzureClientBuilder AddBlobServiceClient(this AzureClientFactoryBuilder builder, string serviceUriOrConnectionString, bool preferMsi)
+        {
+            if (preferMsi && Uri.TryCreate(serviceUriOrConnectionString, UriKind.Absolute, out Uri serviceUri))
+            {
+                return builder.AddBlobServiceClient(serviceUri);
+            }
+            else
+            {
+                return builder.AddBlobServiceClient(serviceUriOrConnectionString);
+            }
+        }
+        public static IAzureClientBuilder AddQueueServiceClient(this AzureClientFactoryBuilder builder, string serviceUriOrConnectionString, bool preferMsi)
+        {
+            if (preferMsi && Uri.TryCreate(serviceUriOrConnectionString, UriKind.Absolute, out Uri serviceUri))
+            {
+                return builder.AddQueueServiceClient(serviceUri);
+            }
+            else
+            {
+                return builder.AddQueueServiceClient(serviceUriOrConnectionString);
+            }
+        }
+    }
+}
diff --git a/ArchiveDiagWeb/appsettings.Development.json b/ArchiveDiagWeb/appsettings.Development.json
new file mode 100644
index 000000000..8983e0fc1
--- /dev/null
+++ b/ArchiveDiagWeb/appsettings.Development.json
@@ -0,0 +1,9 @@
+{
+  "Logging": {
+    "LogLevel": {
+      "Default": "Information",
+      "Microsoft": "Warning",
+      "Microsoft.Hosting.Lifetime": "Information"
+    }
+  }
+}
diff --git a/ArchiveDiagWeb/appsettings.json b/ArchiveDiagWeb/appsettings.json
new file mode 100644
index 000000000..d9d9a9bff
--- /dev/null
+++ b/ArchiveDiagWeb/appsettings.json
@@ -0,0 +1,10 @@
+{
+  "Logging": {
+    "LogLevel": {
+      "Default": "Information",
+      "Microsoft": "Warning",
+      "Microsoft.Hosting.Lifetime": "Information"
+    }
+  },
+  "AllowedHosts": "*"
+}
diff --git a/ICSharpCode.SharpZipLib.sln b/ICSharpCode.SharpZipLib.sln
index cab9675b5..48f01b2ed 100644
--- a/ICSharpCode.SharpZipLib.sln
+++ b/ICSharpCode.SharpZipLib.sln
@@ -19,6 +19,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ICSharpCode.SharpZipLib.Tes
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ICSharpCode.SharpZipLib.Benchmark", "benchmark\ICSharpCode.SharpZipLib.Benchmark\ICSharpCode.SharpZipLib.Benchmark.csproj", "{C51E638B-DDD0-48B6-A6BD-EBC4E6A104C7}"
 EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ArchiveDiag", "ArchiveDiag\ArchiveDiag.csproj", "{6F1432BF-4792-4AEB-BB84-3470B1119515}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ArchiveDiagWeb", "ArchiveDiagWeb\ArchiveDiagWeb.csproj", "{06CBE3B2-4B88-42DA-A4B4-F0A665D5E267}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArchiveDiag-Worker", "ArchiveDiag-Worker\ArchiveDiag-Worker.csproj", "{1270950A-1EC0-4F83-AE03-CF0250F7F2C7}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -41,6 +47,18 @@ Global
 		{C51E638B-DDD0-48B6-A6BD-EBC4E6A104C7}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{C51E638B-DDD0-48B6-A6BD-EBC4E6A104C7}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{C51E638B-DDD0-48B6-A6BD-EBC4E6A104C7}.Release|Any CPU.Build.0 = Release|Any CPU
+		{6F1432BF-4792-4AEB-BB84-3470B1119515}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{6F1432BF-4792-4AEB-BB84-3470B1119515}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{6F1432BF-4792-4AEB-BB84-3470B1119515}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{6F1432BF-4792-4AEB-BB84-3470B1119515}.Release|Any CPU.Build.0 = Release|Any CPU
+		{06CBE3B2-4B88-42DA-A4B4-F0A665D5E267}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{06CBE3B2-4B88-42DA-A4B4-F0A665D5E267}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{06CBE3B2-4B88-42DA-A4B4-F0A665D5E267}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{06CBE3B2-4B88-42DA-A4B4-F0A665D5E267}.Release|Any CPU.Build.0 = Release|Any CPU
+		{1270950A-1EC0-4F83-AE03-CF0250F7F2C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{1270950A-1EC0-4F83-AE03-CF0250F7F2C7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{1270950A-1EC0-4F83-AE03-CF0250F7F2C7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{1270950A-1EC0-4F83-AE03-CF0250F7F2C7}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

From a830d4c44fca4f53cb8abc8e055c83b8727bffe9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?nils=20m=C3=A5s=C3=A9n?= 
Date: Sat, 3 Oct 2020 16:01:01 +0200
Subject: [PATCH 2/7] Add Tar runner

---
 ArchiveDiag/ArchiveDiag.csproj             |   4 +-
 ArchiveDiag/ArchiveDiagRunner.cs           | 515 ++-------------------
 ArchiveDiag/Program.cs                     |  46 +-
 ArchiveDiag/Properties/launchSettings.json |   2 +-
 ArchiveDiag/TarArchiveDiagRunner.cs        | 170 +++++++
 ArchiveDiag/ZipArchiveDiagRunner.cs        | 484 +++++++++++++++++++
 6 files changed, 737 insertions(+), 484 deletions(-)
 create mode 100644 ArchiveDiag/TarArchiveDiagRunner.cs
 create mode 100644 ArchiveDiag/ZipArchiveDiagRunner.cs

diff --git a/ArchiveDiag/ArchiveDiag.csproj b/ArchiveDiag/ArchiveDiag.csproj
index dc0d30a18..fc7c693e9 100644
--- a/ArchiveDiag/ArchiveDiag.csproj
+++ b/ArchiveDiag/ArchiveDiag.csproj
@@ -1,9 +1,9 @@
-
+
 
   
     Exe
     netcoreapp3.1
-    0.3.5
+    0.4.12
   
 
   
diff --git a/ArchiveDiag/ArchiveDiagRunner.cs b/ArchiveDiag/ArchiveDiagRunner.cs
index 4e143ebfa..84d5e0926 100644
--- a/ArchiveDiag/ArchiveDiagRunner.cs
+++ b/ArchiveDiag/ArchiveDiagRunner.cs
@@ -1,30 +1,29 @@
 using System;
 using System.Collections.Generic;
 using System.IO;
-using System.Linq;
+using System.Runtime.CompilerServices;
 using System.Text;
 using ConLib;
 using ConLib.Console;
 using ConLib.HTML;
 using ICSharpCode.SharpZipLib.ArchiveDiag;
-using ICSharpCode.SharpZipLib.Zip;
 using static ConLib.PrettyConsole;
 
 namespace ArchiveDiag
 {
-	class ArchiveDiagRunner: IDisposable
+	abstract class ArchiveDiagRunner : IDisposable
 	{
 		private readonly HTMLWriter? html = null;
 		private readonly ColorFormatter? fmt = null;
 		private Stream archiveStream;
 
-		public int MaxNameHex {get; set;} = 32;
-		public int MaxNameString {get; set;} = 128;
-		public int MaxCommentHex {get; set;} = 32;
-		public int MaxCommentString {get; set;} = 128;
-		public int MaxExtraDataHex {get; set;} = 64;
+		public int MaxNameHex { get; set; } = 32;
+		public int MaxNameString { get; set; } = 128;
+		public int MaxCommentHex { get; set; } = 32;
+		public int MaxCommentString { get; set; } = 128;
+		public int MaxExtraDataHex { get; set; } = 64;
 
-		public bool WarnUnknownSigns {get; set;} = false;
+		public bool WarnUnknownSigns { get; set; } = false;
 
 		public string? FileName { get; }
 
@@ -42,10 +41,11 @@ public void Run()
 		public void Run(params ColorWriter[] writers)
 		{
 			PrettyFormatters.Clear();
-			
+
 			foreach (var writer in writers)
 			{
-				PrettyFormatters.Add(new ColorFormatter(writer));
+				var fmt = new ColorFormatter(writer);
+				PrettyFormatters.Add(fmt);
 			}
 
 			PrettyConsole.ChoreOptions.StartedFormat = "{0} {1}\n";
@@ -65,492 +65,43 @@ public void Run(params ColorWriter[] writers)
 			WriteColor("T", ConCol.DarkGray);
 			Write($"{now:HH:mm:ss}");
 			WriteColor("Z\n", ConCol.DarkGray);
+			WriteLine($"Parser: {Parser}");
+			WriteLine($"Tester: {Tester}");
 
 			WriteLine();
 
-#if DEBUG
-			Console.Read();
-#endif
-
 			// Stream archiveStream = null;
 			DoTask("Open File", async () =>
 			{
 				// archiveStream = File.OpenRead(file);
 			}, continueOnFail: false);
 
-			DoChore("Parse Archive", () =>
-			{
-				ParseArchive(archiveStream);
-			}, continueOnFail: true);
+			DoChore("Parse Archive", () => { ParseArchive(archiveStream); }, continueOnFail: true);
 
-			DoTask("Rewind File Stream", async () =>
-			{
-				archiveStream.Seek(0, SeekOrigin.Begin);
-			}, continueOnFail: false);
+			// DoTask("Rewind File Stream", async () => { archiveStream.Seek(0, SeekOrigin.Begin); },
+			//	continueOnFail: false);
 
 
 			DoChore("Test Archive", () =>
 			{
-				var zf = new ZipFile(archiveStream);
-
-				var result = zf.TestArchive(true, TestStrategy.FindAllErrors, (status, message) =>
-				{
-					if (status.Operation == TestOperation.EntryData) return;
-					var pad = "";//.PadLeft(18 - status.Operation.ToString().Length);
-					if (status.Entry is { } entry)
-					{
-						WriteLine($"[{status.Operation}{pad}] #{entry.ZipFileIndex} {entry.Name}");
-					}
-					else
-					{
-						WriteLine($"[{status.Operation}{pad}]");
-					}
-
-					if (!string.IsNullOrEmpty(message))
-					{
-						WriteLine($"[{status.Operation}{pad}] {message}");
-					}
-				});
+				var result = TestArchive(archiveStream);
 
 
 				WriteLine($"Test result {(result ? "PASSED" : "FAILED")}", result ? ConCol.Green : ConCol.Red);
 			}, continueOnFail: true);
-		}
-
-
-		private void ParseArchive(Stream transportStream)
-		{
-
-			transportStream.Seek(0, SeekOrigin.Begin);
-
-			var br = new BinaryReader(transportStream);
-
-			int readVal;
-			while ((readVal = transportStream.ReadByte()) != -1)
-			{
-				if (readVal != 'P') continue;
-				readVal = transportStream.ReadByte();
-
-				if (readVal != 'K') continue;
-				var b1 = (byte)transportStream.ReadByte();
-				var b2 = (byte)transportStream.ReadByte();
-
-				if (b1 > 0x08 || b2 > 0x08)
-				{
-					transportStream.Seek(-2, SeekOrigin.Current);
-					continue;
-				}
-
-				var sign = 'P' | ('K' << 8) | (b1 << 16) | (b2 << 24);
-
-				var signId = sign switch
-				{
-					ZipConstants.DataDescriptorSignature
-						=> nameof(ZipConstants.DataDescriptorSignature),
-					ZipConstants.CentralHeaderSignature
-						=> nameof(ZipConstants.CentralHeaderSignature),
-					ZipConstants.CentralHeaderDigitalSignature
-						=> nameof(ZipConstants.CentralHeaderDigitalSignature),
-					ZipConstants.EndOfCentralDirectorySignature
-						=> nameof(ZipConstants.EndOfCentralDirectorySignature),
-					ZipConstants.LocalHeaderSignature
-						=> nameof(ZipConstants.LocalHeaderSignature),
-					ZipConstants.SpanningTempSignature
-						=> nameof(ZipConstants.SpanningTempSignature),
-					ZipConstants.Zip64CentralDirLocatorSignature
-						=> nameof(ZipConstants.Zip64CentralDirLocatorSignature),
-					ZipConstants.Zip64CentralFileHeaderSignature
-						=> nameof(ZipConstants.Zip64CentralFileHeaderSignature),
-					_ => $"Unknown (0x{sign:x8})",
-				};
-
-				//if (warnUnknownSigns || signId.StartsWith("Unknown"))
-				//{
-				WriteLine($"{transportStream.Position:x8} PK: {b1:x2} {b2:x2} {signId}");
-				//}
-
-				switch (sign)
-				{
-					case ZipConstants.LocalHeaderSignature:
-						DoChore("Parse Local File Header", ()
-							=> ParseLocalHeader(br), true);
-						break;
-					case ZipConstants.DataDescriptorSignature:
-						DoChore("Parse Data Descriptor", ()
-							=> ParseDataDescriptor(br), true);
-						break;
-					case ZipConstants.CentralHeaderSignature:
-						DoChore("Parse Central Directory Header", ()
-							=> ParseCentralHeader(br), true);
-						break;
-					case ZipConstants.Zip64CentralFileHeaderSignature:
-						DoChore("Parse Zip64 End Of Central Directory Record", ()
-							=> ParseZip64EndOfCentralDirectory(br), true);
-						break;
-					case ZipConstants.Zip64CentralDirLocatorSignature:
-						DoChore("Parse Zip64 Central Directory Locator", ()
-							=> ParseZip64CentralDirLocator(br), true);
-						break;
-					case ZipConstants.EndOfCentralDirectorySignature:
-						DoChore("Parse End of Central Directory Record", ()
-							=> ParseCentralDirectory(br), true);
-						break;
-					default:
-						if (WarnUnknownSigns) WriteLine($"{"Skipping unrecognized signature!"}\n");
-						break;
-				}
-
-			}
-
-		}
-
-		private void ParseArchiveExtraData(BinaryReader br)
-		{
-			var extraLen = br.ReadUInt16();
-			var extraDataBytes = new byte[extraLen];
-			br.Read(extraDataBytes);
-			var extraData = new ZipExtraData(extraDataBytes);
-
-			WriteLine($"Extra data:");
-			ParseExtraData(extraData);
-		}
-
-		private void ParseZip64CentralDirLocator(BinaryReader br)
-		{
-			var diskNumber = br.ReadUInt32();
-			var relativeOffset = br.ReadUInt64();
-			var diskTotal = br.ReadUInt64();
-
-			WriteLine($"Zip64 End Record Disk Number: {diskNumber}");
-			WriteLine($"Zip64 End Record Relative Offset: {relativeOffset} (0x{relativeOffset:x8})");
-			WriteLine($"Total number of disks: {diskTotal}");
-		}
-
-		private void ParseZip64EndOfCentralDirectory(BinaryReader br)
-		{
-			var recordSize = br.ReadUInt64();
-			var versionMadeBy = ZipVersion.From(br.ReadUInt16());
-			var versionToExtract = ZipVersion.From(br.ReadUInt16());
-			var diskNumber = br.ReadUInt32();
-			var startCentralDirDisk = br.ReadUInt32();
-
-			var entriesForDisk = br.ReadUInt64();
-			var entriesForWholeCentralDir = br.ReadUInt64();
-			var centralDirSize = br.ReadUInt64();
-			var offsetOfCentralDir = br.ReadUInt64();
-
-			var extDataSize = recordSize - 44;
-
-			WriteLine($"Size of record: {recordSize} (0x{recordSize:x16})");
-			WriteLine($"Version Made By: {versionMadeBy} (0x{versionMadeBy.OSRaw:x2})");
-			WriteLine($"Version Needed: {versionToExtract} (0x{versionToExtract.OSRaw:x2})");
-			WriteLine($"Disk Number: {diskNumber}");
-			WriteLine($"Start Central Directory Disk: {startCentralDirDisk:x8}");
-
-			WriteLine($"Entries For Disk: {entriesForDisk}");
-			WriteLine($"Entries For Central Directory: {entriesForWholeCentralDir}");
-			WriteLine($"Central Directory Size: {centralDirSize} (0x{centralDirSize:x16})");
-			WriteLine($"Central Directory Offset: {offsetOfCentralDir} (0x{offsetOfCentralDir:x16})");
-
-
-			WriteLine($"Extensible data size: {extDataSize}");
-			if (extDataSize <= 0) return;
-
-			WriteLine($"Parsing of extensible data is not supported! Skipping.", ConCol.Yellow);
-			// Ignore skipping the data for now, since it is more likely to cause issues than it is to prevent them
-			//br.BaseStream.Seek((long)extDataSize, SeekOrigin.Current);
-		}
-
-		private static DateTime DateTimeFromDosTime(uint dosTime)
-		{
-			var sec = Math.Min(59, 2 * (dosTime & 0x1f));
-			var min = Math.Min(59, (dosTime >> 5) & 0x3f);
-			var hrs = Math.Min(23, (dosTime >> 11) & 0x1f);
-			var mon = Math.Max(1, Math.Min(12, ((dosTime >> 21) & 0xf)));
-			var year = ((dosTime >> 25) & 0x7f) + 1980;
-			var day = Math.Max(1, Math.Min(DateTime.DaysInMonth((int)year, (int)mon), (int)((dosTime >> 16) & 0x1f)));
-			return new DateTime((int)year, (int)mon, day, (int)hrs, (int)min, (int)sec);
-		}
-
-		private void ParseCentralHeader(BinaryReader br)
-		{
-			var versionMadeBy = ZipVersion.From(br.ReadUInt16());
-			var versionToExtract = ZipVersion.From(br.ReadUInt16());
-			var bitFlags = (GeneralBitFlags)br.ReadUInt16();
-			var method = (CompressionMethod)br.ReadUInt16();
-			var dosTime = br.ReadUInt32();
-			var dateTime = DateTimeFromDosTime(dosTime);
-			var crc = br.ReadUInt32();
-			var compressedSize = (long)br.ReadUInt32();
-			var size = (long)br.ReadUInt32();
-			var nameLen = br.ReadUInt16();
-			var extraLen = br.ReadUInt16();
-			var commentLen = br.ReadUInt16();
-			var diskStartNo = br.ReadUInt16();  // Not currently used
-			var internalAttributes = br.ReadUInt16();  // Not currently used
-
-			var externalAttributes = (FileAttributes)br.ReadUInt32();
-			var offset = br.ReadUInt32();
-
-			var nameData = new byte[nameLen];
-			br.Read(nameData);
-
-			var extraDataBytes = new byte[extraLen];
-			br.Read(extraDataBytes);
-			var extraData = new ZipExtraData(extraDataBytes);
-
-			var commentData = new byte[commentLen];
-			br.Read(commentData);
-
-
-			WriteLine($"Version Made By: {versionMadeBy} (0x{versionMadeBy.OSRaw:x2})");
-			WriteLine($"Version Needed: {versionToExtract} (0x{versionToExtract.OSRaw:x2})");
-			WriteLine($"Bit flags: {bitFlags:F}");
-			WriteLine($"Compression Method: {method:G}");
-			WriteLine($"DOS Date: 0x{dosTime:x8}");
-			WriteLine($"File Date: {dateTime:yyyy-MM-dd}");
-			WriteLine($"File Time: {dateTime:HH:mm:ss}");
-			WriteLine($"CRC: 0x{crc:x8}");
-			WriteSize($"Compressed Size:", compressedSize);
-			WriteSize($"Uncompressed Size:", size);
-			WriteLine($"Name Length: {nameLen}");
-			WriteLine($"Extra Data Length: {extraLen}");
-			WriteLine($"Comment Length: {commentLen}");
-			WriteLine($"Disk number start: {diskStartNo}");
-			WriteLine($"Internal file attributes: {internalAttributes:x4}");
-			WriteLine($"External file attributes: {externalAttributes:F}");
-			WriteLine($"Relative offset of local header: {offset}");
-
-			Write($"Name (raw): "); WriteHexBytes(nameData, MaxNameHex);
-			Write($"Name: "); WriteString(nameData, MaxNameString);
-
-			if (commentLen > 0)
-			{
-				Write($"Comment (raw): "); WriteHexBytes(commentData, MaxCommentHex);
-				Write($"Comment: "); WriteString(commentData, MaxCommentString);
-			}
-
-			WriteLine($"Extra data:");
-			ParseExtraData(extraData);
 
-		}
-
-		private static void WriteSize(FormattableString fs, in long size)
-		{
-			Write(fs);
-
-			if (size == uint.MaxValue)
-			{
-				WriteLine($" {size} ({"Zip64 Indicator"})", ConCol.DarkGray, ConCol.Blue);
-			}
-			else
-			{
-				WriteLine($" {size}");
-			}
-		}
-
-		private void ParseExtraData(ZipExtraData extraData)
-		{
-			PushGroup("extra-data");
-			var extraDataBytes = extraData.GetEntryData();
-
-			foreach (var (tag, range) in extraData.EnumerateTags().ToList())
-			{
-				var length = range.End.Value - range.Start.Value;
-
-				var knownId = Enum.IsDefined(typeof(ExtraDataType), tag);
-
-				WriteLine($" - Type: {(knownId ? tag : ExtraDataType.Unknown):G} ({(uint)tag:x4}), Length: {length}");
-				PushGroup("extra-raw");
-				if (extraDataBytes.Length < range.End.Value)
-				{
-					WriteLine($" Invalid length! Skipping parse attemmpt!", ConCol.Red);
-					PopGroup(); // extra-raw
-					PopGroup(); // extra-data
-					return;
-				}
-
-				Write($" Raw: ");
-				WriteHexBytes(extraDataBytes[range], MaxExtraDataHex);
-
-				PopGroup();
-
-
-				switch (tag)
-				{
-					case ExtraDataType.NTFS:
-						{
-							PushGroup("extra-ntfs");
-							var tagNtDate = extraData.GetData();
-
-							WriteLine($" Created: {tagNtDate.CreateTime:yyyy-MM-dd HH:mm:ss}");
-							WriteLine($" Accessed: {tagNtDate.LastAccessTime:yyyy-MM-dd HH:mm:ss}");
-							WriteLine($" Modified: {tagNtDate.LastModificationTime:yyyy-MM-dd HH:mm:ss}");
-							PopGroup();
-
-							break;
-						}
-					case ExtraDataType.UnixExtendedTime:
-						{
-							PushGroup("extra-unix-xtime");
-
-							var tagUnixDate = extraData.GetData();
-
-							WriteLine($" Created: {tagUnixDate.CreateTime:yyyy-MM-dd HH:mm:ss}");
-							WriteLine($" Accessed: {tagUnixDate.AccessTime:yyyy-MM-dd HH:mm:ss}");
-							WriteLine($" Modified: {tagUnixDate.ModificationTime:yyyy-MM-dd HH:mm:ss}");
-							PopGroup();
-							break;
-						}
-					case ExtraDataType.UnicodeName:
-						{
-							PushGroup("extra-unicode-name");
-
-							var nameRange = new Range(range.Start.Value + 5, range.End.Value);
-							var unicodeName = Encoding.UTF8.GetString(extraDataBytes[nameRange]);
-							File.WriteAllText("unicodetext.txt", unicodeName);
-							WriteLine($" Unicode name: {unicodeName}");
-							PopGroup();
-							break;
-						}
-					case ExtraDataType.Zip64:
-						{
-							PushGroup("extra-zip64");
-							using (var br = new BinaryReader(extraData.GetStreamForTag((int)tag)))
-							{
-								var usize64 = br.ReadUInt64();
-								var csize64 = br.ReadUInt64();
-								WriteLine($" Compressed Size: {csize64} (0x{csize64:x16})");
-								WriteLine($" Uncompressed Size: {usize64} (0x{usize64:x16})");
-							}
-
-							PopGroup();
-							break;
-						}
-				}
-			}
-
-			PopGroup();
-		}
-
-		private void ParseCentralDirectory(BinaryReader br)
-		{
-			var diskNumber = br.ReadUInt16();
-			var startCentralDirDisk = br.ReadUInt16();
-			var entriesForDisk = br.ReadUInt16();
-			var entriesForWholeCentralDir = br.ReadUInt16();
-			var centralDirSize = br.ReadUInt32();
-			var offsetOfCentralDir = br.ReadUInt32();
-			var commentSize = br.ReadUInt16();
-
-			byte[] commentData = new byte[commentSize];
-			br.Read(commentData);
-
-			WriteLine($"Disk Number: {diskNumber:x4}");
-			WriteLine($"Start Central Directory Disk: {startCentralDirDisk:x4}");
-			WriteLine($"Entries For Central Directory: {entriesForWholeCentralDir}");
-			WriteLine($"Entries For Disk: {entriesForDisk}");
-			WriteLine($"Central Directory Size: {centralDirSize}");
-			WriteLine($"Central Directory Offset: {offsetOfCentralDir:x8}");
-			WriteLine($"Comment Size: {commentSize}");
-
-			if (commentData.Length > 0)
+			foreach (var writer in writers)
 			{
-				Write($"Comment (raw): "); WriteHexBytes(commentData, MaxCommentHex);
-				Write($"Comment: "); WriteString(commentData, MaxCommentString);
+				//writer.Flush();
+				// writer.Close();
 			}
 
-			var zip64 = diskNumber == 0xffff
-						|| startCentralDirDisk == 0xffff
-						|| entriesForDisk == 0xffff
-						|| entriesForWholeCentralDir == 0xffff
-						|| centralDirSize == 0xffffffff
-						|| offsetOfCentralDir == 0xffffffff;
-
-			WriteLine($"Zip64 Indication: {(zip64 ? "Yes" : "No")}", zip64 ? ConCol.Green : ConCol.Red);
-		}
-
-		private static void ParseDataDescriptor(BinaryReader br)
-		{
-			var crc = br.ReadInt32();
-
-			var pos = br.BaseStream.Position;
-			var csize = br.ReadInt32();
-			var usize = br.ReadInt32();
-			var crc32 = br.ReadUInt32();
-
-			br.BaseStream.Seek(-12, SeekOrigin.Current);
-
-			var csize64 = br.ReadInt64();
-			var usize64 = br.ReadInt64();
-			var crc64 = br.ReadUInt32();
-
-			// Revert stream back to non-zip64 descriptor to not risk skipping next entry
-			br.BaseStream.Seek(-8, SeekOrigin.Current);
-
-			WriteLine($"32-bit sizes:");
-			PushGroup("descriptor32");
-			WriteLine($"Compressed Size: {csize} (0x{csize:x8})");
-			WriteLine($"Uncompressed Size: {usize} (0x{usize:x8})");
-			WriteLine($"CRC: 0x{crc32:x8}");
-			PopGroup();
-
-			WriteLine($"64-bit sizes (Zip64):");
-			PushGroup("descriptor64");
-			WriteLine($"Compressed Size: {csize64} (0x{csize64:x16})");
-			WriteLine($"Uncompressed Size: {usize64} (0x{usize64:x16})");
-			WriteLine($"CRC: 0x{crc64:x8}");
-			PopGroup();
-
-		}
-
-		private void ParseLocalHeader(BinaryReader br)
-		{
-
-
-
-			var extractVersion = ZipVersion.From(br.ReadUInt16());
-			var flags = (GeneralBitFlags)br.ReadUInt16();
-			var method = (CompressionMethod)br.ReadUInt16();
-			var dosTime = br.ReadUInt32();
-			var dateTime = DateTimeFromDosTime(dosTime);
-			var crc = br.ReadUInt32();
-			long compressedSize = br.ReadUInt32();
-			long size = br.ReadUInt32();
-			int storedNameLength = br.ReadUInt16();
-			int extraDataLength = br.ReadUInt16();
-
-			var nameData = new byte[storedNameLength];
-			br.Read(nameData);
-
-			var extraDataBytes = new byte[extraDataLength];
-			br.Read(extraDataBytes);
-
-			var extraData = new ZipExtraData(extraDataBytes);
-
-
-			WriteLine($"Version Needed: {extractVersion} (0x{extractVersion.OperatingSystem:x})");
-			WriteLine($"Local Flags: {flags:F}");
-			WriteLine($"Compression Method: {method:G}");
-			WriteLine($"DOS Date: 0x{dosTime:x8}");
-			WriteLine($"File Date: {dateTime:yyyy-MM-dd}");
-			WriteLine($"File Time: {dateTime:HH:mm:ss}");
-			WriteLine($"CRC: 0x{crc:x8}");
-			WriteSize($"Compressed Size:", compressedSize);
-			WriteSize($"Uncompressed Size:", size);
-			WriteLine($"Name Length: {storedNameLength}");
-			WriteLine($"Extra Data Length: {extraDataLength}");
-			WriteLine($"Name (raw): "); WriteHexBytes(nameData, MaxNameHex);
-			Write($"Name: "); WriteString(nameData, MaxNameString);
-			WriteLine($"Extra data:");
-			ParseExtraData(extraData);
-
+#if DEBUG
+			// Console.Read();
+#endif
 		}
 
-
-		// Utils
-
-		private static void WriteHexBytes(IEnumerable data, int maxNumber, bool endLine = true)
+		protected static void WriteHexBytes(IEnumerable data, int maxNumber, bool endLine = true)
 		{
 			int bytesWritten = 0;
 			foreach (var b in data)
@@ -567,9 +118,17 @@ private static void WriteHexBytes(IEnumerable data, int maxNumber, bool en
 		}
 
 
-		private static void WriteEllipsis(bool endLine = true) => WriteColor($" [...]{(endLine ? "\n" : "")}", ConCol.DarkGray);
+		protected static void WriteEllipsis(bool endLine = true) => WriteColor($" [...]{(endLine ? "\n" : "")}", ConCol.DarkGray);
 
-		private static void WriteString(byte[] rawBytes, int maxLength, bool endLine = true)
+		protected static void WriteBool(bool value) => WriteColor(value ? "Yes" : "No", value ? ConCol.Green : ConCol.Red);
+		protected static void WriteBool(string prefix, bool value, bool endLine = true)
+		{
+			Write(FormattableStringFactory.Create(prefix));
+			WriteBool(value);
+			if (endLine) WriteLine();
+		}
+
+		protected static void WriteString(byte[] rawBytes, int maxLength, bool endLine = true)
 		{
 			var chars = Encoding.UTF8.GetChars(rawBytes);
 			var length = Math.Min(maxLength, chars.Length);
@@ -626,5 +185,11 @@ public void Dispose()
 			html?.Dispose();
 			fmt?.Dispose();
 		}
+
+		protected abstract bool TestArchive(Stream archiveStream);
+		protected abstract void ParseArchive(Stream archiveStream);
+
+		protected virtual string Parser => "None";
+		protected virtual string Tester => "None";
 	}
 }
diff --git a/ArchiveDiag/Program.cs b/ArchiveDiag/Program.cs
index 11ed83d06..1ce44e760 100644
--- a/ArchiveDiag/Program.cs
+++ b/ArchiveDiag/Program.cs
@@ -94,20 +94,54 @@ static int Main(string[] args)
 					}
 
 			        using var outputStream = File.Open(outputFile, FileMode.Create);
-					using var htmlWriter = new HTMLWriter(outputStream);
+			        using var htmlWriter = new HTMLWriter(outputStream);
 
-					var runner = new ArchiveDiagRunner(inputStream, inputFile);
+					new TarArchiveDiagRunner(inputStream, inputFile).Run(new ConsoleWriter(), htmlWriter);
 
-					runner.Run(new ConsoleWriter(), new HTMLWriter(outputStream));
 
-					htmlWriter.Flush();
-					htmlWriter.Dispose();
 
 				});
 
 	        return 0;
         }
-		
+
+		static void Lala()
+		{
+			var dataBytes = new byte[] { 0x34, 0x68, 0xf2, 0x8d };
+
+			using var ms = new MemoryStream(dataBytes);
+			using var fs = File.Create("output.zip");
+			using var zip = new ZipOutputStream(fs);
+			zip.PutNextEntry(new ZipEntry("content-file.bin"));
+			ms.WriteTo(zip);
+
+		}
+
+		public void UseCreateZipFileFromData()
+		{
+			var dataBytes = new byte[] { 0x49, 0xe2, 0x9d, 0xa4, 0x5a, 0x49, 0x50 };
+
+			CreateZipFileFromData(File.Create("output.zip"), dataBytes);
+
+			using (var ms = new MemoryStream())
+			{
+				CreateZipFileFromData(ms, dataBytes, closeStream: false, zipEntryName: "data.bin");
+				var outputBytes = ms.ToArray();
+			}
+		}
+
+		public void CreateZipFileFromData(Stream outputStream, byte[] inputData, bool closeStream = true, string zipEntryName = "-")
+		{
+			using (var zipStream = new ZipOutputStream(outputStream))
+			{
+				// Stop ZipStream.Dispose() from also Closing the underlying stream.
+				zipStream.IsStreamOwner = closeStream;
+
+				zipStream.PutNextEntry(new ZipEntry(zipEntryName));
+				zipStream.Write(inputData);
+			}
+		}
+
 	}
 
 
diff --git a/ArchiveDiag/Properties/launchSettings.json b/ArchiveDiag/Properties/launchSettings.json
index b2442d25f..fdc7896d5 100644
--- a/ArchiveDiag/Properties/launchSettings.json
+++ b/ArchiveDiag/Properties/launchSettings.json
@@ -2,7 +2,7 @@
   "profiles": {
     "ArchiveDiag": {
       "commandName": "Project",
-      "commandLineArgs": "-e D:\\shared\\testScript.csx"
+      "commandLineArgs": "C:\\Users\\nils\\Downloads\\tartest\\IOSFile.tar"
     }
   }
 }
\ No newline at end of file
diff --git a/ArchiveDiag/TarArchiveDiagRunner.cs b/ArchiveDiag/TarArchiveDiagRunner.cs
new file mode 100644
index 000000000..4169e631a
--- /dev/null
+++ b/ArchiveDiag/TarArchiveDiagRunner.cs
@@ -0,0 +1,170 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+using ConLib;
+using static ConLib.PrettyConsole;
+
+namespace ArchiveDiag
+{
+	class TarArchiveDiagRunner: ArchiveDiagRunner
+	{
+		protected override string Parser => "ArchiveDiag.TarParser";
+
+		public TarArchiveDiagRunner(Stream archiveStream, string? fileName = null) : base(archiveStream, fileName)
+		{
+		}
+
+		protected override bool TestArchive(Stream archiveStream)
+		{
+			return true;
+		}
+
+		protected override void ParseArchive(Stream archiveStream)
+		{
+			var block = new byte[512];
+			while(archiveStream.Length > archiveStream.Position)
+			{
+				var readCount = 0;
+
+				DoTask("Read Block", async () => readCount = await archiveStream.ReadAsync(block));
+
+				var isEndBlock = block.All(b => b == 0);
+				WriteBool("Block is end block? ", isEndBlock);
+
+				if(isEndBlock) continue;
+
+				long fileSize = 0;
+				DoChore("Parse entry header", () => fileSize = ParseHeader(block), true);
+
+				DoChore("Parse UStar header", () => ParseUstar(block), true);
+
+				if (fileSize >= 0)
+				{
+					DoTask("Skip Content", async () => archiveStream.Seek(fileSize, SeekOrigin.Current));
+				}
+				else
+				{
+					throw new Exception("Cannot seek to next entry, since the previous entry didn't contain a valid size");
+				}
+			} 
+		}
+
+		private long ParseHeader(ReadOnlySpan block)
+		{
+
+			DumpStr("File name: ", block[0..100]);
+			// DumpHex("File name (raw): ", block[0..100]);
+			DumpDec("File mode: ", block[100..108]);
+			DumpDec("Owner ID: ", block[108..116]);
+			DumpDec("Group ID: ", block[116..124]);
+
+			var fileSize = DumpDec("File size: ", block[124..136]);
+
+			DumpTim("Modification time: ", block[136..148]); 
+
+			DumpStr("Checksum: ", block[148..156]);
+
+			DumpStr("File type: ", block[156..157]);
+			DumpStr("Linked file name: ", block[157..257]); 
+
+			return fileSize;
+		}
+
+		private void ParseUstar(ReadOnlySpan block)
+		{
+			var magic = ReadStr(block.Slice(257, 6));
+
+			WriteLine($"UStar indicator: {magic}");
+
+			if (!magic.StartsWith("ustar"))
+			{
+				WriteLine($"UStar indicator not found, skipping!");
+				return;
+			}
+
+			DumpDec("Version: ", block[263..265]);
+			DumpStr("Owner user name: ", block[265..297]);
+			DumpStr("Owner group name: ", block[297..329]);
+			DumpDec($"Device major: ", block[329..337]);
+			DumpDec($"Device minor: ", block[337..345]);
+			DumpStr($"Filename prefix: ", block[345..500]);
+
+
+		}
+
+		private string ReadStr(ReadOnlySpan span) => Encoding.ASCII.GetString(span).Trim('\0', ' ');
+
+		private void DumpStr(string prefix, ReadOnlySpan span)
+		{
+			Write(FormattableStringFactory.Create(prefix));
+			WriteString(span.ToArray().Where(b => b != 0).ToArray(), 48);
+		}
+
+		private void DumpHex(string prefix, ReadOnlySpan span)
+		{
+			Write(FormattableStringFactory.Create(prefix));
+			WriteHexBytes(span.ToArray(), 100);
+		}
+
+		private long DumpDec(string prefix, ReadOnlySpan span)
+		{
+			var dec = -1L;
+
+			Write(FormattableStringFactory.Create(prefix));
+
+			var str = ReadStr(span);
+			if (string.IsNullOrWhiteSpace(str))
+			{
+				WriteColor("\n", ConCol.DarkGray);
+				return -1;
+			}
+			try
+			{
+				dec = Convert.ToInt64(str, 8);
+				WriteLine($"{dec}");
+			}
+			catch (Exception x)
+			{
+				WriteColor($"Invalid! ", ConCol.Red);
+				WriteHexBytes(span.ToArray(), 16);
+				WriteColor($" {x.GetType().Name}: {x.Message}\n", ConCol.DarkRed);
+				return -1;
+			}
+
+			return dec;
+		}
+
+		private DateTimeOffset DumpTim(string prefix, ReadOnlySpan span)
+		{
+			var dto = DateTimeOffset.MinValue;
+
+			Write(FormattableStringFactory.Create(prefix));
+
+			var str = ReadStr(span);
+			if (string.IsNullOrWhiteSpace(str))
+			{
+				WriteColor("\n", ConCol.DarkGray);
+				return dto;
+			}
+			try
+			{
+				dto = DateTimeOffset.FromUnixTimeSeconds(Convert.ToInt64(str, 8));
+
+				WriteLine($"{dto:s}");
+			}
+			catch (Exception x)
+			{
+				WriteColor($"Invalid! ", ConCol.Red);
+				WriteHexBytes(span.ToArray(), 16);
+				WriteColor($" {x.GetType().Name}: {x.Message}\n", ConCol.DarkRed);
+			}
+
+			return dto;
+		}
+
+
+
+	}
+}
diff --git a/ArchiveDiag/ZipArchiveDiagRunner.cs b/ArchiveDiag/ZipArchiveDiagRunner.cs
new file mode 100644
index 000000000..d7ec30328
--- /dev/null
+++ b/ArchiveDiag/ZipArchiveDiagRunner.cs
@@ -0,0 +1,484 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Text;
+using ConLib;
+using ICSharpCode.SharpZipLib.ArchiveDiag;
+using ICSharpCode.SharpZipLib.Zip;
+
+namespace ArchiveDiag
+{
+	class ZipArchiveDiagRunner: ArchiveDiagRunner
+	{
+		protected override string Parser => "ArchiveDiag.ZipParser";
+		protected override string Tester => "ICSharpCode.SharpZipLib.ZipFile";
+
+		public int MaxNameHex {get; set;} = 32;
+		public int MaxNameString {get; set;} = 128;
+		public int MaxCommentHex {get; set;} = 32;
+		public int MaxCommentString {get; set;} = 128;
+		public int MaxExtraDataHex {get; set;} = 64;
+
+		public bool WarnUnknownSigns {get; set;} = false;
+
+		public ZipArchiveDiagRunner(Stream archiveStream, string? fileName = null): base(archiveStream, fileName)
+		{
+		}
+
+		protected override bool TestArchive(Stream archiveStream)
+		{
+			return new ZipFile(archiveStream).TestArchive(true, TestStrategy.FindAllErrors, (status, message) =>
+			{
+				if (status.Operation == TestOperation.EntryData) return;
+				var pad = "";//.PadLeft(18 - status.Operation.ToString().Length);
+				if (status.Entry is { } entry)
+				{
+					PrettyConsole.WriteLine($"[{status.Operation}{pad}] #{entry.ZipFileIndex} {entry.Name}");
+				}
+				else
+				{
+					PrettyConsole.WriteLine($"[{status.Operation}{pad}]");
+				}
+
+				if (!string.IsNullOrEmpty(message))
+				{
+					PrettyConsole.WriteLine($"[{status.Operation}{pad}] {message}");
+				}
+			});
+		}
+
+		protected override void ParseArchive(Stream transportStream)
+		{
+
+			transportStream.Seek(0, SeekOrigin.Begin);
+
+			var br = new BinaryReader(transportStream);
+
+			int readVal;
+			while ((readVal = transportStream.ReadByte()) != -1)
+			{
+				if (readVal != 'P') continue;
+				readVal = transportStream.ReadByte();
+
+				if (readVal != 'K') continue;
+				var b1 = (byte)transportStream.ReadByte();
+				var b2 = (byte)transportStream.ReadByte();
+
+				if (b1 > 0x08 || b2 > 0x08)
+				{
+					transportStream.Seek(-2, SeekOrigin.Current);
+					continue;
+				}
+
+				var sign = 'P' | ('K' << 8) | (b1 << 16) | (b2 << 24);
+
+				var signId = sign switch
+				{
+					ZipConstants.DataDescriptorSignature
+					=> nameof(ZipConstants.DataDescriptorSignature),
+					ZipConstants.CentralHeaderSignature
+					=> nameof(ZipConstants.CentralHeaderSignature),
+					ZipConstants.CentralHeaderDigitalSignature
+					=> nameof(ZipConstants.CentralHeaderDigitalSignature),
+					ZipConstants.EndOfCentralDirectorySignature
+					=> nameof(ZipConstants.EndOfCentralDirectorySignature),
+					ZipConstants.LocalHeaderSignature
+					=> nameof(ZipConstants.LocalHeaderSignature),
+					ZipConstants.SpanningTempSignature
+					=> nameof(ZipConstants.SpanningTempSignature),
+					ZipConstants.Zip64CentralDirLocatorSignature
+					=> nameof(ZipConstants.Zip64CentralDirLocatorSignature),
+					ZipConstants.Zip64CentralFileHeaderSignature
+					=> nameof(ZipConstants.Zip64CentralFileHeaderSignature),
+					_ => $"Unknown (0x{sign:x8})",
+				};
+
+				//if (warnUnknownSigns || signId.StartsWith("Unknown"))
+				//{
+				PrettyConsole.WriteLine($"{transportStream.Position:x8} PK: {b1:x2} {b2:x2} {signId}");
+				//}
+
+				switch (sign)
+				{
+					case ZipConstants.LocalHeaderSignature:
+						PrettyConsole.DoChore("Parse Local File Header", ()
+							=> ParseLocalHeader(br), true);
+						break;
+					case ZipConstants.DataDescriptorSignature:
+						PrettyConsole.DoChore("Parse Data Descriptor", ()
+							=> ParseDataDescriptor(br), true);
+						break;
+					case ZipConstants.CentralHeaderSignature:
+						PrettyConsole.DoChore("Parse Central Directory Header", ()
+							=> ParseCentralHeader(br), true);
+						break;
+					case ZipConstants.Zip64CentralFileHeaderSignature:
+						PrettyConsole.DoChore("Parse Zip64 End Of Central Directory Record", ()
+							=> ParseZip64EndOfCentralDirectory(br), true);
+						break;
+					case ZipConstants.Zip64CentralDirLocatorSignature:
+						PrettyConsole.DoChore("Parse Zip64 Central Directory Locator", ()
+							=> ParseZip64CentralDirLocator(br), true);
+						break;
+					case ZipConstants.EndOfCentralDirectorySignature:
+						PrettyConsole.DoChore("Parse End of Central Directory Record", ()
+							=> ParseCentralDirectory(br), true);
+						break;
+					default:
+						if (WarnUnknownSigns) PrettyConsole.WriteLine($"{"Skipping unrecognized signature!"}\n");
+						break;
+				}
+
+			}
+
+		}
+
+		private void ParseArchiveExtraData(BinaryReader br)
+		{
+			var extraLen = br.ReadUInt16();
+			var extraDataBytes = new byte[extraLen];
+			br.Read(extraDataBytes);
+			var extraData = new ZipExtraData(extraDataBytes);
+
+			PrettyConsole.WriteLine($"Extra data:");
+			ParseExtraData(extraData);
+		}
+
+		private void ParseZip64CentralDirLocator(BinaryReader br)
+		{
+			var diskNumber = br.ReadUInt32();
+			var relativeOffset = br.ReadUInt64();
+			var diskTotal = br.ReadUInt64();
+
+			PrettyConsole.WriteLine($"Zip64 End Record Disk Number: {diskNumber}");
+			PrettyConsole.WriteLine($"Zip64 End Record Relative Offset: {relativeOffset} (0x{relativeOffset:x8})");
+			PrettyConsole.WriteLine($"Total number of disks: {diskTotal}");
+		}
+
+		private void ParseZip64EndOfCentralDirectory(BinaryReader br)
+		{
+			var recordSize = br.ReadUInt64();
+			var versionMadeBy = ZipVersion.From(br.ReadUInt16());
+			var versionToExtract = ZipVersion.From(br.ReadUInt16());
+			var diskNumber = br.ReadUInt32();
+			var startCentralDirDisk = br.ReadUInt32();
+
+			var entriesForDisk = br.ReadUInt64();
+			var entriesForWholeCentralDir = br.ReadUInt64();
+			var centralDirSize = br.ReadUInt64();
+			var offsetOfCentralDir = br.ReadUInt64();
+
+			var extDataSize = recordSize - 44;
+
+			PrettyConsole.WriteLine($"Size of record: {recordSize} (0x{recordSize:x16})");
+			PrettyConsole.WriteLine($"Version Made By: {versionMadeBy} (0x{versionMadeBy.OSRaw:x2})");
+			PrettyConsole.WriteLine($"Version Needed: {versionToExtract} (0x{versionToExtract.OSRaw:x2})");
+			PrettyConsole.WriteLine($"Disk Number: {diskNumber}");
+			PrettyConsole.WriteLine($"Start Central Directory Disk: {startCentralDirDisk:x8}");
+
+			PrettyConsole.WriteLine($"Entries For Disk: {entriesForDisk}");
+			PrettyConsole.WriteLine($"Entries For Central Directory: {entriesForWholeCentralDir}");
+			PrettyConsole.WriteLine($"Central Directory Size: {centralDirSize} (0x{centralDirSize:x16})");
+			PrettyConsole.WriteLine($"Central Directory Offset: {offsetOfCentralDir} (0x{offsetOfCentralDir:x16})");
+
+
+			PrettyConsole.WriteLine($"Extensible data size: {extDataSize}");
+			if (extDataSize <= 0) return;
+
+			PrettyConsole.WriteLine($"Parsing of extensible data is not supported! Skipping.", ConCol.Yellow);
+			// Ignore skipping the data for now, since it is more likely to cause issues than it is to prevent them
+			//br.BaseStream.Seek((long)extDataSize, SeekOrigin.Current);
+		}
+
+		private static DateTime DateTimeFromDosTime(uint dosTime)
+		{
+			var sec = Math.Min(59, 2 * (dosTime & 0x1f));
+			var min = Math.Min(59, (dosTime >> 5) & 0x3f);
+			var hrs = Math.Min(23, (dosTime >> 11) & 0x1f);
+			var mon = Math.Max(1, Math.Min(12, ((dosTime >> 21) & 0xf)));
+			var year = ((dosTime >> 25) & 0x7f) + 1980;
+			var day = Math.Max(1, Math.Min(DateTime.DaysInMonth((int)year, (int)mon), (int)((dosTime >> 16) & 0x1f)));
+			return new DateTime((int)year, (int)mon, day, (int)hrs, (int)min, (int)sec);
+		}
+
+		private void ParseCentralHeader(BinaryReader br)
+		{
+			var versionMadeBy = ZipVersion.From(br.ReadUInt16());
+			var versionToExtract = ZipVersion.From(br.ReadUInt16());
+			var bitFlags = (GeneralBitFlags)br.ReadUInt16();
+			var method = (CompressionMethod)br.ReadUInt16();
+			var dosTime = br.ReadUInt32();
+			var dateTime = DateTimeFromDosTime(dosTime);
+			var crc = br.ReadUInt32();
+			var compressedSize = (long)br.ReadUInt32();
+			var size = (long)br.ReadUInt32();
+			var nameLen = br.ReadUInt16();
+			var extraLen = br.ReadUInt16();
+			var commentLen = br.ReadUInt16();
+			var diskStartNo = br.ReadUInt16();  // Not currently used
+			var internalAttributes = br.ReadUInt16();  // Not currently used
+
+			var externalAttributes = (FileAttributes)br.ReadUInt32();
+			var offset = br.ReadUInt32();
+
+			var nameData = new byte[nameLen];
+			br.Read(nameData);
+
+			var extraDataBytes = new byte[extraLen];
+			br.Read(extraDataBytes);
+			var extraData = new ZipExtraData(extraDataBytes);
+
+			var commentData = new byte[commentLen];
+			br.Read(commentData);
+
+
+			PrettyConsole.WriteLine($"Version Made By: {versionMadeBy} (0x{versionMadeBy.OSRaw:x2})");
+			PrettyConsole.WriteLine($"Version Needed: {versionToExtract} (0x{versionToExtract.OSRaw:x2})");
+			PrettyConsole.WriteLine($"Bit flags: {bitFlags:F}");
+			PrettyConsole.WriteLine($"Compression Method: {method:G}");
+			PrettyConsole.WriteLine($"DOS Date: 0x{dosTime:x8}");
+			PrettyConsole.WriteLine($"File Date: {dateTime:yyyy-MM-dd}");
+			PrettyConsole.WriteLine($"File Time: {dateTime:HH:mm:ss}");
+			PrettyConsole.WriteLine($"CRC: 0x{crc:x8}");
+			WriteSize($"Compressed Size:", compressedSize);
+			WriteSize($"Uncompressed Size:", size);
+			PrettyConsole.WriteLine($"Name Length: {nameLen}");
+			PrettyConsole.WriteLine($"Extra Data Length: {extraLen}");
+			PrettyConsole.WriteLine($"Comment Length: {commentLen}");
+			PrettyConsole.WriteLine($"Disk number start: {diskStartNo}");
+			PrettyConsole.WriteLine($"Internal file attributes: {internalAttributes:x4}");
+			PrettyConsole.WriteLine($"External file attributes: {externalAttributes:F}");
+			PrettyConsole.WriteLine($"Relative offset of local header: {offset}");
+
+			PrettyConsole.Write($"Name (raw): "); WriteHexBytes(nameData, MaxNameHex);
+			PrettyConsole.Write($"Name: "); WriteString(nameData, MaxNameString);
+
+			if (commentLen > 0)
+			{
+				PrettyConsole.Write($"Comment (raw): "); WriteHexBytes(commentData, MaxCommentHex);
+				PrettyConsole.Write($"Comment: "); WriteString(commentData, MaxCommentString);
+			}
+
+			PrettyConsole.WriteLine($"Extra data:");
+			ParseExtraData(extraData);
+
+		}
+
+		private static void WriteSize(FormattableString fs, in long size)
+		{
+			PrettyConsole.Write(fs);
+
+			if (size == uint.MaxValue)
+			{
+				PrettyConsole.WriteLine($" {size} ({"Zip64 Indicator"})", ConCol.DarkGray, ConCol.Blue);
+			}
+			else
+			{
+				PrettyConsole.WriteLine($" {size}");
+			}
+		}
+
+		private void ParseExtraData(ZipExtraData extraData)
+		{
+			PrettyConsole.PushGroup("extra-data");
+			var extraDataBytes = extraData.GetEntryData();
+
+			foreach (var (tag, range) in extraData.EnumerateTags().ToList())
+			{
+				var length = range.End.Value - range.Start.Value;
+
+				var knownId = Enum.IsDefined(typeof(ExtraDataType), tag);
+
+				PrettyConsole.WriteLine($" - Type: {(knownId ? tag : ExtraDataType.Unknown):G} ({(uint)tag:x4}), Length: {length}");
+				PrettyConsole.PushGroup("extra-raw");
+				if (extraDataBytes.Length < range.End.Value)
+				{
+					PrettyConsole.WriteLine($" Invalid length! Skipping parse attemmpt!", ConCol.Red);
+					PrettyConsole.PopGroup(); // extra-raw
+					PrettyConsole.PopGroup(); // extra-data
+					return;
+				}
+
+				PrettyConsole.Write($" Raw: ");
+				WriteHexBytes(extraDataBytes[range], MaxExtraDataHex);
+
+				PrettyConsole.PopGroup();
+
+
+				switch (tag)
+				{
+					case ExtraDataType.NTFS:
+					{
+						PrettyConsole.PushGroup("extra-ntfs");
+						var tagNtDate = extraData.GetData();
+
+						PrettyConsole.WriteLine($" Created: {tagNtDate.CreateTime:yyyy-MM-dd HH:mm:ss}");
+						PrettyConsole.WriteLine($" Accessed: {tagNtDate.LastAccessTime:yyyy-MM-dd HH:mm:ss}");
+						PrettyConsole.WriteLine($" Modified: {tagNtDate.LastModificationTime:yyyy-MM-dd HH:mm:ss}");
+						PrettyConsole.PopGroup();
+
+						break;
+					}
+					case ExtraDataType.UnixExtendedTime:
+					{
+						PrettyConsole.PushGroup("extra-unix-xtime");
+
+						var tagUnixDate = extraData.GetData();
+
+						PrettyConsole.WriteLine($" Created: {tagUnixDate.CreateTime:yyyy-MM-dd HH:mm:ss}");
+						PrettyConsole.WriteLine($" Accessed: {tagUnixDate.AccessTime:yyyy-MM-dd HH:mm:ss}");
+						PrettyConsole.WriteLine($" Modified: {tagUnixDate.ModificationTime:yyyy-MM-dd HH:mm:ss}");
+						PrettyConsole.PopGroup();
+						break;
+					}
+					case ExtraDataType.UnicodeName:
+					{
+						PrettyConsole.PushGroup("extra-unicode-name");
+
+						var nameRange = new Range(range.Start.Value + 5, range.End.Value);
+						var unicodeName = Encoding.UTF8.GetString(extraDataBytes[nameRange]);
+						File.WriteAllText("unicodetext.txt", unicodeName);
+						PrettyConsole.WriteLine($" Unicode name: {unicodeName}");
+						PrettyConsole.PopGroup();
+						break;
+					}
+					case ExtraDataType.Zip64:
+					{
+						PrettyConsole.PushGroup("extra-zip64");
+						using (var br = new BinaryReader(extraData.GetStreamForTag((int)tag)))
+						{
+							var usize64 = br.ReadUInt64();
+							var csize64 = br.ReadUInt64();
+							PrettyConsole.WriteLine($" Compressed Size: {csize64} (0x{csize64:x16})");
+							PrettyConsole.WriteLine($" Uncompressed Size: {usize64} (0x{usize64:x16})");
+						}
+
+						PrettyConsole.PopGroup();
+						break;
+					}
+				}
+			}
+
+			PrettyConsole.PopGroup();
+		}
+
+		private void ParseCentralDirectory(BinaryReader br)
+		{
+			var diskNumber = br.ReadUInt16();
+			var startCentralDirDisk = br.ReadUInt16();
+			var entriesForDisk = br.ReadUInt16();
+			var entriesForWholeCentralDir = br.ReadUInt16();
+			var centralDirSize = br.ReadUInt32();
+			var offsetOfCentralDir = br.ReadUInt32();
+			var commentSize = br.ReadUInt16();
+
+			byte[] commentData = new byte[commentSize];
+			br.Read(commentData);
+
+			PrettyConsole.WriteLine($"Disk Number: {diskNumber:x4}");
+			PrettyConsole.WriteLine($"Start Central Directory Disk: {startCentralDirDisk:x4}");
+			PrettyConsole.WriteLine($"Entries For Central Directory: {entriesForWholeCentralDir}");
+			PrettyConsole.WriteLine($"Entries For Disk: {entriesForDisk}");
+			PrettyConsole.WriteLine($"Central Directory Size: {centralDirSize}");
+			PrettyConsole.WriteLine($"Central Directory Offset: {offsetOfCentralDir:x8}");
+			PrettyConsole.WriteLine($"Comment Size: {commentSize}");
+
+			if (commentData.Length > 0)
+			{
+				PrettyConsole.Write($"Comment (raw): "); WriteHexBytes(commentData, MaxCommentHex);
+				PrettyConsole.Write($"Comment: "); WriteString(commentData, MaxCommentString);
+			}
+
+			var zip64 = diskNumber == 0xffff
+			            || startCentralDirDisk == 0xffff
+			            || entriesForDisk == 0xffff
+			            || entriesForWholeCentralDir == 0xffff
+			            || centralDirSize == 0xffffffff
+			            || offsetOfCentralDir == 0xffffffff;
+
+			WriteBool("Zip64 Indication: ", zip64);
+		}
+
+		private static void ParseDataDescriptor(BinaryReader br)
+		{
+			var crc = br.ReadInt32();
+
+			var pos = br.BaseStream.Position;
+			var csize = br.ReadInt32();
+			var usize = br.ReadInt32();
+			var crc32 = br.ReadUInt32();
+
+			br.BaseStream.Seek(-12, SeekOrigin.Current);
+
+			var csize64 = br.ReadInt64();
+			var usize64 = br.ReadInt64();
+			var crc64 = br.ReadUInt32();
+
+			// Revert stream back to non-zip64 descriptor to not risk skipping next entry
+			br.BaseStream.Seek(-8, SeekOrigin.Current);
+
+			PrettyConsole.WriteLine($"32-bit sizes:");
+			PrettyConsole.PushGroup("descriptor32");
+			PrettyConsole.WriteLine($"Compressed Size: {csize} (0x{csize:x8})");
+			PrettyConsole.WriteLine($"Uncompressed Size: {usize} (0x{usize:x8})");
+			PrettyConsole.WriteLine($"CRC: 0x{crc32:x8}");
+			PrettyConsole.PopGroup();
+
+			PrettyConsole.WriteLine($"64-bit sizes (Zip64):");
+			PrettyConsole.PushGroup("descriptor64");
+			PrettyConsole.WriteLine($"Compressed Size: {csize64} (0x{csize64:x16})");
+			PrettyConsole.WriteLine($"Uncompressed Size: {usize64} (0x{usize64:x16})");
+			PrettyConsole.WriteLine($"CRC: 0x{crc64:x8}");
+			PrettyConsole.PopGroup();
+
+		}
+
+		private void ParseLocalHeader(BinaryReader br)
+		{
+
+
+
+			var extractVersion = ZipVersion.From(br.ReadUInt16());
+			var flags = (GeneralBitFlags)br.ReadUInt16();
+			var method = (CompressionMethod)br.ReadUInt16();
+			var dosTime = br.ReadUInt32();
+			var dateTime = DateTimeFromDosTime(dosTime);
+			var crc = br.ReadUInt32();
+			long compressedSize = br.ReadUInt32();
+			long size = br.ReadUInt32();
+			int storedNameLength = br.ReadUInt16();
+			int extraDataLength = br.ReadUInt16();
+
+			var nameData = new byte[storedNameLength];
+			br.Read(nameData);
+
+			var extraDataBytes = new byte[extraDataLength];
+			br.Read(extraDataBytes);
+
+			var extraData = new ZipExtraData(extraDataBytes);
+
+
+			PrettyConsole.WriteLine($"Version Needed: {extractVersion} (0x{extractVersion.OperatingSystem:x})");
+			PrettyConsole.WriteLine($"Local Flags: {flags:F}");
+			PrettyConsole.WriteLine($"Compression Method: {method:G}");
+			PrettyConsole.WriteLine($"DOS Date: 0x{dosTime:x8}");
+			PrettyConsole.WriteLine($"File Date: {dateTime:yyyy-MM-dd}");
+			PrettyConsole.WriteLine($"File Time: {dateTime:HH:mm:ss}");
+			PrettyConsole.WriteLine($"CRC: 0x{crc:x8}");
+			WriteSize($"Compressed Size:", compressedSize);
+			WriteSize($"Uncompressed Size:", size);
+			PrettyConsole.WriteLine($"Name Length: {storedNameLength}");
+			PrettyConsole.WriteLine($"Extra Data Length: {extraDataLength}");
+			PrettyConsole.WriteLine($"Name (raw): "); WriteHexBytes(nameData, MaxNameHex);
+			PrettyConsole.Write($"Name: "); WriteString(nameData, MaxNameString);
+			PrettyConsole.WriteLine($"Extra data:");
+			ParseExtraData(extraData);
+
+		}
+
+
+		// Utils
+
+		
+	}
+}

From 667b579a59db71f39dfee9c7f6eaf4c02dc12788 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?nils=20m=C3=A5s=C3=A9n?= 
Date: Sat, 13 Feb 2021 15:16:12 +0100
Subject: [PATCH 3/7] Add wasm stub

---
 ArchiveDiag/ArchiveDiag.csproj | 15 +++++++++++++++
 ArchiveDiag/JobStatus.cs       |  6 +-----
 ArchiveDiag/Program.cs         | 10 ----------
 3 files changed, 16 insertions(+), 15 deletions(-)

diff --git a/ArchiveDiag/ArchiveDiag.csproj b/ArchiveDiag/ArchiveDiag.csproj
index fc7c693e9..4d6fdf67f 100644
--- a/ArchiveDiag/ArchiveDiag.csproj
+++ b/ArchiveDiag/ArchiveDiag.csproj
@@ -6,11 +6,26 @@
     0.4.12
   
 
+  
+		
+	  
+    
+    
+  
+
   
     
     
   
 
+  
+		
+		
+		
+    
+    
+  
+
   
     
     
diff --git a/ArchiveDiag/JobStatus.cs b/ArchiveDiag/JobStatus.cs
index e0bb92fed..5ee2774a1 100644
--- a/ArchiveDiag/JobStatus.cs
+++ b/ArchiveDiag/JobStatus.cs
@@ -1,8 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace ArchiveDiagJson
+namespace ArchiveDiagJson
 {
 	public class JobStatus
 	{
diff --git a/ArchiveDiag/Program.cs b/ArchiveDiag/Program.cs
index 1ce44e760..6be8cf64d 100644
--- a/ArchiveDiag/Program.cs
+++ b/ArchiveDiag/Program.cs
@@ -1,23 +1,13 @@
 using System;
 using System.Collections.Generic;
-using System.Collections.Immutable;
-using System.ComponentModel;
-using System.Diagnostics;
 using System.IO;
-using System.Linq;
-using System.Text;
-using System.Threading;
 using ArchiveDiag;
 using CommandLine;
-using ConLib;
 using ConLib.Console;
 using ConLib.HTML;
-using ICSharpCode.SharpZipLib.Core;
 using ICSharpCode.SharpZipLib.Zip;
-using Microsoft.CodeAnalysis;
 using Microsoft.CodeAnalysis.CSharp.Scripting;
 using Microsoft.CodeAnalysis.Scripting;
-using static ConLib.PrettyConsole;
 
 namespace ICSharpCode.SharpZipLib.ArchiveDiag
 {

From 31613fcfdda4d73f5eafe3c1aa6d0a9de11f517c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?nils=20m=C3=A5s=C3=A9n?= 
Date: Mon, 7 Jun 2021 21:06:48 +0200
Subject: [PATCH 4/7] add zis iter test

---
 ArchiveDiag/ArchiveDiag.csproj                |  2 +-
 ArchiveDiag/Program.cs                        | 14 +++-
 ArchiveDiag/ZipArchiveDiagRunner.cs           | 68 ++++++++++++++-----
 .../Streams/InflaterInputStream.cs            |  2 +-
 4 files changed, 65 insertions(+), 21 deletions(-)

diff --git a/ArchiveDiag/ArchiveDiag.csproj b/ArchiveDiag/ArchiveDiag.csproj
index 4d6fdf67f..0057aa392 100644
--- a/ArchiveDiag/ArchiveDiag.csproj
+++ b/ArchiveDiag/ArchiveDiag.csproj
@@ -3,7 +3,7 @@
   
     Exe
     netcoreapp3.1
-    0.4.12
+    0.5.22
   
 
   
diff --git a/ArchiveDiag/Program.cs b/ArchiveDiag/Program.cs
index 6be8cf64d..9121821c6 100644
--- a/ArchiveDiag/Program.cs
+++ b/ArchiveDiag/Program.cs
@@ -15,7 +15,7 @@ public class Program
 	{
 		public class Options
 		{
-			[Value(0, HelpText = "Input filename")]
+			[Value(0, HelpText = "Input filename", Required = true)]
 			public string Filename { get; set; }
 
 			[Option('v', "verbose", Required = false, HelpText = "Set output to verbose messages")]
@@ -29,6 +29,9 @@ public class Options
 
 			[Option('e', "eval", HelpText = "Run the input file as a C# script and create a report from the resulting stream")]
 			public bool Evaluate { get; set; }
+			
+			[Option('t', "tar")]
+			public bool Tar { get; set; }
 		}
 
 
@@ -86,7 +89,14 @@ static int Main(string[] args)
 			        using var outputStream = File.Open(outputFile, FileMode.Create);
 			        using var htmlWriter = new HTMLWriter(outputStream);
 
-					new TarArchiveDiagRunner(inputStream, inputFile).Run(new ConsoleWriter(), htmlWriter);
+					if(o.Tar) {
+						new TarArchiveDiagRunner(inputStream, inputFile).Run(new ConsoleWriter(), htmlWriter);
+					} else {
+						new ZipArchiveDiagRunner(inputStream, inputFile)
+						{
+							WarnUnknownSigns = false,
+						}.Run(new ConsoleWriter(), htmlWriter);
+					}
 
 
 
diff --git a/ArchiveDiag/ZipArchiveDiagRunner.cs b/ArchiveDiag/ZipArchiveDiagRunner.cs
index d7ec30328..696517364 100644
--- a/ArchiveDiag/ZipArchiveDiagRunner.cs
+++ b/ArchiveDiag/ZipArchiveDiagRunner.cs
@@ -11,7 +11,7 @@ namespace ArchiveDiag
 	class ZipArchiveDiagRunner: ArchiveDiagRunner
 	{
 		protected override string Parser => "ArchiveDiag.ZipParser";
-		protected override string Tester => "ICSharpCode.SharpZipLib.ZipFile";
+		protected override string Tester => "ICSharpCode.SharpZipLib.ZipFile+ZipInputStream";
 
 		public int MaxNameHex {get; set;} = 32;
 		public int MaxNameString {get; set;} = 128;
@@ -27,24 +27,49 @@ public ZipArchiveDiagRunner(Stream archiveStream, string? fileName = null): base
 
 		protected override bool TestArchive(Stream archiveStream)
 		{
-			return new ZipFile(archiveStream).TestArchive(true, TestStrategy.FindAllErrors, (status, message) =>
-			{
-				if (status.Operation == TestOperation.EntryData) return;
-				var pad = "";//.PadLeft(18 - status.Operation.ToString().Length);
-				if (status.Entry is { } entry)
-				{
-					PrettyConsole.WriteLine($"[{status.Operation}{pad}] #{entry.ZipFileIndex} {entry.Name}");
-				}
-				else
+			var zipFileTest = false;
+			PrettyConsole.DoChore("[TEST] ZipFile.Test", () => {
+				zipFileTest = new ZipFile(archiveStream).TestArchive(true, TestStrategy.FindAllErrors, (status, message) =>
 				{
-					PrettyConsole.WriteLine($"[{status.Operation}{pad}]");
-				}
+					if (status.Operation == TestOperation.EntryData) return;
+					var pad = "";//.PadLeft(18 - status.Operation.ToString().Length);
+					if (status.Entry is { } entry)
+					{
+						PrettyConsole.WriteLine($"[{status.Operation}{pad}] #{entry.ZipFileIndex} {entry.Name}");
+					}
+					else
+					{
+						PrettyConsole.WriteLine($"[{status.Operation}{pad}]");
+					}
 
-				if (!string.IsNullOrEmpty(message))
-				{
-					PrettyConsole.WriteLine($"[{status.Operation}{pad}] {message}");
-				}
+					if (!string.IsNullOrEmpty(message))
+					{
+						PrettyConsole.WriteLine($"[{status.Operation}{pad}] {message}");
+					}
+				});
 			});
+
+			var zisIterTest = false;
+			zisIterTest = PrettyConsole.DoChore("[TEST] ZipInputStream.Iterate", () => {
+				archiveStream.Seek(0, SeekOrigin.Begin);
+				using(var zis = new ZipInputStream(archiveStream)) {
+					ZipEntry entry;
+					var startEntry = 0l;
+					var entryNum = 0;
+					while ((entry = zis.GetNextEntry()) != null) {
+						entryNum++;
+						var startData = archiveStream.Position + (zis.inputBuffer.RawLength - zis.inputBuffer.Available);
+						var entryName = entry.Name;
+						zis.CloseEntry();
+						var endEntry = archiveStream.Position + (zis.inputBuffer.RawLength - zis.inputBuffer.Available);
+						var headSize = startData - startEntry;
+						var dataSize = endEntry - startData;
+						PrettyConsole.WriteLine($"- Entry #{entryNum,3} @ {startEntry,8}: {entryName} ({headSize} + {dataSize} byte(s))");
+						startEntry = endEntry;
+					}
+				}
+			}, continueOnFail: true);
+			return zisIterTest && zipFileTest;
 		}
 
 		protected override void ParseArchive(Stream transportStream)
@@ -347,10 +372,19 @@ private void ParseExtraData(ZipExtraData extraData)
 						PrettyConsole.PushGroup("extra-zip64");
 						using (var br = new BinaryReader(extraData.GetStreamForTag((int)tag)))
 						{
+							if (length < 16)
+							{
+								PrettyConsole.WriteLine($" Extra data length too small!", ConCol.Red);
+								PrettyConsole.WriteLine($" Zip64 should be {16} bytes, but {length} was indicated!");
+							}
+							
+							if (length < 8) break; 
 							var usize64 = br.ReadUInt64();
+							PrettyConsole.WriteLine($" Uncompressed Size: {usize64} (0x{usize64:x16})");
+							
+							if (length < 16) break;
 							var csize64 = br.ReadUInt64();
 							PrettyConsole.WriteLine($" Compressed Size: {csize64} (0x{csize64:x16})");
-							PrettyConsole.WriteLine($" Uncompressed Size: {usize64} (0x{usize64:x16})");
 						}
 
 						PrettyConsole.PopGroup();
diff --git a/src/ICSharpCode.SharpZipLib/Zip/Compression/Streams/InflaterInputStream.cs b/src/ICSharpCode.SharpZipLib/Zip/Compression/Streams/InflaterInputStream.cs
index 3fb257906..5f4afdf71 100644
--- a/src/ICSharpCode.SharpZipLib/Zip/Compression/Streams/InflaterInputStream.cs
+++ b/src/ICSharpCode.SharpZipLib/Zip/Compression/Streams/InflaterInputStream.cs
@@ -691,7 +691,7 @@ public override int Read(byte[] buffer, int offset, int count)
 		/// 
 		/// Input buffer for this stream.
 		/// 
-		protected InflaterInputBuffer inputBuffer;
+		public InflaterInputBuffer inputBuffer;
 
 		/// 
 		/// Base stream the inflater reads from.

From b1604e18638ea734825255e16ad424c2965305b4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?nils=20m=C3=A5s=C3=A9n?= 
Date: Mon, 7 Jun 2021 21:35:42 +0200
Subject: [PATCH 5/7] feat(zipinputstream): updated entry iteration

---
 .../Zip/ZipInputStream.cs                     | 71 +++++++++++++------
 1 file changed, 51 insertions(+), 20 deletions(-)

diff --git a/src/ICSharpCode.SharpZipLib/Zip/ZipInputStream.cs b/src/ICSharpCode.SharpZipLib/Zip/ZipInputStream.cs
index 147d4043d..c5e3fc26e 100644
--- a/src/ICSharpCode.SharpZipLib/Zip/ZipInputStream.cs
+++ b/src/ICSharpCode.SharpZipLib/Zip/ZipInputStream.cs
@@ -3,6 +3,7 @@
 using ICSharpCode.SharpZipLib.Zip.Compression;
 using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
 using System;
+using System.Diagnostics;
 using System.IO;
 
 namespace ICSharpCode.SharpZipLib.Zip
@@ -163,31 +164,12 @@ public ZipEntry GetNextEntry()
 				CloseEntry();
 			}
 
-			int header = inputBuffer.ReadLeInt();
-
-			if (header == ZipConstants.CentralHeaderSignature ||
-				header == ZipConstants.EndOfCentralDirectorySignature ||
-				header == ZipConstants.CentralHeaderDigitalSignature ||
-				header == ZipConstants.ArchiveExtraDataSignature ||
-				header == ZipConstants.Zip64CentralFileHeaderSignature)
+			if (!SkipUntilNextEntry())
 			{
-				// No more individual entries exist
 				Dispose();
 				return null;
 			}
 
-			// -jr- 07-Dec-2003 Ignore spanning temporary signatures if found
-			// Spanning signature is same as descriptor signature and is untested as yet.
-			if ((header == ZipConstants.SpanningTempSignature) || (header == ZipConstants.SpanningSignature))
-			{
-				header = inputBuffer.ReadLeInt();
-			}
-
-			if (header != ZipConstants.LocalHeaderSignature)
-			{
-				throw new ZipException("Wrong Local header signature: 0x" + String.Format("{0:X}", header));
-			}
-
 			var versionRequiredToExtract = (short)inputBuffer.ReadLeShort();
 
 			flags = inputBuffer.ReadLeShort();
@@ -283,6 +265,54 @@ public ZipEntry GetNextEntry()
 			return entry;
 		}
 
+		/// 
+		/// Reads bytes from the input stream until either a local file header signature, or another signature
+		/// indicating that no more entries should be present, is found.
+		/// 
+		/// Thrown if the end of the input stream is reached without any signatures found
+		/// Returns whether the found signature is for a local entry header
+		private bool SkipUntilNextEntry()
+		{
+			// First let's skip all null bytes since it's the sane padding to add when updating an entry with smaller size
+			var paddingSkipped = 0;
+			while(inputBuffer.ReadLeByte() == 0) {
+				paddingSkipped++;
+			}
+			
+			// Last byte read was not actually consumed, restore the offset
+			inputBuffer.Available += 1;
+			if(paddingSkipped > 0) {
+				Debug.WriteLine("Skipped {0} null byte(s) before reading signature", paddingSkipped);
+			}
+			
+			var offset = 0;
+			// Read initial header quad directly after the last entry
+			var header = (uint)inputBuffer.ReadLeInt();
+			do
+			{
+				switch (header)
+				{
+					case ZipConstants.CentralHeaderSignature:
+					case ZipConstants.EndOfCentralDirectorySignature:
+					case ZipConstants.CentralHeaderDigitalSignature:
+					case ZipConstants.ArchiveExtraDataSignature:
+					case ZipConstants.Zip64CentralFileHeaderSignature:
+						Debug.WriteLine("Non-entry signature found at offset {0,2}: 0x{1:x8}", offset, header);
+						// No more individual entries exist
+						return false;
+
+					case ZipConstants.LocalHeaderSignature:
+						Debug.WriteLine("Entry local header signature found at offset {0,2}: 0x{1:x8}", offset, header);
+						return true;
+					default:
+						// Current header quad did not match any signature, shift in another byte
+						header = (uint) (inputBuffer.ReadLeByte() << 24) | (header >> 8);
+						offset++;
+						break;
+				}
+			} while (true); // Loop until we either get an EOF exception or we find the next signature
+		}
+
 		/// 
 		/// Read data descriptor at the end of compressed data.
 		/// 
@@ -380,6 +410,7 @@ public void CloseEntry()
 
 			if ((inputBuffer.Available > csize) && (csize >= 0))
 			{
+				// Buffer can contain entire entry data. Internally offsetting position inside buffer
 				inputBuffer.Available = (int)((long)inputBuffer.Available - csize);
 			}
 			else

From ecea98ed229474c4d9718fc7263d4e03ad6630e7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nils=20M=C3=A5s=C3=A9n?= 
Date: Wed, 23 Feb 2022 11:27:04 +0100
Subject: [PATCH 6/7] fix(archivediag): descriptor and tar file output

---
 ArchiveDiag/ArchiveDiag.csproj                |  2 +-
 ArchiveDiag/ArchiveDiagRunner.cs              |  3 +++
 ArchiveDiag/TarArchiveDiagRunner.cs           |  6 ++++-
 ArchiveDiag/ZipArchiveDiagRunner.cs           | 26 ++++++++++++++-----
 .../Zip/ZipInputStream.cs                     |  5 ++--
 5 files changed, 31 insertions(+), 11 deletions(-)

diff --git a/ArchiveDiag/ArchiveDiag.csproj b/ArchiveDiag/ArchiveDiag.csproj
index 0057aa392..729075a2f 100644
--- a/ArchiveDiag/ArchiveDiag.csproj
+++ b/ArchiveDiag/ArchiveDiag.csproj
@@ -3,7 +3,7 @@
   
     Exe
     netcoreapp3.1
-    0.5.22
+    0.6.23
   
 
   
diff --git a/ArchiveDiag/ArchiveDiagRunner.cs b/ArchiveDiag/ArchiveDiagRunner.cs
index 84d5e0926..b819a12bf 100644
--- a/ArchiveDiag/ArchiveDiagRunner.cs
+++ b/ArchiveDiag/ArchiveDiagRunner.cs
@@ -1,4 +1,5 @@
 using System;
+using System.Runtime.InteropServices;
 using System.Collections.Generic;
 using System.IO;
 using System.Runtime.CompilerServices;
@@ -58,6 +59,8 @@ public void Run(params ColorWriter[] writers)
 			WriteVersion();
 			WriteLine($"\n");
 
+			
+			WriteLine($"Runtime: {RuntimeInformation.FrameworkDescription} ({RuntimeInformation.OSDescription}/{RuntimeInformation.OSArchitecture})");
 			WriteLine($"File: {FileName ?? ""}");
 			Write($"Size: {archiveStream.Length}");
 			WriteColor(" byte(s)\n", ConCol.DarkGray);
diff --git a/ArchiveDiag/TarArchiveDiagRunner.cs b/ArchiveDiag/TarArchiveDiagRunner.cs
index 4169e631a..cd973d5a3 100644
--- a/ArchiveDiag/TarArchiveDiagRunner.cs
+++ b/ArchiveDiag/TarArchiveDiagRunner.cs
@@ -3,6 +3,7 @@
 using System.Linq;
 using System.Runtime.CompilerServices;
 using System.Text;
+using ArchiveDiag.Tar;
 using ConLib;
 using static ConLib.PrettyConsole;
 
@@ -66,7 +67,10 @@ private long ParseHeader(ReadOnlySpan block)
 
 			DumpStr("Checksum: ", block[148..156]);
 
-			DumpStr("File type: ", block[156..157]);
+			var typeflag = block[156];
+			var identifiedFlag = Enum.IsDefined(typeof(TypeFlag), typeflag) ? ((TypeFlag)typeflag).ToString("F") : "unknown";
+
+			WriteLine($"File type: {identifiedFlag} (0x{typeflag} '{(char)typeflag}')");
 			DumpStr("Linked file name: ", block[157..257]); 
 
 			return fileSize;
diff --git a/ArchiveDiag/ZipArchiveDiagRunner.cs b/ArchiveDiag/ZipArchiveDiagRunner.cs
index 696517364..c55308066 100644
--- a/ArchiveDiag/ZipArchiveDiagRunner.cs
+++ b/ArchiveDiag/ZipArchiveDiagRunner.cs
@@ -52,7 +52,7 @@ protected override bool TestArchive(Stream archiveStream)
 			var zisIterTest = false;
 			zisIterTest = PrettyConsole.DoChore("[TEST] ZipInputStream.Iterate", () => {
 				archiveStream.Seek(0, SeekOrigin.Begin);
-				using(var zis = new ZipInputStream(archiveStream)) {
+				using(var zis = new ZipInputStream(archiveStream){IsStreamOwner = false}) {
 					ZipEntry entry;
 					var startEntry = 0l;
 					var entryNum = 0;
@@ -60,7 +60,20 @@ protected override bool TestArchive(Stream archiveStream)
 						entryNum++;
 						var startData = archiveStream.Position + (zis.inputBuffer.RawLength - zis.inputBuffer.Available);
 						var entryName = entry.Name;
-						zis.CloseEntry();
+
+						// PrettyConsole.WriteLine($"- Entry #{entryNum,3} @ {startEntry,8} // {zis.inputBuffer.RawLength} of {zis.inputBuffer.Available}");
+
+						// while(zis.ReadByte() >= 0) {
+						// 	Console.Write(".");
+						// }
+
+						try {
+							zis.CloseEntry();
+						} catch {
+
+							PrettyConsole.WriteLine($"Error position: {(archiveStream.CanRead ? archiveStream.Position : -1)} // {zis.inputBuffer.RawLength} of {zis.inputBuffer.Available}");
+							throw;
+						}
 						var endEntry = archiveStream.Position + (zis.inputBuffer.RawLength - zis.inputBuffer.Available);
 						var headSize = startData - startEntry;
 						var dataSize = endEntry - startData;
@@ -440,29 +453,28 @@ private static void ParseDataDescriptor(BinaryReader br)
 			var pos = br.BaseStream.Position;
 			var csize = br.ReadInt32();
 			var usize = br.ReadInt32();
-			var crc32 = br.ReadUInt32();
 
-			br.BaseStream.Seek(-12, SeekOrigin.Current);
+			br.BaseStream.Seek(-8, SeekOrigin.Current);
 
+			// var crc64 = br.ReadUInt32();
 			var csize64 = br.ReadInt64();
 			var usize64 = br.ReadInt64();
-			var crc64 = br.ReadUInt32();
 
 			// Revert stream back to non-zip64 descriptor to not risk skipping next entry
 			br.BaseStream.Seek(-8, SeekOrigin.Current);
 
+			PrettyConsole.WriteLine($"CRC: 0x{crc:x8}");
 			PrettyConsole.WriteLine($"32-bit sizes:");
 			PrettyConsole.PushGroup("descriptor32");
 			PrettyConsole.WriteLine($"Compressed Size: {csize} (0x{csize:x8})");
 			PrettyConsole.WriteLine($"Uncompressed Size: {usize} (0x{usize:x8})");
-			PrettyConsole.WriteLine($"CRC: 0x{crc32:x8}");
 			PrettyConsole.PopGroup();
 
 			PrettyConsole.WriteLine($"64-bit sizes (Zip64):");
 			PrettyConsole.PushGroup("descriptor64");
 			PrettyConsole.WriteLine($"Compressed Size: {csize64} (0x{csize64:x16})");
 			PrettyConsole.WriteLine($"Uncompressed Size: {usize64} (0x{usize64:x16})");
-			PrettyConsole.WriteLine($"CRC: 0x{crc64:x8}");
+			// PrettyConsole.WriteLine($"CRC: 0x{crc64:x8}");
 			PrettyConsole.PopGroup();
 
 		}
diff --git a/src/ICSharpCode.SharpZipLib/Zip/ZipInputStream.cs b/src/ICSharpCode.SharpZipLib/Zip/ZipInputStream.cs
index c5e3fc26e..4f77c31e6 100644
--- a/src/ICSharpCode.SharpZipLib/Zip/ZipInputStream.cs
+++ b/src/ICSharpCode.SharpZipLib/Zip/ZipInputStream.cs
@@ -318,9 +318,10 @@ private bool SkipUntilNextEntry()
 		/// 
 		private void ReadDataDescriptor()
 		{
-			if (inputBuffer.ReadLeInt() != ZipConstants.DataDescriptorSignature)
+			var descriptor = inputBuffer.ReadLeInt();
+			if (descriptor != ZipConstants.DataDescriptorSignature)
 			{
-				throw new ZipException("Data descriptor signature not found");
+				throw new ZipException($"Data descriptor signature not found, got: 0x{descriptor:x8} instead of 0x{ZipConstants.DataDescriptorSignature:x8}");
 			}
 
 			entry.Crc = inputBuffer.ReadLeInt() & 0xFFFFFFFFL;

From 521459284ab10fd178efe36866a1837e4f28de85 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?nils=20m=C3=A5s=C3=A9n?= 
Date: Wed, 23 Feb 2022 12:01:45 +0100
Subject: [PATCH 7/7] fix(archivediag): add missing file

---
 ArchiveDiag/Tar/TypeFlag.cs | 120 ++++++++++++++++++++++++++++++++++++
 1 file changed, 120 insertions(+)
 create mode 100644 ArchiveDiag/Tar/TypeFlag.cs

diff --git a/ArchiveDiag/Tar/TypeFlag.cs b/ArchiveDiag/Tar/TypeFlag.cs
new file mode 100644
index 000000000..00c695edd
--- /dev/null
+++ b/ArchiveDiag/Tar/TypeFlag.cs
@@ -0,0 +1,120 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ArchiveDiag.Tar
+{
+	public enum TypeFlag: byte
+	{
+		/// 
+		///  The "old way" of indicating a normal file.
+		/// 
+		OldNorm = 0,
+
+		/// 
+		/// Normal file type.
+		/// 
+		Normal = (byte)'0',
+
+		/// 
+		/// Link file type.
+		/// 
+		Link = (byte)'1',
+
+		/// 
+		/// Symbolic link file type.
+		/// 
+		SymbolicLink = (byte)'2',
+
+		/// 
+		/// Character device file type.
+		/// 
+		CharacterDevice = (byte)'3',
+
+		/// 
+		/// Block device file type.
+		/// 
+		BlockDevice = (byte)'4',
+
+		/// 
+		/// Directory file type.
+		/// 
+		Directory = (byte)'5',
+
+		/// 
+		/// FIFO (pipe) file type.
+		/// 
+		FIFOPipe = (byte)'6',
+
+		/// 
+		/// Contiguous file type.
+		/// 
+		Contiguous = (byte)'7',
+
+		/// 
+		/// Posix.1 2001 global extended header
+		/// 
+		GlobalExtendedHeader = (byte)'g',
+
+		/// 
+		/// Posix.1 2001 extended header
+		/// 
+		ExtendedHeader = (byte)'x',
+
+		// POSIX allows for upper case ascii type as extensions
+
+		/// 
+		/// Solaris access control list file type
+		/// 
+		AccessControlList = (byte)'A',
+
+		/// 
+		/// GNU dir dump file type
+		/// This is a dir entry that contains the names of files that were in the
+		/// dir at the time the dump was made
+		/// 
+		GNU_DumpDir = (byte)'D',
+
+		/// 
+		/// Solaris Extended Attribute File
+		/// 
+		ExtendedAttribute = (byte)'E',
+
+		/// 
+		/// Inode (metadata only) no file content
+		/// 
+		Inode = (byte)'I',
+
+		/// 
+		/// Identifies the next file on the tape as having a long link name
+		/// 
+		GNU_LongLink = (byte)'K',
+
+		/// 
+		/// Identifies the next file on the tape as having a long name
+		/// 
+		GNU_LongName = (byte)'L',
+
+		/// 
+		/// Continuation of a file that began on another volume
+		/// 
+		GNU_MultiVol = (byte)'M',
+
+		/// 
+		/// For storing filenames that dont fit in the main header (old GNU)
+		/// 
+		GNU_Names = (byte)'N',
+
+		/// 
+		/// GNU Sparse file
+		/// 
+		GNU_Sparse = (byte)'S',
+
+		/// 
+		/// GNU Tape/volume header ignore on extraction
+		/// 
+		GNU_VolumeHeader = (byte)'V',
+
+
+	}
+}