From 42456f21a49a7238ce2e96b006cce49ecbf9c4a2 Mon Sep 17 00:00:00 2001 From: Hawkynt Date: Mon, 26 Oct 2026 09:59:07 +0100 Subject: [PATCH 01/34] * screenshots refresh only on branches that change the UI Symptom: after any merge, every open pull request went DIRTY on screenshots/main-window.png (and docs/screenshots/*). The Generate and Branch screenshots workflows re-captured the window on every push to every branch and committed the result back, so branches that only touch a codec or a format each carried their own slightly different PNG, and the first merge turned all the others into binary conflicts. Each resolution re-ran the capture, restarted CI and needed a new approval. Both workflows now run only for pushes that touch the shell (Compression.NativeUI, Compression.Shell), the NativeForms pin, or the capture scripts and workflows themselves. workflow_dispatch still refreshes any branch on demand. --- .github/workflows/branch-screenshots.yml | 11 ++++++++--- .github/workflows/generate.yml | 15 +++++++++++++-- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/.github/workflows/branch-screenshots.yml b/.github/workflows/branch-screenshots.yml index 4a9b6d0c8..c53a63015 100644 --- a/.github/workflows/branch-screenshots.yml +++ b/.github/workflows/branch-screenshots.yml @@ -5,9 +5,14 @@ on: branches-ignore: - main - master - paths-ignore: - - README.md - - docs/screenshots/** + paths: + - Compression.NativeUI/** + - Compression.Shell/** + - build/NativeForms.ref + - build/NativeForms.props + - .github/workflows/scripts/capture-screenshot.sh + - .github/workflows/branch-screenshots.yml + - .github/workflows/scripts/update-readme-screenshots.py workflow_dispatch: permissions: diff --git a/.github/workflows/generate.yml b/.github/workflows/generate.yml index 0fec817fd..2b3018298 100644 --- a/.github/workflows/generate.yml +++ b/.github/workflows/generate.yml @@ -1,14 +1,25 @@ name: Generate -# Cheap generation stage: every push to a working branch refreshes derived files and commits them -# straight back onto that branch. By the time the pull request runs its full CI battery, the README +# Cheap generation stage: a push to a working branch that touches the UI refreshes derived files +# and commits them straight back onto that branch. By the time the pull request runs its full CI battery, the README # screenshot is already part of the branch rather than being a CI-only artifact. # +# Only UI changes trigger it. A format or codec branch at most nudges a few pixels of the same +# window, and refreshing the picture on each of them made every open pull request conflict on +# that one PNG as soon as another one merged. +# # Never on main: that branch takes changes through pull requests only, and the shared action refuses # to touch the default branch as an additional guard. on: push: branches-ignore: [main] + paths: + - Compression.NativeUI/** + - Compression.Shell/** + - build/NativeForms.ref + - build/NativeForms.props + - .github/workflows/scripts/capture-screenshot.sh + - .github/workflows/generate.yml workflow_dispatch: {} permissions: From 11bdfd2bfed09fc7c7649c1b91c89a74939d38b2 Mon Sep 17 00:00:00 2001 From: Hawkynt Date: Mon, 26 Oct 2026 10:01:09 +0100 Subject: [PATCH 02/34] + ZIP method 95 (XZ): read and write ZIP entries compressed with method 95 threw NotSupportedException, so every archive 7-Zip writes with -mm=xz (and WinZip .zipx XZ entries) was unreadable. APPNOTE 4.4.5 defines the payload as one complete .xz stream, so the entry is decoded and encoded with the existing XzStream (LZMA2, CRC-64 check); the writer falls back to Store when XZ would not shrink the data, like every other method. "xz" is exposed in the method lists (descriptor schema, CLI method spec, UI). Oracle: 7-Zip 26.03 tests and extracts our archives byte for byte and reports "Method = xz"; we decode archives 7-Zip writes with -mm=xz. A 7-Zip-written reference archive is committed under Compression.Tests/Zip/ReferenceVectors so the read direction is pinned without the tool. Sourcing: rung 3 (APPNOTE.TXT); the codec is our own XZ implementation. --- .../Containers/Zip/ZipCompatibility.cs | 1 + .../Containers/Zip/ZipCompressionMethod.cs | 3 + .../Containers/Zip/ZipFormatDescriptor.cs | 6 +- .../Containers/Zip/ZipOptionsResolver.cs | 1 + Compression.Core/Containers/Zip/ZipReader.cs | 3 + Compression.Core/Containers/Zip/ZipWriter.cs | 7 + .../Containers/Zip/ZipXzHelper.cs | 31 ++++ Compression.Core/REFERENCE.md | 1 + Compression.Lib/MethodSpec.cs | 1 + .../Views/CreateOptionsViewModel.cs | 3 + Compression.Tests/Compression.Tests.csproj | 4 + .../Zip/ReferenceVectors/README.md | 7 + .../Zip/ReferenceVectors/xz-7zip2603.zip | Bin 0 -> 413 bytes Compression.Tests/Zip/ZipXzTests.cs | 148 ++++++++++++++++++ Hawkynt.FileFormats.Archives/README.md | 2 +- docs/catalogue/reference-formats.csv | 2 +- 16 files changed, 215 insertions(+), 5 deletions(-) create mode 100644 Compression.Core/Containers/Zip/ZipXzHelper.cs create mode 100644 Compression.Tests/Zip/ReferenceVectors/README.md create mode 100644 Compression.Tests/Zip/ReferenceVectors/xz-7zip2603.zip create mode 100644 Compression.Tests/Zip/ZipXzTests.cs diff --git a/Compression.Core/Containers/Zip/ZipCompatibility.cs b/Compression.Core/Containers/Zip/ZipCompatibility.cs index f167be24a..d11aa19dc 100644 --- a/Compression.Core/Containers/Zip/ZipCompatibility.cs +++ b/Compression.Core/Containers/Zip/ZipCompatibility.cs @@ -106,6 +106,7 @@ ZipCompressionMethod.Reduce4 or ZipCompressionMethod.BZip2 => ZipConstants.VersionNeeded46, ZipCompressionMethod.Lzma or ZipCompressionMethod.Zstd or + ZipCompressionMethod.Xz or ZipCompressionMethod.Ppmd => ZipConstants.VersionNeeded63, ZipCompressionMethod.WinZipAes => ZipConstants.VersionNeeded51, _ => throw new NotSupportedException($"ZIP compression method {method} has no known compatibility profile."), diff --git a/Compression.Core/Containers/Zip/ZipCompressionMethod.cs b/Compression.Core/Containers/Zip/ZipCompressionMethod.cs index e9befe5b2..c9867e630 100644 --- a/Compression.Core/Containers/Zip/ZipCompressionMethod.cs +++ b/Compression.Core/Containers/Zip/ZipCompressionMethod.cs @@ -40,6 +40,9 @@ public enum ZipCompressionMethod : ushort { /// Zstandard compression. Zstd = 93, + /// XZ compression (a complete .xz stream, APPNOTE 4.4.5 method 95). + Xz = 95, + /// PPMd version I, Rev 1 compression. Ppmd = 98, diff --git a/Compression.Core/Containers/Zip/ZipFormatDescriptor.cs b/Compression.Core/Containers/Zip/ZipFormatDescriptor.cs index 086a80969..c9ee594d3 100644 --- a/Compression.Core/Containers/Zip/ZipFormatDescriptor.cs +++ b/Compression.Core/Containers/Zip/ZipFormatDescriptor.cs @@ -20,7 +20,7 @@ public sealed class ZipFormatDescriptor : IFormatDescriptor, IArchiveFormatOpera /// public IReadOnlyList OptionsSchema => [ new("Method", "Compression method", FormatOptionKind.Enum, "deflate", - AllowedValues: ["deflate", "store", "deflate64", "bzip2", "lzma", "zstd"]), + AllowedValues: ["deflate", "store", "deflate64", "bzip2", "lzma", "zstd", "xz"]), new("Level", "Compression level", FormatOptionKind.Integer, "5", AllowedValues: ["0", "1", "3", "5", "7", "9"], Description: "Compression level. 0 = fastest, 9 = max."), @@ -177,7 +177,7 @@ public void Remove(Stream archive, string[] entryNames) { public IReadOnlyList Methods => [ new("deflate", "Deflate", SupportsOptimize: true), new("store", "Store"), new("deflate64", "Deflate64"), - new("bzip2", "BZip2"), new("lzma", "LZMA"), new("zstd", "Zstandard"), new("ppmd", "PPMd") + new("bzip2", "BZip2"), new("lzma", "LZMA"), new("zstd", "Zstandard"), new("ppmd", "PPMd"), new("xz", "XZ") ]; /// /// Gets the tar compression format id. @@ -294,7 +294,7 @@ public void Create(Stream output, IReadOnlyList inputs, Format var w = new ZipWriter(output, leaveOpen: true, compressionLevel: level, password: password, encryptionMethod: encryption); - if (options.DictSize > 0 && zipMethod == ZipCompressionMethod.Lzma) + if (options.DictSize > 0 && zipMethod is ZipCompressionMethod.Lzma or ZipCompressionMethod.Xz) w.LzmaDictionarySize = ZipOptionsResolver.NormalizeDictSize( ZipOptionsResolver.ResolveLzmaDictSize(options.DictSize, options.Optimize)); w.LzmaLevel = ZipOptionsResolver.ResolveLzmaLevel(options.Level, options.Optimize); diff --git a/Compression.Core/Containers/Zip/ZipOptionsResolver.cs b/Compression.Core/Containers/Zip/ZipOptionsResolver.cs index a1f6af0fd..cdb18cfff 100644 --- a/Compression.Core/Containers/Zip/ZipOptionsResolver.cs +++ b/Compression.Core/Containers/Zip/ZipOptionsResolver.cs @@ -27,6 +27,7 @@ public static (ZipCompressionMethod Method, DeflateCompressionLevel Level) "lzma" => (ZipCompressionMethod.Lzma, DeflateCompressionLevel.Default), "ppmd" => (ZipCompressionMethod.Ppmd, DeflateCompressionLevel.Default), "zstd" => (ZipCompressionMethod.Zstd, DeflateCompressionLevel.Default), + "xz" => (ZipCompressionMethod.Xz, DeflateCompressionLevel.Default), _ => (ZipCompressionMethod.Deflate, optimize ? DeflateCompressionLevel.Maximum : DeflateCompressionLevel.Default), }; diff --git a/Compression.Core/Containers/Zip/ZipReader.cs b/Compression.Core/Containers/Zip/ZipReader.cs index c52892e86..34a51a7f5 100644 --- a/Compression.Core/Containers/Zip/ZipReader.cs +++ b/Compression.Core/Containers/Zip/ZipReader.cs @@ -154,6 +154,9 @@ public byte[] ExtractEntry(ZipEntry entry) { case ZipCompressionMethod.Zstd: data = ZipZstdHelper.Decompress(compressedData); break; + case ZipCompressionMethod.Xz: + data = ZipXzHelper.Decompress(compressedData); + break; case ZipCompressionMethod.Ppmd: data = ZipPpmdHelper.Decompress(compressedData, (int)entry.UncompressedSize); break; diff --git a/Compression.Core/Containers/Zip/ZipWriter.cs b/Compression.Core/Containers/Zip/ZipWriter.cs index 6223d8807..89374edd4 100644 --- a/Compression.Core/Containers/Zip/ZipWriter.cs +++ b/Compression.Core/Containers/Zip/ZipWriter.cs @@ -132,6 +132,13 @@ public void AddEntry(string fileName, byte[] data, ZipCompressionMethod method = method = ZipCompressionMethod.Store; } break; + case ZipCompressionMethod.Xz: + compressedData = ZipXzHelper.Compress(data, this.LzmaDictionarySize, this.LzmaLevel); + if (compressedData.Length >= data.Length) { + compressedData = data; + method = ZipCompressionMethod.Store; + } + break; case ZipCompressionMethod.Zstd: compressedData = ZipZstdHelper.Compress(data); if (compressedData.Length >= data.Length) { diff --git a/Compression.Core/Containers/Zip/ZipXzHelper.cs b/Compression.Core/Containers/Zip/ZipXzHelper.cs new file mode 100644 index 000000000..d73930446 --- /dev/null +++ b/Compression.Core/Containers/Zip/ZipXzHelper.cs @@ -0,0 +1,31 @@ +using Compression.Core.Dictionary.Lzma; +using Compression.Core.Streams; +using FileFormat.Xz; + +namespace FileFormat.Zip; + +/// +/// Compresses and decompresses ZIP method 95 entry data. APPNOTE 4.4.5 defines the payload +/// as a complete .xz stream (stream header, blocks, index, footer), so the entry bytes are +/// exactly what a stand-alone xz would write. +/// +internal static class ZipXzHelper { + /// Decompresses an XZ-compressed ZIP entry. + public static byte[] Decompress(byte[] compressedData) { + using var input = new MemoryStream(compressedData, writable: false); + using var xz = new XzStream(input, CompressionStreamMode.Decompress, leaveOpen: true); + using var output = new MemoryStream(); + xz.CopyTo(output); + return output.ToArray(); + } + + /// Compresses data into a single .xz stream (LZMA2, CRC-64 check). + public static byte[] Compress(byte[] data, int dictionarySize = 1 << 23, + LzmaCompressionLevel level = LzmaCompressionLevel.Normal) { + using var output = new MemoryStream(); + using (var xz = new XzStream(output, CompressionStreamMode.Compress, dictionarySize, + XzConstants.CheckCrc64, null, level, leaveOpen: true)) + xz.Write(data, 0, data.Length); + return output.ToArray(); + } +} diff --git a/Compression.Core/REFERENCE.md b/Compression.Core/REFERENCE.md index 4aeff3afb..cbf4a3221 100644 --- a/Compression.Core/REFERENCE.md +++ b/Compression.Core/REFERENCE.md @@ -9741,6 +9741,7 @@ ZIP compression methods. | `BZip2` | `12` | BZip2 compression. | | `Lzma` | `14` | LZMA compression. | | `Zstd` | `93` | Zstandard compression. | +| `Xz` | `95` | XZ compression (a complete .xz stream, APPNOTE 4.4.5 method 95). | | `Ppmd` | `98` | PPMd version I, Rev 1 compression. | | `WinZipAes` | `99` | WinZip AES encryption (actual method stored in extra field). | diff --git a/Compression.Lib/MethodSpec.cs b/Compression.Lib/MethodSpec.cs index 65705df74..f3a6a1dd7 100644 --- a/Compression.Lib/MethodSpec.cs +++ b/Compression.Lib/MethodSpec.cs @@ -85,6 +85,7 @@ public static MethodSpec Parse(string? input) { "lzma" => (FileFormat.Zip.ZipCompressionMethod.Lzma, Compression.Core.Deflate.DeflateCompressionLevel.Default), "ppmd" => (FileFormat.Zip.ZipCompressionMethod.Ppmd, Compression.Core.Deflate.DeflateCompressionLevel.Default), "zstd" => (FileFormat.Zip.ZipCompressionMethod.Zstd, Compression.Core.Deflate.DeflateCompressionLevel.Default), + "xz" => (FileFormat.Zip.ZipCompressionMethod.Xz, Compression.Core.Deflate.DeflateCompressionLevel.Default), // deflate or default _ => (FileFormat.Zip.ZipCompressionMethod.Deflate, Optimize ? Compression.Core.Deflate.DeflateCompressionLevel.Maximum diff --git a/Compression.NativeUI/Views/CreateOptionsViewModel.cs b/Compression.NativeUI/Views/CreateOptionsViewModel.cs index e65e1fd9d..05231f4c4 100644 --- a/Compression.NativeUI/Views/CreateOptionsViewModel.cs +++ b/Compression.NativeUI/Views/CreateOptionsViewModel.cs @@ -335,6 +335,9 @@ public string HintText { "Zstd: Fast modern compression with good ratio. Requires extractors supporting ZIP method 93. " + "Not yet widely supported.\n" + "Opens with: 7-Zip 21+, PeaZip, NanaZip (very limited support elsewhere)", + (F.Zip, "xz") => + "XZ: LZMA2 in a complete .xz stream (ZIP method 95). Ratio close to LZMA.\n" + + "Opens with: 7-Zip, PeaZip, NanaZip, WinZip (not Windows Explorer)", (F.Zip, "shrink") => "Shrink: Legacy LZW method from PKZIP 1.0 (1989). Use only for vintage compatibility. " + "Very limited compression ratio by modern standards.\n" diff --git a/Compression.Tests/Compression.Tests.csproj b/Compression.Tests/Compression.Tests.csproj index acfd18620..b4fff4dd8 100644 --- a/Compression.Tests/Compression.Tests.csproj +++ b/Compression.Tests/Compression.Tests.csproj @@ -122,6 +122,10 @@ VdfsVectors.%(Filename)%(Extension) + + + ZipVectors.%(Filename)%(Extension) + DarVectors.%(Filename)%(Extension) diff --git a/Compression.Tests/Zip/ReferenceVectors/README.md b/Compression.Tests/Zip/ReferenceVectors/README.md new file mode 100644 index 000000000..f7af882f8 --- /dev/null +++ b/Compression.Tests/Zip/ReferenceVectors/README.md @@ -0,0 +1,7 @@ +# ZIP reference archives + +Every file here was written by a third-party archiver, never by CompressionWorkbench. + +| File | Writer | Command | What it pins | +| --- | --- | --- | --- | +| `xz-7zip2603.zip` | 7-Zip 26.03 (x64, Windows) | `7z a -tzip -mm=xz xz-7zip2603.zip text.txt hello.txt` | method 95 (XZ): `text.txt` is `The quick brown fox jumps over the lazy dog. ` × 50 (2250 bytes, CRC-32 `5FE03AFB`) as a complete .xz stream; `hello.txt` (`hello xz\n`) fell back to Store | diff --git a/Compression.Tests/Zip/ReferenceVectors/xz-7zip2603.zip b/Compression.Tests/Zip/ReferenceVectors/xz-7zip2603.zip new file mode 100644 index 0000000000000000000000000000000000000000..f141f793c2694ce25f542dbf784ccb09e4ca1f1d GIT binary patch literal 413 zcmWIWW@h1H00Ga0;MmYQy*f@H8-_Ddb8_zYBIYR2`Ut7>&#O$VM9g)x!dVIFnh~KvpmT;Z`7h3B+Ll05g(b AGXMYp literal 0 HcmV?d00001 diff --git a/Compression.Tests/Zip/ZipXzTests.cs b/Compression.Tests/Zip/ZipXzTests.cs new file mode 100644 index 000000000..8b7308118 --- /dev/null +++ b/Compression.Tests/Zip/ZipXzTests.cs @@ -0,0 +1,148 @@ +using System.Reflection; +using FileFormat.Zip; + +namespace Compression.Tests.Zip; + +/// +/// ZIP method 95 (XZ, APPNOTE 4.4.5): our writer and reader, the 7-Zip 26.03 reference archive +/// (Zip/ReferenceVectors), and 7-Zip as a live oracle in both directions. +/// +[TestFixture] +public class ZipXzTests { + + private static readonly byte[] FoxText = + System.Text.Encoding.ASCII.GetBytes(string.Concat(Enumerable.Repeat("The quick brown fox jumps over the lazy dog. ", 50))); + + private string _tmpDir = null!; + + [SetUp] + public void Setup() { + this._tmpDir = Path.Combine(Path.GetTempPath(), $"cwb_zipxz_{Guid.NewGuid():N}"); + Directory.CreateDirectory(this._tmpDir); + } + + [TearDown] + public void Teardown() { + try { Directory.Delete(this._tmpDir, true); } catch { /* best effort */ } + } + + [Test, Category("HappyPath"), Category("RoundTrip")] + public void GivenCompressibleData_WhenWrittenAsXz_ThenReadsBackIdenticalWithMethod95() { + var archive = CreateArchive(("fox.txt", FoxText)); + + using var reader = new ZipReader(new MemoryStream(archive)); + var entry = reader.Entries.Single(); + Assert.That((ushort)entry.CompressionMethod, Is.EqualTo(95)); + Assert.That(entry.CompressedSize, Is.LessThan(FoxText.Length)); + Assert.That(reader.ExtractEntry(entry), Is.EqualTo(FoxText)); + } + + [Test, Category("HappyPath"), Category("RoundTrip")] + public void GivenSeveralEntries_WhenWrittenAsXz_ThenEachEntryCarriesItsOwnXzStream() { + var a = FoxText; + var b = Enumerable.Range(0, 70_000).Select(i => (byte)(i * 7 % 13)).ToArray(); + var archive = CreateArchive(("a.txt", a), ("b.bin", b)); + + using var reader = new ZipReader(new MemoryStream(archive)); + Assert.That(reader.ExtractEntry(reader.Entries[0]), Is.EqualTo(a)); + Assert.That(reader.ExtractEntry(reader.Entries[1]), Is.EqualTo(b)); + } + + [Test, Category("Boundary")] + public void GivenEmptyFile_WhenWrittenAsXz_ThenStoredBecauseXzWouldBeLarger() { + var archive = CreateArchive(("empty.txt", [])); + + using var reader = new ZipReader(new MemoryStream(archive)); + Assert.That(reader.Entries[0].CompressionMethod, Is.EqualTo(ZipCompressionMethod.Store)); + Assert.That(reader.ExtractEntry(reader.Entries[0]), Is.Empty); + } + + [Test, Category("Boundary")] + public void GivenSingleByte_WhenWrittenAsXz_ThenFallsBackToStore() { + var archive = CreateArchive(("one.bin", [0x42])); + + using var reader = new ZipReader(new MemoryStream(archive)); + Assert.That(reader.Entries[0].CompressionMethod, Is.EqualTo(ZipCompressionMethod.Store)); + Assert.That(reader.ExtractEntry(reader.Entries[0]), Is.EqualTo(new byte[] { 0x42 })); + } + + [Test, Category("Exception")] + public void GivenCorruptXzPayload_WhenExtracted_ThenThrowsInsteadOfReturningWrongBytes() { + var archive = CreateArchive(("fox.txt", FoxText)); + using (var probe = new ZipReader(new MemoryStream(archive))) { + // Damage a byte inside the compressed payload (past the 30-byte local header + name). + var dataStart = (int)probe.Entries[0].LocalHeaderOffset + 30 + "fox.txt".Length; + archive[dataStart + 20] ^= 0xFF; + } + + using var reader = new ZipReader(new MemoryStream(archive)); + Assert.That(() => reader.ExtractEntry(reader.Entries[0]), Throws.InstanceOf()); + } + + [Test, Category("ThemVsUs")] + public void GivenSevenZipReferenceArchive_WhenRead_ThenXzEntryDecodesToTheSourceText() { + using var reader = new ZipReader(new MemoryStream(LoadVector("xz-7zip2603.zip"))); + var text = reader.Entries.Single(e => e.FileName == "text.txt"); + var hello = reader.Entries.Single(e => e.FileName == "hello.txt"); + + Assert.That(text.CompressionMethod, Is.EqualTo(ZipCompressionMethod.Xz)); + Assert.That(text.Crc32, Is.EqualTo(0x5FE03AFBu)); + Assert.That(reader.ExtractEntry(text), Is.EqualTo(FoxText)); + Assert.That(reader.ExtractEntry(hello), Is.EqualTo("hello xz\n"u8.ToArray())); + } + + [Test, Category("ExternalInterop")] + public void GivenOurXzArchive_WhenSevenZipTestsAndExtractsIt_ThenEveryByteMatches() { + FsInteropToolbox.Require7z(); + var b = Enumerable.Range(0, 70_000).Select(i => (byte)(i * 7 % 13)).ToArray(); + var path = Path.Combine(this._tmpDir, "ours.zip"); + File.WriteAllBytes(path, CreateArchive(("fox.txt", FoxText), ("b.bin", b))); + + var t = FsInteropToolbox.Run7z($"t \"{path}\""); + Assert.That(t.ExitCode, Is.EqualTo(0), $"7z t rejected the archive:\n{t.StdOut}\n{t.StdErr}"); + var l = FsInteropToolbox.Run7z($"l -slt \"{path}\""); + Assert.That(l.StdOut, Does.Contain("Method = xz")); + + var outDir = Path.Combine(this._tmpDir, "out"); + var x = FsInteropToolbox.Run7z($"x -y \"-o{outDir}\" \"{path}\""); + Assert.That(x.ExitCode, Is.EqualTo(0), x.StdOut + x.StdErr); + Assert.That(File.ReadAllBytes(Path.Combine(outDir, "fox.txt")), Is.EqualTo(FoxText)); + Assert.That(File.ReadAllBytes(Path.Combine(outDir, "b.bin")), Is.EqualTo(b)); + } + + [Test, Category("ExternalInterop")] + public void GivenSevenZipWritesXzZip_WhenWeReadIt_ThenEveryByteMatches() { + FsInteropToolbox.Require7z(); + var src = Path.Combine(this._tmpDir, "src"); + Directory.CreateDirectory(src); + var b = Enumerable.Range(0, 200_000).Select(i => (byte)(i * 31 % 251)).ToArray(); + File.WriteAllBytes(Path.Combine(src, "fox.txt"), FoxText); + File.WriteAllBytes(Path.Combine(src, "b.bin"), b); + var path = Path.Combine(this._tmpDir, "theirs.zip"); + + var a = FsInteropToolbox.Run7z($"a -tzip -mm=xz \"{path}\" \"{Path.Combine(src, "fox.txt")}\" \"{Path.Combine(src, "b.bin")}\""); + if (a.ExitCode != 0) + Assert.Ignore($"this 7-Zip cannot write ZIP method 95:\n{a.StdOut}\n{a.StdErr}"); + + using var reader = new ZipReader(File.OpenRead(path)); + Assert.That(reader.Entries.Select(e => e.CompressionMethod), Has.All.EqualTo(ZipCompressionMethod.Xz)); + Assert.That(reader.ExtractEntry(reader.Entries.Single(e => e.FileName == "fox.txt")), Is.EqualTo(FoxText)); + Assert.That(reader.ExtractEntry(reader.Entries.Single(e => e.FileName == "b.bin")), Is.EqualTo(b)); + } + + private static byte[] CreateArchive(params (string Name, byte[] Data)[] files) { + using var ms = new MemoryStream(); + using (var writer = new ZipWriter(ms, leaveOpen: true)) + foreach (var (name, data) in files) + writer.AddEntry(name, data, ZipCompressionMethod.Xz); + return ms.ToArray(); + } + + private static byte[] LoadVector(string name) { + using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream($"ZipVectors.{name}") + ?? throw new InvalidOperationException($"missing embedded vector {name}"); + using var ms = new MemoryStream(); + stream.CopyTo(ms); + return ms.ToArray(); + } +} diff --git a/Hawkynt.FileFormats.Archives/README.md b/Hawkynt.FileFormats.Archives/README.md index a4ff6ec17..bc43ea12b 100644 --- a/Hawkynt.FileFormats.Archives/README.md +++ b/Hawkynt.FileFormats.Archives/README.md @@ -149,7 +149,7 @@ Every State, Test, Maintenance, Compress/Decompress and Demux/Mux/Remux cell is | Wrapster | `Wrapster` | | WORM | ✅ | defrag · wipe | MP3-wrapper archive carrying one member; stays WORM by design | [Archive Team](http://fileformats.archiveteam.org/wiki/Wrapster) | | [XAR](https://en.wikipedia.org/wiki/Xar_(archiver)) | `Xar` | `.xar` | R/W | ✅ | defrag · wipe | | [GitHub](https://github.com/mackyle/xar) | | ZAP (Amiga) | `Zap` | `.zap` | WORM | ✅ | wipe | Whole-disk archiver: entries are track_NNN.raw | [Aminet](https://aminet.net/) | -| [ZIP](https://en.wikipedia.org/wiki/ZIP_(file_format)) | `Zip` | `.zip` `.zipx` | R/W | ✅ | compress · repack · shrink · wipe | Store, Deflate, Deflate64, Shrink, Reduce, Implode, BZip2, LZMA, PPMd, Zstd, AES | [pkware.cachefly.net](https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT) | +| [ZIP](https://en.wikipedia.org/wiki/ZIP_(file_format)) | `Zip` | `.zip` `.zipx` | R/W | ✅ | compress · repack · shrink · wipe | Store, Deflate, Deflate64, Shrink, Reduce, Implode, BZip2, LZMA, XZ, PPMd, Zstd, AES | [pkware.cachefly.net](https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT) | | [ZOO](https://en.wikipedia.org/wiki/Zoo_(file_format)) | `Zoo` | `.zoo` | R/W | ✅ | defrag · wipe | | [Archive Team](http://fileformats.archiveteam.org/wiki/ZOO) | | [ZPAQ](https://en.wikipedia.org/wiki/ZPAQ) | `Zpaq` | `.zpaq` | R/W | ✅ | defrag | Reader covers the stored/simple models; ZPAQL virtual-machine execution is not implemented | [mattmahoney.net](http://mattmahoney.net/dc/zpaq.html) | diff --git a/docs/catalogue/reference-formats.csv b/docs/catalogue/reference-formats.csv index fcd97191b..f18495d3a 100644 --- a/docs/catalogue/reference-formats.csv +++ b/docs/catalogue/reference-formats.csv @@ -1,5 +1,5 @@ name,aliases_extensions,kind,src_7zip,src_libarchive,src_xad,src_binwalk,src_uniextract,src_pronom,src_wikipedia,source_count,reference_url,ours,ours_read,ours_create,ours_modify,gap,native_writer,notes -ZIP,zip;zipx;z01;sfx-zip;apk;docx;ear;epub;exe;imz;jar;odt;pptx;war;wz;xlsx;xpi,archive,RW,RW,R,D,R,x-fmt/263,RW,7,https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT,Zip,Y,Y,Y,PARTIAL,,"methods xz (95), JPEG (96), WavPack (97) and PKWARE strong encryption unsupported (ZipReader.cs)" +ZIP,zip;zipx;z01;sfx-zip;apk;docx;ear;epub;exe;imz;jar;odt;pptx;war;wz;xlsx;xpi,archive,RW,RW,R,D,R,x-fmt/263,RW,7,https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT,Zip,Y,Y,Y,PARTIAL,,"methods JPEG (96), WavPack (97) and PKWARE strong encryption unsupported (ZipReader.cs)" Java Archive (JAR),jar,archive,,,,,,x-fmt/412,Y,2,https://docs.oracle.com/en/java/javase/21/docs/specs/jar/jar.html,Jar,Y,Y,Y,OK,, Web Application Archive (WAR),war,archive,,,,,,,Y,1,https://jakarta.ee/specifications/servlet/,War,Y,Y,Y,OK,, Enterprise Archive (EAR),ear,archive,,,,,,,Y,1,https://jakarta.ee/specifications/platform/,Ear,Y,Y,Y,OK,, From c01351bb0073ec54ac786fb01066941bf768522d Mon Sep 17 00:00:00 2001 From: Hawkynt Date: Mon, 26 Oct 2026 10:07:41 +0100 Subject: [PATCH 03/34] * regenerate references --- docs/catalogue/GAPS.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/catalogue/GAPS.md b/docs/catalogue/GAPS.md index 3ebbcae5f..9d102290e 100644 --- a/docs/catalogue/GAPS.md +++ b/docs/catalogue/GAPS.md @@ -16,17 +16,17 @@ exists. It warns when a capability letter has drifted from the live descriptor. | Catalogue rows | 983 | | … formats listed by at least one source | 525 | | … registry ids no source lists (OURS-ONLY) | 458 | -| Source-listed rows mapped to something here | 288 / 525 (54.9 %) | +| Source-listed rows mapped to something here | 284 / 525 (54.1 %) | | … of rows listed by ≥ 2 sources | 122 / 138 (88 %) | | … of rows listed by ≥ 3 sources | 66 / 67 (PEA is the one missing) | | … of rows listed by ≥ 4 sources | 31 / 31 | | Gap type | Rows | Meaning | | --- | ---: | --- | -| `MISSING` | 237 | a source lists it; no descriptor or building block covers it | +| `MISSING` | 241 | a source lists it; no descriptor or building block covers it | | `READ-ONLY-BUT-SOURCES-WRITE` | 23 | we only read it; a source or the format's own tool writes it | -| `PARTIAL` | 43 | covered, but the reader handles only part of the format (see the table below) | -| `OK` | 222 | covered with no known gap against what the sources list | +| `PARTIAL` | 41 | covered, but the reader handles only part of the format (see the table below) | +| `OK` | 220 | covered with no known gap against what the sources list | | `OURS-ONLY` | 458 | in our registry, listed by none of the seven sources | ### Per source @@ -36,13 +36,13 @@ read-only rows. | Source | Rows it lists | Mapped | % | OK | | --- | ---: | ---: | ---: | ---: | -| 7-Zip (formats + codecs) | 96 | 89 | 92.7 | 70 | +| 7-Zip (formats + codecs) | 96 | 89 | 92.7 | 71 | | libarchive (formats + filters) | 28 | 27 | 96.4 | 22 | -| XADMaster / The Unarchiver | 126 | 98 | 77.8 | 80 | -| binwalk v3 | 79 | 48 | 60.8 | 35 | -| Universal Extractor 2 | 124 | 71 | 57.3 | 47 | -| PRONOM | 87 | 63 | 72.4 | 41 | -| Wikipedia | 266 | 156 | 58.6 | 114 | +| XADMaster / The Unarchiver | 126 | 94 | 74.6 | 77 | +| binwalk v3 | 79 | 48 | 60.8 | 36 | +| Universal Extractor 2 | 124 | 71 | 57.3 | 48 | +| PRONOM | 87 | 63 | 72.4 | 42 | +| Wikipedia | 266 | 156 | 58.6 | 116 | The libarchive and 7-Zip lists are almost fully covered. The rows still open are libarchive's grzip, plus seven 7-Zip rows: LVM, Android `super.img` (LP), AVB, TE, the Swap2/Swap4 filter, PKWARE strong From 0edd488b51fbbe89c7b8af0085addbfea6e0e877 Mon Sep 17 00:00:00 2001 From: Hawkynt Date: Mon, 26 Oct 2026 10:17:28 +0100 Subject: [PATCH 04/34] # XAR: bzip2, lzma and xz members decoded and checked against the TOC # XAR: bzip2, lzma and xz members decoded and checked against the TOC Symptom: lzma and xz members came back still compressed, bzip2 members threw, and any other encoding style was returned raw. Root cause: Extract only knew zlib and "none"; every lzma/xz style was folded into one name and fell through to the raw-bytes default. Change: encoding styles map one-to-one (octet-stream, x-gzip, x-bzip2, x-lzma, x-xz). bzip2 and xz go through our own stream decoders; x-lzma is sniffed, because xar's own encoder writes .xz there while libarchive writes the legacy .lzma format. Unknown styles are refused by name. Every member is checked against its declared size and its SHA-1, MD5 or SHA-256 extracted-checksum. Evidence: none/gzip/bzip2/lzma/xz archives written by bsdtar 3.7.2 (libarchive 3.7.2), committed with provenance; every member passes its SHA-1, all five decode to identical bytes, inputs match the generator's SHA-256s. XarOracleTests re-runs bsdtar live. Sourcing: rung 3 (xar format documentation) for the encoding styles; codecs are our own. * regenerate package references --- Compression.Tests/Compression.Tests.csproj | 4 + .../Xar/ReferenceVectors/README.md | 21 +++ .../Xar/ReferenceVectors/bzip2.xar | Bin 0 -> 9134 bytes .../Xar/ReferenceVectors/gzip.xar | Bin 0 -> 7892 bytes .../Xar/ReferenceVectors/lzma.xar | Bin 0 -> 7381 bytes .../Xar/ReferenceVectors/none.xar | Bin 0 -> 27711 bytes Compression.Tests/Xar/ReferenceVectors/xz.xar | Bin 0 -> 7511 bytes .../Xar/XarEncodingVectorTests.cs | 124 ++++++++++++++++++ Compression.Tests/Xar/XarOracleTests.cs | 72 ++++++++++ .../FileFormats/FileFormat.Xar/XarEntry.cs | 5 +- .../FileFormats/FileFormat.Xar/XarReader.cs | 108 +++++++++++---- Hawkynt.FileFormats.Archives/REFERENCE.md | 4 +- docs/catalogue/GAPS.md | 1 - docs/catalogue/reference-formats.csv | 2 +- 14 files changed, 309 insertions(+), 32 deletions(-) create mode 100644 Compression.Tests/Xar/ReferenceVectors/README.md create mode 100644 Compression.Tests/Xar/ReferenceVectors/bzip2.xar create mode 100644 Compression.Tests/Xar/ReferenceVectors/gzip.xar create mode 100644 Compression.Tests/Xar/ReferenceVectors/lzma.xar create mode 100644 Compression.Tests/Xar/ReferenceVectors/none.xar create mode 100644 Compression.Tests/Xar/ReferenceVectors/xz.xar create mode 100644 Compression.Tests/Xar/XarEncodingVectorTests.cs create mode 100644 Compression.Tests/Xar/XarOracleTests.cs diff --git a/Compression.Tests/Compression.Tests.csproj b/Compression.Tests/Compression.Tests.csproj index b4fff4dd8..41d6588b8 100644 --- a/Compression.Tests/Compression.Tests.csproj +++ b/Compression.Tests/Compression.Tests.csproj @@ -134,6 +134,10 @@ ArjVectors.%(Filename)%(Extension) + + + XarVectors.%(Filename)%(Extension) + diff --git a/Compression.Tests/Xar/ReferenceVectors/README.md b/Compression.Tests/Xar/ReferenceVectors/README.md new file mode 100644 index 000000000..cad3653b2 --- /dev/null +++ b/Compression.Tests/Xar/ReferenceVectors/README.md @@ -0,0 +1,21 @@ +# XAR reference archives + +Written by libarchive's `bsdtar 3.7.2` (libarchive 3.7.2, zlib 1.3, liblzma 5.4.5, bz2lib 1.0.8; +Ubuntu 24.04 package `libarchive-tools`) on 2026-10-19 with + +``` +bsdtar --format xar --options xar:compression= -cf .xar TEXT.TXT RANDOM.BIN REPEAT.BIN ONE.TXT +``` + +The inputs are the ones `Arj/ReferenceVectors/generate.sh` makes (same SHA-256s, listed in +`Arj/ReferenceVectors/README.md`). + +| Archive | Encoding style in the TOC | SHA-256 | +|---------|---------------------------|---------| +| `none.xar` | `application/octet-stream` | `995a3e5c7f0acb4f02afb508527d47a7395cd8e36b087fdc504a57202333900c` | +| `gzip.xar` | `application/x-gzip` (a zlib stream) | `bec70f7f56a92d2dd3863f05ac3c843a201a5e09a1ae4c6259ac56416736244d` | +| `bzip2.xar` | `application/x-bzip2` | `1e1259563b7c82d5b74813d42ae6f86db2a277311037924c043881dfd88af841` | +| `lzma.xar` | `application/x-lzma` (libarchive writes the legacy `.lzma` format) | `d938ef9be3fc1bb70f24bc4401a8b63fe257a97cfee76b413e034188f3efac85` | +| `xz.xar` | `application/x-xz` | `667081a142509a9ca4810a4187cb511a5a8b83dfc0efae28ccf2b013592496a5` | + +Every member carries the SHA-1 `extracted-checksum` bsdtar recorded, which the reader checks. diff --git a/Compression.Tests/Xar/ReferenceVectors/bzip2.xar b/Compression.Tests/Xar/ReferenceVectors/bzip2.xar new file mode 100644 index 0000000000000000000000000000000000000000..72dbfc87f02628545c8291a323a4b7953b3cf951 GIT binary patch literal 9134 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%0DJm!goeEZ$>JFPQM`6=x>em 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 literal 0 HcmV?d00001 diff --git a/Compression.Tests/Xar/ReferenceVectors/gzip.xar b/Compression.Tests/Xar/ReferenceVectors/gzip.xar new file mode 100644 index 0000000000000000000000000000000000000000..d33d84a019f8d8816fc16bc70b8bef2cf1367ab6 GIT binary patch literal 7892 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 U?JwMS_wPBg@$XCdf1Q;71@g%$g#Z8m literal 0 HcmV?d00001 diff --git a/Compression.Tests/Xar/ReferenceVectors/none.xar b/Compression.Tests/Xar/ReferenceVectors/none.xar new file mode 100644 index 0000000000000000000000000000000000000000..312dd4fdcfb247a0eddbf7ccb8f667c8eccd5793 GIT binary patch literal 27711 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 literal 0 HcmV?d00001 diff --git a/Compression.Tests/Xar/ReferenceVectors/xz.xar b/Compression.Tests/Xar/ReferenceVectors/xz.xar new file mode 100644 index 0000000000000000000000000000000000000000..9885afddb9e5f11539211573d980673db02e3ce3 GIT binary patch literal 7511 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 G(*FUH5p6&K literal 0 HcmV?d00001 diff --git a/Compression.Tests/Xar/XarEncodingVectorTests.cs b/Compression.Tests/Xar/XarEncodingVectorTests.cs new file mode 100644 index 000000000..758aab9a2 --- /dev/null +++ b/Compression.Tests/Xar/XarEncodingVectorTests.cs @@ -0,0 +1,124 @@ +using System.Buffers.Binary; +using System.Reflection; +using System.Security.Cryptography; +using System.Text; +using FileFormat.Xar; + +namespace Compression.Tests.Xar; + +///

+/// XAR members in every encoding xar defines, from archives libarchive's bsdtar wrote (see +/// Xar/ReferenceVectors/README.md). The reader checks each member against the SHA-1 the TOC records, +/// and the five archives hold the same files, so all encodings must decode to identical bytes. +/// +[TestFixture] +public class XarEncodingVectorTests { + private static byte[] Vector(string name) { + using var s = Assembly.GetExecutingAssembly().GetManifestResourceStream("XarVectors." + name) + ?? throw new FileNotFoundException("Embedded XAR vector missing: " + name); + using var ms = new MemoryStream(); + s.CopyTo(ms); + return ms.ToArray(); + } + + private static Dictionary ExtractAll(byte[] archive, out List entries) { + var reader = new XarReader(new MemoryStream(archive)); + entries = reader.Entries.ToList(); + return reader.Entries.Where(e => !e.IsDirectory).ToDictionary(e => e.FileName, reader.Extract, StringComparer.Ordinal); + } + + [Test, Category("HappyPath")] + [TestCase("none.xar", "none")] + [TestCase("gzip.xar", "zlib")] + [TestCase("bzip2.xar", "bzip2")] + [TestCase("lzma.xar", "lzma")] + [TestCase("xz.xar", "xz")] + public void RealArchive_EveryEncodingDecodesAndPassesItsChecksum(string vector, string method) { + var files = ExtractAll(Vector(vector), out var entries); + Assert.That(files.Keys, Is.EquivalentTo(new[] { "TEXT.TXT", "RANDOM.BIN", "REPEAT.BIN", "ONE.TXT" })); + Assert.That(entries.Single(e => e.FileName == "TEXT.TXT").Method, Is.EqualTo(method)); + Assert.That(Encoding.ASCII.GetString(files["TEXT.TXT"]), Does.StartWith("line 00001: the quick brown fox")); + Assert.That(files["REPEAT.BIN"], Is.EqualTo(files["RANDOM.BIN"].Concat(files["RANDOM.BIN"]).ToArray())); + Assert.That(files["ONE.TXT"], Is.EqualTo("a"u8.ToArray())); + // SHA-256 of the inputs as the generator script printed them (see ReferenceVectors/README.md). + Assert.That(Convert.ToHexStringLower(System.Security.Cryptography.SHA256.HashData(files["TEXT.TXT"])), + Is.EqualTo("c0541069eb5a1c9d4a11c2abb3993a554ba4a272fa671ea926f035775548de8e")); + Assert.That(Convert.ToHexStringLower(System.Security.Cryptography.SHA256.HashData(files["RANDOM.BIN"])), + Is.EqualTo("44763bf1aa0863dd94c239c350c8fe4aa8dc640d51fe567e94f1b956b261a46f")); + } + + [Test, Category("HappyPath")] + public void AllEncodings_DecodeToTheSameFiles() { + var reference = ExtractAll(Vector("none.xar"), out _); + foreach (var vector in new[] { "gzip.xar", "bzip2.xar", "lzma.xar", "xz.xar" }) { + var other = ExtractAll(Vector(vector), out _); + foreach (var (name, bytes) in reference) + Assert.That(other[name], Is.EqualTo(bytes).AsCollection, $"{vector}: {name}"); + } + } + + // ── Hand-built archives for the refusal paths ───────────────────────────────── + + /// Builds a one-member XAR whose heap holds under the given encoding style. + private static byte[] BuildXar(byte[] heap, long size, string style, string? sha1Hex) { + var checksum = sha1Hex is null ? "" : $"{sha1Hex}"; + var toc = $""" + + m.binfile + {heap.Length}0{size} + {checksum} + """; + var tocBytes = Encoding.UTF8.GetBytes(toc); + using var z = new MemoryStream(); + using (var zs = new System.IO.Compression.ZLibStream(z, System.IO.Compression.CompressionLevel.Optimal, leaveOpen: true)) + zs.Write(tocBytes); + var tocZ = z.ToArray(); + + var header = new byte[28]; + "xar!"u8.CopyTo(header); + BinaryPrimitives.WriteUInt16BigEndian(header.AsSpan(4), 28); + BinaryPrimitives.WriteUInt16BigEndian(header.AsSpan(6), 1); + BinaryPrimitives.WriteUInt64BigEndian(header.AsSpan(8), (ulong)tocZ.Length); + BinaryPrimitives.WriteUInt64BigEndian(header.AsSpan(16), (ulong)tocBytes.Length); + BinaryPrimitives.WriteUInt32BigEndian(header.AsSpan(24), 0); + return [.. header, .. tocZ, .. heap]; + } + + [Test, Category("Exception")] + public void UnknownEncoding_IsRefusedNotReturnedRaw() { + var archive = BuildXar("payload"u8.ToArray(), 7, "application/x-unheard-of", null); + var reader = new XarReader(new MemoryStream(archive)); + Assert.That(reader.Entries[0].Method, Is.EqualTo("application/x-unheard-of")); + Assert.Throws(() => reader.Extract(reader.Entries[0])); + } + + [Test, Category("Exception")] + public void ChecksumMismatch_Throws() { + var data = "payload"u8.ToArray(); + var wrong = Convert.ToHexString(SHA1.HashData("other"u8)); + var reader = new XarReader(new MemoryStream(BuildXar(data, data.Length, "application/octet-stream", wrong))); + Assert.Throws(() => reader.Extract(reader.Entries[0])); + } + + [Test, Category("HappyPath")] + public void ChecksumMatch_LowerCaseHex_IsAccepted() { + var data = "payload"u8.ToArray(); + var right = Convert.ToHexString(SHA1.HashData(data)).ToLowerInvariant(); + var reader = new XarReader(new MemoryStream(BuildXar(data, data.Length, "application/octet-stream", right))); + Assert.That(reader.Extract(reader.Entries[0]), Is.EqualTo(data)); + } + + [Test, Category("Exception")] + public void SizeMismatch_Throws() { + var data = "payload"u8.ToArray(); + var reader = new XarReader(new MemoryStream(BuildXar(data, data.Length + 1, "application/octet-stream", null))); + Assert.Throws(() => reader.Extract(reader.Entries[0])); + } + + [Test, Category("Exception")] + public void XzStyleWithoutXzMagic_Throws() { + var data = "not an xz stream"u8.ToArray(); + var reader = new XarReader(new MemoryStream(BuildXar(data, 5, "application/x-xz", null))); + Assert.Throws(() => reader.Extract(reader.Entries[0])); + } +} diff --git a/Compression.Tests/Xar/XarOracleTests.cs b/Compression.Tests/Xar/XarOracleTests.cs new file mode 100644 index 000000000..59b4d7b25 --- /dev/null +++ b/Compression.Tests/Xar/XarOracleTests.cs @@ -0,0 +1,72 @@ +#pragma warning disable CS1591 +using FileFormat.Xar; + +namespace Compression.Tests.Xar; + +/// +/// libarchive's bsdtar writes a XAR archive with each encoding, and our reader must hand back the +/// original bytes. The committed reference vectors pin the same thing offline; this re-asks the +/// tool on every run so a different tool version is noticed. +/// +[TestFixture] +[Category("ExternalInterop")] +public class XarOracleTests { + private string _dir = null!; + + [SetUp] + public void SetUp() { + this._dir = Path.Combine(Path.GetTempPath(), "cwb_theirs_" + Guid.NewGuid().ToString("N")[..8]); + Directory.CreateDirectory(this._dir); + } + + [TearDown] + public void TearDown() { + try { Directory.Delete(this._dir, true); } catch { /* best effort */ } + } + + private static void RequireTool(string tool, string package) { + if (!FsInteropToolbox.WslAvailable) + Assert.Ignore("WSL not available."); + if (!FsInteropToolbox.WslHasTool(tool)) + Assert.Ignore($"{tool} is not installed. Run inside WSL / on Linux: `sudo apt install -y {package}`."); + } + + private Dictionary Inputs() { + var text = string.Concat(Enumerable.Range(1, 400).Select(i => $"line {i:D5}: the quick brown fox jumps over the lazy dog\n")); + var random = new byte[3000]; + new Random(23).NextBytes(random); + var files = new Dictionary(StringComparer.Ordinal) { + ["TEXT.TXT"] = System.Text.Encoding.ASCII.GetBytes(text), + ["RANDOM.BIN"] = random, + ["REPEAT.BIN"] = [.. random, .. random], + ["ONE.TXT"] = "a"u8.ToArray(), + }; + foreach (var (name, bytes) in files) + File.WriteAllBytes(Path.Combine(this._dir, name), bytes); + return files; + } + + private void Run(string command) { + var r = FsInteropToolbox.RunWsl($"cd {FsInteropToolbox.WinToWsl(this._dir)} && {command}"); + Assert.That(r.ExitCode, Is.EqualTo(0), $"{command}\n{r.StdOut}\n{r.StdErr}"); + } + + [TestCase("none", "none")] + [TestCase("gzip", "zlib")] + [TestCase("bzip2", "bzip2")] + [TestCase("lzma", "lzma")] + [TestCase("xz", "xz")] + public void XarEncoding_WrittenByBsdtar_ReadByUs(string compression, string method) { + RequireTool("bsdtar", "libarchive-tools"); + var inputs = this.Inputs(); + this.Run($"bsdtar --format xar --options xar:compression={compression} -cf out.xar {string.Join(' ', inputs.Keys)}"); + + using var fs = File.OpenRead(Path.Combine(this._dir, "out.xar")); + var reader = new XarReader(fs); + foreach (var entry in reader.Entries.Where(e => !e.IsDirectory)) { + if (entry.FileName == "TEXT.TXT") + Assert.That(entry.Method, Is.EqualTo(method)); + Assert.That(reader.Extract(entry), Is.EqualTo(inputs[entry.FileName]).AsCollection, entry.FileName); + } + } +} diff --git a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Xar/XarEntry.cs b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Xar/XarEntry.cs index 5904a1802..3173b6ed3 100644 --- a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Xar/XarEntry.cs +++ b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Xar/XarEntry.cs @@ -16,7 +16,7 @@ public sealed class XarEntry { /// Whether this entry is a directory. public bool IsDirectory { get; init; } - /// Compression method name (e.g., "zlib", "bzip2", "none"). + /// Compression method: "none", "zlib", "bzip2", "lzma", "xz", or the raw encoding style when unrecognised. public string Method { get; init; } = "none"; /// Last modification date. @@ -30,4 +30,7 @@ public sealed class XarEntry { /// Extracted checksum (from TOC). internal string? ExtractedChecksum { get; init; } + + /// Hash named by the extracted checksum's style attribute (e.g. "sha1", "md5"). + internal string? ExtractedChecksumStyle { get; init; } } diff --git a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Xar/XarReader.cs b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Xar/XarReader.cs index 63344934e..39b6b0c3c 100644 --- a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Xar/XarReader.cs +++ b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Xar/XarReader.cs @@ -1,6 +1,10 @@ using System.Buffers.Binary; +using Compression.Core.Checksums; using Compression.Core.Deflate; using Compression.Core.Streams; +using FileFormat.Bzip2; +using FileFormat.Lzma; +using FileFormat.Xz; using System.Xml.Linq; namespace FileFormat.Xar; @@ -86,26 +90,18 @@ private void ParseFiles(XElement parent, string pathPrefix) { var dataEl = fileEl.Element("data"); long origSize = 0, compSize = 0, heapOffset = 0; var method = "none"; - string? archivedChecksum = null, extractedChecksum = null; + string? archivedChecksum = null, extractedChecksum = null, extractedChecksumStyle = null; DateTime? lastModified = null; if (dataEl != null) { origSize = ParseLong(dataEl.Element("size")?.Value); compSize = ParseLong(dataEl.Element("length")?.Value); heapOffset = ParseLong(dataEl.Element("offset")?.Value); - method = dataEl.Element("encoding")?.Attribute("style")?.Value ?? "none"; - // Normalize method names - if (method.Contains("zlib") || method.Contains("x-gzip")) - method = "zlib"; - else if (method.Contains("bzip2")) - method = "bzip2"; - else if (method.Contains("lzma") || method.Contains("xz")) - method = "lzma"; - else if (method == "application/octet-stream" || method.Contains("none")) - method = "none"; + method = NormalizeMethod(dataEl.Element("encoding")?.Attribute("style")?.Value); archivedChecksum = dataEl.Element("archived-checksum")?.Value; extractedChecksum = dataEl.Element("extracted-checksum")?.Value; + extractedChecksumStyle = dataEl.Element("extracted-checksum")?.Attribute("style")?.Value; } var mtimeStr = fileEl.Element("mtime")?.Value; @@ -122,12 +118,31 @@ private void ParseFiles(XElement parent, string pathPrefix) { HeapOffset = heapOffset, ArchivedChecksum = archivedChecksum, ExtractedChecksum = extractedChecksum, + ExtractedChecksumStyle = extractedChecksumStyle, }); } } } - /// Extracts the data for a given entry. + /// Maps an encoding style (a MIME type) to the method name reports. + /// + /// xar names its encodings by MIME type: application/octet-stream (stored), + /// application/x-gzip (a zlib stream, despite the name), application/x-bzip2, + /// application/x-lzma and application/x-xz. An unrecognised style is kept verbatim so + /// extraction can refuse it by name rather than hand back encoded bytes. + /// + private static string NormalizeMethod(string? style) => style switch { + null or "" or "application/octet-stream" => "none", + "application/x-gzip" or "application/zlib" or "application/x-zlib" => "zlib", + "application/x-bzip2" => "bzip2", + "application/x-lzma" => "lzma", + "application/x-xz" => "xz", + _ => style, + }; + + /// Extracts the data for a given entry and checks it against the TOC's extracted checksum. + /// The entry's encoding is not one xar defines. + /// The decoded data is the wrong size or fails its checksum. public byte[] Extract(XarEntry entry) { if (entry.IsDirectory) return []; @@ -135,21 +150,67 @@ public byte[] Extract(XarEntry entry) { var compressedData = new byte[entry.CompressedSize]; _stream.ReadExactly(compressedData); - return entry.Method switch { + var data = entry.Method switch { + "none" => compressedData, "zlib" => DecompressZlib(compressedData, (int)entry.OriginalSize), - "bzip2" => DecompressBzip2(compressedData), - "none" or "" => compressedData, - _ => compressedData, // unknown method, return raw + "bzip2" => DecodeStream(compressedData, s => new Bzip2Stream(s, CompressionStreamMode.Decompress, leaveOpen: true)), + // xar's own lzma encoder (lzma_easy_encoder) writes .xz streams under "application/x-lzma"; + // libarchive writes the legacy .lzma ("alone") format there. Both readers sniff, so do we. + "lzma" or "xz" when IsXz(compressedData) => + DecodeStream(compressedData, s => new XzStream(s, CompressionStreamMode.Decompress, leaveOpen: true)), + "lzma" => DecodeLzmaAlone(compressedData), + "xz" => throw new InvalidDataException($"XAR entry '{entry.FileName}' is marked xz but lacks the xz stream magic."), + _ => throw new NotSupportedException($"XAR entry '{entry.FileName}' uses unsupported encoding '{entry.Method}'."), }; + + if (data.LongLength != entry.OriginalSize) + throw new InvalidDataException( + $"XAR entry '{entry.FileName}' decoded to {data.LongLength} bytes; the TOC says {entry.OriginalSize}."); + VerifyChecksum(entry, data); + return data; + } + + private static bool IsXz(ReadOnlySpan data) => + data.Length >= 6 && data[..6].SequenceEqual((ReadOnlySpan)[0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00]); + + private static byte[] DecodeStream(byte[] data, Func open) { + using var input = new MemoryStream(data, writable: false); + using var decoder = open(input); + using var output = new MemoryStream(); + decoder.CopyTo(output); + return output.ToArray(); + } + + private static byte[] DecodeLzmaAlone(byte[] data) { + using var input = new MemoryStream(data, writable: false); + using var output = new MemoryStream(); + LzmaStream.Decompress(input, output); + return output.ToArray(); + } + + /// Checks against the TOC's extracted-checksum when it names a known hash. + private static void VerifyChecksum(XarEntry entry, byte[] data) { + if (string.IsNullOrWhiteSpace(entry.ExtractedChecksum) || entry.ExtractedChecksumStyle is null) + return; + byte[]? actual = entry.ExtractedChecksumStyle.ToLowerInvariant() switch { + "sha1" => Sha1.Compute(data), + "md5" => Md5.Compute(data), + "sha256" => Sha256.Compute(data), + _ => null, + }; + if (actual is null) + return; + if (!string.Equals(Convert.ToHexString(actual), entry.ExtractedChecksum.Trim(), StringComparison.OrdinalIgnoreCase)) + throw new InvalidDataException( + $"XAR entry '{entry.FileName}' fails its {entry.ExtractedChecksumStyle} extracted-checksum."); } private static byte[] DecompressZlib(byte[] data, int expectedSize) { - // Zlib data: 2-byte header + deflate stream + 4-byte Adler32 - // The raw Deflate decoder takes it once the zlib header is skipped + // Zlib data: 2-byte header + deflate stream + 4-byte Adler32; the raw Deflate decoder takes it + // once the header is skipped, and stops by itself before the trailer. if (data.Length < 2) - return data; + throw new InvalidDataException("XAR zlib stream is shorter than its two-byte header."); - // Skip zlib header (2 bytes) and omit trailing Adler32 (4 bytes) using var ms = new MemoryStream(data, 2, data.Length - 2); using var ds = new RawDeflateStream(ms, CompressionStreamMode.Decompress); var result = new byte[expectedSize]; @@ -162,13 +223,6 @@ private static byte[] DecompressZlib(byte[] data, int expectedSize) { return result[..totalRead]; } - private static byte[] DecompressBzip2(byte[] data) { - // Bzip2 decompression requires FileFormat.Bzip2 which we don't reference. - // For XAR archives with bzip2 entries, return the raw compressed data. - // The higher-level Compression.Lib can re-decompress if needed. - throw new NotSupportedException("XAR entries compressed with bzip2 are not supported in standalone mode. Use Compression.Lib for full format support."); - } - private static long ParseLong(string? s) => long.TryParse(s, out var v) ? v : 0; } diff --git a/Hawkynt.FileFormats.Archives/REFERENCE.md b/Hawkynt.FileFormats.Archives/REFERENCE.md index a536b218e..379092af7 100644 --- a/Hawkynt.FileFormats.Archives/REFERENCE.md +++ b/Hawkynt.FileFormats.Archives/REFERENCE.md @@ -32127,7 +32127,7 @@ Represents a single file entry in a XAR archive. | `FileName` | `string FileName { get; init; }` | File name (path within archive). | | `IsDirectory` | `bool IsDirectory { get; init; }` | Whether this entry is a directory. | | `LastModified` | `DateTime? LastModified { get; init; }` | Last modification date. | -| `Method` | `string Method { get; init; }` | Compression method name (e.g., "zlib", "bzip2", "none"). | +| `Method` | `string Method { get; init; }` | Compression method: "none", "zlib", "bzip2", "lzma", "xz", or the raw encoding style when unrecognised. | | `OriginalSize` | `long OriginalSize { get; init; }` | Uncompressed size in bytes. | #### `XarFormatDescriptor` @@ -32178,7 +32178,7 @@ Reads XAR (eXtensible ARchive) archives. XAR is Apple's archive format used for | `XarReader` | `XarReader(Stream stream)` | Creates a new XAR reader for the given stream. | | `Entries` | `IReadOnlyList Entries { get; }` | All entries in the archive. | | `HeapStart` | `long HeapStart { get; }` | Byte offset where the data heap begins (after header + TOC). | -| `Extract` | `byte[] Extract(XarEntry entry)` | Extracts the data for a given entry. | +| `Extract` | `byte[] Extract(XarEntry entry)` | Extracts the data for a given entry and checks it against the TOC's extracted checksum. | #### `XarWriter` diff --git a/docs/catalogue/GAPS.md b/docs/catalogue/GAPS.md index 9d102290e..1f0c98853 100644 --- a/docs/catalogue/GAPS.md +++ b/docs/catalogue/GAPS.md @@ -163,7 +163,6 @@ running real-world samples. The file named at the end of each row is where the l | ISO 9660 (Joliet / Rock Ridge / El Torito) | `Iso` | 7 | Rock Ridge limited to NM names (no SL/PX/TF/CE, no zisofs); interleaved files throw (IsoReader.cs) | | ZIP | `Zip` | 7 | methods xz (95), JPEG (96), WavPack (97) and PKWARE strong encryption unsupported (ZipReader.cs) | | LHA / LZH | `Lzh` | 6 | lh2/lh3 are decoded with the lh5 decoder (approximation) (LhaReader.cs) | -| XAR (eXtensible ARchive) | `Xar` | 5 | bzip2 throws; lzma/xz members are returned raw (XarReader.cs) | | StuffIt (classic + 5) | `StuffIt` | 4 | store and RLE only; LZW/Huffman/LZAH/Arsenic/DES methods throw (StuffItReader.cs) | | StuffIt X | `StuffItX` | 4 | stored and deflate only; Brimstone/Cyanide/Darkhorse/Iron throw (StuffItXReader.cs) | | zoo | `Zoo` | 4 | LZH method (2) unsupported (ZooReader.cs) | diff --git a/docs/catalogue/reference-formats.csv b/docs/catalogue/reference-formats.csv index f18495d3a..f6b9d2f79 100644 --- a/docs/catalogue/reference-formats.csv +++ b/docs/catalogue/reference-formats.csv @@ -24,7 +24,7 @@ Shell archive (shar),shar;run;sh,archive,,W,,,,fmt/329,Y,3,https://www.gnu.org/s mtree manifest,mtree;spec,archive,,RW,,,,,,1,https://man.freebsd.org/mtree/5,Mtree,Y,Y,,OK,, WARC (Web ARChive),warc,archive,,RW,R,,R,fmt/1281;fmt/1355;fmt/289,,4,https://iipc.github.io/warc-specifications/,Warc,Y,Y,,OK,, Internet Archive ARC (crawl),arc,archive,,,,,,fmt/410;x-fmt/219,,1,https://archive.org/web/researcher/ArcFileFormat.php,,,,,MISSING,, -XAR (eXtensible ARchive),xar;pkg;xip;mpkg,archive,R,RW,R,,,fmt/600,Y,5,https://github.com/mackyle/xar/wiki/xarformat,Xar,Y,Y,Y,PARTIAL,,bzip2 throws; lzma/xz members are returned raw (XarReader.cs) +XAR (eXtensible ARchive),xar;pkg;xip;mpkg,archive,R,RW,R,,,fmt/600,Y,5,https://github.com/mackyle/xar/wiki/xarformat,Xar,Y,Y,Y,OK,,"every xar encoding (none, gzip/zlib, bzip2, lzma, xz) decoded and checked against the TOC checksum; verified on bsdtar-written archives" Xar Image Format (PRONOM fmt/922),xar,archive,,,,,,fmt/922,,1,,,,,,MISSING,,PRONOM lists this separately from eXtensible ARchive Microsoft Cabinet (CAB),cab;exe;mscf,archive,R,R,R,R,,x-fmt/414,RW,6,https://learn.microsoft.com/previous-versions/bb417343(v=msdn.10),Cab,Y,Y,Y,OK,, CAB self-extractor (IExpress / MSCF SFX),exe,installer,,,,,R,,,1,,,,,,MISSING,, From 393d095fb859da81afce257929c870a92ffaa6e7 Mon Sep 17 00:00:00 2001 From: Hawkynt Date: Mon, 26 Oct 2026 13:01:35 +0100 Subject: [PATCH 05/34] * regenerate references --- docs/catalogue/GAPS.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/catalogue/GAPS.md b/docs/catalogue/GAPS.md index 1f0c98853..424dda614 100644 --- a/docs/catalogue/GAPS.md +++ b/docs/catalogue/GAPS.md @@ -25,8 +25,8 @@ exists. It warns when a capability letter has drifted from the live descriptor. | --- | ---: | --- | | `MISSING` | 241 | a source lists it; no descriptor or building block covers it | | `READ-ONLY-BUT-SOURCES-WRITE` | 23 | we only read it; a source or the format's own tool writes it | -| `PARTIAL` | 41 | covered, but the reader handles only part of the format (see the table below) | -| `OK` | 220 | covered with no known gap against what the sources list | +| `PARTIAL` | 40 | covered, but the reader handles only part of the format (see the table below) | +| `OK` | 221 | covered with no known gap against what the sources list | | `OURS-ONLY` | 458 | in our registry, listed by none of the seven sources | ### Per source @@ -36,13 +36,13 @@ read-only rows. | Source | Rows it lists | Mapped | % | OK | | --- | ---: | ---: | ---: | ---: | -| 7-Zip (formats + codecs) | 96 | 89 | 92.7 | 71 | -| libarchive (formats + filters) | 28 | 27 | 96.4 | 22 | -| XADMaster / The Unarchiver | 126 | 94 | 74.6 | 77 | +| 7-Zip (formats + codecs) | 96 | 89 | 92.7 | 72 | +| libarchive (formats + filters) | 28 | 27 | 96.4 | 23 | +| XADMaster / The Unarchiver | 126 | 94 | 74.6 | 78 | | binwalk v3 | 79 | 48 | 60.8 | 36 | | Universal Extractor 2 | 124 | 71 | 57.3 | 48 | -| PRONOM | 87 | 63 | 72.4 | 42 | -| Wikipedia | 266 | 156 | 58.6 | 116 | +| PRONOM | 87 | 63 | 72.4 | 43 | +| Wikipedia | 266 | 156 | 58.6 | 117 | The libarchive and 7-Zip lists are almost fully covered. The rows still open are libarchive's grzip, plus seven 7-Zip rows: LVM, Android `super.img` (LP), AVB, TE, the Swap2/Swap4 filter, PKWARE strong From 3bcfeb8c0d70d703328e1fe6498bf555bce17e16 Mon Sep 17 00:00:00 2001 From: Hawkynt Date: Mon, 26 Oct 2026 13:01:41 +0100 Subject: [PATCH 06/34] # LHA: -lh2- and -lh3- decoded with their own decoders, writing them refused # LHA: -lh2- and -lh3- decoded with their own decoders, writing them refused Symptom: -lh2- and -lh3- members were decoded with the -lh5- decoder, and the writer emitted -lh5- data labelled -lh2-/-lh3-, which no real LHA reads. Root cause: both are distinct LHarc 2.x methods. -lh2- is LZSS over an 8 KiB window with two adaptive Huffman trees (the position tree grows a leaf every 64 output bytes); -lh3- sends static trees per block, with a "ready made" position table when none is sent. Neither shares -lh5-'s bit stream. Change: Lh2Decoder and Lh3Decoder (with the LHarc fillbuf/getbits bit reader) decode them; LhaWriter refuses both methods and NativeUI no longer offers them. Evidence: lh2.lzh and lh3.lzh from lhasa's test suite (ISC), written by UNLHA32.DLL 2.67a, committed with provenance. Both decode to the CRC-16 UNLHA32 recorded, to 2,853 bytes of MIT licence text, and to identical bytes as each other. Sourcing: rung 1, converted from XADMaster's XADLZHOldHandles.m (LGPL-2.1-or-later; descended from LHa for UNIX via libxad), notice in THIRD-PARTY-NOTICE.XADMaster.txt. XADMaster sizes the -lh3- position alphabet as 1 << 10; the ready-made table only satisfies Kraft's equality with 128 symbols (8 KiB / 64), which is what the real files need. * regenerate package references --- Compression.Core/Dictionary/Lzh/Lh2Decoder.cs | 306 ++++++++++++++++++ Compression.Core/Dictionary/Lzh/Lh3Decoder.cs | 234 ++++++++++++++ .../Dictionary/Lzh/LhaLegacyBitReader.cs | 41 +++ .../Lzh/THIRD-PARTY-NOTICE.XADMaster.txt | 37 +++ Compression.Core/REFERENCE.md | 18 +- .../Views/CreateOptionsViewModel.cs | 7 - Compression.Tests/Compression.Tests.csproj | 4 + .../Lzh/LhaLegacyMethodVectorTests.cs | 89 +++++ Compression.Tests/Lzh/LhaTests.cs | 48 +-- .../Lzh/ReferenceVectors/README.md | 38 +++ .../Lzh/ReferenceVectors/lh2.lzh | Bin 0 -> 1377 bytes .../Lzh/ReferenceVectors/lh3.lzh | Bin 0 -> 1418 bytes .../FileFormat.Lzh/LhaCompatibility.cs | 5 +- .../FileFormats/FileFormat.Lzh/LhaReader.cs | 4 +- .../FileFormats/FileFormat.Lzh/LhaWriter.cs | 1 - Hawkynt.FileFormats.Archives/README.md | 2 +- docs/catalogue/GAPS.md | 2 +- docs/catalogue/reference-formats.csv | 2 +- 18 files changed, 782 insertions(+), 56 deletions(-) create mode 100644 Compression.Core/Dictionary/Lzh/Lh2Decoder.cs create mode 100644 Compression.Core/Dictionary/Lzh/Lh3Decoder.cs create mode 100644 Compression.Core/Dictionary/Lzh/LhaLegacyBitReader.cs create mode 100644 Compression.Core/Dictionary/Lzh/THIRD-PARTY-NOTICE.XADMaster.txt create mode 100644 Compression.Tests/Lzh/LhaLegacyMethodVectorTests.cs create mode 100644 Compression.Tests/Lzh/ReferenceVectors/README.md create mode 100644 Compression.Tests/Lzh/ReferenceVectors/lh2.lzh create mode 100644 Compression.Tests/Lzh/ReferenceVectors/lh3.lzh diff --git a/Compression.Core/Dictionary/Lzh/Lh2Decoder.cs b/Compression.Core/Dictionary/Lzh/Lh2Decoder.cs new file mode 100644 index 000000000..1c33f5ba7 --- /dev/null +++ b/Compression.Core/Dictionary/Lzh/Lh2Decoder.cs @@ -0,0 +1,306 @@ +namespace Compression.Core.Dictionary.Lzh; + +/// +/// Decodes LHA -lh2-: LZSS over an 8 KiB window with two adaptive Huffman trees. +/// +/// +/// A short-lived LHarc 2.x method. Unlike -lh1-, whose single tree is the classic +/// LZHUF one, -lh2- keeps two trees with "block" bookkeeping (a list of runs of equal +/// frequency) so a symbol's increment is one swap with its run leader: +/// +/// a character/length tree of 286 leaves: 0-255 literals, 256-284 match lengths 3-31, +/// and 285 followed by 8 raw bits for the longer lengths; +/// a position tree that starts with one leaf and gains a leaf for every 64 bytes of +/// output, until it covers the 8 KiB window (128 leaves). Its symbol is the high bits of the distance; +/// 6 raw bits follow. +/// +/// Both trees halve their frequencies when the root count reaches 0x8000. +/// Sourcing: converted from the -lh2- path of The Unarchiver's XADMaster +/// (XADLZHOldHandles.m, LGPL-2.1-or-later), itself descended from LHa for UNIX's +/// dhuf.c; checked against lh2.lzh from the lhasa test suite (ISC), written by +/// UNLHA32.DLL 2.67a, whose CRC-16 the output must match. See THIRD-PARTY-NOTICE.XADMaster.txt beside this file. +/// +public static class Lh2Decoder { + private const int DicBit = 13; + private const int DicSize = 1 << DicBit; + private const int Threshold = 3; + private const int NMax = 286; + private const int NChar = 256 + 60 - Threshold + 1; // 314: sizes the shared node arrays + private const int TreeSizeC = NChar * 2; // 628 + private const int TreeSizeP = 128 * 2; + private const int TreeSize = TreeSizeC + TreeSizeP; + private const int RootC = 0; + private const int RootP = TreeSizeC; + private const int N1 = NMax - 1; // the escape symbol (maxmatch 256 < 510) + + /// Decodes bytes of -lh2- data. + public static byte[] Decode(byte[] compressed, int originalSize) { + ArgumentNullException.ThrowIfNull(compressed); + ArgumentOutOfRangeException.ThrowIfNegative(originalSize); + var output = new byte[originalSize]; + if (originalSize == 0) + return output; + + var state = new State(new LhaLegacyBitReader(compressed)); + var text = new byte[DicSize]; + Array.Fill(text, (byte)' '); + var loc = 0; + var outPos = 0; + + while (outPos < originalSize) { + var c = state.DecodeC(); + if (c <= byte.MaxValue) { + output[outPos++] = text[loc] = (byte)c; + loc = (loc + 1) & (DicSize - 1); + ++state.Count; + continue; + } + + var length = c - (256 - Threshold); + var from = loc - state.DecodeP() - 1; + state.Count += (uint)length; + while (length-- > 0 && outPos < originalSize) { + output[outPos++] = text[loc] = text[from++ & (DicSize - 1)]; + loc = (loc + 1) & (DicSize - 1); + } + } + + return output; + } + + private sealed class State { + private readonly LhaLegacyBitReader _bits; + private readonly ushort[] _freq = new ushort[TreeSize]; + private readonly short[] _child = new short[TreeSize]; + private readonly short[] _stock = new short[TreeSize]; + private readonly short[] _sNode = new short[TreeSize / 2]; + private readonly short[] _block = new short[TreeSize]; + private readonly short[] _parent = new short[TreeSize]; + private readonly short[] _edge = new short[TreeSize]; + private int _avail; + private int _mostP; + private ushort _totalP; + private uint _nextCount; + + /// Bytes produced so far; the position tree grows with it. + public uint Count; + + public State(LhaLegacyBitReader bits) { + this._bits = bits; + this.StartC(); + this.StartP(); + } + + private void StartC() { + for (var i = 0; i < TreeSizeC; ++i) { + this._stock[i] = (short)i; + this._block[i] = 0; + } + + int j; + int k; + for (k = 0, j = NMax * 2 - 2; k < NMax; ++k, --j) { + this._freq[j] = 1; + this._child[j] = (short)~k; + this._sNode[k] = (short)j; + this._block[j] = 1; + } + + this._avail = 2; + this._edge[1] = NMax - 1; + var i2 = NMax * 2 - 2; + while (j >= 0) { + var f = this._freq[j] = (ushort)(this._freq[i2] + this._freq[i2 - 1]); + this._child[j] = (short)i2; + this._parent[i2] = this._parent[i2 - 1] = (short)j; + if (f == this._freq[j + 1]) + this._edge[this._block[j] = this._block[j + 1]] = (short)j; + else + this._edge[this._block[j] = this._stock[this._avail++]] = (short)j; + i2 -= 2; + --j; + } + } + + private void StartP() { + this._freq[RootP] = 1; + this._child[RootP] = ~NChar; + this._sNode[NChar] = RootP; + this._edge[this._block[RootP] = this._stock[this._avail++]] = RootP; + this._mostP = RootP; + this._totalP = 0; + this._nextCount = 64; + } + + /// Halves the leaf counts in [start, end) and rebuilds that tree and its runs. + private void Reconstruct(int start, int end) { + int i, j, k, l, b = 0; + uint f, g; + + for (i = j = start; i < end; ++i) { + if ((k = this._child[i]) < 0) { + this._freq[j] = (ushort)((this._freq[i] + 1) / 2); + this._child[j] = (short)k; + ++j; + } + if (this._edge[b = this._block[i]] == i) + this._stock[--this._avail] = (short)b; + } + + --j; + i = end - 1; + l = end - 2; + while (i >= start) { + while (i >= l) { + this._freq[i] = this._freq[j]; + this._child[i] = this._child[j]; + --i; + --j; + } + f = (uint)(this._freq[l] + this._freq[l + 1]); + for (k = start; f < this._freq[k]; ++k) { } + while (j >= k) { + this._freq[i] = this._freq[j]; + this._child[i] = this._child[j]; + --i; + --j; + } + this._freq[i] = (ushort)f; + this._child[i] = (short)(l + 1); + --i; + l -= 2; + } + + f = 0; + for (i = start; i < end; ++i) { + if ((j = this._child[i]) < 0) + this._sNode[~j] = (short)i; + else + this._parent[j] = this._parent[j - 1] = (short)i; + if ((g = this._freq[i]) == f) + this._block[i] = (short)b; + else { + this._edge[b = this._block[i] = this._stock[this._avail++]] = (short)i; + f = g; + } + } + } + + /// Increments node , first swapping it with its run leader; returns the parent. + private int SwapInc(int p) { + int b = this._block[p], q, r, s; + if ((q = this._edge[b]) != p) { + r = this._child[p]; + s = this._child[q]; + this._child[p] = (short)s; + this._child[q] = (short)r; + if (r >= 0) + this._parent[r] = this._parent[r - 1] = (short)q; + else + this._sNode[~r] = (short)q; + if (s >= 0) + this._parent[s] = this._parent[s - 1] = (short)p; + else + this._sNode[~s] = (short)p; + p = q; + ++this._edge[b]; + if (++this._freq[p] == this._freq[p - 1]) + this._block[p] = this._block[p - 1]; + else + this._edge[this._block[p] = this._stock[this._avail++]] = (short)p; + } else if (b == this._block[p + 1]) { + ++this._edge[b]; + if (++this._freq[p] == this._freq[p - 1]) + this._block[p] = this._block[p - 1]; + else + this._edge[this._block[p] = this._stock[this._avail++]] = (short)p; + } else if (++this._freq[p] == this._freq[p - 1]) { + this._stock[--this._avail] = (short)b; + this._block[p] = this._block[p - 1]; + } + return this._parent[p]; + } + + private void UpdateC(int p) { + if (this._freq[RootC] == 0x8000) + this.Reconstruct(0, NMax * 2 - 1); + ++this._freq[RootC]; + var q = (int)this._sNode[p]; + do + q = this.SwapInc(q); + while (q != RootC); + } + + private void UpdateP(int p) { + if (this._totalP == 0x8000) { + this.Reconstruct(RootP, this._mostP + 1); + this._totalP = this._freq[RootP]; + this._freq[RootP] = 0xffff; + } + var q = (int)this._sNode[p + NChar]; + while (q != RootP) + q = this.SwapInc(q); + ++this._totalP; + } + + /// Splits the last position leaf to add position symbol . + private void MakeNewNode(int p) { + var r = this._mostP + 1; + var q = r + 1; + this._sNode[~(this._child[r] = this._child[this._mostP])] = (short)r; + this._child[q] = (short)~(p + NChar); + this._child[this._mostP] = (short)q; + this._freq[r] = this._freq[this._mostP]; + this._freq[q] = 0; + this._block[r] = this._block[this._mostP]; + if (this._mostP == RootP) { + this._freq[RootP] = 0xffff; + ++this._edge[this._block[RootP]]; + } + this._parent[r] = this._parent[q] = (short)this._mostP; + this._edge[this._block[q] = this._stock[this._avail++]] = + this._sNode[p + NChar] = (short)(this._mostP = q); + this.UpdateP(p); + } + + /// Walks a tree from the child pointer to a leaf; returns the leaf (~symbol). + private int Walk(int c, bool atLeastOne) { + var buf = (short)this._bits.BitBuf; + var cnt = 0; + if (!atLeastOne && c <= 0) { + this._bits.FillBuf(0); + return c; + } + do { + c = this._child[c - (buf < 0 ? 1 : 0)]; + buf <<= 1; + if (++cnt == 16) { + this._bits.FillBuf(16); + buf = (short)this._bits.BitBuf; + cnt = 0; + } + } while (c > 0); + this._bits.FillBuf(cnt); + return c; + } + + public int DecodeC() { + var c = ~this.Walk(this._child[RootC], atLeastOne: true); + this.UpdateC(c); + if (c == N1) + c += this._bits.GetBits(8); + return c; + } + + public int DecodeP() { + while (this.Count > this._nextCount) { + this.MakeNewNode((int)(this._nextCount / 64)); + if ((this._nextCount += 64) >= DicSize) + this._nextCount = uint.MaxValue; + } + var c = ~this.Walk(this._child[RootP], atLeastOne: false) - NChar; + this.UpdateP(c); + return (c << 6) + this._bits.GetBits(6); + } + } +} diff --git a/Compression.Core/Dictionary/Lzh/Lh3Decoder.cs b/Compression.Core/Dictionary/Lzh/Lh3Decoder.cs new file mode 100644 index 000000000..8376760a0 --- /dev/null +++ b/Compression.Core/Dictionary/Lzh/Lh3Decoder.cs @@ -0,0 +1,234 @@ +namespace Compression.Core.Dictionary.Lzh; + +/// +/// Decodes LHA -lh3-: LZSS over an 8 KiB window with static Huffman trees sent per block. +/// +/// +/// The forerunner of -lh5-, with simpler tree transmission: +/// +/// Each block starts with a 16-bit symbol count, then the character/length tree: for +/// each of 286 symbols a presence bit and, if set, a 4-bit length minus one. Three leading length-1 +/// codes are the escape for a single-symbol tree, whose 9-bit value follows. +/// One bit selects the position tree: 1 sends 128 four-bit lengths (three leading +/// 1s again escape to a single 7-bit value); 0 uses the fixed "ready made" table whose run starts +/// are 2; 1, 1, 3, 6, 13, 31, 78. Its 128 symbols are the distance's high bits; 6 raw bits +/// follow. +/// Symbols 0-255 are literals, 256-284 match lengths 3-31, and 285 is followed by 8 +/// raw bits for the longer lengths. +/// +/// Sourcing: converted from the -lh3- path of The Unarchiver's XADMaster +/// (XADLZHOldHandles.m, LGPL-2.1-or-later), itself descended from LHa for UNIX's +/// shuf.c. XADMaster sizes the position alphabet as 1 << (16 - 6); the +/// ready-made table only satisfies Kraft's equality with 128 symbols (8 KiB / 64), which is also what +/// -lh3-'s window needs, so 128 is used here. Checked against lh3.lzh from the lhasa +/// test suite (ISC), written by UNLHA32.DLL 2.67a, whose CRC-16 the output must match. See +/// THIRD-PARTY-NOTICE.XADMaster.txt beside this file. +/// +public static class Lh3Decoder { + private const int DicBit = 13; + private const int DicSize = 1 << DicBit; + private const int Threshold = 3; + private const int N1 = 286; // character/length alphabet + private const int Np = DicSize / 64; // position alphabet: 128 + private const int ExtraBits = 8; + private const int LenField = 4; + private const int CBit = 9; + private const int MaxMatch = 256; + private const int Nc = byte.MaxValue + MaxMatch + 2 - Threshold; // 510: sizes the overflow tree + + private static readonly int[] ReadyMade = [2, 0x01, 0x01, 0x03, 0x06, 0x0D, 0x1F, 0x4E, 0]; + + /// Decodes bytes of -lh3- data. + /// A block's code lengths do not describe a complete tree. + public static byte[] Decode(byte[] compressed, int originalSize) { + ArgumentNullException.ThrowIfNull(compressed); + ArgumentOutOfRangeException.ThrowIfNegative(originalSize); + var output = new byte[originalSize]; + if (originalSize == 0) + return output; + + var state = new State(new LhaLegacyBitReader(compressed)); + var text = new byte[DicSize]; + Array.Fill(text, (byte)' '); + var loc = 0; + var outPos = 0; + + while (outPos < originalSize) { + var c = state.DecodeC(); + if (c <= byte.MaxValue) { + output[outPos++] = text[loc] = (byte)c; + loc = (loc + 1) & (DicSize - 1); + continue; + } + + var length = c - (256 - Threshold); + var from = loc - state.DecodeP() - 1; + while (length-- > 0 && outPos < originalSize) { + output[outPos++] = text[loc] = text[from++ & (DicSize - 1)]; + loc = (loc + 1) & (DicSize - 1); + } + } + + return output; + } + + private sealed class State(LhaLegacyBitReader bits) { + private readonly ushort[] _cTable = new ushort[4096]; + private readonly ushort[] _ptTable = new ushort[256]; + // One overflow tree shared by both tables, as in the reference: the position tree's nodes start at + // index 128 and the character tree's at 286, so neither reaches the other's. + private readonly ushort[] _left = new ushort[2 * Nc - 1]; + private readonly ushort[] _right = new ushort[2 * Nc - 1]; + private readonly byte[] _cLen = new byte[N1]; + private readonly byte[] _ptLen = new byte[Np]; + private int _blockSize; + + public int DecodeC() { + if (this._blockSize == 0) { + this._blockSize = bits.GetBits(16); + this.ReadTreeC(); + if (bits.GetBits(1) != 0) + this.ReadTreeP(); + else + this.ReadyMadeP(); + this.MakeTable(Np, this._ptLen, 8, this._ptTable); + } + --this._blockSize; + + int j = this._cTable[bits.BitBuf >> 4]; + if (j < N1) + bits.FillBuf(this._cLen[j]); + else { + bits.FillBuf(12); + var i = bits.BitBuf; + do { + j = (i & 0x8000) != 0 ? this._right[j] : this._left[j]; + i <<= 1; + } while (j >= N1); + bits.FillBuf(this._cLen[j] - 12); + } + + if (j == N1 - 1) + j += bits.GetBits(ExtraBits); + return j; + } + + public int DecodeP() { + int j = this._ptTable[bits.BitBuf >> 8]; + if (j < Np) + bits.FillBuf(this._ptLen[j]); + else { + bits.FillBuf(8); + var i = bits.BitBuf; + do { + j = (i & 0x8000) != 0 ? this._right[j] : this._left[j]; + i <<= 1; + } while (j >= Np); + bits.FillBuf(this._ptLen[j] - 8); + } + return (j << 6) + bits.GetBits(6); + } + + private void ReadTreeC() { + var i = 0; + while (i < N1) { + this._cLen[i] = bits.GetBits(1) != 0 ? (byte)(bits.GetBits(LenField) + 1) : (byte)0; + if (++i == 3 && this._cLen[0] == 1 && this._cLen[1] == 1 && this._cLen[2] == 1) { + var c = (ushort)bits.GetBits(CBit); + Array.Clear(this._cLen); + Array.Fill(this._cTable, c); + return; + } + } + this.MakeTable(N1, this._cLen, 12, this._cTable); + } + + private void ReadTreeP() { + var i = 0; + while (i < Np) { + this._ptLen[i] = (byte)bits.GetBits(LenField); + if (++i == 3 && this._ptLen[0] == 1 && this._ptLen[1] == 1 && this._ptLen[2] == 1) { + var c = (ushort)bits.GetBits(DicBit - 6); + Array.Clear(this._ptLen); + Array.Fill(this._ptTable, c); + return; + } + } + } + + private void ReadyMadeP() { + var t = 0; + var j = ReadyMade[t++]; + for (var i = 0; i < Np; ++i) { + while (ReadyMade[t] == i) { + ++j; + ++t; + } + this._ptLen[i] = (byte)j; + } + } + + /// + /// Builds a -bit lookup table for canonical codes of the given lengths, + /// hanging longer codes off it in the shared overflow tree. + /// + private void MakeTable(int nChar, byte[] bitLen, int tableBits, ushort[] table) { + Span count = stackalloc ushort[17]; + Span weight = stackalloc ushort[17]; + Span start = stackalloc ushort[18]; + + for (var i = 1; i <= 16; ++i) + weight[i] = (ushort)(1 << (16 - i)); + for (var i = 0; i < nChar; ++i) + ++count[bitLen[i]]; + + ushort total = 0; + for (var i = 1; i <= 16; ++i) { + start[i] = total; + total = (ushort)(total + weight[i] * count[i]); + } + if (total != 0) + throw new InvalidDataException("LHA -lh3- block has code lengths that do not form a complete tree."); + + var m = 16 - tableBits; + for (var i = 1; i <= tableBits; ++i) { + start[i] >>= m; + weight[i] >>= m; + } + + var first = start[tableBits + 1] >> m; + if (first != 0) + for (var i = first; i < 1 << tableBits; ++i) + table[i] = 0; + + var avail = nChar; + for (var j = 0; j < nChar; ++j) { + int k = bitLen[j]; + if (k == 0) + continue; + var l = (ushort)(start[k] + weight[k]); + if (k <= tableBits) { + for (int i = start[k]; i < l; ++i) + table[i] = (ushort)j; + } else { + uint code = start[k]; + ref var p = ref table[code >> m]; + code <<= tableBits; + for (var n = k - tableBits; --n >= 0;) { + if (p == 0) { + this._right[avail] = this._left[avail] = 0; + p = (ushort)avail++; + } + if ((code & 0x8000) != 0) + p = ref this._right[p]; + else + p = ref this._left[p]; + code <<= 1; + } + p = (ushort)j; + } + start[k] = l; + } + } + } +} diff --git a/Compression.Core/Dictionary/Lzh/LhaLegacyBitReader.cs b/Compression.Core/Dictionary/Lzh/LhaLegacyBitReader.cs new file mode 100644 index 000000000..29da80a0d --- /dev/null +++ b/Compression.Core/Dictionary/Lzh/LhaLegacyBitReader.cs @@ -0,0 +1,41 @@ +namespace Compression.Core.Dictionary.Lzh; + +/// +/// The 16-bit look-ahead bit buffer of the LHarc/LHa decoders (fillbuf/getbits): +/// MSB-first, with the next 16 bits always visible in . Reads past the end yield +/// zero bits, as the reference decoders do. +/// +internal sealed class LhaLegacyBitReader { + private readonly byte[] _data; + private int _pos; + private byte _subBitBuf; + private int _bitCount; + + /// The next 16 bits of the stream, most significant first. + public ushort BitBuf { get; private set; } + + public LhaLegacyBitReader(byte[] data) { + this._data = data; + this.FillBuf(16); + } + + /// Drops bits (0-16) from and shifts new ones in. + public void FillBuf(int n) { + while (n > this._bitCount) { + n -= this._bitCount; + this.BitBuf = (ushort)((this.BitBuf << this._bitCount) + (this._subBitBuf >> (8 - this._bitCount))); + this._subBitBuf = this._pos < this._data.Length ? this._data[this._pos++] : (byte)0; + this._bitCount = 8; + } + this._bitCount -= n; + this.BitBuf = (ushort)((this.BitBuf << n) + (this._subBitBuf >> (8 - n))); + this._subBitBuf = (byte)(this._subBitBuf << n); + } + + /// Reads bits (0-16) as an unsigned value. + public int GetBits(int n) { + var x = this.BitBuf >> (16 - n); + this.FillBuf(n); + return x; + } +} diff --git a/Compression.Core/Dictionary/Lzh/THIRD-PARTY-NOTICE.XADMaster.txt b/Compression.Core/Dictionary/Lzh/THIRD-PARTY-NOTICE.XADMaster.txt new file mode 100644 index 000000000..0c9607a18 --- /dev/null +++ b/Compression.Core/Dictionary/Lzh/THIRD-PARTY-NOTICE.XADMaster.txt @@ -0,0 +1,37 @@ +XADMaster (LHA -lh2- / -lh3-) - converted to C# + +Source: https://github.com/MacPaw/XADMaster (The Unarchiver's unarchiving engine) +Revision: 7cb9ee0abbb163f261e4cb74501e15067032319c (master, 2026-09-21) +Authors: Dag Ågren (original author); MacPaw Way Ltd. (2017-present); + parts descend from libxad (Dirk Stöcker and contributors) +Licence: GNU Lesser General Public License, version 2.1 or (at your option) + any later version. This repository is LGPL-3.0-or-later, which the + "or any later version" grant permits; the full LGPL text is in the + repository's LICENSE file. + +Upstream notice, as carried in each XADMaster source file: + + Copyright (c) 2017-present, MacPaw Way Ltd. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + +Files converted: + + XADLZHOldHandles.m (LZHUFF2/LZHUFF3 paths) -> Lh2Decoder.cs, Lh3Decoder.cs, + LhaLegacyBitReader.cs + +The decoders are conversions: the adaptive two-tree bookkeeping of -lh2- and +the per-block static trees with the "ready made" position table of -lh3- are +carried across, with the tables copied exactly. One deliberate difference: +XADMaster sizes the -lh3- position alphabet as 1 << (16 - 6); the ready-made +table only satisfies Kraft's equality with 128 symbols (8 KiB / 64), so 128 is +used. Checked against lh2.lzh and lh3.lzh from the lhasa test suite (written +by UNLHA32.DLL 2.67a), whose CRC-16s the output matches. diff --git a/Compression.Core/REFERENCE.md b/Compression.Core/REFERENCE.md index cbf4a3221..ecc3be136 100644 --- a/Compression.Core/REFERENCE.md +++ b/Compression.Core/REFERENCE.md @@ -1484,7 +1484,7 @@ Implements `IBuildingBlock`. ### Namespace `Compression.Core.Dictionary.Lzh` -[`Lh1Decoder`](#lh1decoder) · [`Lh1Encoder`](#lh1encoder) · [`Lz5Decoder`](#lz5decoder) · [`Lz5Encoder`](#lz5encoder) · [`LzhBuildingBlock`](#lzhbuildingblock) · [`LzhConstants`](#lzhconstants) · [`LzhDecoder`](#lzhdecoder) · [`LzhEncoder`](#lzhencoder) · [`LzsDecoder`](#lzsdecoder) · [`LzsEncoder`](#lzsencoder) · [`PmaDecoder`](#pmadecoder) · [`PmaEncoder`](#pmaencoder) +[`Lh1Decoder`](#lh1decoder) · [`Lh1Encoder`](#lh1encoder) · [`Lh2Decoder`](#lh2decoder) · [`Lh3Decoder`](#lh3decoder) · [`Lz5Decoder`](#lz5decoder) · [`Lz5Encoder`](#lz5encoder) · [`LzhBuildingBlock`](#lzhbuildingblock) · [`LzhConstants`](#lzhconstants) · [`LzhDecoder`](#lzhdecoder) · [`LzhEncoder`](#lzhencoder) · [`LzsDecoder`](#lzsdecoder) · [`LzsEncoder`](#lzsencoder) · [`PmaDecoder`](#pmadecoder) · [`PmaEncoder`](#pmaencoder) #### `Lh1Decoder` @@ -1504,6 +1504,22 @@ Encodes data using the LHA -lh1- adaptive Huffman method. | `Lh1Encoder` | `Lh1Encoder()` | | | `Encode` | `static byte[] Encode(ReadOnlySpan data)` | Compresses data using the -lh1- algorithm. | +#### `Lh2Decoder` + +Decodes LHA `-lh2-`: LZSS over an 8 KiB window with two adaptive Huffman trees. + +| Member | Signature | Summary | +| --- | --- | --- | +| `Decode` | `static byte[] Decode(byte[] compressed, int originalSize)` | Decodes `originalSize` bytes of `-lh2-` data. | + +#### `Lh3Decoder` + +Decodes LHA `-lh3-`: LZSS over an 8 KiB window with static Huffman trees sent per block. + +| Member | Signature | Summary | +| --- | --- | --- | +| `Decode` | `static byte[] Decode(byte[] compressed, int originalSize)` | Decodes `originalSize` bytes of `-lh3-` data. | + #### `Lz5Decoder` Decodes -lz5- compressed data (LArc variant). Uses LZSS with a 4KB sliding window, no Huffman coding. diff --git a/Compression.NativeUI/Views/CreateOptionsViewModel.cs b/Compression.NativeUI/Views/CreateOptionsViewModel.cs index 05231f4c4..ee1d767e2 100644 --- a/Compression.NativeUI/Views/CreateOptionsViewModel.cs +++ b/Compression.NativeUI/Views/CreateOptionsViewModel.cs @@ -387,12 +387,6 @@ public string HintText { (F.Lzh, "lh1") => "lh1: Adaptive Huffman with 4 KB sliding window. The original LHA method from 1988.\n" + "Opens with: LHA/lha, 7-Zip (read), Unlha32.dll", - (F.Lzh, "lh2") => - "lh2: Adaptive Huffman variant. Rarely used — lh5 or lh7 are generally preferred.\n" - + "Opens with: LHA/lha (some builds), Unlha32.dll", - (F.Lzh, "lh3") => - "lh3: Adaptive Huffman with 8 KB window. Transitional method.\n" - + "Opens with: LHA/lha (some builds), Unlha32.dll", (F.Lzh, "lh4") => "lh4: Static Huffman with 4 KB window. Older format, good compatibility with vintage tools.\n" + "Opens with: 7-Zip, LHA/lha, PeaZip, Unlha32.dll", @@ -561,7 +555,6 @@ private void PopulateMethods() { F.Lzh => [ "lh5 (default, 8 KB)", "lh7 (64 KB)", "lh6 (32 KB)", "lh4 (4 KB)", "lh1 (adaptive Huffman 4 KB)", - "lh2 (adaptive Huffman)", "lh3 (adaptive Huffman 8 KB)", "lh0 (store)", "lzs (LZSS 2 KB)", "lz5 (LZSS 4 KB)", "pm0 (PMA store)", "pm1 (PMA PPMd order-2)", "pm2 (PMA PPMd order-3)", "pm2 (PPMd order-3)", "pm1 (PPMd order-2)", "pm0 (PMA store)"], diff --git a/Compression.Tests/Compression.Tests.csproj b/Compression.Tests/Compression.Tests.csproj index 41d6588b8..37d03e897 100644 --- a/Compression.Tests/Compression.Tests.csproj +++ b/Compression.Tests/Compression.Tests.csproj @@ -138,6 +138,10 @@ XarVectors.%(Filename)%(Extension) + + + LzhVectors.%(Filename)%(Extension) + diff --git a/Compression.Tests/Lzh/LhaLegacyMethodVectorTests.cs b/Compression.Tests/Lzh/LhaLegacyMethodVectorTests.cs new file mode 100644 index 000000000..fcec6694e --- /dev/null +++ b/Compression.Tests/Lzh/LhaLegacyMethodVectorTests.cs @@ -0,0 +1,89 @@ +using System.Reflection; +using System.Text; +using Compression.Core.Dictionary.Lzh; +using FileFormat.Lzh; + +namespace Compression.Tests.Lzh; + +/// +/// -lh2- and -lh3- against archives UNLHA32.DLL wrote (from lhasa's test suite; see +/// ReferenceVectors/README.md). Both archives hold the same file, so besides the CRC-16 UNLHA32 +/// recorded, the two independent decoders must agree with each other byte for byte. +/// +[TestFixture] +public class LhaLegacyMethodVectorTests { + private static byte[] Vector(string name) { + using var s = Assembly.GetExecutingAssembly().GetManifestResourceStream("LzhVectors." + name) + ?? throw new FileNotFoundException("Embedded LHA vector missing: " + name); + using var ms = new MemoryStream(); + s.CopyTo(ms); + return ms.ToArray(); + } + + private static (LhaEntry Entry, byte[] Data) ExtractOnly(string vector, string expectedMethod) { + using var reader = new LhaReader(new MemoryStream(Vector(vector))); + var entry = reader.Entries.Single(); + Assert.That(entry.Method, Is.EqualTo(expectedMethod)); + return (entry, reader.ExtractEntry(entry)); // ExtractEntry verifies the header's CRC-16 + } + + [Test, Category("HappyPath")] + [TestCase("lh2.lzh", LhaConstants.MethodLh2)] + [TestCase("lh3.lzh", LhaConstants.MethodLh3)] + public void RealArchive_DecodesToItsCrc(string vector, string method) { + var (entry, data) = ExtractOnly(vector, method); + Assert.That(entry.FileName, Is.EqualTo("LICENSE.MIT")); + Assert.That(data, Has.Length.EqualTo(2853)); + var text = Encoding.ASCII.GetString(data); + Assert.That(text, Does.Contain("Permission is hereby granted, free of charge")); + Assert.That(text, Does.Contain("THE SOFTWARE IS PROVIDED \"AS IS\"")); + } + + [Test, Category("HappyPath")] + public void Lh2AndLh3_OfTheSameFile_Agree() { + var (_, lh2) = ExtractOnly("lh2.lzh", LhaConstants.MethodLh2); + var (_, lh3) = ExtractOnly("lh3.lzh", LhaConstants.MethodLh3); + Assert.That(lh2, Is.EqualTo(lh3).AsCollection); + } + + [Test, Category("EdgeCase")] + public void EmptyOutput_ReadsNothing() { + Assert.That(Lh2Decoder.Decode([], 0), Is.Empty); + Assert.That(Lh3Decoder.Decode([], 0), Is.Empty); + } + + [Test, Category("Exception")] + public void NegativeSize_Throws() { + Assert.Throws(() => Lh2Decoder.Decode([], -1)); + Assert.Throws(() => Lh3Decoder.Decode([], -1)); + } + + [Test, Category("Exception")] + public void Lh3_IncompleteTree_Throws() { + // Block of 1 symbol; character tree: symbol 0 present with length 2 and every other symbol absent, + // which leaves half the code space unassigned. + var bits = new StringBuilder(); + bits.Append(Convert.ToString(1, 2).PadLeft(16, '0')); // block size + bits.Append("1" + "0001"); // symbol 0: present, length 1+1 = 2 + bits.Append(new string('0', 285)); // symbols 1..285 absent + var bytes = new byte[(bits.Length + 7) / 8 + 4]; + for (var i = 0; i < bits.Length; ++i) + if (bits[i] == '1') bytes[i >> 3] |= (byte)(0x80 >> (i & 7)); + Assert.Throws(() => Lh3Decoder.Decode(bytes, 1)); + } + + [Test, Category("Exception")] + public void TruncatedArchive_FailsTheCrcInsteadOfReturningGarbage() { + var archive = Vector("lh3.lzh"); + using var reader = new LhaReader(new MemoryStream(archive)); + var entry = reader.Entries.Single(); + // Zero the second half of the compressed body in place: the decoder reads zero bits there, and the + // result must be rejected by the CRC check rather than handed back. + var damaged = (byte[])archive.Clone(); + var bodyStart = (int)entry.DataOffset; + for (var i = bodyStart + (int)entry.CompressedSize / 2; i < bodyStart + entry.CompressedSize; ++i) + damaged[i] = 0; + using var damagedReader = new LhaReader(new MemoryStream(damaged)); + Assert.Catch(() => damagedReader.ExtractEntry(damagedReader.Entries.Single())); + } +} diff --git a/Compression.Tests/Lzh/LhaTests.cs b/Compression.Tests/Lzh/LhaTests.cs index b0557c531..71ca1d1c5 100644 --- a/Compression.Tests/Lzh/LhaTests.cs +++ b/Compression.Tests/Lzh/LhaTests.cs @@ -116,47 +116,13 @@ public void RoundTrip_SingleFile_Lh1() { Assert.That(extracted, Is.EqualTo(data)); } - [Category("HappyPath")] - [Category("RoundTrip")] - [Test] - public void RoundTrip_SingleFile_Lh2() { - var data = new byte[500]; - for (var i = 0; i < data.Length; ++i) - data[i] = (byte)(i % 13); - - var writer = new LhaWriter(LhaConstants.MethodLh2); - writer.AddFile("lh2.bin", data); - - var archive = writer.ToArray(); - - using var ms = new MemoryStream(archive); - var reader = new LhaReader(ms); - - Assert.That(reader.Entries, Has.Count.EqualTo(1)); - var extracted = reader.ExtractEntry(reader.Entries[0]); - Assert.That(extracted, Is.EqualTo(data)); - } - - [Category("HappyPath")] - [Category("RoundTrip")] - [Test] - public void RoundTrip_SingleFile_Lh3() { - var data = new byte[500]; - for (var i = 0; i < data.Length; ++i) - data[i] = (byte)(i % 7); - - var writer = new LhaWriter(LhaConstants.MethodLh3); - writer.AddFile("lh3.bin", data); - - var archive = writer.ToArray(); - - using var ms = new MemoryStream(archive); - var reader = new LhaReader(ms); - - Assert.That(reader.Entries, Has.Count.EqualTo(1)); - var extracted = reader.ExtractEntry(reader.Entries[0]); - Assert.That(extracted, Is.EqualTo(data)); - } + // -lh2- and -lh3- are read-only: the writer once emitted lh5 data under those IDs, which no real + // LHA reads. Reading them is pinned against real archives in LhaLegacyMethodVectorTests. + [Category("Exception")] + [TestCase(LhaConstants.MethodLh2)] + [TestCase(LhaConstants.MethodLh3)] + public void Writer_RefusesReadOnlyMethod(string method) => + Assert.Throws(() => _ = new LhaWriter(method)); [Category("HappyPath")] [Category("RoundTrip")] diff --git a/Compression.Tests/Lzh/ReferenceVectors/README.md b/Compression.Tests/Lzh/ReferenceVectors/README.md new file mode 100644 index 000000000..1e28019d2 --- /dev/null +++ b/Compression.Tests/Lzh/ReferenceVectors/README.md @@ -0,0 +1,38 @@ +# LHA reference archives + +Archives written by other LHA implementations, for the methods our writer cannot produce. + +| File | Method | Written by | Taken from | +|------|--------|------------|------------| +| `lh2.lzh` | `-lh2-`, level-2 header | UNLHA32.DLL 2.67a (`a archive.lzh filename`, switch `-jm5`) | lhasa `test/archives/unlha32/lh2.lzh` | +| `lh3.lzh` | `-lh3-`, level-2 header | UNLHA32.DLL 2.67a (switch `-jm6`) | lhasa `test/archives/unlha32/lh3.lzh` | + +Both come from the test suite of lhasa (, ISC licence, +Copyright (c) 2011-2025 Simon Howard), fetched from `master` on 2026-10-19: + +| File | SHA-256 | +|------|---------| +| `lh2.lzh` | `16e1cba85552ddc930442874dbfd42c0042547da482793e31ac2aaf819dc765e` | +| `lh3.lzh` | `095f5675a1224db59db9f3c3075b234c2b62ee8710e34968882fccae4659e63e` | + +Each holds one member, `LICENSE.MIT` (2,853 bytes, an MIT licence text). lhasa itself does not +decode either method, so the oracle is the member's CRC-16 recorded by UNLHA32 in the header: the +decoded bytes must match it, along with the size and the text's shape. + +ISC licence (lhasa): + +``` +Permission to use, copy, modify, and/or distribute this software +for any purpose with or without fee is hereby granted, provided +that the above copyright notice and this permission notice appear +in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL +WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE +AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR +CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, +NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +``` diff --git a/Compression.Tests/Lzh/ReferenceVectors/lh2.lzh b/Compression.Tests/Lzh/ReferenceVectors/lh2.lzh new file mode 100644 index 0000000000000000000000000000000000000000..a56bfea6a59fa791098305467e9ebaf55f21f0ec GIT binary patch literal 1377 zcmV-n1)lm)04;22GA$4V001Qm005iri(w!FSJ6!e07kY0000gE0Zd6lMNU&iE=@^P z8vsEiY9B>S*4F`*J1j*_*4F`*J1j*_*4F_B007XQ2mk;WEGhB`hVbtQ7L)Ip8|l^T z#53E@%?C3yJvQ1a;5gN#v!Qp}>Gj6AOIh9nxw%F74D4AtqOP6XjJsCFD7a%R`ts1H z3ZjInVWgUDa$zN_3B?}>l}zW##B`xlotg{dZQW@!&2e1h3>Qf@XQi>P+%_z0UWduHHlj~XvczlP&kw|#QSN7nfvI6J^Y{i4*%`(YUat_oP0iv^0WilIEmJZ zqWsX6gzmTU`_{j?XTO{IF@^nI8mQ}ZIH%VxD# zVZ1tGR%$8Y}MRe8b{^mUN;0TRZyhmr43y9Mqy>zPGs;K(R2dpSQ<%zC(FLNRybVpWP@MqyuR&PAoGjH zGm#73e*KYP`kKsn65@t?gFIEJH7~aelG4xSR7Aao<6@f7y8}p?KKSY^>e@>`LJp0x zWjX?7;OasVF{+;8XkPTu7yL{)1p5o;HW>9glvk{|QEX{nNKyV5KJ#E(5z$9>yQ7(h-=*Wb4P?R~8ifVLlP`dEA8 zma5Oc`4#tKSQOA*dxsk0))5`3><3=H!-vBG(SS{K7i-)+j{*7e|lv=B`>OeVB3wWc4mIc}p^u>vw0KeK6Y%d_BHs zZoYcUp;8s9i|0#RGjO&Ud#gKHH(ZsE)qB|(?SMyic*w)}0pp>(27COUYDgFQ2Zlu} z!A>S>4$*0XsjZ)S4NrRkU>o~kvM;Jl64ZuEl_^$7@j-*e= zOtS-aWXBIz;ie~EGKHO|2e2=au3_)#p6}D$cHE`@)*xgrS>h|79GG>n&vIX&p6~n0 zO>4W<1smUc^-YV7+N#9MGaS$Ux9n>FklS~U@(Zc*lB)_4I<7`5*C+EXVfbjTbnW3p jy7F$Z=3d%Ow;w%$VFlhTNT$XfE?$ zX|2g6)mafUnOie#%UsGW9ActQZ%=Q|@6Gr5JkJjeK*NGzP|IHcKo<-EWlLEXwRd@Q zk+J}sDFpzE0LT?#?}+knG)5w@8o*iIP%TH~$a_$cmC=7H0{}@Ly8%Gj4=?}#IT~09 zd2}yGn{9c?Yq-&bh`Gyu%DnPBe}i-LhoM9d%f@;a!yo-d@+#a1%^P3TNw14xsWn9&3XEki5Y3ct1XMR zEW#r*bG-u#i;Qpgvv+q&6b?~L#swBd^{roh5ETMFA7p%}xze5WFhMU?FaG1cDd}2` zl;m)$H_xBKLQ)$0niZ`Kabx9>(vZe`S9g`DQmAE};+;hX%@1FpeO@ExGyz7_F`rN3 zf>$zVC$x|p;ldPsDqI|%bg5Zwh^uB9xJ@hTbzn1@tz#b}hN1Kt4NA;RDoTdR6PmTK z|9;FHV|k`X6F@9Tea7$@l}6{a2LjD1aXC5onvT{Le>F$kD`rc;DxuBp`zhxTtJ)P3 zHopdH+AX4b$l+~|E6v5Wd)g4pP-kv=iy!zP#@)IaUf|AwUq2AL*4_OxA8QCNExe;g z6=h)|x$RG+Vy6yT+OblrR`)iXa6AT6huPs$PAVpvKE!Sb&PCOd2NDLH-HJ=5@1sLA zB`&DC8{MXMTgt`Rmh2>H2j-spN zFFRch;Ox8RDeJ!bfmizNLu?BZyM-h^UAWN^WGUK##+soINIpy&1B<1Ym2PB3w19Z z@t%RymC~0d3Z5GCZ|zwe3m!sjC>&X5rJb3vI*o~>SMv#dKH9Hye#L0^+vI&9bI zf%w+<6B2pz-?k!y82rIe?)_qStxRu@dR%W<>7>-KZ&~MfG%V2QVgD?1V#E6LwQ@$_ zQsIRq5BTByh#Za0unn;942yqd1CzMZ(ixmR-)`Xm*i3%G`8D$%4>2q4pN%JwVn(}d zbEPN>3&cVruwwj2T8uK0*F>vnRkeK`{k=ieb1g}7{GzBn11D*ED~XEX`P`Zo2!w{6 zXM)uj&1ALU8DDx8t((%TM8tEr*_xE1=mpvXUX=pIHi#@2Uczr2&N5mj-8!_+FTYQa6RAhCvE5=d?mK8 zPY$fsG?`jS(4~4pP)p?SUOmuZnK=DC^TL-E%G!){sKj9~8FlH~R+9GN zS^h-g_OGVS3hr&-dkW?agQ&W7RQ~6DCqKlDYlnOijFhHQaLQ=E#@jTBFiNDI>MncJ zKis~f4~D&7eSy(tgd~H`riPyB7vpn+QbkKHHe%qH^ng#oStB$JvMuAo@+!pSp z1~1<%5|oPESZ|1yoAn(%BZeZM590G;hm(dirPgzIoVAuKmoZFUvHZH}<8>It048>6 KBX!z9fPVpT52yzK literal 0 HcmV?d00001 diff --git a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Lzh/LhaCompatibility.cs b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Lzh/LhaCompatibility.cs index 540bde0f6..b168fffb5 100644 --- a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Lzh/LhaCompatibility.cs +++ b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Lzh/LhaCompatibility.cs @@ -66,11 +66,12 @@ public static void EnsureSupported(LhaArchiverGeneration generation, string meth throw new NotSupportedException($"LHA method '{method}' is not available in archiver generation {generation}."); } + // Methods the writer can encode. -lh2- and -lh3- are read-only: their encoders (adaptive two-tree + // Huffman and the per-block static trees with the ready-made position table) are not implemented, + // and writing lh5 data under their IDs, as this once did, produces archives no real LHA reads. internal static bool IsImplemented(string method) => method is LhaConstants.MethodLh0 or LhaConstants.MethodLh1 or - LhaConstants.MethodLh2 or - LhaConstants.MethodLh3 or LhaConstants.MethodLh4 or LhaConstants.MethodLh5 or LhaConstants.MethodLh6 or diff --git a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Lzh/LhaReader.cs b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Lzh/LhaReader.cs index a4b76897c..d3c01a759 100644 --- a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Lzh/LhaReader.cs +++ b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Lzh/LhaReader.cs @@ -46,8 +46,10 @@ public byte[] ExtractEntry(LhaEntry entry) { data = DecompressLh1(compressedData, (int)entry.OriginalSize); break; case LhaConstants.MethodLh2: + data = Lh2Decoder.Decode(compressedData, (int)entry.OriginalSize); + break; case LhaConstants.MethodLh3: - data = DecompressLzh(compressedData, (int)entry.OriginalSize, LzhConstants.Lh5PositionBits); + data = Lh3Decoder.Decode(compressedData, (int)entry.OriginalSize); break; case LhaConstants.MethodLh4: data = DecompressLzh(compressedData, (int)entry.OriginalSize, LzhConstants.Lh4PositionBits); diff --git a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Lzh/LhaWriter.cs b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Lzh/LhaWriter.cs index 4e7972a8c..ceb120008 100644 --- a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Lzh/LhaWriter.cs +++ b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Lzh/LhaWriter.cs @@ -41,7 +41,6 @@ public LhaWriter( LhaConstants.MethodLh1 => -3, LhaConstants.MethodPm1 => -5, LhaConstants.MethodPm2 => -6, - LhaConstants.MethodLh2 or LhaConstants.MethodLh3 => LzhConstants.Lh5PositionBits, LhaConstants.MethodLh4 => LzhConstants.Lh4PositionBits, LhaConstants.MethodLh5 => LzhConstants.Lh5PositionBits, LhaConstants.MethodLh6 => LzhConstants.Lh6PositionBits, diff --git a/Hawkynt.FileFormats.Archives/README.md b/Hawkynt.FileFormats.Archives/README.md index bc43ea12b..29ca6e9e2 100644 --- a/Hawkynt.FileFormats.Archives/README.md +++ b/Hawkynt.FileFormats.Archives/README.md @@ -128,7 +128,7 @@ Every State, Test, Maintenance, Compress/Decompress and Demux/Mux/Remux cell is | LhF (LhFloppy) | `LhF` | `.lhf` | R/W | ✅ | defrag · wipe | Whole-disk archiver: entries are track_NNN.raw | [Aminet](https://aminet.net) | | [lrzip](https://en.wikipedia.org/wiki/Rzip#lrzip) | `Lrzip` | `.lrz` | WORM | ✅ | defrag | LZMA-wrapped subtype only; other lrzip subtypes are rejected; single data member | [GitHub](https://github.com/ckolivas/lrzip) | | Lynx (Commodore) | `Lynx` | `.lnx` | R/W | ✅ | defrag · shrink · wipe | Stored entries only | [Archive Team](http://fileformats.archiveteam.org/wiki/Lynx_(Commodore_64)) | -| [LHA / LZH](https://en.wikipedia.org/wiki/LHA_(file_format)) | `Lzh` | `.lzh` `.lha` | R/W | ✅ | defrag · wipe | | [GitHub](https://github.com/jca02266/lha) | +| [LHA / LZH](https://en.wikipedia.org/wiki/LHA_(file_format)) | `Lzh` | `.lzh` `.lha` | R/W | ✅ | defrag · wipe | -lh2- and -lh3- are read-only | [GitHub](https://github.com/jca02266/lha) | | [LZX (Amiga)](https://en.wikipedia.org/wiki/LZX) | `LzxAmiga` | `.lzx` | R/W | ✅ | defrag · wipe | | [Aminet](https://aminet.net) | | [mtree](https://man.freebsd.org/cgi/man.cgi?query=mtree&sektion=5) | `Mtree` | `.mtree` | WORM | ✅ | — | Filesystem metadata manifest; does not embed file bodies, so CWB does not dereference `contents=` host paths | [FreeBSD mtree(5)](https://man.freebsd.org/cgi/man.cgi?query=mtree&sektion=5) | | [NuFX / ShrinkIt](https://en.wikipedia.org/wiki/ShrinkIt) | `NuFx` | `.shk` `.sdk` `.bxy` | R/W | ✅ | defrag · shrink · wipe | | [nulib.com](https://nulib.com/library/FTN.e08002.htm) | diff --git a/docs/catalogue/GAPS.md b/docs/catalogue/GAPS.md index 424dda614..828948241 100644 --- a/docs/catalogue/GAPS.md +++ b/docs/catalogue/GAPS.md @@ -162,7 +162,7 @@ running real-world samples. The file named at the end of each row is where the l | 7z | `SevenZip` | 7 | Deflate64, ARM64, RISC-V, Zstd/Brotli/LZ4 coders and external header references unsupported (SevenZipReader.cs) | | ISO 9660 (Joliet / Rock Ridge / El Torito) | `Iso` | 7 | Rock Ridge limited to NM names (no SL/PX/TF/CE, no zisofs); interleaved files throw (IsoReader.cs) | | ZIP | `Zip` | 7 | methods xz (95), JPEG (96), WavPack (97) and PKWARE strong encryption unsupported (ZipReader.cs) | -| LHA / LZH | `Lzh` | 6 | lh2/lh3 are decoded with the lh5 decoder (approximation) (LhaReader.cs) | +| LHA / LZH | `Lzh` | 6 | lh2/lh3 decoded but read-only; lh1, lzs, lz5, pm1, pm2, level-1 extended headers and level-3 headers fail on lhasa's test archives (refused by the CRC check), lhx and lk7 unsupported (LhaReader.cs) | | StuffIt (classic + 5) | `StuffIt` | 4 | store and RLE only; LZW/Huffman/LZAH/Arsenic/DES methods throw (StuffItReader.cs) | | StuffIt X | `StuffItX` | 4 | stored and deflate only; Brimstone/Cyanide/Darkhorse/Iron throw (StuffItXReader.cs) | | zoo | `Zoo` | 4 | LZH method (2) unsupported (ZooReader.cs) | diff --git a/docs/catalogue/reference-formats.csv b/docs/catalogue/reference-formats.csv index f6b9d2f79..d65e39d0f 100644 --- a/docs/catalogue/reference-formats.csv +++ b/docs/catalogue/reference-formats.csv @@ -34,7 +34,7 @@ Windows Update package (MSU),msu,installer,,,,,R,,,1,,,,,,MISSING,, Compiled HTML Help (CHM),chm;chi;chq;chw;hxs,document,R,,,,R,fmt/634,,3,https://www.nongnu.org/chmspec/latest/,Chm,Y,Y,Y,PARTIAL,,content sections above 1 unsupported (ChmReader.cs) Windows Imaging (WIM/SWM/ESD),wim;swm;esd;ppkg,disk-image,RW,,,,R,,Y,3,https://learn.microsoft.com/windows-hardware/manufacture/desktop/wim-vs-ffu-image-file-formats,Wim;Swm;Esd,Y,Y,,OK,, ARJ,arj,archive,R,,R,D,R,fmt/610,RW,6,https://en.wikipedia.org/wiki/ARJ,Arj,Y,Y,Y,OK,,methods 0-4 decoded; method 4 verified on archives the open-source ARJ port wrote -LHA / LZH,lzh;lha;lzs;exe;sfx,archive,R,R,R,,R,fmt/1558;fmt/626,RW,6,https://en.wikipedia.org/wiki/LHA_(file_format),Lzh,Y,Y,Y,PARTIAL,,lh2/lh3 are decoded with the lh5 decoder (approximation) (LhaReader.cs) +LHA / LZH,lzh;lha;lzs;exe;sfx,archive,R,R,R,,R,fmt/1558;fmt/626,RW,6,https://en.wikipedia.org/wiki/LHA_(file_format),Lzh,Y,Y,Y,PARTIAL,,"lh2/lh3 decoded but read-only; lh1, lzs, lz5, pm1, pm2, level-1 extended headers and level-3 headers fail on lhasa's test archives (refused by the CRC check), lhx and lk7 unsupported (LhaReader.cs)" ACE,ace,archive,,,R,,R,fmt/2075;fmt/2076,RW,4,https://en.wikipedia.org/wiki/ACE_(compressed_file_format),Ace,Y,Y,Y,OK,, ARC (SEA),arc;ark,archive,,,R,,R,,RW,3,https://en.wikipedia.org/wiki/ARC_(file_format),Arc,Y,Y,Y,OK,, PAK (NoGate ARC extension),pak,archive,,,R,,,,Y,2,,,,,,MISSING,, From 6493e5c70be6642dfb27d9e52e892bbe254adceb Mon Sep 17 00:00:00 2001 From: Hawkynt Date: Mon, 26 Oct 2026 13:02:19 +0100 Subject: [PATCH 07/34] * regenerate references --- Compression.Core/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Compression.Core/README.md b/Compression.Core/README.md index 12d7b1214..483e87991 100644 --- a/Compression.Core/README.md +++ b/Compression.Core/README.md @@ -205,7 +205,7 @@ Use the concrete version you intend to consume; this document does not predict a -Every public and protected member of all 726 types, generated from the built assembly and its XML documentation, is in [REFERENCE.md](https://github.com/Hawkynt/CompressionWorkbench/blob/main/Compression.Core/REFERENCE.md). +Every public and protected member of all 728 types, generated from the built assembly and its XML documentation, is in [REFERENCE.md](https://github.com/Hawkynt/CompressionWorkbench/blob/main/Compression.Core/REFERENCE.md). From e199a5f000b367778592ccd8ecab5cca13e9c1a1 Mon Sep 17 00:00:00 2001 From: Hawkynt Date: Mon, 26 Oct 2026 13:02:23 +0100 Subject: [PATCH 08/34] # LHA: header levels 1/3, lh1, lzs, lz5, pm1, pm2 and lhx read as their archivers wrote them # LHA: level-1 extended headers, level-3 headers and directory names read Symptom: on lhasa's test suite, members behind level-1 headers with extended headers came out a few bytes off (CRC mismatch or end of stream), level-3 archives were refused, directory names were dropped, and the modifier and layout map walked level-2 chains with the level-0 size byte. Root cause: a level-1 member's compressed size includes its extended headers, which the reader did not subtract; level 3 (32-bit sizes) was not implemented; extended header 0x02 (directory, 0xFF-separated) was ignored; each of the three walkers had its own partial parser. Change: one LhaHeaderParser for levels 0-3 (header checksum, the level-1 size correction, the OS-9/68k level-2 quirk, 0x01 file and 0x02 directory headers) used by the reader, the modifier and the layout map. A truncated last member is listed and fails on extraction instead of failing the whole archive. Evidence: archives from lhasa's test suite (LHA for Amiga 1.22, LHA OS/2 2.08, ...) committed with provenance; every member passes the CRC-16 its archiver recorded. Sourcing: rung 1, converted from lhasa's lha_file_header.c and ext_header.c (ISC), notice in THIRD-PARTY-NOTICE.lhasa.txt. # LHA: -lh1-, -lzs-, -lz5-, -pm1-, -pm2- decoded as their archivers wrote them Symptom: on lhasa's test suite every -lh1-, -lzs-, -lz5-, -pm1- and -pm2- member failed (CRC mismatch, end of stream, divide by zero); our writer's output for these methods was not readable by lhasa; -lhx- was refused. Root cause: the decoders implemented other schemes: -lh1- rebuilt a static tree every 4 KiB instead of LZHUF's adaptive tree; -lzs- was read byte-aligned with relative offsets instead of an MSB-first bit stream of absolute ring positions; -lz5- lacked LArc's predefined initial ring and start position; -pm1-/-pm2- were a PPMd model with no relation to PMarc. The encoders mirrored the same mistakes. Change: decoders converted from lhasa for -lh1- (shared adaptive tree), -lzs-, -lz5-, -pm1- and -pm2- (move-to-front history, PMarc's static and rebuilt trees); new -lh1-, -lzs- and -lz5- encoders built on the same tree and ring rules; writing -pm1-/-pm2- is refused (no encoder) and NativeUI stops offering them; -lhx- reads through the lh5 decoder with a 1 MiB window. Evidence: LHarc 1.13, LArc 3.33, PMarc 1.24/2 and UNLHA32 archives from lhasa's suite pass their CRC-16s, and five methods decode the same GPL-2 text identically. LhaOracleTests: every method x header level (0-2) our writer produces extracts byte-identically in lhasa (lha t / lha x). Remaining: LHARK's lk7 variant of -lh7-. Sourcing: rung 1, decoders converted from lhasa (ISC), tables copied exactly, notice in THIRD-PARTY-NOTICE.lhasa.txt; encoders are our own. * regenerate package references --- Compression.Core/Dictionary/Lzh/Lh1Decoder.cs | 253 +++--------------- Compression.Core/Dictionary/Lzh/Lh1Encoder.cs | 134 +++------- Compression.Core/Dictionary/Lzh/Lh1Tree.cs | 235 ++++++++++++++++ .../Dictionary/Lzh/LhaBitInput.cs | 39 +++ .../Dictionary/Lzh/LhaMatchFinder.cs | 66 +++++ Compression.Core/Dictionary/Lzh/Lz5Decoder.cs | 110 ++++---- Compression.Core/Dictionary/Lzh/Lz5Encoder.cs | 103 +++---- Compression.Core/Dictionary/Lzh/LzhDecoder.cs | 3 +- Compression.Core/Dictionary/Lzh/LzsDecoder.cs | 90 +++---- Compression.Core/Dictionary/Lzh/LzsEncoder.cs | 105 +++----- Compression.Core/Dictionary/Lzh/Pm1Decoder.cs | 171 ++++++++++++ Compression.Core/Dictionary/Lzh/Pm2Decoder.cs | 219 +++++++++++++++ Compression.Core/Dictionary/Lzh/PmaDecoder.cs | 32 --- Compression.Core/Dictionary/Lzh/PmaEncoder.cs | 31 --- Compression.Core/Dictionary/Lzh/PmaHistory.cs | 60 +++++ .../Lzh/THIRD-PARTY-NOTICE.lhasa.txt | 36 +++ Compression.Core/REFERENCE.md | 48 ++-- .../Views/CreateOptionsViewModel.cs | 9 +- Compression.Tests/Compression.Tests.csproj | 3 +- Compression.Tests/Lzh/LhaHeaderVectorTests.cs | 55 ++++ Compression.Tests/Lzh/LhaOracleTests.cs | 86 ++++++ Compression.Tests/Lzh/LhaSuiteVectorTests.cs | 61 +++++ Compression.Tests/Lzh/LhaTests.cs | 39 ++- Compression.Tests/Lzh/PmaTests.cs | 138 ---------- .../Lzh/ReferenceVectors/README.md | 25 +- .../Lzh/ReferenceVectors/generated_lzs.lzs | Bin 0 -> 12696 bytes .../Lzh/ReferenceVectors/larc333_initial.lzs | Bin 0 -> 675 bytes .../Lzh/ReferenceVectors/larc333_lz5.lzs | Bin 0 -> 8510 bytes .../ReferenceVectors/lha_amiga_122_level1.lzh | Bin 0 -> 76 bytes .../lha_amiga_122_lh0_dirs_bug.lzh | Bin 0 -> 308 bytes .../ReferenceVectors/lha_os2_208_h3_lh5.lzh | Bin 0 -> 7075 bytes .../lha_os2_208_h3_subdir.lzh | Bin 0 -> 271 bytes .../Lzh/ReferenceVectors/lharc113_lh1.lzh | Bin 0 -> 7548 bytes .../Lzh/ReferenceVectors/pmarc124_pm1.pma | Bin 0 -> 9984 bytes .../Lzh/ReferenceVectors/pmarc2_pm2.pma | Bin 0 -> 7168 bytes .../Lzh/ReferenceVectors/unlha32_h2_lhx.lzh | Bin 0 -> 6903 bytes .../FileFormat.Lzh/LhaCompatibility.cs | 10 +- .../FileFormat.Lzh/LhaHeaderParser.cs | 226 ++++++++++++++++ .../FileFormats/FileFormat.Lzh/LhaModifier.cs | 68 +---- .../FileFormats/FileFormat.Lzh/LhaReader.cs | 170 ++---------- .../FileFormats/FileFormat.Lzh/LhaWriter.cs | 8 +- .../FileFormat.Lzh/LzhConstants.cs | 3 + .../FileFormat.Lzh/LzhFormatDescriptor.cs | 2 +- .../FileFormat.Lzh/LzhLayoutMap.cs | 89 +----- .../THIRD-PARTY-NOTICE.lhasa.txt | 27 ++ Hawkynt.FileFormats.Archives/README.md | 2 +- Hawkynt.FileFormats.Archives/REFERENCE.md | 1 + docs/catalogue/GAPS.md | 2 +- docs/catalogue/reference-formats.csv | 2 +- 49 files changed, 1663 insertions(+), 1098 deletions(-) create mode 100644 Compression.Core/Dictionary/Lzh/Lh1Tree.cs create mode 100644 Compression.Core/Dictionary/Lzh/LhaBitInput.cs create mode 100644 Compression.Core/Dictionary/Lzh/LhaMatchFinder.cs create mode 100644 Compression.Core/Dictionary/Lzh/Pm1Decoder.cs create mode 100644 Compression.Core/Dictionary/Lzh/Pm2Decoder.cs delete mode 100644 Compression.Core/Dictionary/Lzh/PmaDecoder.cs delete mode 100644 Compression.Core/Dictionary/Lzh/PmaEncoder.cs create mode 100644 Compression.Core/Dictionary/Lzh/PmaHistory.cs create mode 100644 Compression.Core/Dictionary/Lzh/THIRD-PARTY-NOTICE.lhasa.txt create mode 100644 Compression.Tests/Lzh/LhaHeaderVectorTests.cs create mode 100644 Compression.Tests/Lzh/LhaOracleTests.cs create mode 100644 Compression.Tests/Lzh/LhaSuiteVectorTests.cs delete mode 100644 Compression.Tests/Lzh/PmaTests.cs create mode 100644 Compression.Tests/Lzh/ReferenceVectors/generated_lzs.lzs create mode 100644 Compression.Tests/Lzh/ReferenceVectors/larc333_initial.lzs create mode 100644 Compression.Tests/Lzh/ReferenceVectors/larc333_lz5.lzs create mode 100644 Compression.Tests/Lzh/ReferenceVectors/lha_amiga_122_level1.lzh create mode 100644 Compression.Tests/Lzh/ReferenceVectors/lha_amiga_122_lh0_dirs_bug.lzh create mode 100644 Compression.Tests/Lzh/ReferenceVectors/lha_os2_208_h3_lh5.lzh create mode 100644 Compression.Tests/Lzh/ReferenceVectors/lha_os2_208_h3_subdir.lzh create mode 100644 Compression.Tests/Lzh/ReferenceVectors/lharc113_lh1.lzh create mode 100644 Compression.Tests/Lzh/ReferenceVectors/pmarc124_pm1.pma create mode 100644 Compression.Tests/Lzh/ReferenceVectors/pmarc2_pm2.pma create mode 100644 Compression.Tests/Lzh/ReferenceVectors/unlha32_h2_lhx.lzh create mode 100644 Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Lzh/LhaHeaderParser.cs create mode 100644 Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Lzh/THIRD-PARTY-NOTICE.lhasa.txt diff --git a/Compression.Core/Dictionary/Lzh/Lh1Decoder.cs b/Compression.Core/Dictionary/Lzh/Lh1Decoder.cs index 45a0187ef..d7c5dbff5 100644 --- a/Compression.Core/Dictionary/Lzh/Lh1Decoder.cs +++ b/Compression.Core/Dictionary/Lzh/Lh1Decoder.cs @@ -1,234 +1,65 @@ -using Compression.Core.BitIO; - namespace Compression.Core.Dictionary.Lzh; /// -/// Decodes LHA -lh1- compressed data. -/// Uses a 4KB sliding window with dynamic (adaptive) Huffman coding. +/// Decodes LHarc -lh1-: LZSS over a 4 KiB window with an adaptive Huffman tree for literals and +/// copy lengths, and a fixed code for the high six bits of the copy distance. /// /// -/// The -lh1- format encodes 314 symbols: 256 literals (0-255) and -/// 58 length-position codes (256-313). The Huffman tree is rebuilt -/// periodically every symbols based on -/// accumulated frequencies. Positions use a fixed 6-bit encoding -/// with extra bits. +/// See for the tree and the position code. The window starts filled with spaces. +/// A copy of length n (codes 256-313 → 3-60) starts offset + 1 bytes back. +/// Sourcing: converted from lhasa's lh1_decoder.c (ISC, Simon Howard); see +/// THIRD-PARTY-NOTICE.lhasa.txt beside this file. Checked against the -lh1- archives of lhasa's +/// test suite. /// public sealed class Lh1Decoder { - private const int NChar = 256; - private const int WindowSize = 4096; - private const int WindowMask = WindowSize - 1; - private const int Threshold = 2; - private const int MaxMatch = 60; - private const int NumCodes = NChar + MaxMatch - Threshold + 1; // 314 - private const int BlockSize = 4096; - private const int PositionBits = 6; - private const int PositionSlots = 64; - - private readonly BitBuffer _bits; + internal const int WindowSize = 4096; + private readonly byte[] _data; - /// - /// Initializes a new . - /// - /// The stream containing -lh1- compressed data. + /// Initializes a new over the rest of . public Lh1Decoder(Stream input) { - this._bits = new(input); + ArgumentNullException.ThrowIfNull(input); + using var ms = new MemoryStream(); + input.CopyTo(ms); + this._data = ms.ToArray(); } - /// - /// Decodes the compressed data. - /// - /// The expected uncompressed size. - /// The decompressed data. + /// Decodes bytes. public byte[] Decode(int originalSize) { + ArgumentOutOfRangeException.ThrowIfNegative(originalSize); var output = new byte[originalSize]; - var window = new byte[WindowSize]; - // LH1 initializes the window with spaces (0x20) - Array.Fill(window, (byte)0x20); - var windowPos = 0; + var tree = new Lh1Tree(); + var bits = new LhaBitInput(this._data); + var ring = new byte[WindowSize]; + Array.Fill(ring, (byte)' '); + var ringPos = 0; var outPos = 0; - // Frequency table for adaptive Huffman - var freq = new int[NumCodes]; - Array.Fill(freq, 1); - var symbolCount = 0; - - // Build initial Huffman tree - (var codeTable, var tableBits) = BuildDecodeTable(freq); - while (outPos < originalSize) { - // Rebuild tree periodically - if (symbolCount >= BlockSize) { - // Halve frequencies to adapt (prevents overflow, forgets old data) - for (var i = 0; i < NumCodes; ++i) - freq[i] = (freq[i] + 1) >> 1; - (codeTable, tableBits) = BuildDecodeTable(freq); - symbolCount = 0; + if (bits.Exhausted) + throw new InvalidDataException("-lh1- stream ends before the member is complete."); + var node = 0; + while (!tree.IsLeaf(node)) + node = tree.Child(node) - bits.ReadBit(); + var code = tree.Child(node); + tree.Increment(code); + + if (code < 0x100) { + output[outPos++] = ring[ringPos] = (byte)code; + ringPos = (ringPos + 1) % WindowSize; + continue; } - var code = DecodeFromTable(codeTable, tableBits); - ++freq[code]; - ++symbolCount; - - if (code < NChar) { - var b = (byte)code; - output[outPos++] = b; - window[windowPos] = b; - windowPos = (windowPos + 1) & WindowMask; - } else { - var length = code - NChar + Threshold; - var posHigh = (int)this._bits.ReadBits(PositionBits); - var posLow = (int)this._bits.ReadBits(6); - var position = (posHigh << 6) | posLow; - - var srcPos = (windowPos - position - 1 + WindowSize) & WindowMask; - for (var j = 0; j < length && outPos < originalSize; ++j) { - var b = window[srcPos]; - output[outPos++] = b; - window[windowPos] = b; - windowPos = (windowPos + 1) & WindowMask; - srcPos = (srcPos + 1) & WindowMask; - } + int high = tree.OffsetLookup[bits.PeekBits(8)]; + bits.Skip(tree.OffsetLengths[high]); + var offset = (high << 6) | bits.ReadBits(6); + var count = code - 0x100 + Lh1Tree.CopyThreshold; + var start = ringPos - offset + WindowSize - 1; + for (var i = 0; i < count && outPos < originalSize; ++i) { + var b = ring[(start + i) % WindowSize]; + output[outPos++] = ring[ringPos] = b; + ringPos = (ringPos + 1) % WindowSize; } } - return output; } - - private int DecodeFromTable(int[] table, int maxBits) { - this._bits.EnsureBits(maxBits); - var peekBits = this._bits.PeekBits(maxBits); - - var entry = table[(int)peekBits]; - if (entry < 0) - throw new InvalidDataException("Failed to decode Huffman symbol in -lh1- data."); - - var symbol = entry & 0xFFFF; - var codeLen = entry >> 16; - this._bits.DropBits(codeLen); - return symbol; - } - - private static (int[] Table, int TableBits) BuildDecodeTable(int[] freq) { - // Build canonical Huffman codes from frequencies - var n = freq.Length; - var codeLengths = BuildCodeLengths(freq, n, 16); - - var maxLen = 0; - for (var i = 0; i < n; ++i) - if (codeLengths[i] > maxLen) - maxLen = codeLengths[i]; - - if (maxLen == 0) { - // All zero - shouldn't happen with freq>=1 init - maxLen = 1; - codeLengths[0] = 1; - } - - var tableBits = Math.Min(maxLen, 12); - var tableSize = 1 << tableBits; - var table = new int[tableSize]; - table.AsSpan().Fill(-1); - - var blCount = new int[maxLen + 1]; - foreach (var v in codeLengths) - if (v > 0 && v <= maxLen) - ++blCount[v]; - - var nextCode = new int[maxLen + 1]; - var code = 0; - for (var bits = 1; bits <= maxLen; ++bits) { - code = (code + blCount[bits - 1]) << 1; - nextCode[bits] = code; - } - - for (var sym = 0; sym < n; ++sym) { - var len = codeLengths[sym]; - if (len == 0 || len > tableBits) - continue; - - var symCode = nextCode[len]++; - var fillBits = tableBits - len; - var baseIdx = symCode << fillBits; - var packedValue = sym | (len << 16); - for (var fill = 0; fill < (1 << fillBits); ++fill) - table[baseIdx + fill] = packedValue; - } - - return (table, tableBits); - } - - private static int[] BuildCodeLengths(int[] freq, int n, int maxBits) { - // Package-merge algorithm simplified: build from frequencies - // Use a simple heap-based Huffman tree construction - var codeLengths = new int[n]; - - // Count non-zero - var activeCount = 0; - for (var i = 0; i < n; ++i) - if (freq[i] > 0) - ++activeCount; - - if (activeCount <= 1) { - // Find the one symbol (or pick 0) - for (var i = 0; i < n; ++i) - if (freq[i] > 0) { - codeLengths[i] = 1; - break; - } - return codeLengths; - } - - // Build Huffman tree using priority queue - // Node: (freq, depth, symbol or -1 for internal, left, right) - var pq = new SortedList<(long Freq, int Order), int>(); - var order = 0; - var nodeCount = 0; - var capacity = 2 * n; - var nodeLeft = new int[capacity]; - var nodeRight = new int[capacity]; - var nodeSymbol = new int[capacity]; // -1 for internal - - for (var i = 0; i < n; ++i) { - if (freq[i] > 0) { - var idx = nodeCount++; - nodeLeft[idx] = -1; - nodeRight[idx] = -1; - nodeSymbol[idx] = i; - pq.Add((freq[i], order++), idx); - } - } - - while (pq.Count > 1) { - var key1 = pq.Keys[0]; - var n1 = pq[key1]; - pq.RemoveAt(0); - - var key2 = pq.Keys[0]; - var n2 = pq[key2]; - pq.RemoveAt(0); - - var parent = nodeCount++; - nodeLeft[parent] = n1; - nodeRight[parent] = n2; - nodeSymbol[parent] = -1; - pq.Add((key1.Freq + key2.Freq, order++), parent); - } - - // Assign depths - var root = pq.Values[0]; - AssignDepths(root, 0, nodeLeft, nodeRight, nodeSymbol, codeLengths, maxBits); - - return codeLengths; - } - - private static void AssignDepths(int node, int depth, - int[] left, int[] right, int[] symbol, int[] codeLengths, int maxBits) { - if (symbol[node] >= 0) { - codeLengths[symbol[node]] = Math.Min(depth, maxBits); - return; - } - - AssignDepths(left[node], depth + 1, left, right, symbol, codeLengths, maxBits); - AssignDepths(right[node], depth + 1, left, right, symbol, codeLengths, maxBits); - } } diff --git a/Compression.Core/Dictionary/Lzh/Lh1Encoder.cs b/Compression.Core/Dictionary/Lzh/Lh1Encoder.cs index ebbe7ebc2..f263b7f03 100644 --- a/Compression.Core/Dictionary/Lzh/Lh1Encoder.cs +++ b/Compression.Core/Dictionary/Lzh/Lh1Encoder.cs @@ -3,111 +3,55 @@ namespace Compression.Core.Dictionary.Lzh; /// -/// Encodes data using the LHA -lh1- adaptive Huffman method. +/// Encodes LHarc -lh1- (see ): greedy LZSS matches over a 4 KiB window, +/// coded through the same adaptive the decoder evolves. /// /// -/// 4KB sliding window (initialized to 0x20), 314 symbols (256 literals + -/// 58 match-length codes), adaptive Huffman rebuilt every 4096 symbols. -/// Positions encoded as 6+6 = 12 bits. +/// A code is written as the path from the root to its leaf. Matches reach back only into bytes already +/// emitted. One zero byte is appended, because LHA decoders look ahead eight bits for each position +/// code. Verified by lhasa extracting what this writes. /// public sealed class Lh1Encoder { - private const int NChar = 256; - private const int WindowSize = 4096; - private const int WindowMask = WindowSize - 1; - private const int Threshold = 2; - private const int MaxMatch = 60; - private const int NumCodes = NChar + MaxMatch - Threshold + 1; // 314 - private const int BlockSize = 4096; - private const int PositionBits = 6; + private const int MaxLength = Lh1Tree.NumCodes - 0x100 + Lh1Tree.CopyThreshold - 1; // 60 - /// - /// Compresses data using the -lh1- algorithm. - /// - /// The input data to compress. - /// The compressed data. + /// Encodes . public static byte[] Encode(ReadOnlySpan data) { - if (data.Length == 0) - return []; - - using var output = new MemoryStream(); - var bits = new BitWriter(output); - - var window = new byte[WindowSize]; - Array.Fill(window, (byte)0x20); - var windowPos = 0; - - var freq = new int[NumCodes]; - Array.Fill(freq, 1); - var symbolCount = 0; - - var (codeLengths, codes) = BuildHuffmanTable(freq); - - var i = 0; - while (i < data.Length) { - // Rebuild tree periodically - if (symbolCount >= BlockSize) { - for (var s = 0; s < NumCodes; ++s) - freq[s] = (freq[s] + 1) >> 1; - (codeLengths, codes) = BuildHuffmanTable(freq); - symbolCount = 0; - } - - // Find best match in window - var bestLen = 0; - var bestPos = 0; - - for (var j = 1; j < WindowSize && j <= i; ++j) { - var srcIdx = (windowPos - j + WindowSize) & WindowMask; - var len = 0; - while (len < MaxMatch && i + len < data.Length) { - var wIdx = (srcIdx + len) & WindowMask; - if (window[wIdx] != data[i + len]) - break; - ++len; - } - if (len > bestLen && len >= Threshold) { - bestLen = len; - bestPos = j - 1; // position = distance - 1 - } + var input = data.ToArray(); + using var ms = new MemoryStream(); + var bits = new BitWriter(ms); + var tree = new Lh1Tree(); + var finder = new LhaMatchFinder(input, Lh1Decoder.WindowSize, Lh1Tree.CopyThreshold, MaxLength); + var path = new Stack(32); + + void WriteCode(int code) { + for (int node = tree.LeafOf(code); node != 0;) { + var parent = tree.Parent(node); + path.Push(tree.Child(parent) - node); + node = parent; } + while (path.Count > 0) + bits.WriteBit(path.Pop()); + tree.Increment(code); + } - if (bestLen >= Threshold) { - var code = NChar + bestLen - Threshold; - bits.WriteBits(codes[code], codeLengths[code]); - ++freq[code]; - ++symbolCount; - - // Write position: 6 high bits + 6 low bits - var posHigh = bestPos >> 6; - var posLow = bestPos & 0x3F; - bits.WriteBits((uint)posHigh, PositionBits); - bits.WriteBits((uint)posLow, 6); - - for (var k = 0; k < bestLen; ++k) { - window[windowPos] = data[i + k]; - windowPos = (windowPos + 1) & WindowMask; - } - i += bestLen; - } - else { - var b = data[i]; - bits.WriteBits(codes[b], codeLengths[b]); - ++freq[b]; - ++symbolCount; - - window[windowPos] = b; - windowPos = (windowPos + 1) & WindowMask; - ++i; + var pos = 0; + while (pos < input.Length) { + var (length, distance) = finder.Find(pos); + if (length == 0) { + WriteCode(input[pos++]); + continue; } + WriteCode(0x100 + length - Lh1Tree.CopyThreshold); + var offset = distance - 1; + var high = offset >> 6; + int codeLength = tree.OffsetLengths[high]; + bits.WriteBits((uint)(tree.OffsetCodes[high] >> (8 - codeLength)), codeLength); + bits.WriteBits((uint)(offset & 0x3F), 6); + pos += length; } - bits.FlushBits(); - return output.ToArray(); - } - - private static (int[] Lengths, uint[] Codes) BuildHuffmanTable(int[] freq) { - var lengths = LzhEncoder.BuildCodeLengths(freq, 16); - var codes = LzhEncoder.BuildCanonicalCodes(lengths); - return (lengths, codes); + if (input.Length > 0) + ms.WriteByte(0); + return ms.ToArray(); } } diff --git a/Compression.Core/Dictionary/Lzh/Lh1Tree.cs b/Compression.Core/Dictionary/Lzh/Lh1Tree.cs new file mode 100644 index 000000000..49eac3a51 --- /dev/null +++ b/Compression.Core/Dictionary/Lzh/Lh1Tree.cs @@ -0,0 +1,235 @@ +namespace Compression.Core.Dictionary.Lzh; + +/// +/// The adaptive Huffman tree of LHarc -lh1- (LZHUF), shared by and +/// so both evolve it identically. +/// +/// +/// 314 leaves (256 literals and 58 copy lengths 3-60) in a table kept sorted by decreasing +/// frequency; node 0 is the root and a branch's children are child (bit 0) and child - 1 +/// (bit 1). Nodes of equal frequency form a group whose leftmost node is its leader; incrementing a node +/// first swaps it with its leader, which keeps the table sorted. When the root count reaches 32 KiB the +/// leaf counts are halved and the tree rebuilt. +/// The 64 position codes use a fixed table: 1 code of 3 bits, 3 of 4, 8 of 5, 12 of 6, 24 of 7 +/// and 16 of 8, followed by 6 raw low bits. +/// Sourcing: converted from lhasa's lh1_decoder.c (ISC, Simon Howard), tables copied +/// exactly; see THIRD-PARTY-NOTICE.lhasa.txt beside this file. +/// +internal sealed class Lh1Tree { + public const int NumCodes = 314; + public const int NumNodes = NumCodes * 2 - 1; + public const int NumOffsets = 64; + public const int CopyThreshold = 3; + private const int ReorderLimit = 32 * 1024; + private static readonly int[] OffsetDistribution = [1, 3, 8, 12, 24, 16]; + + private readonly bool[] _leaf = new bool[NumNodes]; + private readonly ushort[] _child = new ushort[NumNodes]; + private readonly ushort[] _parent = new ushort[NumNodes]; + private readonly ushort[] _freq = new ushort[NumNodes]; + private readonly ushort[] _group = new ushort[NumNodes]; + private readonly ushort[] _leafNodes = new ushort[NumCodes]; + private readonly ushort[] _groups = new ushort[NumNodes]; + private readonly ushort[] _groupLeader = new ushort[NumNodes]; + private int _numGroups; + + /// Maps the next 8 input bits to a position code. + public readonly byte[] OffsetLookup = new byte[256]; + + /// Bit length of each position code. + public readonly byte[] OffsetLengths = new byte[NumOffsets]; + + /// Each position code's bits, left-aligned in a byte. + public readonly byte[] OffsetCodes = new byte[NumOffsets]; + + public Lh1Tree() { + this.InitGroups(); + this.InitTree(); + this.InitOffsetTable(); + } + + public bool IsLeaf(int node) => this._leaf[node]; + + public int Child(int node) => this._child[node]; + + public int Parent(int node) => this._parent[node]; + + public int LeafOf(int code) => this._leafNodes[code]; + + private ushort AllocGroup() => this._groups[this._numGroups++]; + + private void FreeGroup(ushort group) => this._groups[--this._numGroups] = group; + + private void InitGroups() { + for (var i = 0; i < NumNodes; ++i) + this._groups[i] = (ushort)i; + this._numGroups = 0; + } + + private void InitTree() { + var nodeIndex = NumNodes - 1; + var leafGroup = this.AllocGroup(); + for (var i = 0; i < NumCodes; ++i) { + this._leaf[nodeIndex] = true; + this._child[nodeIndex] = (ushort)i; + this._freq[nodeIndex] = 1; + this._group[nodeIndex] = leafGroup; + this._groupLeader[leafGroup] = (ushort)nodeIndex; + this._leafNodes[i] = (ushort)nodeIndex; + --nodeIndex; + } + + var child = NumNodes - 1; + while (nodeIndex >= 0) { + this._leaf[nodeIndex] = false; + this._child[nodeIndex] = (ushort)child; + this._parent[child] = (ushort)nodeIndex; + this._parent[child - 1] = (ushort)nodeIndex; + this._freq[nodeIndex] = (ushort)(this._freq[child] + this._freq[child - 1]); + this._group[nodeIndex] = this._freq[nodeIndex] == this._freq[nodeIndex + 1] + ? this._group[nodeIndex + 1] + : this.AllocGroup(); + this._groupLeader[this._group[nodeIndex]] = (ushort)nodeIndex; + --nodeIndex; + child -= 2; + } + } + + private void InitOffsetTable() { + var code = 0; + var offset = 0; + for (var i = 0; i < OffsetDistribution.Length; ++i) { + var length = i + 3; + var iterBit = 1 << (8 - length); + for (var j = 0; j < OffsetDistribution[i]; ++j) { + for (var k = 0; k < iterBit; ++k) + this.OffsetLookup[code | k] = (byte)offset; + this.OffsetLengths[offset] = (byte)length; + this.OffsetCodes[offset] = (byte)code; + code += iterBit; + ++offset; + } + } + } + + private int MakeGroupLeader(int nodeIndex) { + var group = this._group[nodeIndex]; + int leaderIndex = this._groupLeader[group]; + if (leaderIndex == nodeIndex) + return nodeIndex; + + (this._leaf[leaderIndex], this._leaf[nodeIndex]) = (this._leaf[nodeIndex], this._leaf[leaderIndex]); + (this._child[leaderIndex], this._child[nodeIndex]) = (this._child[nodeIndex], this._child[leaderIndex]); + this.Reparent(nodeIndex); + this.Reparent(leaderIndex); + return leaderIndex; + } + + private void Reparent(int index) { + if (this._leaf[index]) + this._leafNodes[this._child[index]] = (ushort)index; + else { + this._parent[this._child[index]] = (ushort)index; + this._parent[this._child[index] - 1] = (ushort)index; + } + } + + private void IncrementNodeFreq(int nodeIndex) { + var other = nodeIndex - 1; + ++this._freq[nodeIndex]; + if (nodeIndex < NumNodes - 1 && this._group[nodeIndex] == this._group[nodeIndex + 1]) { + ++this._groupLeader[this._group[nodeIndex]]; + if (this._freq[nodeIndex] == this._freq[other]) + this._group[nodeIndex] = this._group[other]; + else { + this._group[nodeIndex] = this.AllocGroup(); + this._groupLeader[this._group[nodeIndex]] = (ushort)nodeIndex; + } + } else if (this._freq[nodeIndex] == this._freq[other]) { + this.FreeGroup(this._group[nodeIndex]); + this._group[nodeIndex] = this._group[other]; + } + } + + private void Reconstruct() { + // Gather the leaves at the start, halving their counts. + var leafCount = 0; + var leafLeaf = new bool[NumCodes]; + var leafChild = new ushort[NumCodes]; + var leafFreq = new ushort[NumCodes]; + for (var i = 0; i < NumNodes; ++i) { + if (!this._leaf[i]) + continue; + leafChild[leafCount] = this._child[i]; + leafFreq[leafCount] = (ushort)((this._freq[i] + 1) / 2); + leafLeaf[leafCount] = true; + ++leafCount; + } + for (var i = 0; i < NumCodes; ++i) { + this._leaf[i] = leafLeaf[i]; + this._child[i] = leafChild[i]; + this._freq[i] = leafFreq[i]; + } + + var leaf = NumCodes - 1; + var child = NumNodes - 1; + var index = NumNodes - 1; + while (index >= 0) { + while (child - index < 2) { + this.CopyNode(index, leaf); + --index; + --leaf; + } + var freq = this._freq[child] + this._freq[child - 1]; + while (leaf >= 0 && freq >= this._freq[leaf]) { + this.CopyNode(index, leaf); + --index; + --leaf; + } + this._leaf[index] = false; + this._freq[index] = (ushort)freq; + this._child[index] = (ushort)child; + this._parent[child] = (ushort)index; + this._parent[child - 1] = (ushort)index; + --index; + child -= 2; + } + + this.InitGroups(); + var group = this.AllocGroup(); + this._group[0] = group; + this._groupLeader[group] = 0; + for (var i = 1; i < NumNodes; ++i) { + if (this._freq[i] == this._freq[i - 1]) + this._group[i] = this._group[i - 1]; + else { + group = this.AllocGroup(); + this._group[i] = group; + this._groupLeader[group] = (ushort)i; + } + } + } + + /// Copies leaf node to (from ≤ to), as the reference's struct copy does. + private void CopyNode(int to, int from) { + this._leaf[to] = this._leaf[from]; + this._child[to] = this._child[from]; + this._freq[to] = this._freq[from]; + this._parent[to] = this._parent[from]; + this._group[to] = this._group[from]; + this._leafNodes[this._child[from]] = (ushort)to; + } + + /// Counts one occurrence of , reshaping the tree. + public void Increment(int code) { + if (this._freq[0] >= ReorderLimit) + this.Reconstruct(); + ++this._freq[0]; + int nodeIndex = this._leafNodes[code]; + while (nodeIndex != 0) { + nodeIndex = this.MakeGroupLeader(nodeIndex); + this.IncrementNodeFreq(nodeIndex); + nodeIndex = this._parent[nodeIndex]; + } + } +} diff --git a/Compression.Core/Dictionary/Lzh/LhaBitInput.cs b/Compression.Core/Dictionary/Lzh/LhaBitInput.cs new file mode 100644 index 000000000..35387f1e6 --- /dev/null +++ b/Compression.Core/Dictionary/Lzh/LhaBitInput.cs @@ -0,0 +1,39 @@ +namespace Compression.Core.Dictionary.Lzh; + +/// +/// MSB-first bit reader for the LArc/LHarc/PMarc decoders. Reads past the end of the data return +/// zero bits, as the reference decoders' zero-filled input buffers do; callers bound their work by the +/// member's declared size. +/// +internal sealed class LhaBitInput(byte[] data) { + private readonly byte[] _data = data; + private long _bitPos; + + /// Gets whether every bit of the input has been consumed. + public bool Exhausted => this._bitPos >= (long)this._data.Length * 8; + + public int ReadBit() { + var index = (int)(this._bitPos >> 3); + var bit = index < this._data.Length ? (this._data[index] >> (7 - (int)(this._bitPos & 7))) & 1 : 0; + ++this._bitPos; + return bit; + } + + /// Reads bits (0-24) as an unsigned value. + public int ReadBits(int count) { + var value = 0; + for (var i = 0; i < count; ++i) + value = (value << 1) | this.ReadBit(); + return value; + } + + /// Returns the next bits without consuming them. + public int PeekBits(int count) { + var saved = this._bitPos; + var value = this.ReadBits(count); + this._bitPos = saved; + return value; + } + + public void Skip(int count) => this._bitPos += count; +} diff --git a/Compression.Core/Dictionary/Lzh/LhaMatchFinder.cs b/Compression.Core/Dictionary/Lzh/LhaMatchFinder.cs new file mode 100644 index 000000000..f821a30d3 --- /dev/null +++ b/Compression.Core/Dictionary/Lzh/LhaMatchFinder.cs @@ -0,0 +1,66 @@ +namespace Compression.Core.Dictionary.Lzh; + +/// +/// Hash-chain longest-match search over the input for the small-window LHA encoders +/// (-lzs-, -lz5-, -lh1-). A match at distance d copies from d bytes +/// back and may overlap the bytes it produces. +/// +internal sealed class LhaMatchFinder { + private const int HashBits = 14; + private const int MaxChain = 128; + private readonly byte[] _data; + private readonly int _maxDistance; + private readonly int _minLength; + private readonly int _maxLength; + private readonly int[] _head = new int[1 << HashBits]; + private readonly int[] _prev; + private int _inserted; + + public LhaMatchFinder(byte[] data, int maxDistance, int minLength, int maxLength) { + this._data = data; + this._maxDistance = maxDistance; + this._minLength = minLength; + this._maxLength = maxLength; + this._prev = new int[data.Length]; + Array.Fill(this._head, -1); + } + + private int Hash(int pos) => + ((this._data[pos] << 10) ^ (this._data[pos + 1] << 5) ^ this._data[pos + 2]) & ((1 << HashBits) - 1); + + /// Indexes every position before that is not yet indexed. + private void InsertUpTo(int pos) { + for (; this._inserted < pos; ++this._inserted) { + if (this._inserted + 2 >= this._data.Length) + continue; + var h = this.Hash(this._inserted); + this._prev[this._inserted] = this._head[h]; + this._head[h] = this._inserted; + } + } + + /// Finds the longest match for the bytes at ; returns length 0 when none reaches the minimum. + public (int Length, int Distance) Find(int pos) { + this.InsertUpTo(pos); + var limit = Math.Min(this._maxLength, this._data.Length - pos); + if (limit < this._minLength || pos + 2 >= this._data.Length) + return (0, 0); + + int bestLength = 0, bestDistance = 0, chain = 0; + for (var candidate = this._head[this.Hash(pos)]; candidate >= 0 && chain < MaxChain; candidate = this._prev[candidate], ++chain) { + var distance = pos - candidate; + if (distance > this._maxDistance) + break; + var length = 0; + while (length < limit && this._data[candidate + length] == this._data[pos + length]) + ++length; + if (length > bestLength) { + bestLength = length; + bestDistance = distance; + if (length == limit) + break; + } + } + return bestLength >= this._minLength ? (bestLength, bestDistance) : (0, 0); + } +} diff --git a/Compression.Core/Dictionary/Lzh/Lz5Decoder.cs b/Compression.Core/Dictionary/Lzh/Lz5Decoder.cs index c68f176cf..5a75b5285 100644 --- a/Compression.Core/Dictionary/Lzh/Lz5Decoder.cs +++ b/Compression.Core/Dictionary/Lzh/Lz5Decoder.cs @@ -1,76 +1,80 @@ namespace Compression.Core.Dictionary.Lzh; /// -/// Decodes -lz5- compressed data (LArc variant). -/// Uses LZSS with a 4KB sliding window, no Huffman coding. +/// Decodes LArc -lz5-: LZSS over a 4 KiB ring buffer, byte-aligned. /// /// -/// Similar to -lzs- but with a 4KB window. -/// Flag byte encodes 8 tokens. Bit 1 = literal, bit 0 = match. -/// Match: 12-bit offset (first byte + high 4 bits of second byte), -/// 4-bit length in low nibble of second byte. Minimum match = 2. -/// Window initialized to spaces. +/// A flag byte governs the next eight commands, least significant bit first: 1 is a literal byte, +/// 0 is a two-byte copy whose first byte and the high nibble of the second give a 12-bit absolute ring +/// position, and whose low nibble gives a length of 3-18. The ring buffer starts with a fixed pattern +/// (13 copies of each byte value, the values ascending then descending, 128 zeros, 110 spaces and 18 +/// zeros) and the write position starts 18 bytes before its end. +/// Sourcing: converted from lhasa's lz5_decoder.c (ISC, Simon Howard), initial ring +/// contents copied exactly; see THIRD-PARTY-NOTICE.lhasa.txt beside this file. Checked against the +/// -lz5- archives of lhasa's test suite. /// public static class Lz5Decoder { - private const int WindowSize = 4096; - private const int WindowMask = WindowSize - 1; - private const int Threshold = 2; + internal const int RingSize = 4096; + internal const int StartOffset = 18; + internal const int Threshold = 3; - /// - /// Decodes -lz5- compressed data. - /// - /// The compressed data. - /// The expected uncompressed size. - /// The decompressed data. + /// Decodes bytes of -lz5- data. public static byte[] Decode(ReadOnlySpan compressed, int originalSize) => Decode(compressed.ToArray(), originalSize); - /// - /// Decodes -lz5- compressed data. - /// - /// The compressed data. - /// The expected uncompressed size. - /// The decompressed data. + /// Decodes bytes of -lz5- data. public static byte[] Decode(byte[] compressed, int originalSize) { + ArgumentNullException.ThrowIfNull(compressed); + ArgumentOutOfRangeException.ThrowIfNegative(originalSize); var output = new byte[originalSize]; - var window = new byte[WindowSize]; - Array.Fill(window, (byte)0x20); - var windowPos = 0; + var ring = InitialRing(); + var ringPos = RingSize - StartOffset; + var src = 0; var outPos = 0; - var srcPos = 0; - - while (outPos < originalSize && srcPos < compressed.Length) { - int flags = compressed[srcPos++]; + while (outPos < originalSize) { + if (src >= compressed.Length) + throw new InvalidDataException("-lz5- stream ends before the member is complete."); + int flags = compressed[src++]; for (var bit = 0; bit < 8 && outPos < originalSize; ++bit) { if ((flags & (1 << bit)) != 0) { - // Literal - if (srcPos >= compressed.Length) - return output; - var b = compressed[srcPos++]; - output[outPos++] = b; - window[windowPos] = b; - windowPos = (windowPos + 1) & WindowMask; - } else { - // Match - if (srcPos + 1 >= compressed.Length) - return output; - int b1 = compressed[srcPos++]; - int b2 = compressed[srcPos++]; - - var offset = b1 | ((b2 & 0xF0) << 4); - var length = (b2 & 0x0F) + Threshold; - - for (var j = 0; j < length && outPos < originalSize; ++j) { - var b = window[(offset + j) & WindowMask]; - output[outPos++] = b; - window[windowPos] = b; - windowPos = (windowPos + 1) & WindowMask; - } + if (src >= compressed.Length) + throw new InvalidDataException("-lz5- stream ends inside a literal."); + output[outPos++] = ring[ringPos] = compressed[src++]; + ringPos = (ringPos + 1) % RingSize; + continue; + } + if (src + 1 >= compressed.Length) + throw new InvalidDataException("-lz5- stream ends inside a copy."); + int c0 = compressed[src++]; + int c1 = compressed[src++]; + var pos = ((c1 & 0xF0) << 4) | c0; + var length = (c1 & 0x0F) + Threshold; + for (var i = 0; i < length && outPos < originalSize; ++i) { + var b = ring[(pos + i) % RingSize]; + output[outPos++] = ring[ringPos] = b; + ringPos = (ringPos + 1) % RingSize; } } } - return output; } + + /// The ring buffer's initial contents, as LArc defines them. + internal static byte[] InitialRing() { + var ring = new byte[RingSize]; + var p = 0; + for (var i = 0; i < 256; ++i) + for (var j = 0; j < 13; ++j) + ring[p++] = (byte)i; + for (var i = 0; i < 256; ++i) + ring[p++] = (byte)i; + for (var i = 0; i < 256; ++i) + ring[p++] = (byte)(255 - i); + p += 128; // zeros + for (var i = 0; i < 110; ++i) + ring[p++] = (byte)' '; + // the final 18 bytes stay zero + return ring; + } } diff --git a/Compression.Core/Dictionary/Lzh/Lz5Encoder.cs b/Compression.Core/Dictionary/Lzh/Lz5Encoder.cs index 1d138e334..12063a99d 100644 --- a/Compression.Core/Dictionary/Lzh/Lz5Encoder.cs +++ b/Compression.Core/Dictionary/Lzh/Lz5Encoder.cs @@ -1,86 +1,43 @@ namespace Compression.Core.Dictionary.Lzh; /// -/// Encodes data using the -lz5- format (LArc variant). -/// Plain LZSS with a 4KB sliding window, no Huffman coding. +/// Encodes LArc -lz5- (see for the stream layout). /// +/// +/// Matches only reach back into bytes already emitted (never into the predefined initial ring) and are +/// written as absolute ring positions. Verified by lhasa extracting what this writes. +/// public static class Lz5Encoder { - private const int WindowSize = 4096; - private const int WindowMask = WindowSize - 1; - private const int Threshold = 2; - private const int MaxMatch = 17; // 4-bit length + 2 + private const int MaxLength = 15 + Lz5Decoder.Threshold; - /// - /// Encodes data using the -lz5- format. - /// - /// The data to compress. - /// The compressed data. - public static byte[] Encode(ReadOnlySpan data) - => Encode(data.ToArray()); + /// Encodes . + public static byte[] Encode(ReadOnlySpan data) => Encode(data.ToArray()); - /// - /// Encodes data using the -lz5- format. - /// - /// The data to compress. - /// The compressed data. + /// Encodes . public static byte[] Encode(byte[] data) { - var window = new byte[WindowSize]; - Array.Fill(window, (byte)0x20); - var windowPos = 0; - - using var output = new MemoryStream(); - var srcPos = 0; - - while (srcPos < data.Length) { - var flagPos = (int)output.Position; - output.WriteByte(0); // placeholder for flag byte - var flags = 0; - - for (var bit = 0; bit < 8 && srcPos < data.Length; ++bit) { - // Find best match in window - var bestLen = 0; - var bestOff = 0; - - for (var off = 1; off < WindowSize && off <= srcPos; ++off) { - var matchPos = (windowPos - off) & WindowMask; - var len = 0; - while (len < MaxMatch && srcPos + len < data.Length - && window[(matchPos + len) & WindowMask] == data[srcPos + len]) - ++len; - - if (len > bestLen) { - bestLen = len; - bestOff = matchPos; - } - } - - if (bestLen >= Threshold) { - // Match: encode offset + length - var b1 = bestOff & 0xFF; - var b2 = ((bestOff >> 4) & 0xF0) | ((bestLen - Threshold) & 0x0F); - output.WriteByte((byte)b1); - output.WriteByte((byte)b2); - - for (var j = 0; j < bestLen; ++j) { - window[windowPos] = data[srcPos++]; - windowPos = (windowPos + 1) & WindowMask; - } - } else { - // Literal - flags |= 1 << bit; - output.WriteByte(data[srcPos]); - window[windowPos] = data[srcPos++]; - windowPos = (windowPos + 1) & WindowMask; + ArgumentNullException.ThrowIfNull(data); + using var ms = new MemoryStream(); + var finder = new LhaMatchFinder(data, Lz5Decoder.RingSize - 1, Lz5Decoder.Threshold, MaxLength); + var group = new List(17); + var pos = 0; + while (pos < data.Length) { + byte flags = 0; + group.Clear(); + for (var bit = 0; bit < 8 && pos < data.Length; ++bit) { + var (length, distance) = finder.Find(pos); + if (length == 0) { + flags |= (byte)(1 << bit); + group.Add(data[pos++]); + continue; } + var ringPos = (Lz5Decoder.RingSize - Lz5Decoder.StartOffset + pos - distance) % Lz5Decoder.RingSize; + group.Add((byte)ringPos); + group.Add((byte)(((ringPos >> 4) & 0xF0) | (length - Lz5Decoder.Threshold))); + pos += length; } - - // Patch flag byte - var savedPos = output.Position; - output.Position = flagPos; - output.WriteByte((byte)flags); - output.Position = savedPos; + ms.WriteByte(flags); + ms.Write(group.ToArray()); } - - return output.ToArray(); + return ms.ToArray(); } } diff --git a/Compression.Core/Dictionary/Lzh/LzhDecoder.cs b/Compression.Core/Dictionary/Lzh/LzhDecoder.cs index c0b37c9a7..7145a8f0e 100644 --- a/Compression.Core/Dictionary/Lzh/LzhDecoder.cs +++ b/Compression.Core/Dictionary/Lzh/LzhDecoder.cs @@ -24,7 +24,7 @@ public sealed class LzhDecoder { /// Initializes a new . ///
/// The stream containing compressed data. - /// Number of position bits (13 for lh5, 15 for lh6, 16 for lh7). + /// Number of position bits (13 for lh5, 15 for lh6, 16 for lh7, 20 for lhx). public LzhDecoder(Stream input, int positionBits = LzhConstants.Lh5PositionBits) { this._bits = new(input); this._windowSize = 1 << positionBits; @@ -33,6 +33,7 @@ public LzhDecoder(Stream input, int positionBits = LzhConstants.Lh5PositionBits) 13 => 4, // lh5 15 => 5, // lh6 16 => 5, // lh7 + 20 => 5, // lhx (UNLHA32's 1 MiB window) _ => 4 }; } diff --git a/Compression.Core/Dictionary/Lzh/LzsDecoder.cs b/Compression.Core/Dictionary/Lzh/LzsDecoder.cs index d9c8dbbf7..68207650d 100644 --- a/Compression.Core/Dictionary/Lzh/LzsDecoder.cs +++ b/Compression.Core/Dictionary/Lzh/LzsDecoder.cs @@ -1,76 +1,54 @@ namespace Compression.Core.Dictionary.Lzh; /// -/// Decodes -lzs- compressed data (LArc format). -/// Uses plain LZSS with a 2KB sliding window, no Huffman coding. +/// Decodes LArc -lzs-: LZSS over a 2 KiB ring buffer, with no entropy coding. /// /// -/// Format: flag byte, then 8 tokens per flag. -/// Flag bit 1 = literal byte. -/// Flag bit 0 = match: 11-bit offset (big-endian across 2 bytes) + 4-bit length. -/// The 2 bytes encode: high 8 bits = first byte, low 3 bits of offset in top 3 bits of second byte, -/// length in low 4 bits of second byte. Minimum match = 2. Window initialized to spaces. +/// The stream is MSB-first bits. Each command opens with a flag bit: 1 is followed by an 8-bit +/// literal, 0 by an 11-bit absolute ring-buffer position and a 4-bit length (copies of 2-17 bytes). +/// The ring buffer starts filled with spaces and the write position starts 17 bytes before its end; +/// positions are absolute, not distances. +/// Sourcing: converted from lhasa's lzs_decoder.c (ISC, Simon Howard); see +/// THIRD-PARTY-NOTICE.lhasa.txt beside this file. Checked against the -lzs- archives of lhasa's +/// test suite. /// public static class LzsDecoder { - private const int WindowSize = 2048; - private const int WindowMask = WindowSize - 1; - private const int Threshold = 2; + internal const int RingSize = 2048; + internal const int StartOffset = 17; + internal const int Threshold = 2; - /// - /// Decodes -lzs- compressed data. - /// - /// The compressed data. - /// The expected uncompressed size. - /// The decompressed data. + /// Decodes bytes of -lzs- data. public static byte[] Decode(ReadOnlySpan compressed, int originalSize) => Decode(compressed.ToArray(), originalSize); - /// - /// Decodes -lzs- compressed data. - /// - /// The compressed data. - /// The expected uncompressed size. - /// The decompressed data. + /// Decodes bytes of -lzs- data. public static byte[] Decode(byte[] compressed, int originalSize) { + ArgumentNullException.ThrowIfNull(compressed); + ArgumentOutOfRangeException.ThrowIfNegative(originalSize); var output = new byte[originalSize]; - var window = new byte[WindowSize]; - Array.Fill(window, (byte)0x20); - var windowPos = 0; + var ring = new byte[RingSize]; + Array.Fill(ring, (byte)' '); + var ringPos = RingSize - StartOffset; + var bits = new LhaBitInput(compressed); var outPos = 0; - var srcPos = 0; - while (outPos < originalSize && srcPos < compressed.Length) { - int flags = compressed[srcPos++]; - - for (var bit = 0; bit < 8 && outPos < originalSize; ++bit) { - if ((flags & (1 << bit)) != 0) { - // Literal - if (srcPos >= compressed.Length) - return output; - var b = compressed[srcPos++]; - output[outPos++] = b; - window[windowPos] = b; - windowPos = (windowPos + 1) & WindowMask; - } else { - // Match - if (srcPos + 1 >= compressed.Length) - return output; - int b1 = compressed[srcPos++]; - int b2 = compressed[srcPos++]; - - var offset = b1 | ((b2 & 0xE0) << 3); - var length = (b2 & 0x0F) + Threshold; - - for (var j = 0; j < length && outPos < originalSize; ++j) { - var b = window[(offset + j) & WindowMask]; - output[outPos++] = b; - window[windowPos] = b; - windowPos = (windowPos + 1) & WindowMask; - } - } + while (outPos < originalSize) { + if (bits.Exhausted) + throw new InvalidDataException("-lzs- stream ends before the member is complete."); + if (bits.ReadBit() != 0) { + var b = (byte)bits.ReadBits(8); + output[outPos++] = ring[ringPos] = b; + ringPos = (ringPos + 1) % RingSize; + continue; + } + var pos = bits.ReadBits(11); + var length = bits.ReadBits(4) + Threshold; + for (var i = 0; i < length && outPos < originalSize; ++i) { + var b = ring[(pos + i) % RingSize]; + output[outPos++] = ring[ringPos] = b; + ringPos = (ringPos + 1) % RingSize; } } - return output; } } diff --git a/Compression.Core/Dictionary/Lzh/LzsEncoder.cs b/Compression.Core/Dictionary/Lzh/LzsEncoder.cs index 78151bde0..7c14f9175 100644 --- a/Compression.Core/Dictionary/Lzh/LzsEncoder.cs +++ b/Compression.Core/Dictionary/Lzh/LzsEncoder.cs @@ -1,86 +1,43 @@ +using Compression.Core.BitIO; + namespace Compression.Core.Dictionary.Lzh; /// -/// Encodes data using the -lzs- format (LArc). -/// Plain LZSS with a 2KB sliding window, no Huffman coding. +/// Encodes LArc -lzs- (see for the stream layout). /// +/// +/// Matches only reach back into bytes already emitted (never into the space-filled initial ring), +/// and are written as the absolute ring position of their source, as the format requires. Verified by +/// lhasa extracting what this writes. +/// public static class LzsEncoder { - private const int WindowSize = 2048; - private const int WindowMask = WindowSize - 1; - private const int Threshold = 2; - private const int MaxMatch = 17; // 4-bit length + 2 + private const int MaxLength = 15 + LzsDecoder.Threshold; - /// - /// Encodes data using the -lzs- format. - /// - /// The data to compress. - /// The compressed data. - public static byte[] Encode(ReadOnlySpan data) - => Encode(data.ToArray()); + /// Encodes . + public static byte[] Encode(ReadOnlySpan data) => Encode(data.ToArray()); - /// - /// Encodes data using the -lzs- format. - /// - /// The data to compress. - /// The compressed data. + /// Encodes . public static byte[] Encode(byte[] data) { - var window = new byte[WindowSize]; - Array.Fill(window, (byte)0x20); - var windowPos = 0; - - using var output = new MemoryStream(); - var srcPos = 0; - - while (srcPos < data.Length) { - var flagPos = (int)output.Position; - output.WriteByte(0); // placeholder for flag byte - var flags = 0; - - for (var bit = 0; bit < 8 && srcPos < data.Length; ++bit) { - // Find best match in window - var bestLen = 0; - var bestOff = 0; - - for (var off = 1; off < WindowSize && off <= srcPos; ++off) { - var matchPos = (windowPos - off) & WindowMask; - var len = 0; - while (len < MaxMatch && srcPos + len < data.Length - && window[(matchPos + len) & WindowMask] == data[srcPos + len]) - ++len; - - if (len > bestLen) { - bestLen = len; - bestOff = matchPos; - } - } - - if (bestLen >= Threshold) { - // Match: encode offset + length - var b1 = bestOff & 0xFF; - var b2 = ((bestOff >> 3) & 0xE0) | ((bestLen - Threshold) & 0x0F); - output.WriteByte((byte)b1); - output.WriteByte((byte)b2); - - for (var j = 0; j < bestLen; ++j) { - window[windowPos] = data[srcPos++]; - windowPos = (windowPos + 1) & WindowMask; - } - } else { - // Literal - flags |= 1 << bit; - output.WriteByte(data[srcPos]); - window[windowPos] = data[srcPos++]; - windowPos = (windowPos + 1) & WindowMask; - } + ArgumentNullException.ThrowIfNull(data); + using var ms = new MemoryStream(); + var bits = new BitWriter(ms); + var finder = new LhaMatchFinder(data, LzsDecoder.RingSize - 1, 3, MaxLength); + var pos = 0; + while (pos < data.Length) { + var (length, distance) = finder.Find(pos); + if (length == 0) { + bits.WriteBit(1); + bits.WriteBits(data[pos], 8); + ++pos; + continue; } - - // Patch flag byte - var savedPos = output.Position; - output.Position = flagPos; - output.WriteByte((byte)flags); - output.Position = savedPos; + var ringPos = (LzsDecoder.RingSize - LzsDecoder.StartOffset + pos - distance) % LzsDecoder.RingSize; + bits.WriteBit(0); + bits.WriteBits((uint)ringPos, 11); + bits.WriteBits((uint)(length - LzsDecoder.Threshold), 4); + pos += length; } - - return output.ToArray(); + bits.FlushBits(); + return ms.ToArray(); } } diff --git a/Compression.Core/Dictionary/Lzh/Pm1Decoder.cs b/Compression.Core/Dictionary/Lzh/Pm1Decoder.cs new file mode 100644 index 000000000..5c8a75608 --- /dev/null +++ b/Compression.Core/Dictionary/Lzh/Pm1Decoder.cs @@ -0,0 +1,171 @@ +namespace Compression.Core.Dictionary.Lzh; + +/// +/// Decodes PMarc -pm1-: alternating runs of literals and copies over a 16 KiB window, all +/// coded with fixed (static) prefix codes whose reach grows with the amount of output. +/// +/// +/// A 5-bit header selects one of 32 small trees that code the literal-range index; literals are +/// distances along a move-to-front history (). Each command is either a copy +/// (flag 0) or a block of 1-216 literals that, unless at its maximum length, is followed by a copy. +/// Copy distances use ranges that open up after 64, 320, 576, 832, ... bytes of output. +/// Sourcing: converted from lhasa's pm1_decoder.c (ISC, Simon Howard; reverse-engineered +/// there from the UNPMA10 decoder), tables copied exactly; see THIRD-PARTY-NOTICE.lhasa.txt beside this +/// file. Checked against the -pm1- archives of lhasa's test suite. Like the reference, input past +/// the end of the member reads as zero bytes. +/// +public static class Pm1Decoder { + private const int RingSize = 16384; + private const int MaxByteBlock = 216; + + private static readonly (int Offset, int Bits)[] CopyRanges = [ + (0, 6), (64, 8), (0, 6), (64, 9), (576, 11), (2624, 13), + (64, 8), + (576, 8), (576, 9), (576, 10), + (2624, 8), (2624, 9), (2624, 10), (2624, 11), (2624, 12), + ]; + + private static readonly (int Offset, int Bits)[] ByteRanges = [(0, 4), (16, 4), (32, 5), (64, 6), (128, 6), (192, 6)]; + + private static readonly byte[][] ByteDecodeTrees = [ + [0x12, 0x2d, 0xef, 0x1c, 0xab], [0x12, 0x23, 0xde, 0xab, 0xcf], [0x12, 0x2c, 0xd2, 0xab, 0xef], + [0x12, 0xa2, 0xd2, 0xbc, 0xef], [0x12, 0xa2, 0xc2, 0xbd, 0xef], [0x12, 0xa2, 0xcd, 0xb1, 0xef], + [0x12, 0xab, 0x12, 0xcd, 0xef], [0x12, 0xab, 0x1d, 0xc1, 0xef], [0x12, 0xab, 0xc1, 0xd1, 0xef], + [0xa1, 0x12, 0x2c, 0xde, 0xbf], [0xa1, 0x1d, 0x1c, 0xb1, 0xef], [0xa1, 0x12, 0x2d, 0xef, 0xbc], + [0xa1, 0x12, 0xb2, 0xde, 0xcf], [0xa1, 0x12, 0xbc, 0xd1, 0xef], [0xa1, 0x1c, 0xb1, 0xd1, 0xef], + [0xa1, 0xb1, 0x12, 0xcd, 0xef], [0xa1, 0xb1, 0xc1, 0xd1, 0xef], [0x12, 0x1c, 0xde, 0xab], + [0x12, 0xa2, 0xcd, 0xbe], [0x12, 0xab, 0xc1, 0xde], [0xa1, 0x1d, 0x1c, 0xbe], + [0xa1, 0x12, 0xbc, 0xde], [0xa1, 0x1c, 0xb1, 0xde], [0xa1, 0xb1, 0xc1, 0xde], + [0x1d, 0x1c, 0xab], [0x1c, 0xa1, 0xbd], [0x12, 0xab, 0xcd], + [0xa1, 0x1c, 0xbd], [0xa1, 0xb1, 0xcd], [0xa1, 0xbc], + [0xab], [0x00], + ]; + + /// Decodes bytes of -pm1- data. + /// The stream is damaged. + public static byte[] Decode(byte[] compressed, int originalSize) { + ArgumentNullException.ThrowIfNull(compressed); + ArgumentOutOfRangeException.ThrowIfNegative(originalSize); + return new State(compressed, originalSize).Run(); + } + + private sealed class State(byte[] compressed, int originalSize) { + private readonly LhaBitInput _bits = new(compressed); + private readonly byte[] _output = new byte[originalSize]; + private readonly byte[] _ring = new byte[RingSize]; + private readonly PmaHistory _history = new(); + private byte[] _byteTree = []; + private int _ringPos; + private int _pos; // bytes output so far + + public byte[] Run() { + if (this._output.Length == 0) + return this._output; + this._byteTree = ByteDecodeTrees[this._bits.ReadBits(5)]; + var budget = (long)compressed.Length * 8 + 64 * 1024; // zero-fill reads past the end; bound a runaway + while (this._pos < this._output.Length) { + if (--budget < 0) + throw new InvalidDataException("-pm1- stream does not produce its declared size."); + if (this._bits.ReadBit() == 0) + this.CopyCommand(); + else + this.ByteBlock(); + } + return this._output; + } + + private void Output(byte b) { + this._ring[this._ringPos] = b; + this._ringPos = (this._ringPos + 1) % RingSize; + this._history.Update(b); + if (this._pos < this._output.Length) + this._output[this._pos] = b; + ++this._pos; + } + + private int BitAfter(int threshold, int fallback) => this._pos >= threshold ? this._bits.ReadBit() : fallback; + + private int CopyTypeRange() { + if (this._bits.ReadBit() == 0) + return this.BitAfter(576, 0) != 0 ? 4 : this.BitAfter(64, 0); + if (this.BitAfter(64, 1) == 0) + return 3; + return this.BitAfter(2624, 1) != 0 ? 2 : 5; + } + + private int CopyCount() { + var x = this._bits.ReadBits(2); + if (x < 3) return x + 3; + x = this._bits.ReadBits(3); + if (x < 5) return x + 6; + if (x == 5) return this._bits.ReadBits(2) + 11; + if (x == 6) return this._bits.ReadBits(3) + 15; + x = this._bits.ReadBits(6); + if (x < 62) return x + 23; + return x == 62 ? this._bits.ReadBits(5) + 85 : this._bits.ReadBits(7) + 117; + } + + private void CopyCommand() { + var range = this.CopyTypeRange(); + var count = range < 2 ? 2 : this.CopyCount(); + range = range switch { + 3 when this._pos < 320 => 6, + 4 when this._pos < 832 => 7, + 4 when this._pos < 1088 => 8, + 4 when this._pos < 1600 => 9, + 5 when this._pos < 2880 => 10, + 5 when this._pos < 3136 => 11, + 5 when this._pos < 3648 => 12, + 5 when this._pos < 4672 => 13, + 5 when this._pos < 6720 => 14, + _ => range, + }; + var distance = CopyRanges[range].Offset + this._bits.ReadBits(CopyRanges[range].Bits); + if (distance >= this._pos) + throw new InvalidDataException("-pm1- copy reaches before the start of the output."); + var index = (this._ringPos + RingSize - distance - 1) % RingSize; + for (var i = 0; i < count; ++i) { + this.Output(this._ring[index]); + index = (index + 1) % RingSize; + } + } + + private int ByteDecodeIndex() { + var tree = this._byteTree; + if (tree[0] == 0) + return 0; + var at = 0; + while (true) { + var child = this._bits.ReadBit() == 0 ? (tree[at] >> 4) & 0x0F : tree[at] & 0x0F; + if (child >= 10) + return child - 10; + at += child; + if (at >= tree.Length) + throw new InvalidDataException("-pm1- literal tree walk leaves its table."); + } + } + + private int ByteBlockCount() { + var x = this._bits.ReadBits(2); + if (x < 3) return x + 1; + x = this._bits.ReadBits(3); + if (x < 7) return x + 4; + x = this._bits.ReadBits(4); + if (x < 14) return x + 11; + return x == 14 ? this._bits.ReadBits(6) + 25 : this._bits.ReadBits(7) + 89; + } + + private void ByteBlock() { + var length = this.ByteBlockCount(); + for (var i = 0; i < length; ++i) { + var index = this.ByteDecodeIndex(); + if (index >= ByteRanges.Length) + throw new InvalidDataException("-pm1- literal range is out of range."); + var count = ByteRanges[index].Offset + this._bits.ReadBits(ByteRanges[index].Bits); + this.Output(this._history.Find(count)); + } + if (length != MaxByteBlock && this._pos < this._output.Length) + this.CopyCommand(); + } + } +} diff --git a/Compression.Core/Dictionary/Lzh/Pm2Decoder.cs b/Compression.Core/Dictionary/Lzh/Pm2Decoder.cs new file mode 100644 index 000000000..7c0e829da --- /dev/null +++ b/Compression.Core/Dictionary/Lzh/Pm2Decoder.cs @@ -0,0 +1,219 @@ +namespace Compression.Core.Dictionary.Lzh; + +/// +/// Decodes PMarc -pm2-: LZSS over an 8 KiB window with literals coded against a move-to-front +/// history and two small Huffman trees that are re-sent as the stream grows. +/// +/// +/// The first bit is discarded. A code tree (up to 29 symbols) is read; codes 0-7 select a literal +/// range in the history (), codes 8+ a copy whose length is 2-16 directly or a +/// variable-length value, and whose distance uses an offset tree of 5 to 8 symbols. The trees are rebuilt +/// after 1, 2 and 4 KiB of output and then every 4 KiB, each time with a flag bit saying whether a new +/// code tree follows. +/// Sourcing: converted from lhasa's pm2_decoder.c and tree_decode.c (ISC, Simon +/// Howard), tables copied exactly; see THIRD-PARTY-NOTICE.lhasa.txt beside this file. Checked against +/// the -pm2- archives of lhasa's test suite. +/// +public static class Pm2Decoder { + private const int RingSize = 8192; + private const int CodeTreeElements = 65; + private const int OffsetTreeElements = 17; + private const byte LeafFlag = 0x80; + + private static readonly (int Offset, int Bits)[] HistoryDecode = + [(0, 3), (8, 3), (16, 4), (32, 5), (64, 5), (96, 5), (128, 6), (192, 6)]; + + private static readonly (int Offset, int Bits)[] CopyDecode = + [(17, 3), (25, 3), (33, 5), (65, 6), (129, 7), (256, 0)]; + + /// Decodes bytes of -pm2- data. + /// The stream is damaged. + public static byte[] Decode(byte[] compressed, int originalSize) { + ArgumentNullException.ThrowIfNull(compressed); + ArgumentOutOfRangeException.ThrowIfNegative(originalSize); + return new State(compressed, originalSize).Run(); + } + + private enum Rebuild { Unbuilt, Build1, Build2, Build3, Continuing } + + private sealed class State(byte[] compressed, int originalSize) { + private readonly LhaBitInput _bits = new(compressed); + private readonly byte[] _output = new byte[originalSize]; + private readonly byte[] _ring = Filled(RingSize, (byte)' '); + private readonly PmaHistory _history = new(); + private readonly byte[] _codeTree = Filled(CodeTreeElements, LeafFlag); + private readonly byte[] _offsetTree = Filled(OffsetTreeElements, LeafFlag); + private Rebuild _treeState = Rebuild.Unbuilt; + private int _rebuildRemaining; + private bool _needOffsetTree; + private int _ringPos; + private int _outPos; + + private static byte[] Filled(int length, byte value) { + var a = new byte[length]; + Array.Fill(a, value); + return a; + } + + public byte[] Run() { + if (this._output.Length == 0) + return this._output; + this._bits.ReadBit(); // the first bit is discarded + this.RebuildTrees(); + while (this._outPos < this._output.Length) { + if (this._bits.Exhausted) + throw new InvalidDataException("-pm2- stream ends before the member is complete."); + var code = ReadFromTree(this._bits, this._codeTree); + if (code < 8) + this.Output(this._history.Find(Variable(HistoryDecode, code))); + else + this.Copy(code - 8); + } + return this._output; + } + + private int Variable((int Offset, int Bits)[] table, int header) { + if (header >= table.Length) + throw new InvalidDataException("-pm2- code is out of range."); + return table[header].Offset + this._bits.ReadBits(table[header].Bits); + } + + private void Output(byte b) { + this._ring[this._ringPos] = b; + this._ringPos = (this._ringPos + 1) % RingSize; + if (this._outPos < this._output.Length) + this._output[this._outPos] = b; + ++this._outPos; + this._history.Update(b); + if (--this._rebuildRemaining == 0) + this.RebuildTrees(); + } + + private void Copy(int code) { + var count = code < 15 ? code + 2 : this.Variable(CopyDecode, code - 15); + int offset; + if (code == 0) + offset = this._bits.ReadBits(6); + else if (code < 20) { + var val = ReadFromTree(this._bits, this._offsetTree); + offset = val == 0 ? this._bits.ReadBits(6) : (1 << (val + 5)) + this._bits.ReadBits(val + 5); + } else + offset = 0; + var start = this._ringPos + RingSize - 1 - offset; + for (var i = 0; i < count; ++i) + this.Output(this._ring[(start + i) % RingSize]); + } + + private void RebuildTrees() { + switch (this._treeState) { + case Rebuild.Unbuilt: + this.ReadCodeTree(); + this.ReadOffsetTree(5); + this._treeState = Rebuild.Build1; + this._rebuildRemaining = 1024; + break; + case Rebuild.Build1: + this.ReadOffsetTree(6); + this._treeState = Rebuild.Build2; + this._rebuildRemaining = 1024; + break; + case Rebuild.Build2: + this.ReadOffsetTree(7); + this._treeState = Rebuild.Build3; + this._rebuildRemaining = 2048; + break; + case Rebuild.Build3: + if (this._bits.ReadBit() == 1) + this.ReadCodeTree(); + this.ReadOffsetTree(8); + this._treeState = Rebuild.Continuing; + this._rebuildRemaining = 4096; + break; + default: + if (this._bits.ReadBit() == 1) { + this.ReadCodeTree(); + this.ReadOffsetTree(8); + } + this._rebuildRemaining = 4096; + break; + } + } + + private void ReadCodeTree() { + var numCodes = this._bits.ReadBits(5); + var minCodeLength = this._bits.ReadBits(3); + if (numCodes > 29) + throw new InvalidDataException("-pm2- code tree has more than 29 codes."); + this._needOffsetTree = numCodes >= 10 && !(numCodes == 29 && minCodeLength == 0); + if (minCodeLength == 0) { + this._codeTree[0] = (byte)((numCodes - 1) | LeafFlag); + return; + } + var lengthBits = this._bits.ReadBits(3); + var lengths = new byte[numCodes]; + for (var i = 0; i < numCodes; ++i) { + var val = this._bits.ReadBits(lengthBits); + lengths[i] = val == 0 ? (byte)0 : (byte)(minCodeLength + val - 1); + } + BuildTree(this._codeTree, lengths); + } + + private void ReadOffsetTree(int numOffsets) { + if (!this._needOffsetTree) + return; + var lengths = new byte[numOffsets]; + int numCodes = 0, single = 0; + for (var off = 0; off < numOffsets; ++off) { + lengths[off] = (byte)this._bits.ReadBits(3); + if (lengths[off] != 0) { + single = off; + ++numCodes; + } + } + if (numCodes == 1) { + this._offsetTree[0] = (byte)(single | LeafFlag); + return; + } + BuildTree(this._offsetTree, lengths); + } + } + + /// Builds a canonical tree breadth-first: shorter codes take the earlier slots at each depth. + internal static void BuildTree(byte[] tree, byte[] lengths) { + int nextEntry = 0, allocated = 1, codeLength = 0; + bool remaining; + do { + // Expand every open slot into a branch with two children, one level deeper. + var newNodes = (allocated - nextEntry) * 2; + if (allocated + newNodes <= tree.Length) { + var end = allocated; + while (nextEntry < end) { + tree[nextEntry] = (byte)allocated; + allocated += 2; + ++nextEntry; + } + } + ++codeLength; + remaining = false; + for (var i = 0; i < lengths.Length; ++i) { + if (lengths[i] == codeLength) { + if (nextEntry >= allocated) + continue; // over-subscribed: the reference ignores the code + tree[nextEntry++] = (byte)(i | LeafFlag); + } else if (lengths[i] > codeLength) + remaining = true; + } + } while (remaining); + } + + internal static int ReadFromTree(LhaBitInput bits, byte[] tree) { + var code = tree[0]; + while ((code & LeafFlag) == 0) { + var index = code + bits.ReadBit(); + if (index >= tree.Length) + throw new InvalidDataException("-pm2- tree walk leaves the tree."); + code = tree[index]; + } + return code & ~LeafFlag; + } +} diff --git a/Compression.Core/Dictionary/Lzh/PmaDecoder.cs b/Compression.Core/Dictionary/Lzh/PmaDecoder.cs deleted file mode 100644 index 647aa4ccb..000000000 --- a/Compression.Core/Dictionary/Lzh/PmaDecoder.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Compression.Core.Entropy.Ppmd; - -namespace Compression.Core.Dictionary.Lzh; - -/// -/// Decodes data compressed with the PMA (Prediction by Matching of Algorithms) method -/// used in LHA archives with methods -pm1- and -pm2-. -/// PMA uses PPMd (Prediction by Partial Matching) context modeling with arithmetic coding. -/// -public static class PmaDecoder { - /// - /// Decodes PMA-compressed data. - /// - /// The compressed data. - /// The expected decompressed size. - /// Context order: 2 for -pm1-, 3 for -pm2-. - /// The decompressed data. - public static byte[] Decode(byte[] compressed, int originalSize, int order) { - if (originalSize == 0) - return []; - - using var ms = new MemoryStream(compressed); - var decoder = new PpmdRangeDecoder(ms); - var model = new PpmdModelH(order); - - var output = new byte[originalSize]; - for (var i = 0; i < originalSize; ++i) - output[i] = model.DecodeSymbol(decoder); - - return output; - } -} diff --git a/Compression.Core/Dictionary/Lzh/PmaEncoder.cs b/Compression.Core/Dictionary/Lzh/PmaEncoder.cs deleted file mode 100644 index e14af2e37..000000000 --- a/Compression.Core/Dictionary/Lzh/PmaEncoder.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Compression.Core.Entropy.Ppmd; - -namespace Compression.Core.Dictionary.Lzh; - -/// -/// Encodes data using the PMA (Prediction by Matching of Algorithms) method -/// used in LHA archives with methods -pm1- and -pm2-. -/// PMA uses PPMd (Prediction by Partial Matching) context modeling with arithmetic coding. -/// -public static class PmaEncoder { - /// - /// Encodes data using PMA compression. - /// - /// The data to compress. - /// Context order: 2 for -pm1-, 3 for -pm2-. - /// The compressed data. - public static byte[] Encode(ReadOnlySpan data, int order) { - if (data.Length == 0) - return []; - - using var ms = new MemoryStream(); - var encoder = new PpmdRangeEncoder(ms); - var model = new PpmdModelH(order); - - for (var i = 0; i < data.Length; ++i) - model.EncodeSymbol(encoder, data[i]); - - encoder.Finish(); - return ms.ToArray(); - } -} diff --git a/Compression.Core/Dictionary/Lzh/PmaHistory.cs b/Compression.Core/Dictionary/Lzh/PmaHistory.cs new file mode 100644 index 000000000..2faf182a6 --- /dev/null +++ b/Compression.Core/Dictionary/Lzh/PmaHistory.cs @@ -0,0 +1,60 @@ +namespace Compression.Core.Dictionary.Lzh; + +/// +/// The move-to-front byte history PMarc's -pm1- and -pm2- code literals against: a +/// literal is sent as how far back along this ring of all 256 byte values it lies from the last byte +/// written, and every byte written moves to the front. +/// +/// +/// The ring starts as 0x20-0x7F, 0x00-0x1F, 0xA0-0xDF, 0x80-0x9F, 0xE0-0xFF. Sourcing: converted from +/// lhasa's pma_common.c (ISC, Simon Howard); see THIRD-PARTY-NOTICE.lhasa.txt beside this file. +/// +internal sealed class PmaHistory { + private readonly byte[] _prev = new byte[256]; + private readonly byte[] _next = new byte[256]; + private byte _head; + + public PmaHistory() { + for (var i = 0; i < 256; ++i) { + this._prev[i] = (byte)(i + 1); + this._next[i] = (byte)(i - 1); + } + this._head = 0x20; + this.Link(0x7F, 0x00); + this.Link(0x1F, 0xA0); + this.Link(0xDF, 0x80); + this.Link(0x9F, 0xE0); + this.Link(0xFF, 0x20); + } + + private void Link(byte from, byte to) { + this._prev[from] = to; + this._next[to] = from; + } + + /// Returns the byte steps back from the most recent one. + public byte Find(int count) { + var code = this._head; + if (count < 128) + for (var i = 0; i < count; ++i) + code = this._prev[code]; + else + for (var i = 0; i < 256 - count; ++i) + code = this._next[code]; + return code; + } + + /// Moves to the front. + public void Update(byte b) { + if (this._head == b) + return; + this._next[this._prev[b]] = this._next[b]; + this._prev[this._next[b]] = this._prev[b]; + var oldHead = this._head; + this._prev[b] = oldHead; + this._next[b] = this._next[oldHead]; + this._prev[this._next[oldHead]] = b; + this._next[oldHead] = b; + this._head = b; + } +} diff --git a/Compression.Core/Dictionary/Lzh/THIRD-PARTY-NOTICE.lhasa.txt b/Compression.Core/Dictionary/Lzh/THIRD-PARTY-NOTICE.lhasa.txt new file mode 100644 index 000000000..a44a0b9f4 --- /dev/null +++ b/Compression.Core/Dictionary/Lzh/THIRD-PARTY-NOTICE.lhasa.txt @@ -0,0 +1,36 @@ +lhasa (LHA-family decoders) - converted to C# + +Source: https://github.com/fragglet/lhasa +Revision: 75ed83559f23e9538e0045c62f53f77ab03d03d6 (master, 2026-06-17) +Author: Simon Howard +Licence: ISC (reproduced below) + +Copyright (c) 2011-2025, Simon Howard + +Permission to use, copy, modify, and/or distribute this software +for any purpose with or without fee is hereby granted, provided +that the above copyright notice and this permission notice appear +in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL +WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE +AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR +CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, +NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +Files converted (decoders only; lhasa has no encoders): + + lib/lh1_decoder.c -> Lh1Tree.cs, Lh1Decoder.cs + lib/lzs_decoder.c -> LzsDecoder.cs + lib/lz5_decoder.c -> Lz5Decoder.cs (initial ring contents copied exactly) + lib/pm1_decoder.c -> Pm1Decoder.cs (tables copied exactly) + lib/pm2_decoder.c, tree_decode.c -> Pm2Decoder.cs (tables copied exactly) + lib/pma_common.c -> PmaHistory.cs + lib/bit_stream_reader.c -> LhaBitInput.cs + +The -lh1-, -lzs- and -lz5- encoders (Lh1Encoder.cs, LzsEncoder.cs, Lz5Encoder.cs) +are our own, built to the bit streams these decoders read and verified by lhasa +extracting what they write. diff --git a/Compression.Core/REFERENCE.md b/Compression.Core/REFERENCE.md index ecc3be136..02761f7a1 100644 --- a/Compression.Core/REFERENCE.md +++ b/Compression.Core/REFERENCE.md @@ -1484,25 +1484,25 @@ Implements `IBuildingBlock`. ### Namespace `Compression.Core.Dictionary.Lzh` -[`Lh1Decoder`](#lh1decoder) · [`Lh1Encoder`](#lh1encoder) · [`Lh2Decoder`](#lh2decoder) · [`Lh3Decoder`](#lh3decoder) · [`Lz5Decoder`](#lz5decoder) · [`Lz5Encoder`](#lz5encoder) · [`LzhBuildingBlock`](#lzhbuildingblock) · [`LzhConstants`](#lzhconstants) · [`LzhDecoder`](#lzhdecoder) · [`LzhEncoder`](#lzhencoder) · [`LzsDecoder`](#lzsdecoder) · [`LzsEncoder`](#lzsencoder) · [`PmaDecoder`](#pmadecoder) · [`PmaEncoder`](#pmaencoder) +[`Lh1Decoder`](#lh1decoder) · [`Lh1Encoder`](#lh1encoder) · [`Lh2Decoder`](#lh2decoder) · [`Lh3Decoder`](#lh3decoder) · [`Lz5Decoder`](#lz5decoder) · [`Lz5Encoder`](#lz5encoder) · [`LzhBuildingBlock`](#lzhbuildingblock) · [`LzhConstants`](#lzhconstants) · [`LzhDecoder`](#lzhdecoder) · [`LzhEncoder`](#lzhencoder) · [`LzsDecoder`](#lzsdecoder) · [`LzsEncoder`](#lzsencoder) · [`Pm1Decoder`](#pm1decoder) · [`Pm2Decoder`](#pm2decoder) #### `Lh1Decoder` -Decodes LHA -lh1- compressed data. Uses a 4KB sliding window with dynamic (adaptive) Huffman coding. +Decodes LHarc `-lh1-`: LZSS over a 4 KiB window with an adaptive Huffman tree for literals and copy lengths, and a fixed code for the high six bits of the copy distance. | Member | Signature | Summary | | --- | --- | --- | -| `Lh1Decoder` | `Lh1Decoder(Stream input)` | Initializes a new `Lh1Decoder`. | -| `Decode` | `byte[] Decode(int originalSize)` | Decodes the compressed data. | +| `Lh1Decoder` | `Lh1Decoder(Stream input)` | Initializes a new `Lh1Decoder` over the rest of `input`. | +| `Decode` | `byte[] Decode(int originalSize)` | Decodes `originalSize` bytes. | #### `Lh1Encoder` -Encodes data using the LHA -lh1- adaptive Huffman method. +Encodes LHarc `-lh1-` (see `Lh1Decoder`): greedy LZSS matches over a 4 KiB window, coded through the same adaptive `Lh1Tree` the decoder evolves. | Member | Signature | Summary | | --- | --- | --- | | `Lh1Encoder` | `Lh1Encoder()` | | -| `Encode` | `static byte[] Encode(ReadOnlySpan data)` | Compresses data using the -lh1- algorithm. | +| `Encode` | `static byte[] Encode(ReadOnlySpan data)` | Encodes `data`. | #### `Lh2Decoder` @@ -1522,21 +1522,21 @@ Decodes LHA `-lh3-`: LZSS over an 8 KiB window with static Huffman trees sent pe #### `Lz5Decoder` -Decodes -lz5- compressed data (LArc variant). Uses LZSS with a 4KB sliding window, no Huffman coding. +Decodes LArc `-lz5-`: LZSS over a 4 KiB ring buffer, byte-aligned. | Member | Signature | Summary | | --- | --- | --- | -| `Decode` | `static byte[] Decode(ReadOnlySpan compressed, int originalSize)` | Decodes -lz5- compressed data. | -| `Decode` | `static byte[] Decode(byte[] compressed, int originalSize)` | Decodes -lz5- compressed data. | +| `Decode` | `static byte[] Decode(ReadOnlySpan compressed, int originalSize)` | Decodes `originalSize` bytes of `-lz5-` data. | +| `Decode` | `static byte[] Decode(byte[] compressed, int originalSize)` | Decodes `originalSize` bytes of `-lz5-` data. | #### `Lz5Encoder` -Encodes data using the -lz5- format (LArc variant). Plain LZSS with a 4KB sliding window, no Huffman coding. +Encodes LArc `-lz5-` (see `Lz5Decoder` for the stream layout). | Member | Signature | Summary | | --- | --- | --- | -| `Encode` | `static byte[] Encode(ReadOnlySpan data)` | Encodes data using the -lz5- format. | -| `Encode` | `static byte[] Encode(byte[] data)` | Encodes data using the -lz5- format. | +| `Encode` | `static byte[] Encode(ReadOnlySpan data)` | Encodes `data`. | +| `Encode` | `static byte[] Encode(byte[] data)` | Encodes `data`. | #### `LzhBuildingBlock` @@ -1601,37 +1601,37 @@ Encodes data using LZH compression (methods -lh5-, -lh6-, -lh7-). Uses LZSS with #### `LzsDecoder` -Decodes -lzs- compressed data (LArc format). Uses plain LZSS with a 2KB sliding window, no Huffman coding. +Decodes LArc `-lzs-`: LZSS over a 2 KiB ring buffer, with no entropy coding. | Member | Signature | Summary | | --- | --- | --- | -| `Decode` | `static byte[] Decode(ReadOnlySpan compressed, int originalSize)` | Decodes -lzs- compressed data. | -| `Decode` | `static byte[] Decode(byte[] compressed, int originalSize)` | Decodes -lzs- compressed data. | +| `Decode` | `static byte[] Decode(ReadOnlySpan compressed, int originalSize)` | Decodes `originalSize` bytes of `-lzs-` data. | +| `Decode` | `static byte[] Decode(byte[] compressed, int originalSize)` | Decodes `originalSize` bytes of `-lzs-` data. | #### `LzsEncoder` -Encodes data using the -lzs- format (LArc). Plain LZSS with a 2KB sliding window, no Huffman coding. +Encodes LArc `-lzs-` (see `LzsDecoder` for the stream layout). | Member | Signature | Summary | | --- | --- | --- | -| `Encode` | `static byte[] Encode(ReadOnlySpan data)` | Encodes data using the -lzs- format. | -| `Encode` | `static byte[] Encode(byte[] data)` | Encodes data using the -lzs- format. | +| `Encode` | `static byte[] Encode(ReadOnlySpan data)` | Encodes `data`. | +| `Encode` | `static byte[] Encode(byte[] data)` | Encodes `data`. | -#### `PmaDecoder` +#### `Pm1Decoder` -Decodes data compressed with the PMA (Prediction by Matching of Algorithms) method used in LHA archives with methods -pm1- and -pm2-. PMA uses PPMd (Prediction by Partial Matching) context modeling with arithmetic coding. +Decodes PMarc `-pm1-`: alternating runs of literals and copies over a 16 KiB window, all coded with fixed (static) prefix codes whose reach grows with the amount of output. | Member | Signature | Summary | | --- | --- | --- | -| `Decode` | `static byte[] Decode(byte[] compressed, int originalSize, int order)` | Decodes PMA-compressed data. | +| `Decode` | `static byte[] Decode(byte[] compressed, int originalSize)` | Decodes `originalSize` bytes of `-pm1-` data. | -#### `PmaEncoder` +#### `Pm2Decoder` -Encodes data using the PMA (Prediction by Matching of Algorithms) method used in LHA archives with methods -pm1- and -pm2-. PMA uses PPMd (Prediction by Partial Matching) context modeling with arithmetic coding. +Decodes PMarc `-pm2-`: LZSS over an 8 KiB window with literals coded against a move-to-front history and two small Huffman trees that are re-sent as the stream grows. | Member | Signature | Summary | | --- | --- | --- | -| `Encode` | `static byte[] Encode(ReadOnlySpan data, int order)` | Encodes data using PMA compression. | +| `Decode` | `static byte[] Decode(byte[] compressed, int originalSize)` | Decodes `originalSize` bytes of `-pm2-` data. | ### Namespace `Compression.Core.Dictionary.Lzham` diff --git a/Compression.NativeUI/Views/CreateOptionsViewModel.cs b/Compression.NativeUI/Views/CreateOptionsViewModel.cs index ee1d767e2..dce6dfd6b 100644 --- a/Compression.NativeUI/Views/CreateOptionsViewModel.cs +++ b/Compression.NativeUI/Views/CreateOptionsViewModel.cs @@ -408,12 +408,6 @@ public string HintText { (F.Lzh, "pm0") => "pm0: PMA store mode — no compression. PMA-compatible container format.\n" + "Opens with: PMarc, Unlha32.dll", - (F.Lzh, "pm1") => - "pm1: PMA with PPMd order-2 prediction. Good for text files.\n" - + "Opens with: PMarc, Unlha32.dll", - (F.Lzh, "pm2") => - "pm2: PMA with PPMd order-3 prediction. Best LHA method for text and source code.\n" - + "Opens with: PMarc, Unlha32.dll", (F.Lzh, _) => "lh5 (8 KB) is the standard default. lh7 (64 KB) gives best general compression. " + "pm2 uses PPMd and excels on text. lh1-lh3 are legacy adaptive Huffman.\n" @@ -556,8 +550,7 @@ private void PopulateMethods() { "lh5 (default, 8 KB)", "lh7 (64 KB)", "lh6 (32 KB)", "lh4 (4 KB)", "lh1 (adaptive Huffman 4 KB)", "lh0 (store)", "lzs (LZSS 2 KB)", "lz5 (LZSS 4 KB)", - "pm0 (PMA store)", "pm1 (PMA PPMd order-2)", "pm2 (PMA PPMd order-3)", - "pm2 (PPMd order-3)", "pm1 (PPMd order-2)", "pm0 (PMA store)"], + "pm0 (PMA store)"], F.Arj => [ "Method 1 (default, best)", "Method 2", "Method 3 (fastest)", "Store"], F.Arc => [ diff --git a/Compression.Tests/Compression.Tests.csproj b/Compression.Tests/Compression.Tests.csproj index 37d03e897..67f6c0399 100644 --- a/Compression.Tests/Compression.Tests.csproj +++ b/Compression.Tests/Compression.Tests.csproj @@ -139,7 +139,8 @@ XarVectors.%(Filename)%(Extension) - + + LzhVectors.%(Filename)%(Extension) diff --git a/Compression.Tests/Lzh/LhaHeaderVectorTests.cs b/Compression.Tests/Lzh/LhaHeaderVectorTests.cs new file mode 100644 index 000000000..ae2f18d36 --- /dev/null +++ b/Compression.Tests/Lzh/LhaHeaderVectorTests.cs @@ -0,0 +1,55 @@ +using System.Reflection; +using FileFormat.Lzh; + +namespace Compression.Tests.Lzh; + +/// +/// Header levels 1 and 3 and directory names, from archives in lhasa's test suite written by LHA for +/// Amiga 1.22 and LHA OS/2 2.08 (see ReferenceVectors/README.md). Each member must land at the data its +/// header describes, which the CRC-16 check on extraction proves. +/// +[TestFixture] +public class LhaHeaderVectorTests { + private static byte[] Vector(string name) { + using var s = Assembly.GetExecutingAssembly().GetManifestResourceStream("LzhVectors." + name) + ?? throw new FileNotFoundException("Embedded LHA vector missing: " + name); + using var ms = new MemoryStream(); + s.CopyTo(ms); + return ms.ToArray(); + } + + private static List<(LhaEntry Entry, byte[] Data)> ExtractAll(string vector) { + using var reader = new LhaReader(new MemoryStream(Vector(vector))); + return reader.Entries.Select(e => (e, reader.ExtractEntry(e))).ToList(); + } + + [Test, Category("HappyPath")] + public void Level1_ExtendedHeaders_GiveTheDirectoryAndTheDataStart() { + var member = ExtractAll("lha_amiga_122_level1.lzh").Single(); + Assert.That(member.Entry.HeaderLevel, Is.EqualTo(1)); + Assert.That(member.Entry.FileName, Is.EqualTo("subdir/subdir2/hello.txt")); + } + + [Test, Category("HappyPath")] + public void Level3_ExtendedHeaders_GiveTheDirectory() { + var member = ExtractAll("lha_os2_208_h3_subdir.lzh").Single(m => m.Entry.FileName.EndsWith("HELLO.TXT", StringComparison.Ordinal)); + Assert.That(member.Entry.FileName, Is.EqualTo("subdir/subdir2/HELLO.TXT")); + } + + [Test, Category("EdgeCase")] + public void AmigaArchive_WithDirectoriesStoredAsLh0_ReadsEveryFile() { + var names = ExtractAll("lha_amiga_122_lh0_dirs_bug.lzh").Select(m => m.Entry.FileName).ToList(); + Assert.That(names, Does.Contain("MainDir/00_File.txt")); + Assert.That(names, Does.Contain("MainDir/03_Dir/File01.txt")); + } + + [Test, Category("EdgeCase")] + public void TruncatedLastMember_IsListedButFailsToExtract() { + var archive = Vector("lha_os2_208_h3_subdir.lzh"); + using var full = new LhaReader(new MemoryStream(archive)); + var last = full.Entries[^1]; + using var reader = new LhaReader(new MemoryStream(archive[..(int)(last.DataOffset + last.CompressedSize - 1)])); + Assert.That(reader.Entries, Has.Count.EqualTo(full.Entries.Count)); + Assert.Catch(() => reader.ExtractEntry(reader.Entries[^1])); + } +} diff --git a/Compression.Tests/Lzh/LhaOracleTests.cs b/Compression.Tests/Lzh/LhaOracleTests.cs new file mode 100644 index 000000000..2107f487e --- /dev/null +++ b/Compression.Tests/Lzh/LhaOracleTests.cs @@ -0,0 +1,86 @@ +#pragma warning disable CS1591 +using FileFormat.Lzh; + +namespace Compression.Tests.Lzh; + +/// +/// Every method and header level our LHA writer produces must extract in lhasa (lha), the +/// reference reader of the LHA family; and archives lhasa's own test suite holds must read here. +/// +[TestFixture] +[Category("ExternalInterop")] +public class LhaOracleTests { + private string _dir = null!; + + [SetUp] + public void SetUp() { + this._dir = Path.Combine(Path.GetTempPath(), "cwb_lha_" + Guid.NewGuid().ToString("N")[..8]); + Directory.CreateDirectory(this._dir); + } + + [TearDown] + public void TearDown() { + try { Directory.Delete(this._dir, true); } catch { /* best effort */ } + } + + private static void RequireLha() { + if (!FsInteropToolbox.WslAvailable) + Assert.Ignore("WSL not available."); + if (!FsInteropToolbox.WslHasTool("lha")) + Assert.Ignore("lha is not installed. Run inside WSL / on Linux: `sudo apt install -y lhasa`."); + } + + private static Dictionary Inputs() { + var text = System.Text.Encoding.ASCII.GetBytes(string.Concat( + Enumerable.Range(1, 2500).Select(i => $"line {i:D5}: the quick brown fox jumps over the lazy dog #{i % 37}\n"))); + var random = new byte[9000]; + new Random(41).NextBytes(random); + return new(StringComparer.Ordinal) { + ["TEXT.TXT"] = text, // > 64 KiB: tree rebuilds, window wraps + ["RANDOM.BIN"] = random, // incompressible: stored fallback + ["REPEAT.BIN"] = [.. random, .. random, .. random], + ["ONE.TXT"] = "a"u8.ToArray(), + ["EMPTY.TXT"] = [], + }; + } + + public static IEnumerable WritableCases() { + string[] methods = [ + LhaConstants.MethodLh0, LhaConstants.MethodLh1, LhaConstants.MethodLh4, LhaConstants.MethodLh5, + LhaConstants.MethodLh6, LhaConstants.MethodLh7, LhaConstants.MethodLz4, LhaConstants.MethodLzs, + LhaConstants.MethodLz5, LhaConstants.MethodPm0, + ]; + foreach (var method in methods) + foreach (var level in new[] { LhaHeaderLevel.Level0, LhaHeaderLevel.Level1, LhaHeaderLevel.Level2 }) + yield return new TestCaseData(method, level).SetName($"Writer_{method.Trim('-')}_{level}_ExtractsInLhasa"); + } + + [TestCaseSource(nameof(WritableCases))] + public void Writer_Output_ExtractsInLhasa(string method, LhaHeaderLevel level) { + RequireLha(); + var inputs = Inputs(); + var generation = method is LhaConstants.MethodLz4 or LhaConstants.MethodLzs or LhaConstants.MethodLz5 + ? LhaArchiverGeneration.LArc + : method == LhaConstants.MethodPm0 ? LhaArchiverGeneration.PmArc1 : LhaArchiverGeneration.Extended; + var writer = new LhaWriter(method, generation, level); + foreach (var (name, data) in inputs) + writer.AddFile(name, data); + var archive = Path.Combine(this._dir, "ours.lzh"); + File.WriteAllBytes(archive, writer.ToArray()); + + var into = Path.Combine(this._dir, "out"); + Directory.CreateDirectory(into); + var test = FsInteropToolbox.RunWsl($"lha t {FsInteropToolbox.WinToWsl(archive)}"); + Assert.That(test.ExitCode, Is.EqualTo(0), $"lha t rejected our {method} {level} archive:\n{test.StdOut}\n{test.StdErr}"); + var x = FsInteropToolbox.RunWsl($"cd {FsInteropToolbox.WinToWsl(into)} && lha xq {FsInteropToolbox.WinToWsl(archive)}"); + Assert.That(x.ExitCode, Is.EqualTo(0), $"lha x failed:\n{x.StdOut}\n{x.StdErr}"); + foreach (var (name, data) in inputs) { + var path = Path.Combine(into, name); + if (!File.Exists(path)) + path = Directory.EnumerateFiles(into, "*", SearchOption.AllDirectories) + .FirstOrDefault(f => string.Equals(Path.GetFileName(f), name, StringComparison.OrdinalIgnoreCase)) ?? path; + Assert.That(File.Exists(path), Is.True, $"lha did not extract {name}"); + Assert.That(File.ReadAllBytes(path), Is.EqualTo(data).AsCollection, name); + } + } +} diff --git a/Compression.Tests/Lzh/LhaSuiteVectorTests.cs b/Compression.Tests/Lzh/LhaSuiteVectorTests.cs new file mode 100644 index 000000000..bfc0d54fb --- /dev/null +++ b/Compression.Tests/Lzh/LhaSuiteVectorTests.cs @@ -0,0 +1,61 @@ +using System.Reflection; +using System.Security.Cryptography; +using FileFormat.Lzh; + +namespace Compression.Tests.Lzh; + +/// +/// Archives from lhasa's test suite, written by LHarc 1.13, LArc 3.33, PMarc 1.24 and 2, LHA for +/// Amiga 1.22, LHA OS/2 2.08 and UNLHA32 (see ReferenceVectors/README.md). Every member must pass the +/// CRC-16 its archiver recorded; several of them hold the same GPL-2 text, so the methods must also +/// agree with each other byte for byte. +/// +[TestFixture] +public class LhaSuiteVectorTests { + private static byte[] Vector(string name) { + using var s = Assembly.GetExecutingAssembly().GetManifestResourceStream("LzhVectors." + name) + ?? throw new FileNotFoundException("Embedded LHA vector missing: " + name); + using var ms = new MemoryStream(); + s.CopyTo(ms); + return ms.ToArray(); + } + + private static List<(LhaEntry Entry, byte[] Data)> ExtractAll(string vector) { + using var reader = new LhaReader(new MemoryStream(Vector(vector))); + return reader.Entries.Select(e => (e, reader.ExtractEntry(e))).ToList(); + } + + [Test, Category("HappyPath")] + [TestCase("lharc113_lh1.lzh", "-lh1-", "GPL-2", 0, TestName = "LHarc 1.13 -lh1-, level 0")] + [TestCase("larc333_lz5.lzs", "-lz5-", "GPL-2", 0, TestName = "LArc 3.33 -lz5-, level 0")] + [TestCase("larc333_initial.lzs", "-lz5-", "initial.bin", 0, TestName = "LArc 3.33 -lz5- reaching into the initial ring")] + [TestCase("generated_lzs.lzs", "-lzs-", "GPL-2", 0, TestName = "-lzs-, level 0")] + [TestCase("pmarc124_pm1.pma", "-pm1-", "COPYING.TXT", 0, TestName = "PMarc 1.24 -pm1-")] + [TestCase("pmarc2_pm2.pma", "-pm2-", "GPL-2.", 0, TestName = "PMarc 2 -pm2-")] + [TestCase("lha_os2_208_h3_lh5.lzh", "-lh5-", "GPL-2", 3, TestName = "LHA OS/2 2.08 level-3 header")] + [TestCase("unlha32_h2_lhx.lzh", "-lhx-", "GPL-2", 2, TestName = "UNLHA32 -lhx- (1 MiB window)")] + public void Member_DecodesToItsCrc(string vector, string method, string name, int level) { + var (entry, data) = ExtractAll(vector).Single(m => m.Entry.FileName == name); + Assert.That(entry.Method, Is.EqualTo(method)); + Assert.That(entry.HeaderLevel, Is.EqualTo(level)); + Assert.That(data, Has.Length.EqualTo(entry.OriginalSize)); + } + + [Test, Category("HappyPath")] + public void DifferentMethods_OfTheSameText_Agree() { + var expected = SHA256.HashData(ExtractAll("lharc113_lh1.lzh").Single().Data); + foreach (var vector in new[] { "larc333_lz5.lzs", "generated_lzs.lzs", "lha_os2_208_h3_lh5.lzh", "unlha32_h2_lhx.lzh" }) + Assert.That(SHA256.HashData(ExtractAll(vector).Single(m => m.Entry.FileName == "GPL-2").Data), Is.EqualTo(expected), vector); + } + + [Test, Category("Exception")] + public void DamagedMember_FailsItsCrc() { + var archive = Vector("lharc113_lh1.lzh"); + using var probe = new LhaReader(new MemoryStream(archive)); + var entry = probe.Entries.Single(); + archive[entry.DataOffset + entry.CompressedSize / 2] ^= 0x40; + using var reader = new LhaReader(new MemoryStream(archive)); + Assert.Catch(() => reader.ExtractEntry(reader.Entries.Single())); + } + +} diff --git a/Compression.Tests/Lzh/LhaTests.cs b/Compression.Tests/Lzh/LhaTests.cs index 71ca1d1c5..390018580 100644 --- a/Compression.Tests/Lzh/LhaTests.cs +++ b/Compression.Tests/Lzh/LhaTests.cs @@ -192,11 +192,31 @@ public class LzsDecoderTests { [Category("HappyPath")] [Test] public void Decode_LiteralOnly() { - // All literal flags, 3 bytes - byte[] compressed = [0x07, 0x41, 0x42, 0x43]; // flags=0b111, A B C + // Bit stream: 1 + 'A', 1 + 'B', 1 + 'C' (each literal is a 1 flag bit and 8 bits), zero-padded. + byte[] compressed = [0xA0, 0xD0, 0xA8, 0x60]; var result = LzsDecoder.Decode(compressed, 3); Assert.That(result, Is.EqualTo("ABC"u8.ToArray())); } + + [Category("HappyPath")] + [Test] + public void Decode_CopyFromInitialSpaces() { + // 0 + position 0 (11 bits) + length 3-2 (4 bits): copies three of the spaces the ring starts with. + byte[] compressed = [0b0000_0000, 0b0000_0001]; + Assert.That(LzsDecoder.Decode(compressed, 3), Is.EqualTo(" "u8.ToArray())); + } + + [Category("RoundTrip")] + [Test] + public void Encoder_RoundTrips() { + var data = System.Text.Encoding.ASCII.GetBytes(string.Concat(Enumerable.Repeat("lzs round trip ", 300))); + Assert.That(LzsDecoder.Decode(LzsEncoder.Encode(data), data.Length), Is.EqualTo(data)); + } + + [Category("Exception")] + [Test] + public void TruncatedStream_Throws() => + Assert.Throws(() => LzsDecoder.Decode([0xA0], 5)); } [TestFixture] @@ -209,4 +229,19 @@ public void Decode_LiteralOnly() { var result = Lz5Decoder.Decode(compressed, 3); Assert.That(result, Is.EqualTo("ABC"u8.ToArray())); } + + [Category("HappyPath")] + [Test] + public void Decode_CopyFromInitialRing() { + // Copy (flag 0) from position 13 (the run of byte 0x01), length 3. + byte[] compressed = [0x00, 13, 0x00]; + Assert.That(Lz5Decoder.Decode(compressed, 3), Is.EqualTo(new byte[] { 1, 1, 1 })); + } + + [Category("RoundTrip")] + [Test] + public void Encoder_RoundTrips() { + var data = System.Text.Encoding.ASCII.GetBytes(string.Concat(Enumerable.Repeat("lz5 round trip ", 400))); + Assert.That(Lz5Decoder.Decode(Lz5Encoder.Encode(data), data.Length), Is.EqualTo(data)); + } } diff --git a/Compression.Tests/Lzh/PmaTests.cs b/Compression.Tests/Lzh/PmaTests.cs deleted file mode 100644 index 2e2409406..000000000 --- a/Compression.Tests/Lzh/PmaTests.cs +++ /dev/null @@ -1,138 +0,0 @@ -using Compression.Core.Dictionary.Lzh; -using FileFormat.Lzh; - -namespace Compression.Tests.Lzh; - -[TestFixture] -public class PmaTests { - [Category("HappyPath")] - [Category("RoundTrip")] - [Test] - public void Pm2_RoundTrip_Pattern() { - var data = new byte[500]; - for (var i = 0; i < data.Length; ++i) - data[i] = (byte)(i % 10); - - var compressed = PmaEncoder.Encode(data, 3); - var decompressed = PmaDecoder.Decode(compressed, data.Length, 3); - Assert.That(decompressed, Is.EqualTo(data)); - } - - [Category("HappyPath")] - [Category("RoundTrip")] - [Test] - public void Pm1_RoundTrip_Pattern() { - var data = new byte[500]; - for (var i = 0; i < data.Length; ++i) - data[i] = (byte)(i % 7); - - var compressed = PmaEncoder.Encode(data, 2); - var decompressed = PmaDecoder.Decode(compressed, data.Length, 2); - Assert.That(decompressed, Is.EqualTo(data)); - } - - [Category("HappyPath")] - [Category("RoundTrip")] - [Test] - public void Pm2_RoundTrip_Text() { - var data = "Hello, PMA compression! This is a test of PPMd-based encoding in LHA archives."u8.ToArray(); - - var compressed = PmaEncoder.Encode(data, 3); - var decompressed = PmaDecoder.Decode(compressed, data.Length, 3); - Assert.That(decompressed, Is.EqualTo(data)); - } - - [Category("EdgeCase")] - [Category("RoundTrip")] - [Test] - public void Pm2_Empty() { - var compressed = PmaEncoder.Encode([], 3); - Assert.That(compressed, Is.Empty); - - var decompressed = PmaDecoder.Decode(compressed, 0, 3); - Assert.That(decompressed, Is.Empty); - } - - [Category("End2End")] - [Category("RoundTrip")] - [Test] - public void Pm2_Archive_RoundTrip() { - var data = new byte[300]; - for (var i = 0; i < data.Length; ++i) - data[i] = (byte)(i % 13); - - var writer = new LhaWriter(LhaConstants.MethodPm2); - writer.AddFile("test.bin", data); - var archive = writer.ToArray(); - - using var ms = new MemoryStream(archive); - var reader = new LhaReader(ms); - - Assert.That(reader.Entries, Has.Count.EqualTo(1)); - Assert.That(reader.Entries[0].Method, Is.EqualTo(LhaConstants.MethodPm2)); - var extracted = reader.ExtractEntry(reader.Entries[0]); - Assert.That(extracted, Is.EqualTo(data)); - } - - [Category("End2End")] - [Category("RoundTrip")] - [Test] - public void Pm1_Archive_RoundTrip() { - var data = new byte[200]; - for (var i = 0; i < data.Length; ++i) - data[i] = (byte)(i % 5); - - var writer = new LhaWriter(LhaConstants.MethodPm1); - writer.AddFile("test.bin", data); - var archive = writer.ToArray(); - - using var ms = new MemoryStream(archive); - var reader = new LhaReader(ms); - - Assert.That(reader.Entries, Has.Count.EqualTo(1)); - Assert.That(reader.Entries[0].Method, Is.EqualTo(LhaConstants.MethodPm1)); - var extracted = reader.ExtractEntry(reader.Entries[0]); - Assert.That(extracted, Is.EqualTo(data)); - } - - [Category("End2End")] - [Category("RoundTrip")] - [Test] - public void Pm0_Archive_RoundTrip() { - var data = "Stored PMA data"u8.ToArray(); - - var writer = new LhaWriter(LhaConstants.MethodPm0); - writer.AddFile("test.txt", data); - var archive = writer.ToArray(); - - using var ms = new MemoryStream(archive); - var reader = new LhaReader(ms); - - Assert.That(reader.Entries, Has.Count.EqualTo(1)); - Assert.That(reader.Entries[0].Method, Is.EqualTo(LhaConstants.MethodPm0)); - var extracted = reader.ExtractEntry(reader.Entries[0]); - Assert.That(extracted, Is.EqualTo(data)); - } - - [Category("End2End")] - [Category("RoundTrip")] - [Test] - public void Pm2_MultipleFiles_RoundTrip() { - var data1 = new byte[100]; - var data2 = new byte[200]; - for (var i = 0; i < data1.Length; ++i) data1[i] = (byte)(i % 10); - for (var i = 0; i < data2.Length; ++i) data2[i] = (byte)(i % 7); - - var writer = new LhaWriter(LhaConstants.MethodPm2); - writer.AddFile("f1.bin", data1); - writer.AddFile("f2.bin", data2); - var archive = writer.ToArray(); - - using var ms = new MemoryStream(archive); - var reader = new LhaReader(ms); - - Assert.That(reader.Entries, Has.Count.EqualTo(2)); - Assert.That(reader.ExtractEntry(reader.Entries[0]), Is.EqualTo(data1)); - Assert.That(reader.ExtractEntry(reader.Entries[1]), Is.EqualTo(data2)); - } -} diff --git a/Compression.Tests/Lzh/ReferenceVectors/README.md b/Compression.Tests/Lzh/ReferenceVectors/README.md index 1e28019d2..86236b0f8 100644 --- a/Compression.Tests/Lzh/ReferenceVectors/README.md +++ b/Compression.Tests/Lzh/ReferenceVectors/README.md @@ -1,6 +1,7 @@ # LHA reference archives -Archives written by other LHA implementations, for the methods our writer cannot produce. +Archives written by other LHA-family archivers, for methods and header levels our writer does not +produce or that must read exactly as their archiver wrote them. | File | Method | Written by | Taken from | |------|--------|------------|------------| @@ -19,6 +20,28 @@ Each holds one member, `LICENSE.MIT` (2,853 bytes, an MIT licence text). lhasa i decode either method, so the oracle is the member's CRC-16 recorded by UNLHA32 in the header: the decoded bytes must match it, along with the size and the text's shape. +## lhasa suite archives + +Taken from `test/archives/` of lhasa at `75ed83559f23e9538e0045c62f53f77ab03d03d6` (2026-06-17), renamed +`_`: + +| File | Written by | What it covers | SHA-256 | +|------|------------|----------------|---------| +| `lharc113_lh1.lzh` | LHarc 1.13 | `-lh1-`, level 0 | `594af58cb27d6a80e9bebd108cc82a8ab881a4b5549e4fc10379fbf7a0d83069` | +| `larc333_lz5.lzs` | LArc 3.33 | `-lz5-` | `ebacc5d6fa4f35ef4e2ff7176e9867e410e4dfc5922b5bcfb804185954e1ab89` | +| `larc333_initial.lzs` | LArc 3.33 | `-lz5-` copies from the predefined initial ring | `3d1a8bb82174986a0e4581d83763f22c5bdae42f5a34eb4841bdafb2f14a6f3e` | +| `generated_lzs.lzs` | lhasa's generated `-lzs-` set | `-lzs-` | `a7a0ff6acfef86a49300e4f0dcfbe7a61ba21d05b1585809e5a393ce5635c3b3` | +| `pmarc124_pm1.pma` | PMarc 1.24 | `-pm1-` | `13dfdbd992c2fac4964b3028c646aa68eb7a4a941a4224c4837058c3771b6346` | +| `pmarc2_pm2.pma` | PMarc 2 | `-pm2-` | `4428e8d9d4e5f61aa7fd041fc6048cfdb64052eaf2776f8b23b9430f506e7979` | +| `lha_amiga_122_level1.lzh` | LHA (Amiga) 1.22 | level 1 with directory extended headers | `dae46e78a2709eb664abfb904e859a653d178f53abfac7255afec6e3fe2c340f` | +| `lha_amiga_122_lh0_dirs_bug.lzh` | LHA (Amiga) 1.22 | directories written as `-lh0-` | `6aa959e8cde1419925d3e85b95ff16b2963301df114fc8f2137c7af6b8f4f17a` | +| `lha_os2_208_h3_lh5.lzh` | LHA (OS/2) 2.08 | level 3 header | `d980d1ddc47323fc860b0fb7829d72ce26afbe1f3f59f89e52f0eda54f425212` | +| `lha_os2_208_h3_subdir.lzh` | LHA (OS/2) 2.08 | level 3 with directories | `201051c47213d0964c596d40f574486dbb683665b2ba2a27a81bae69e3a9b655` | +| `unlha32_h2_lhx.lzh` | UNLHA32.DLL 2.67a | `-lhx-` (1 MiB window), level 2 | `c9ab661c5502714f9556be89617bddff237e0e21e72657dc019707aed9751d9b` | + +The oracle for each member is the CRC-16 its archiver recorded. Several hold the same GPL-2 text, and +the tests check that `-lh1-`, `-lz5-`, `-lzs-`, `-lh5-` and `-lhx-` all decode it identically. + ISC licence (lhasa): ``` diff --git a/Compression.Tests/Lzh/ReferenceVectors/generated_lzs.lzs b/Compression.Tests/Lzh/ReferenceVectors/generated_lzs.lzs new file mode 100644 index 0000000000000000000000000000000000000000..981642182f4105f0f0f0709a325923ccfa3f4965 GIT binary patch literal 12696 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 OL?jYMydp*vx9bms8Zt!y literal 0 HcmV?d00001 diff --git a/Compression.Tests/Lzh/ReferenceVectors/larc333_lz5.lzs b/Compression.Tests/Lzh/ReferenceVectors/larc333_lz5.lzs new file mode 100644 index 0000000000000000000000000000000000000000..35223cc32c9aa92f813f8e41fd01c21fd5a40786 GIT binary patch literal 8510 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 literal 0 HcmV?d00001 diff --git a/Compression.Tests/Lzh/ReferenceVectors/lha_os2_208_h3_lh5.lzh b/Compression.Tests/Lzh/ReferenceVectors/lha_os2_208_h3_lh5.lzh new file mode 100644 index 0000000000000000000000000000000000000000..5dd917d4257967fdd480ed304ee171921cbd9d96 GIT binary patch literal 7075 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 N%Q(-kCU1sNKmZO5rWF7H literal 0 HcmV?d00001 diff --git a/Compression.Tests/Lzh/ReferenceVectors/lha_os2_208_h3_subdir.lzh b/Compression.Tests/Lzh/ReferenceVectors/lha_os2_208_h3_subdir.lzh new file mode 100644 index 0000000000000000000000000000000000000000..cc745341fd00b9f84aa779feb659bf11e5af4d58 GIT binary patch literal 271 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 SBqL`dhi)D6)2rajU4Q^0EuBLE literal 0 HcmV?d00001 diff --git a/Compression.Tests/Lzh/ReferenceVectors/pmarc124_pm1.pma b/Compression.Tests/Lzh/ReferenceVectors/pmarc124_pm1.pma new file mode 100644 index 0000000000000000000000000000000000000000..a02c477e8f8437df41f1b68736723084130622de GIT binary patch literal 9984 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 L!x9z$|3Cackn7h^ literal 0 HcmV?d00001 diff --git a/Compression.Tests/Lzh/ReferenceVectors/pmarc2_pm2.pma b/Compression.Tests/Lzh/ReferenceVectors/pmarc2_pm2.pma new file mode 100644 index 0000000000000000000000000000000000000000..f1f7cf80f16ac9fe45b3bd49efa0f35230958da0 GIT binary patch literal 7168 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 C`lcEH literal 0 HcmV?d00001 diff --git a/Compression.Tests/Lzh/ReferenceVectors/unlha32_h2_lhx.lzh b/Compression.Tests/Lzh/ReferenceVectors/unlha32_h2_lhx.lzh new file mode 100644 index 0000000000000000000000000000000000000000..c63395b55e83956ff5f7e32eceb2e53ed647d268 GIT binary patch literal 6903 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 method is LhaConstants.MethodLh0 or LhaConstants.MethodLh1 or @@ -79,7 +79,5 @@ LhaConstants.MethodLh7 or LhaConstants.MethodLz4 or LhaConstants.MethodLzs or LhaConstants.MethodLz5 or - LhaConstants.MethodPm0 or - LhaConstants.MethodPm1 or - LhaConstants.MethodPm2; + LhaConstants.MethodPm0; } diff --git a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Lzh/LhaHeaderParser.cs b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Lzh/LhaHeaderParser.cs new file mode 100644 index 000000000..d2679ecfb --- /dev/null +++ b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Lzh/LhaHeaderParser.cs @@ -0,0 +1,226 @@ +using System.Buffers.Binary; +using System.Text; + +namespace FileFormat.Lzh; + +/// One parsed LHA member header and where its data lies. +/// Offset of the header's first byte. +/// Offset of the member's compressed data (just past every header byte). +/// Size of the compressed data, extended headers excluded. +/// Uncompressed size. +/// The five-character method id, e.g. -lh5-. +/// Directory and file name joined with '/', or empty. +/// CRC-16 of the uncompressed data. +/// Header level, 0-3. +/// Operating-system id byte (0 for level 0, which has none). +/// Modification time. +internal sealed record LhaHeader( + long HeaderOffset, long DataOffset, long CompressedSize, long OriginalSize, + string Method, string Path, ushort Crc16, byte Level, byte OsId, DateTime LastModified) { + /// Offset just past the member's data, where the next header starts. + public long End => this.DataOffset + this.CompressedSize; +} + +/// +/// Parses LHA member headers of every level. +/// +/// +/// Layouts (all little-endian): +/// +/// Level 0 and 1: [0] header size N (bytes after the first two), [1] byte-sum of +/// those N bytes, [2..7] method, [7..11] compressed size, [11..15] original size, [15..19] MS-DOS time, +/// [19] attribute, [20] level, [21] name length L, name, CRC-16, then for level 0 an optional extended +/// area and for level 1 the OS id and the size of the first extended header. A level-1 member's +/// compressed size includes its extended headers, which follow the base header. +/// Level 2: [0..2] total header size, fields as above but with a Unix time, [21..23] +/// CRC-16, [23] OS id, then extended headers each led by a 16-bit size. LHA for OS-9/68k ('K') writes +/// a total two bytes short. +/// Level 3: [0..2] word size (4), the same fields, [24..28] total header size, then +/// extended headers each led by a 32-bit size. +/// +/// Extended header 0x01 carries the file name, 0x02 the directory name with 0xFF as separator. +/// Sourcing: converted from lhasa's lha_file_header.c (ISC, Simon Howard); see +/// THIRD-PARTY-NOTICE.lhasa.txt in this directory. +/// +internal static class LhaHeaderParser { + private const int CommonHeaderLength = 22; + private const int Level0MinLength = 22; + private const int Level1MinLength = 25; + private const int Level2Length = 26; + private const int Level3Length = 32; + private const int Level3MaxLength = 1024 * 1024; + private const byte OsTypeOs9_68k = (byte)'K'; + + /// + /// Reads the header at the stream's position. Returns at the end-of-archive + /// marker or end of stream. + /// + /// The header is damaged or of an unknown level. + public static LhaHeader? Read(Stream stream) { + var start = stream.Position; + if (stream.Length - start < 1) + return null; + var first = stream.ReadByte(); + if (first <= 0) + return null; // a zero size byte ends the archive + if (stream.Length - start < CommonHeaderLength) + throw new InvalidDataException("Truncated LHA header."); + + var raw = new List(256) { (byte)first }; + Append(stream, raw, CommonHeaderLength - 1); + var level = raw[20]; + + return level switch { + 0 or 1 => ReadLevel01(stream, raw, start, level), + 2 => ReadLevel2(stream, raw, start), + 3 => ReadLevel3(stream, raw, start), + _ => throw new InvalidDataException($"Unsupported LHA header level: {level}"), + }; + } + + private static LhaHeader ReadLevel01(Stream stream, List raw, long start, byte level) { + var headerLength = raw[0]; + if (headerLength < (level == 0 ? Level0MinLength : Level1MinLength)) + throw new InvalidDataException("LHA level-0/1 header is shorter than its fixed fields."); + Append(stream, raw, headerLength + 2 - raw.Count); + + byte sum = 0; + for (var i = 2; i < raw.Count; ++i) + sum += raw[i]; + if (sum != raw[1]) + throw new InvalidDataException("LHA header checksum mismatch."); + + var pathLength = raw[21]; + if ((level == 0 ? Level0MinLength : Level1MinLength) + pathLength > headerLength) + throw new InvalidDataException("LHA header name runs past the header."); + + long compressed = U32(raw, 7); + var name = Latin1(raw, 22, pathLength).Replace('\\', '/'); + var crc = U16(raw, 22 + pathLength); + byte os = level == 0 ? (byte)0 : raw[24 + pathLength]; + string? directory = null; + + if (level == 1) { + // The size of the first extended header closes the base header; each extended header ends with the next one's. + var offset = raw.Count - 2; + while (true) { + var size = U16(raw, raw.Count - 2); + if (size == 0) + break; + if (size < 3) + throw new InvalidDataException("LHA level-1 extended header is shorter than its own fields."); + if (compressed < size) + throw new InvalidDataException("LHA level-1 extended headers are larger than the member."); + compressed -= size; // the compressed size counts the extended headers too + Append(stream, raw, size); + } + ParseExtended(raw, offset, 2, ref name, ref directory); + } + + return Build(start, stream.Position, compressed, raw, name, directory, crc, level, os, + DosTime(U32(raw, 15))); + } + + private static LhaHeader ReadLevel2(Stream stream, List raw, long start) { + var headerLength = U16(raw, 0); + if (headerLength < Level2Length) + throw new InvalidDataException("LHA level-2 header is shorter than its fixed fields."); + Append(stream, raw, headerLength - raw.Count); + var os = raw[23]; + if (os == OsTypeOs9_68k) + Append(stream, raw, 2); // OS-9/68k LHA counts the header two bytes short + + var name = string.Empty; + string? directory = null; + ParseExtended(raw, 24, 2, ref name, ref directory); + return Build(start, stream.Position, U32(raw, 7), raw, name, directory, U16(raw, 21), 2, os, + DateTimeOffset.FromUnixTimeSeconds(U32(raw, 15)).UtcDateTime); + } + + private static LhaHeader ReadLevel3(Stream stream, List raw, long start) { + if (U16(raw, 0) != 4) + throw new InvalidDataException("LHA level-3 header has an unsupported word size."); + Append(stream, raw, Level3Length - raw.Count); + var headerLength = U32(raw, 24); + if (headerLength > Level3MaxLength || headerLength < raw.Count) + throw new InvalidDataException("LHA level-3 header size is out of range."); + Append(stream, raw, (int)headerLength - raw.Count); + + var name = string.Empty; + string? directory = null; + ParseExtended(raw, 28, 4, ref name, ref directory); + return Build(start, stream.Position, U32(raw, 7), raw, name, directory, U16(raw, 21), 3, raw[23], + DateTimeOffset.FromUnixTimeSeconds(U32(raw, 15)).UtcDateTime); + } + + /// Walks the extended-header chain starting at the size field at . + private static void ParseExtended(List raw, int offset, int fieldSize, ref string name, ref string? directory) { + while (offset + fieldSize <= raw.Count) { + long size = fieldSize == 4 ? U32(raw, offset) : U16(raw, offset); + if (size == 0) + break; + if (size < fieldSize + 1 || offset + size > raw.Count) + throw new InvalidDataException("LHA extended header size is out of range."); + var type = raw[offset + fieldSize]; + var dataStart = offset + fieldSize + 1; + var dataLength = (int)size - fieldSize - 1; + if (dataStart + dataLength > raw.Count) + throw new InvalidDataException("LHA extended header runs past the header."); + switch (type) { + case 0x01: + name = Latin1(raw, dataStart, dataLength); + break; + case 0x02: + var dir = new StringBuilder(dataLength); + for (var i = 0; i < dataLength; ++i) { + var b = raw[dataStart + i]; + dir.Append(b == 0xFF ? '/' : (char)b); + } + directory = dir.ToString(); + break; + } + offset += (int)size; + } + } + + private static LhaHeader Build(long start, long dataOffset, long compressed, List raw, string name, + string? directory, ushort crc, byte level, byte os, DateTime modified) { + var method = Encoding.ASCII.GetString([raw[2], raw[3], raw[4], raw[5], raw[6]]); + var path = name; + if (!string.IsNullOrEmpty(directory)) + path = directory.EndsWith('/') ? directory + name : directory + "/" + name; + return new LhaHeader(start, dataOffset, compressed, U32(raw, 11), method, path.TrimEnd('/'), crc, level, os, modified); + } + + private static void Append(Stream stream, List raw, int count) { + if (count <= 0) + return; + var buffer = new byte[count]; + var read = stream.ReadAtLeast(buffer, count, throwOnEndOfStream: false); + if (read < count) + throw new InvalidDataException("Truncated LHA header."); + raw.AddRange(buffer); + } + + private static ushort U16(List raw, int at) => (ushort)(raw[at] | (raw[at + 1] << 8)); + + private static uint U32(List raw, int at) => + (uint)(raw[at] | (raw[at + 1] << 8) | (raw[at + 2] << 16) | (raw[at + 3] << 24)); + + private static string Latin1(List raw, int at, int count) { + var bytes = new byte[count]; + raw.CopyTo(at, bytes, 0, count); + return Encoding.Latin1.GetString(bytes); + } + + private static DateTime DosTime(uint timestamp) { + var time = (int)(timestamp & 0xFFFF); + var date = (int)(timestamp >> 16); + try { + return new DateTime(((date >> 9) & 0x7F) + 1980, (date >> 5) & 0x0F, date & 0x1F, + (time >> 11) & 0x1F, (time >> 5) & 0x3F, (time & 0x1F) * 2); + } catch (ArgumentOutOfRangeException) { + return DateTime.MinValue; + } + } +} diff --git a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Lzh/LhaModifier.cs b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Lzh/LhaModifier.cs index bd83350dd..161929f7a 100644 --- a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Lzh/LhaModifier.cs +++ b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Lzh/LhaModifier.cs @@ -75,31 +75,16 @@ public static bool RemoveFile(Stream lha, string name, bool wipeData = true) { } // ── Header walking ──────────────────────────────────────────────────── - // Level-1 LHA layout per entry: - // byte 0: header_size (= N, count of bytes after the checksum) - // 0 = end-of-archive - // byte 1: checksum - // bytes 2..6 (5): method string ("-lh0-", "-lh5-", ...) - // bytes 7..10 (4): compressed_size (uint32 LE) - // ... rest of header (variable name + extras) ... - // Total entry = 2 + N + compressed_size bytes. + // Every header level is parsed by LhaHeaderParser; a member spans + // [header start, data offset + compressed size). private static long FindEofOffset(Stream lha) { lha.Position = 0; - Span rest = stackalloc byte[10]; while (lha.Position < lha.Length) { var entryStart = lha.Position; - var headerSize = lha.ReadByte(); - if (headerSize <= 0) return entryStart; - - // Read just enough to extract compressed_size (offset 7..10 from entry start). - var read = ReadFully(lha, rest); - if (read < rest.Length) return entryStart; // truncated → treat as EOF - - // rest[i] maps to file byte (entryStart + 1 + i). compressed_size lives at - // file bytes 7..10 (inclusive) → rest indices 6..9. - var compressedSize = (uint)(rest[6] | rest[7] << 8 | rest[8] << 16 | rest[9] << 24); - lha.Position = entryStart + 2 + headerSize + compressedSize; + var header = LhaHeaderParser.Read(lha); + if (header == null) return entryStart; + lha.Position = header.End; } return lha.Length; } @@ -109,50 +94,17 @@ private static long FindEofOffset(Stream lha) { private static EntryLocator LocateEntry(Stream lha, string targetName) { lha.Position = 0; while (lha.Position < lha.Length) { - var entryStart = lha.Position; - var headerSize = lha.ReadByte(); - if (headerSize <= 0) break; - - var headerBuf = new byte[headerSize + 1]; // checksum byte + N payload bytes - var read = 0; - while (read < headerBuf.Length) { - var n = lha.Read(headerBuf, read, headerBuf.Length - read); - if (n <= 0) break; - read += n; - } - if (read < headerBuf.Length) break; - - // headerBuf[0] = checksum; headerBuf[1+k] = payload byte k. Payload layout: - // 0..4 method, 5..8 compressed_size, 9..12 original_size, 13..16 timestamp, - // 17 reserved, 18 level, 19 nameLen, 20..20+nameLen-1 name. - var compressedSize = (uint)(headerBuf[6] | headerBuf[7] << 8 | headerBuf[8] << 16 | headerBuf[9] << 24); - var nameLen = headerBuf[20]; - var name = nameLen > 0 - ? System.Text.Encoding.ASCII.GetString(headerBuf, 21, Math.Min(nameLen, headerBuf.Length - 21)) - : ""; - - var totalEntrySize = 2L + headerSize + compressedSize; - - if (string.Equals(name, targetName, StringComparison.OrdinalIgnoreCase)) - return new EntryLocator(true, entryStart, totalEntrySize); - - lha.Position = entryStart + totalEntrySize; + var header = LhaHeaderParser.Read(lha); + if (header == null) break; + if (string.Equals(header.Path, targetName, StringComparison.OrdinalIgnoreCase)) + return new EntryLocator(true, header.HeaderOffset, header.End - header.HeaderOffset); + lha.Position = header.End; } return new EntryLocator(false, 0, 0); } // ── Helpers ─────────────────────────────────────────────────────────── - private static int ReadFully(Stream s, Span buf) { - var read = 0; - while (read < buf.Length) { - var n = s.Read(buf[read..]); - if (n <= 0) break; - read += n; - } - return read; - } - private static void ZeroRange(Stream s, long offset, long length) { var buf = new byte[(int)Math.Min(length, 8192)]; s.Position = offset; diff --git a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Lzh/LhaReader.cs b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Lzh/LhaReader.cs index d3c01a759..21b2c29e5 100644 --- a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Lzh/LhaReader.cs +++ b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Lzh/LhaReader.cs @@ -63,6 +63,9 @@ public byte[] ExtractEntry(LhaEntry entry) { case LhaConstants.MethodLh7: data = DecompressLzh(compressedData, (int)entry.OriginalSize, LzhConstants.Lh7PositionBits); break; + case LhaConstants.MethodLhx: + data = DecompressLzh(compressedData, (int)entry.OriginalSize, 20); + break; case LhaConstants.MethodLzs: data = LzsDecoder.Decode(compressedData, (int)entry.OriginalSize); break; @@ -73,10 +76,10 @@ public byte[] ExtractEntry(LhaEntry entry) { data = compressedData; break; case LhaConstants.MethodPm1: - data = PmaDecoder.Decode(compressedData, (int)entry.OriginalSize, 2); + data = Pm1Decoder.Decode(compressedData, (int)entry.OriginalSize); break; case LhaConstants.MethodPm2: - data = PmaDecoder.Decode(compressedData, (int)entry.OriginalSize, 3); + data = Pm2Decoder.Decode(compressedData, (int)entry.OriginalSize); break; default: throw new NotSupportedException($"Unsupported LHA method: {entry.Method}"); @@ -103,149 +106,26 @@ private static byte[] DecompressLh1(byte[] compressedData, int originalSize) { private void ReadEntries() { while (this._stream.Position < this._stream.Length) { - var entry = this.ReadHeader(); - if (entry == null) - break; - - if (entry.Method != LhaConstants.MethodLhd) - this._entries.Add(entry); - - this._stream.Position = entry.DataOffset + entry.CompressedSize; - } - } - - private LhaEntry? ReadHeader() { - var firstByte = this._stream.ReadByte(); - if (firstByte <= 0) - return null; - - var secondByte = this._stream.ReadByte(); - if (secondByte < 0) - return null; - - var methodBytes = new byte[5]; - if (this._stream.Read(methodBytes, 0, 5) < 5) - return null; - var method = Encoding.ASCII.GetString(methodBytes); - if (!method.StartsWith('-') || !method.EndsWith('-')) - return null; - - var reader = new BinaryReader(this._stream, Encoding.ASCII, leaveOpen: true); - var compressedSize = reader.ReadUInt32(); - var originalSize = reader.ReadUInt32(); - var timestamp = reader.ReadUInt32(); - var reserved = reader.ReadByte(); - var level = reader.ReadByte(); - - var entry = new LhaEntry { - Method = method, - CompressedSize = compressedSize, - OriginalSize = originalSize, - HeaderLevel = level, - LastModified = level == LhaConstants.HeaderLevel2 - ? DateTimeFromUnix(timestamp) - : DateTimeFromMsdos(timestamp), - }; - - switch (level) { - case LhaConstants.HeaderLevel0: - this.ReadLevel0Header(reader, entry, firstByte); - break; - case LhaConstants.HeaderLevel1: - ReadLevel1Header(reader, entry); - break; - case LhaConstants.HeaderLevel2: - ReadLevel2Header(reader, entry, firstByte | (secondByte << 8)); - break; - default: - throw new InvalidDataException($"Unsupported LHA header level: {level}"); - } - - return entry; - } - - private void ReadLevel0Header(BinaryReader reader, LhaEntry entry, int headerSize) { - var nameLength = reader.ReadByte(); - var nameBytes = reader.ReadBytes(nameLength); - entry.FileName = Encoding.ASCII.GetString(nameBytes); - entry.Crc16 = reader.ReadUInt16(); - - var headerStart = this._stream.Position - 2 - 5 - 4 - 4 - 4 - 1 - 1 - 1 - nameLength - 2; - entry.DataOffset = headerStart + 2 + headerSize; - this._stream.Position = entry.DataOffset; - } - - private static void ReadLevel1Header(BinaryReader reader, LhaEntry entry) { - var nameLength = reader.ReadByte(); - var nameBytes = reader.ReadBytes(nameLength); - entry.FileName = Encoding.ASCII.GetString(nameBytes); - entry.Crc16 = reader.ReadUInt16(); - entry.OsId = reader.ReadByte(); - - while (true) { - var extSize = reader.ReadUInt16(); - if (extSize == 0) - break; - if (extSize < 3) - throw new InvalidDataException("Invalid LHA level-1 extended-header size."); - - var extType = reader.ReadByte(); - var extData = reader.ReadBytes(extSize - 3); - if (extData.Length != extSize - 3) - throw new EndOfStreamException("Truncated LHA level-1 extended header."); - - if (extType == 0x01 && extData.Length > 0) - entry.FileName = Encoding.ASCII.GetString(extData); - } - - entry.DataOffset = reader.BaseStream.Position; - } - - private static void ReadLevel2Header(BinaryReader reader, LhaEntry entry, int totalHeaderSize) { - entry.Crc16 = reader.ReadUInt16(); - entry.OsId = reader.ReadByte(); - - var headerStart = reader.BaseStream.Position - 2 - 5 - 4 - 4 - 4 - 1 - 1 - 2 - 1; - var headerEnd = headerStart + totalHeaderSize; - if (headerEnd > reader.BaseStream.Length) - throw new EndOfStreamException("Truncated LHA level-2 header."); - - while (reader.BaseStream.Position < headerEnd) { - var extSize = reader.ReadUInt16(); - if (extSize == 0) - break; - if (extSize < 3 || reader.BaseStream.Position + extSize - 2 > headerEnd) - throw new InvalidDataException("Invalid LHA level-2 extended-header size."); - - var extType = reader.ReadByte(); - var extData = reader.ReadBytes(extSize - 3); - if (extData.Length != extSize - 3) - throw new EndOfStreamException("Truncated LHA level-2 extended header."); - - if (extType == 0x01 && extData.Length > 0) - entry.FileName = Encoding.ASCII.GetString(extData); - } - - reader.BaseStream.Position = headerEnd; - entry.DataOffset = headerEnd; - } - - private static DateTime DateTimeFromUnix(uint timestamp) - => DateTimeOffset.FromUnixTimeSeconds(timestamp).UtcDateTime; - - private static DateTime DateTimeFromMsdos(uint timestamp) { - var time = (int)(timestamp & 0xFFFF); - var date = (int)(timestamp >> 16); - try { - return new DateTime( - ((date >> 9) & 0x7F) + 1980, - (date >> 5) & 0x0F, - date & 0x1F, - (time >> 11) & 0x1F, - (time >> 5) & 0x3F, - (time & 0x1F) * 2); - } catch { - return DateTime.MinValue; + var header = LhaHeaderParser.Read(this._stream); + if (header == null) + break; + + if (header.Method != LhaConstants.MethodLhd) + this._entries.Add(new LhaEntry { + FileName = header.Path, + Method = header.Method, + CompressedSize = header.CompressedSize, + OriginalSize = header.OriginalSize, + LastModified = header.LastModified, + Crc16 = header.Crc16, + HeaderLevel = header.Level, + OsId = header.OsId, + DataOffset = header.DataOffset, + }); + + if (header.End > this._stream.Length) + break; // a truncated last member is listed; extracting it fails + this._stream.Position = header.End; } } diff --git a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Lzh/LhaWriter.cs b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Lzh/LhaWriter.cs index ceb120008..7132d6a35 100644 --- a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Lzh/LhaWriter.cs +++ b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Lzh/LhaWriter.cs @@ -39,8 +39,6 @@ public LhaWriter( LhaConstants.MethodLzs => -1, LhaConstants.MethodLz5 => -2, LhaConstants.MethodLh1 => -3, - LhaConstants.MethodPm1 => -5, - LhaConstants.MethodPm2 => -6, LhaConstants.MethodLh4 => LzhConstants.Lh4PositionBits, LhaConstants.MethodLh5 => LzhConstants.Lh5PositionBits, LhaConstants.MethodLh6 => LzhConstants.Lh6PositionBits, @@ -90,10 +88,6 @@ private void WriteEntry(Stream output, string name, byte[] data) { if (data.Length == 0 || this._positionBits == 0) { compressed = data; - } else if (this._positionBits == -5) { - compressed = PmaEncoder.Encode(data, 2); - } else if (this._positionBits == -6) { - compressed = PmaEncoder.Encode(data, 3); } else if (this._positionBits == -1) { compressed = LzsEncoder.Encode(data); } else if (this._positionBits == -2) { @@ -135,7 +129,7 @@ private void WriteEntry(Stream output, string name, byte[] data) { private string GetStoredMethod() => this._method switch { LhaConstants.MethodLz4 or LhaConstants.MethodLzs or LhaConstants.MethodLz5 => LhaConstants.MethodLz4, - LhaConstants.MethodPm0 or LhaConstants.MethodPm1 or LhaConstants.MethodPm2 => LhaConstants.MethodPm0, + LhaConstants.MethodPm0 => LhaConstants.MethodPm0, _ => LhaConstants.MethodLh0, }; diff --git a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Lzh/LzhConstants.cs b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Lzh/LzhConstants.cs index 90d4b32b6..982d01485 100644 --- a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Lzh/LzhConstants.cs +++ b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Lzh/LzhConstants.cs @@ -40,6 +40,9 @@ public static class LhaConstants { /// Method: directory marker. public const string MethodLhd = "-lhd-"; + /// Method: lh5-style static Huffman with a 1 MiB window (UNLHA32), read-only. + public const string MethodLhx = "-lhx-"; + /// Method: store (same as lh0, legacy). public const string MethodLz4 = "-lz4-"; diff --git a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Lzh/LzhFormatDescriptor.cs b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Lzh/LzhFormatDescriptor.cs index 4eb422857..139bafffb 100644 --- a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Lzh/LzhFormatDescriptor.cs +++ b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Lzh/LzhFormatDescriptor.cs @@ -98,7 +98,7 @@ public void Remove(Stream archive, string[] entryNames) { public IReadOnlyList Methods => [ new("lh5", "LH5"), new("lh0", "LH0 (Store)"), new("lh1", "LH1"), new("lh4", "LH4"), new("lh6", "LH6"), new("lh7", "LH7"), new("lzs", "LZS"), new("lz5", "LZ5"), - new("pm0", "PM0"), new("pm1", "PM1"), new("pm2", "PM2") + new("pm0", "PM0") ]; /// /// Gets the tar compression format id. diff --git a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Lzh/LzhLayoutMap.cs b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Lzh/LzhLayoutMap.cs index 24159a27d..9ca4734a0 100644 --- a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Lzh/LzhLayoutMap.cs +++ b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Lzh/LzhLayoutMap.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -using System.Text; using Compression.Registry; namespace FileFormat.Lzh; @@ -18,85 +17,19 @@ public static IEnumerable Enumerate(Stream archive) { archive.Position = 0; while (archive.Position < archive.Length) { - var entryStart = archive.Position; - - // Peek at first two bytes - var firstByte = archive.ReadByte(); - if (firstByte <= 0) yield break; - var secondByte = archive.ReadByte(); - if (secondByte < 0) yield break; - - // Read method string (5 bytes at offset 2) - var methodBytes = new byte[5]; - if (archive.Read(methodBytes, 0, 5) < 5) yield break; - var method = Encoding.ASCII.GetString(methodBytes); - - // Validate method - if (!method.StartsWith('-') || !method.EndsWith('-')) + LhaHeader? header; + try { + header = LhaHeaderParser.Read(archive); + } catch (InvalidDataException) { yield break; - - // Read common fields - var reader = new BinaryReader(archive, Encoding.ASCII, leaveOpen: true); - var compressedSize = reader.ReadUInt32(); - var originalSize = reader.ReadUInt32(); - var timestamp = reader.ReadUInt32(); - var reserved = reader.ReadByte(); - var level = reader.ReadByte(); - - string fileName; - long dataOffset; - - switch (level) { - case 0: { - var nameLength = reader.ReadByte(); - var nameBytes = reader.ReadBytes(nameLength); - fileName = Encoding.ASCII.GetString(nameBytes); - var crc16 = reader.ReadUInt16(); - // Level 0: headerSize (firstByte) includes everything from offset 2 to end of header - // Total header = 2 + firstByte bytes - dataOffset = entryStart + 2 + firstByte; - break; - } - case 1: { - var nameLength = reader.ReadByte(); - var nameBytes = reader.ReadBytes(nameLength); - fileName = Encoding.ASCII.GetString(nameBytes); - var crc16 = reader.ReadUInt16(); - var osId = reader.ReadByte(); - // Read extended headers - while (true) { - var extSize = reader.ReadUInt16(); - if (extSize == 0) break; - var extType = reader.ReadByte(); - var extData = reader.ReadBytes(extSize - 3); - if (extType == 0x01 && extData.Length > 0) - fileName = Encoding.ASCII.GetString(extData); - } - dataOffset = archive.Position; - break; - } - case 2: { - var totalHeaderSize = firstByte | (secondByte << 8); - var crc16 = reader.ReadUInt16(); - var osId = reader.ReadByte(); - fileName = ""; // will be set by extended headers - - var headerEnd = entryStart + totalHeaderSize; - while (archive.Position < headerEnd) { - var extSize = reader.ReadUInt16(); - if (extSize == 0) break; - var extType = reader.ReadByte(); - var extData = reader.ReadBytes(extSize - 3); - if (extType == 0x01 && extData.Length > 0) - fileName = Encoding.ASCII.GetString(extData); - } - archive.Position = headerEnd; - dataOffset = headerEnd; - break; - } - default: - yield break; } + if (header == null) yield break; + + var entryStart = header.HeaderOffset; + var dataOffset = header.DataOffset; + var compressedSize = header.CompressedSize; + var method = header.Method; + var fileName = header.Path; var headerSize = dataOffset - entryStart; diff --git a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Lzh/THIRD-PARTY-NOTICE.lhasa.txt b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Lzh/THIRD-PARTY-NOTICE.lhasa.txt new file mode 100644 index 000000000..63d1e77ae --- /dev/null +++ b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Lzh/THIRD-PARTY-NOTICE.lhasa.txt @@ -0,0 +1,27 @@ +lhasa (LHA header parsing) - converted to C# + +Source: https://github.com/fragglet/lhasa +Revision: 75ed83559f23e9538e0045c62f53f77ab03d03d6 (master, 2026-06-17) +Author: Simon Howard +Licence: ISC (reproduced below) + +Copyright (c) 2011-2025, Simon Howard + +Permission to use, copy, modify, and/or distribute this software +for any purpose with or without fee is hereby granted, provided +that the above copyright notice and this permission notice appear +in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL +WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE +AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR +CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, +NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +Files converted: + + lib/lha_file_header.c, lib/ext_header.c -> LhaHeaderParser.cs (levels 0-3, + extended headers 0x01 and 0x02) diff --git a/Hawkynt.FileFormats.Archives/README.md b/Hawkynt.FileFormats.Archives/README.md index 29ca6e9e2..3affa6901 100644 --- a/Hawkynt.FileFormats.Archives/README.md +++ b/Hawkynt.FileFormats.Archives/README.md @@ -128,7 +128,7 @@ Every State, Test, Maintenance, Compress/Decompress and Demux/Mux/Remux cell is | LhF (LhFloppy) | `LhF` | `.lhf` | R/W | ✅ | defrag · wipe | Whole-disk archiver: entries are track_NNN.raw | [Aminet](https://aminet.net) | | [lrzip](https://en.wikipedia.org/wiki/Rzip#lrzip) | `Lrzip` | `.lrz` | WORM | ✅ | defrag | LZMA-wrapped subtype only; other lrzip subtypes are rejected; single data member | [GitHub](https://github.com/ckolivas/lrzip) | | Lynx (Commodore) | `Lynx` | `.lnx` | R/W | ✅ | defrag · shrink · wipe | Stored entries only | [Archive Team](http://fileformats.archiveteam.org/wiki/Lynx_(Commodore_64)) | -| [LHA / LZH](https://en.wikipedia.org/wiki/LHA_(file_format)) | `Lzh` | `.lzh` `.lha` | R/W | ✅ | defrag · wipe | -lh2- and -lh3- are read-only | [GitHub](https://github.com/jca02266/lha) | +| [LHA / LZH](https://en.wikipedia.org/wiki/LHA_(file_format)) | `Lzh` | `.lzh` `.lha` | R/W | ✅ | defrag · wipe | -lh2-, -lh3-, -lhx-, -pm1- and -pm2- are read-only | [GitHub](https://github.com/jca02266/lha) | | [LZX (Amiga)](https://en.wikipedia.org/wiki/LZX) | `LzxAmiga` | `.lzx` | R/W | ✅ | defrag · wipe | | [Aminet](https://aminet.net) | | [mtree](https://man.freebsd.org/cgi/man.cgi?query=mtree&sektion=5) | `Mtree` | `.mtree` | WORM | ✅ | — | Filesystem metadata manifest; does not embed file bodies, so CWB does not dereference `contents=` host paths | [FreeBSD mtree(5)](https://man.freebsd.org/cgi/man.cgi?query=mtree&sektion=5) | | [NuFX / ShrinkIt](https://en.wikipedia.org/wiki/ShrinkIt) | `NuFx` | `.shk` `.sdk` `.bxy` | R/W | ✅ | defrag · shrink · wipe | | [nulib.com](https://nulib.com/library/FTN.e08002.htm) | diff --git a/Hawkynt.FileFormats.Archives/REFERENCE.md b/Hawkynt.FileFormats.Archives/REFERENCE.md index 379092af7..abb0035eb 100644 --- a/Hawkynt.FileFormats.Archives/REFERENCE.md +++ b/Hawkynt.FileFormats.Archives/REFERENCE.md @@ -19634,6 +19634,7 @@ Constants for the LHA/LZH archive format. | `MethodLh6` | `const string MethodLh6` | Method: LZH compression with 32KB window. | | `MethodLh7` | `const string MethodLh7` | Method: LZH compression with 64KB window. | | `MethodLhd` | `const string MethodLhd` | Method: directory marker. | +| `MethodLhx` | `const string MethodLhx` | Method: lh5-style static Huffman with a 1 MiB window (UNLHA32), read-only. | | `MethodLz4` | `const string MethodLz4` | Method: store (same as lh0, legacy). | | `MethodLz5` | `const string MethodLz5` | Method: LZSS with 4KB window (LArc variant). | | `MethodLzs` | `const string MethodLzs` | Method: LZSS with 4KB window (no Huffman). | diff --git a/docs/catalogue/GAPS.md b/docs/catalogue/GAPS.md index 828948241..fb9d0648c 100644 --- a/docs/catalogue/GAPS.md +++ b/docs/catalogue/GAPS.md @@ -162,7 +162,7 @@ running real-world samples. The file named at the end of each row is where the l | 7z | `SevenZip` | 7 | Deflate64, ARM64, RISC-V, Zstd/Brotli/LZ4 coders and external header references unsupported (SevenZipReader.cs) | | ISO 9660 (Joliet / Rock Ridge / El Torito) | `Iso` | 7 | Rock Ridge limited to NM names (no SL/PX/TF/CE, no zisofs); interleaved files throw (IsoReader.cs) | | ZIP | `Zip` | 7 | methods xz (95), JPEG (96), WavPack (97) and PKWARE strong encryption unsupported (ZipReader.cs) | -| LHA / LZH | `Lzh` | 6 | lh2/lh3 decoded but read-only; lh1, lzs, lz5, pm1, pm2, level-1 extended headers and level-3 headers fail on lhasa's test archives (refused by the CRC check), lhx and lk7 unsupported (LhaReader.cs) | +| LHA / LZH | `Lzh` | 6 | every method and header level of lhasa's test suite reads except LHARK's lk7; lh2, lh3, lhx, pm1 and pm2 are read-only; self-extracting executables are not opened (LhaReader.cs) | | StuffIt (classic + 5) | `StuffIt` | 4 | store and RLE only; LZW/Huffman/LZAH/Arsenic/DES methods throw (StuffItReader.cs) | | StuffIt X | `StuffItX` | 4 | stored and deflate only; Brimstone/Cyanide/Darkhorse/Iron throw (StuffItXReader.cs) | | zoo | `Zoo` | 4 | LZH method (2) unsupported (ZooReader.cs) | diff --git a/docs/catalogue/reference-formats.csv b/docs/catalogue/reference-formats.csv index d65e39d0f..bd05e5eee 100644 --- a/docs/catalogue/reference-formats.csv +++ b/docs/catalogue/reference-formats.csv @@ -34,7 +34,7 @@ Windows Update package (MSU),msu,installer,,,,,R,,,1,,,,,,MISSING,, Compiled HTML Help (CHM),chm;chi;chq;chw;hxs,document,R,,,,R,fmt/634,,3,https://www.nongnu.org/chmspec/latest/,Chm,Y,Y,Y,PARTIAL,,content sections above 1 unsupported (ChmReader.cs) Windows Imaging (WIM/SWM/ESD),wim;swm;esd;ppkg,disk-image,RW,,,,R,,Y,3,https://learn.microsoft.com/windows-hardware/manufacture/desktop/wim-vs-ffu-image-file-formats,Wim;Swm;Esd,Y,Y,,OK,, ARJ,arj,archive,R,,R,D,R,fmt/610,RW,6,https://en.wikipedia.org/wiki/ARJ,Arj,Y,Y,Y,OK,,methods 0-4 decoded; method 4 verified on archives the open-source ARJ port wrote -LHA / LZH,lzh;lha;lzs;exe;sfx,archive,R,R,R,,R,fmt/1558;fmt/626,RW,6,https://en.wikipedia.org/wiki/LHA_(file_format),Lzh,Y,Y,Y,PARTIAL,,"lh2/lh3 decoded but read-only; lh1, lzs, lz5, pm1, pm2, level-1 extended headers and level-3 headers fail on lhasa's test archives (refused by the CRC check), lhx and lk7 unsupported (LhaReader.cs)" +LHA / LZH,lzh;lha;lzs;exe;sfx,archive,R,R,R,,R,fmt/1558;fmt/626,RW,6,https://en.wikipedia.org/wiki/LHA_(file_format),Lzh,Y,Y,Y,PARTIAL,,"every method and header level of lhasa's test suite reads except LHARK's lk7; lh2, lh3, lhx, pm1 and pm2 are read-only; self-extracting executables are not opened (LhaReader.cs)" ACE,ace,archive,,,R,,R,fmt/2075;fmt/2076,RW,4,https://en.wikipedia.org/wiki/ACE_(compressed_file_format),Ace,Y,Y,Y,OK,, ARC (SEA),arc;ark,archive,,,R,,R,,RW,3,https://en.wikipedia.org/wiki/ARC_(file_format),Arc,Y,Y,Y,OK,, PAK (NoGate ARC extension),pak,archive,,,R,,,,Y,2,,,,,,MISSING,, From fc95094746ae3b931d507486685f2a1f9a3b7abe Mon Sep 17 00:00:00 2001 From: Hawkynt Date: Mon, 26 Oct 2026 13:03:37 +0100 Subject: [PATCH 09/34] # PackIt and DiskDoubler: real on-disk formats, decode or refuse # PackIt: real on-disk layout, Huffman entries decoded, encrypted ones refused Symptom: real PackIt archives did not read: "PMa4" Huffman members came back as compressed bytes, and stored ones were cut at the wrong offsets. Archives we wrote were not PackIt files at all. Root cause: reader, writer and modifier shared an invented 87-byte header. The real header is 94 bytes after the magic (name, type, creator, Finder flags, fork sizes, dates, CRC-16), the forks are followed by a CRC-16, the archive ends with "PEnd", and a "PMa4" entry Huffman- codes the header, both forks and the CRC as one stream with no stored length. Change: reader, writer and modifier rewritten to the real layout. PMa4 entries are decoded (StuffIt method-3 style pre-order tree, MSB-first) and walked to find the next entry; both CRC-16s are checked; PMa5/PMa6 (XOR/DES-encrypted) entries are listed and refused. The writer emits stored entries with both CRCs and the end marker; defrag carries each entry over byte for byte so resource forks and Huffman coding survive. Evidence: nine real PackIt archives (dexvert sample set; seven Huffman- coded) decode with every header and fork CRC matching and every data fork identical to unar's; they are not committed (unknown redistribution terms) and run from test-corpus/packit when present. unar extracts archives we write, and decodes a test-encoded PMa4 entry to the same bytes we do. The header CRC (unchecked by XADMaster) is CRC-16/XMODEM over the 92 bytes before it, measured on every real header. Sourcing: rung 1, layout and Huffman scheme converted from XADMaster's XADPackItParser.m and XADStuffItHuffmanHandle.m (LGPL-2.1-or-later), notice in THIRD-PARTY-NOTICE.XADMaster.txt. # DiskDoubler: real header, methods 0/1/6/9 decoded, the rest refused Symptom: compressed DiskDoubler forks were returned as their compressed bytes, without an error, and real DiskDoubler files did not parse. Root cause: the reader and writer used an invented 82-byte header with a name field. A real file opens with the magic 0xABCD0054 and an 84-byte header (sizes, per-fork method, version bytes, dates, type/creator, per-fork checksums and delta types, CRC-16), often behind a MacBinary wrapper; the extract path had no decoder for any method but stored. Change: reader and writer rewritten to the real layout, with header CRC check and MacBinary detection; magic-based detection added. Decoded: 0 (stored), 1 (Unix compress with its 0x5A-masked header and byte-sum check), 6 and 9 ("AD" LZSS blocks with per-block header XOR). Methods 2-5, 7, 8, 10 and delta pre-processing throw NotSupportedException. The writer emits a stored single file. Evidence: on the dexvert DiskDoubler sample set (not committed: unknown redistribution terms; runs from test-corpus/diskdoubler when present) every method 6/9 fork is identical to unar's output; the one method-1 sample decodes 2 bytes short of its size in unar and in gzip alike and we refuse it the same way; the Compact Pro (8) samples are refused. unar extracts files we write. Sourcing: rung 1, layout and the AD decoder converted from XADMaster's XADDiskDoublerParser.m and XADDiskDoublerADnHandle.m (LGPL-2.1-or-later), notice in THIRD-PARTY-NOTICE.XADMaster.txt. * regenerate package references --- .../DiskDoubler/DiskDoublerOracleTests.cs | 103 ++++ .../DiskDoubler/DiskDoublerTests.cs | 408 ++++++++-------- .../PackIt/PackItInPlaceModifyTests.cs | 37 +- Compression.Tests/PackIt/PackItOracleTests.cs | 108 +++++ Compression.Tests/PackIt/PackItTests.cs | 458 +++++++++--------- .../DiskDoublerAdDecoder.cs | 109 +++++ .../DiskDoublerConstants.cs | 32 +- .../DiskDoublerEntry.cs | 33 +- .../DiskDoublerFormatDescriptor.cs | 14 +- .../DiskDoublerReader.cs | 283 ++++++----- .../DiskDoublerWriter.cs | 75 ++- .../THIRD-PARTY-NOTICE.XADMaster.txt | 34 ++ .../FileFormat.PackIt/PackItConstants.cs | 23 +- .../FileFormat.PackIt/PackItEntry.cs | 28 +- .../PackItFormatDescriptor.cs | 42 +- .../FileFormat.PackIt/PackItHuffmanDecoder.cs | 85 ++++ .../FileFormat.PackIt/PackItModifier.cs | 197 ++------ .../FileFormat.PackIt/PackItReader.cs | 287 ++++++----- .../FileFormat.PackIt/PackItWriter.cs | 95 ++-- .../THIRD-PARTY-NOTICE.XADMaster.txt | 34 ++ Hawkynt.FileFormats.Archives/README.md | 4 +- Hawkynt.FileFormats.Archives/REFERENCE.md | 62 ++- docs/catalogue/GAPS.md | 4 +- docs/catalogue/reference-formats.csv | 6 +- 24 files changed, 1557 insertions(+), 1004 deletions(-) create mode 100644 Compression.Tests/DiskDoubler/DiskDoublerOracleTests.cs create mode 100644 Compression.Tests/PackIt/PackItOracleTests.cs create mode 100644 Hawkynt.FileFormats.Archives/FileFormats/FileFormat.DiskDoubler/DiskDoublerAdDecoder.cs create mode 100644 Hawkynt.FileFormats.Archives/FileFormats/FileFormat.DiskDoubler/THIRD-PARTY-NOTICE.XADMaster.txt create mode 100644 Hawkynt.FileFormats.Archives/FileFormats/FileFormat.PackIt/PackItHuffmanDecoder.cs create mode 100644 Hawkynt.FileFormats.Archives/FileFormats/FileFormat.PackIt/THIRD-PARTY-NOTICE.XADMaster.txt diff --git a/Compression.Tests/DiskDoubler/DiskDoublerOracleTests.cs b/Compression.Tests/DiskDoubler/DiskDoublerOracleTests.cs new file mode 100644 index 000000000..4b788c66c --- /dev/null +++ b/Compression.Tests/DiskDoubler/DiskDoublerOracleTests.cs @@ -0,0 +1,103 @@ +#pragma warning disable CS1591 +using System.Security.Cryptography; +using FileFormat.DiskDoubler; + +namespace Compression.Tests.DiskDoubler; + +/// +/// DiskDoubler against The Unarchiver (unar, XADMaster), the maintained reader of the format. +/// +[TestFixture] +[Category("ExternalInterop")] +public class DiskDoublerOracleTests { + private string _dir = null!; + + [SetUp] + public void SetUp() { + this._dir = Path.Combine(Path.GetTempPath(), "cwb_dd_" + Guid.NewGuid().ToString("N")[..8]); + Directory.CreateDirectory(this._dir); + } + + [TearDown] + public void TearDown() { + try { Directory.Delete(this._dir, true); } catch { /* best effort */ } + } + + private static void RequireUnar() { + if (!FsInteropToolbox.WslAvailable) + Assert.Ignore("WSL not available."); + if (!FsInteropToolbox.WslHasTool("unar")) + Assert.Ignore("unar is not installed. Run inside WSL / on Linux: `sudo apt install -y unar`."); + } + + /// Extracts with unar, data forks only; returns whether unar succeeded and the single file it wrote. + private (bool Ok, byte[]? Data) UnarDataFork(string file) { + var into = Path.Combine(this._dir, "unar_" + Guid.NewGuid().ToString("N")[..6]); + Directory.CreateDirectory(into); + var r = FsInteropToolbox.RunWsl( + $"unar -q -f -k skip -D -o {FsInteropToolbox.WinToWsl(into)} {FsInteropToolbox.WinToWsl(file)}"); + var files = Directory.GetFiles(into, "*", SearchOption.AllDirectories); + return (r.ExitCode == 0, files.Length == 1 ? File.ReadAllBytes(files[0]) : null); + } + + [Test] + public void FileWeWrote_ExtractsInUnar() { + RequireUnar(); + var payload = new byte[6000]; + new Random(11).NextBytes(payload); + var path = Path.Combine(this._dir, "ours.dd"); + var w = new DiskDoublerWriter(); + w.SetFile(payload, "BINA", "????"); + using (var fs = File.Create(path)) + w.WriteTo(fs); + + var (ok, data) = this.UnarDataFork(path); + Assert.That(ok, Is.True, "unar rejected a file we wrote"); + Assert.That(data, Is.EqualTo(payload)); + } + + /// + /// Real DiskDoubler files, dropped into test-corpus/diskdoubler/ (not committed: their + /// redistribution terms are unknown). The dexvert sample set (sembiance.com/fileFormatSamples/archive/ + /// diskDoubler) covers methods 1, 6, 8 and 9. Every data fork we decode must match unar's; the ones we + /// refuse must be refused with , never returned undecoded. + /// + [Test] + public void RealFiles_InTestCorpus_MatchUnarOrAreRefused() { + var corpus = Path.GetFullPath(Path.Combine(TestContext.CurrentContext.TestDirectory, "..", "..", "..", "..", "test-corpus", "diskdoubler")); + if (!Directory.Exists(corpus)) + Assert.Ignore($"No real DiskDoubler files under {corpus}."); + RequireUnar(); + + var decoded = 0; + foreach (var file in Directory.EnumerateFiles(corpus).Order()) { + var head = new byte[300]; + using (var fs = File.OpenRead(file)) + head = head[..fs.ReadAtLeast(head, head.Length, throwOnEndOfStream: false)]; + if (!DiskDoublerReader.IsDiskDoubler(head)) + continue; + + using var r = new DiskDoublerReader(File.OpenRead(file)); + var data = r.Entries.FirstOrDefault(e => e.IsDataFork && e.OriginalSize > 0); + if (data is null) + continue; + if (!data.IsSupported) { + Assert.Throws(() => r.Extract(data), Path.GetFileName(file)); + continue; + } + var (ok, theirs) = this.UnarDataFork(file); + if (!ok) { + // unar rejects the fork (the corpus's one method-1 file decodes 2 bytes short of its size and + // fails its checksum, in unar and in gzip alike): we must reject it too, not return it. + Assert.Throws(() => r.Extract(data), $"{Path.GetFileName(file)}: unar refused it"); + continue; + } + Assert.That(theirs, Is.Not.Null, $"{Path.GetFileName(file)}: unar produced no single data fork"); + Assert.That(SHA256.HashData(r.Extract(data)), Is.EqualTo(SHA256.HashData(theirs!)), + $"{Path.GetFileName(file)} (method {data.Method})"); + ++decoded; + } + if (decoded == 0) + Assert.Ignore("No DiskDoubler file in the corpus uses a method this reader decodes."); + } +} diff --git a/Compression.Tests/DiskDoubler/DiskDoublerTests.cs b/Compression.Tests/DiskDoubler/DiskDoublerTests.cs index db7a20151..5522b0e4b 100644 --- a/Compression.Tests/DiskDoubler/DiskDoublerTests.cs +++ b/Compression.Tests/DiskDoubler/DiskDoublerTests.cs @@ -1,5 +1,7 @@ using System.Buffers.Binary; -using System.Text; +using Compression.Core.Checksums; +using Compression.Core.Streams; +using FileFormat.Compress; using FileFormat.DiskDoubler; namespace Compression.Tests.DiskDoubler; @@ -7,248 +9,272 @@ namespace Compression.Tests.DiskDoubler; [TestFixture] public class DiskDoublerTests { - // ── Archive builder helper ──────────────────────────────────────────────────── - - /// - /// Builds a minimal DiskDoubler file with the given data and resource fork parameters. - /// - private static byte[] BuildDdFile( - string name, - byte dataMethod, - byte[] dataBytes, - uint dataOrigSize, - byte rsrcMethod, - byte[] rsrcBytes, - uint rsrcOrigSize) { - - var hdr = new byte[DiskDoublerReader.HeaderSize]; - // [4..8] file type "TEXT" - Encoding.ASCII.GetBytes("TEXT").CopyTo(hdr, 4); - // [8..12] creator "CWIE" - Encoding.ASCII.GetBytes("CWIE").CopyTo(hdr, 8); - // [16..20] data fork original size - BinaryPrimitives.WriteUInt32BigEndian(hdr.AsSpan(16, 4), dataOrigSize); - // [20..24] data fork compressed size - BinaryPrimitives.WriteUInt32BigEndian(hdr.AsSpan(20, 4), (uint)dataBytes.Length); - // [24..28] resource fork original size - BinaryPrimitives.WriteUInt32BigEndian(hdr.AsSpan(24, 4), rsrcOrigSize); - // [28..32] resource fork compressed size - BinaryPrimitives.WriteUInt32BigEndian(hdr.AsSpan(28, 4), (uint)rsrcBytes.Length); - // [32] data method, [33] rsrc method - hdr[32] = dataMethod; - hdr[33] = rsrcMethod; - // [34] Pascal name length, [35..] name - var nameBytes = Encoding.Latin1.GetBytes(name); - var nameLen = Math.Min(nameBytes.Length, 47); - hdr[34] = (byte)nameLen; - Array.Copy(nameBytes, 0, hdr, 35, nameLen); + // ── Builders ────────────────────────────────────────────────────────────────── - using var ms = new MemoryStream(); - ms.Write(hdr); - ms.Write(dataBytes); - ms.Write(rsrcBytes); - return ms.ToArray(); + /// Builds a DiskDoubler file in the real 84-byte layout around already-encoded forks. + internal static byte[] BuildDdFile( + byte dataMethod, byte[] dataBytes, uint dataSize, + byte rsrcMethod = 0, byte[]? rsrcBytes = null, uint rsrcSize = 0, + ushort dataChecksum = 0, ushort dataDelta = 0, byte info1 = 0x2A, bool zeroHeaderCrc = false) { + rsrcBytes ??= []; + var hdr = new byte[84]; + BinaryPrimitives.WriteUInt32BigEndian(hdr, 0xABCD0054); + BinaryPrimitives.WriteUInt32BigEndian(hdr.AsSpan(4), dataSize); + BinaryPrimitives.WriteUInt32BigEndian(hdr.AsSpan(8), (uint)dataBytes.Length); + BinaryPrimitives.WriteUInt32BigEndian(hdr.AsSpan(12), rsrcSize); + BinaryPrimitives.WriteUInt32BigEndian(hdr.AsSpan(16), (uint)rsrcBytes.Length); + hdr[20] = dataMethod; + hdr[21] = rsrcMethod; + hdr[22] = info1; + "TEXT"u8.CopyTo(hdr.AsSpan(32)); + "ttxt"u8.CopyTo(hdr.AsSpan(36)); + BinaryPrimitives.WriteUInt16BigEndian(hdr.AsSpan(48), dataChecksum); + BinaryPrimitives.WriteUInt16BigEndian(hdr.AsSpan(54), dataDelta); + if (!zeroHeaderCrc) + BinaryPrimitives.WriteUInt16BigEndian(hdr.AsSpan(82), Crc16Ccitt.Compute(hdr.AsSpan(0, 82))); + return [.. hdr, .. dataBytes, .. rsrcBytes]; } - /// Builds a DiskDoubler file with a stored data fork only. - private static byte[] BuildStoredFile(string name, byte[] data) => - BuildDdFile(name, 0, data, (uint)data.Length, 0, [], 0); + private static DiskDoublerReader Open(byte[] file, string name = "doc") => new(new MemoryStream(file), name: name); - // ── Header parsing ──────────────────────────────────────────────────────────── + /// Encodes a method-1 fork: a .Z stream of the XOR-masked data, its header masked too. + private static (byte[] Fork, ushort Checksum) CompressFork(byte[] data) { + var masked = data.Select(b => (byte)(b ^ 0x5A)).ToArray(); + using var ms = new MemoryStream(); + using (var z = new CompressStream(ms, CompressionStreamMode.Compress, leaveOpen: true)) + z.Write(masked); + var fork = ms.ToArray(); + var checksum = (ushort)(data.Sum(b => (int)b) + fork[0] + fork[1] + fork[2]); + for (var i = 0; i < 3; ++i) fork[i] ^= 0x5A; + return (fork, checksum); + } - [Test, Category("HappyPath")] - public void Parse_StoredDataFork_OneEntry() { - var archive = BuildStoredFile("hello.txt", "Hello!"u8.ToArray()); - using var ms = new MemoryStream(archive); - var r = new DiskDoublerReader(ms); - Assert.That(r.Entries, Has.Count.EqualTo(1)); + /// Builds one "AD" block header for a block body of bytes. + private static byte[] AdBlockHeader(int compSize, int uncompSize, byte flags) { + var h = new byte[12]; + BinaryPrimitives.WriteUInt16BigEndian(h, (ushort)compSize); + BinaryPrimitives.WriteUInt16BigEndian(h.AsSpan(2), (ushort)uncompSize); + h[9] = flags; + byte x = 0; + for (var i = 0; i < 11; ++i) x ^= h[i]; + h[11] = x; + return h; } - [Test, Category("HappyPath")] - public void Parse_Entry_IsDataFork() { - var archive = BuildStoredFile("readme.txt", [0x41]); - using var ms = new MemoryStream(archive); - var r = new DiskDoublerReader(ms); - Assert.That(r.Entries[0].IsDataFork, Is.True); + private static byte[] PackBits(string bits) { + bits = bits.Replace(" ", ""); + var result = new byte[(bits.Length + 7) / 8]; + for (var i = 0; i < bits.Length; ++i) + if (bits[i] == '1') result[i >> 3] |= (byte)(0x80 >> (i & 7)); + return result; } + // ── Header ──────────────────────────────────────────────────────────────────── + [Test, Category("HappyPath")] - public void Parse_Entry_Name() { - var archive = BuildStoredFile("document.txt", [0x41]); - using var ms = new MemoryStream(archive); - var r = new DiskDoublerReader(ms); - Assert.That(r.Entries[0].Name, Is.EqualTo("document.txt")); + public void Writer_Layout_MagicSizesAndHeaderCrc() { + var payload = "hello diskdoubler"u8.ToArray(); + var w = new DiskDoublerWriter(); + w.SetFile(payload, "TEXT", "ttxt"); + using var ms = new MemoryStream(); + w.WriteTo(ms); + var file = ms.ToArray(); + + Assert.That(file, Has.Length.EqualTo(84 + payload.Length)); + Assert.That(BinaryPrimitives.ReadUInt32BigEndian(file), Is.EqualTo(0xABCD0054u)); + Assert.That(BinaryPrimitives.ReadUInt32BigEndian(file.AsSpan(4)), Is.EqualTo((uint)payload.Length)); + Assert.That(BinaryPrimitives.ReadUInt32BigEndian(file.AsSpan(8)), Is.EqualTo((uint)payload.Length)); + Assert.That(file[20], Is.Zero, "stored"); + Assert.That(BinaryPrimitives.ReadUInt16BigEndian(file.AsSpan(82)), Is.EqualTo(Crc16Ccitt.Compute(file.AsSpan(0, 82)))); + Assert.That(file.AsSpan(84).SequenceEqual(payload), Is.True); + } + + [Test, Category("HappyPath"), Category("RoundTrip")] + public void Writer_Reader_RoundTrip() { + var payload = new byte[4096]; + new Random(3).NextBytes(payload); + var w = new DiskDoublerWriter(); + w.SetFile(payload); + using var ms = new MemoryStream(); + w.WriteTo(ms); + + using var r = Open(ms.ToArray(), "test.txt"); + var e = r.Entries.Single(); + Assert.That((e.Name, e.IsDataFork, e.Method, e.OriginalSize), Is.EqualTo(("test.txt", true, 0, 4096L))); + Assert.That(r.Extract(e), Is.EqualTo(payload)); } [Test, Category("HappyPath")] - public void Parse_Entry_OriginalSize() { - var data = new byte[512]; - var archive = BuildStoredFile("data.bin", data); - using var ms = new MemoryStream(archive); - var r = new DiskDoublerReader(ms); - Assert.That(r.Entries[0].OriginalSize, Is.EqualTo(512)); + public void Reader_TakesNameFromFileWithoutDdExtension() { + var path = Path.Combine(Path.GetTempPath(), $"cwb_dd_{Guid.NewGuid():N}.txt.dd"); + try { + File.WriteAllBytes(path, BuildDdFile(0, [1, 2, 3], 3)); + using var r = new DiskDoublerReader(File.OpenRead(path)); + Assert.That(r.Entries[0].Name, Is.EqualTo(Path.GetFileName(path)[..^3])); + } finally { + File.Delete(path); + } } [Test, Category("HappyPath")] - public void Parse_Entry_CompressedSize() { - var data = new byte[100]; - var archive = BuildStoredFile("data.bin", data); - using var ms = new MemoryStream(archive); - var r = new DiskDoublerReader(ms); - Assert.That(r.Entries[0].CompressedSize, Is.EqualTo(100)); + public void Reader_BothForks_SecondFollowsFirst() { + var file = BuildDdFile(0, "data"u8.ToArray(), 4, 0, "resource"u8.ToArray(), 8); + using var r = Open(file); + Assert.That(r.Entries.Select(e => e.Name), Is.EqualTo(new[] { "doc", "doc.rsrc" })); + Assert.That(r.Extract(r.Entries[0]), Is.EqualTo("data"u8.ToArray())); + Assert.That(r.Extract(r.Entries[1]), Is.EqualTo("resource"u8.ToArray())); } [Test, Category("HappyPath")] - public void Parse_WithResourceFork_TwoEntries() { - var data = "data fork"u8.ToArray(); - var rsrc = "rsrc fork"u8.ToArray(); - var archive = BuildDdFile("mac.txt", 0, data, (uint)data.Length, - 0, rsrc, (uint)rsrc.Length); - using var ms = new MemoryStream(archive); - var r = new DiskDoublerReader(ms); - Assert.That(r.Entries, Has.Count.EqualTo(2)); - Assert.That(r.Entries[0].IsDataFork, Is.True); - Assert.That(r.Entries[1].IsDataFork, Is.False); + public void Reader_ResourceForkOnly_HasNoDataEntry() { + using var r = Open(BuildDdFile(0, [], 0, 0, "rsrc"u8.ToArray(), 4)); + Assert.That(r.Entries.Single().IsDataFork, Is.False); } [Test, Category("HappyPath")] - public void Parse_ResourceForkEntry_NameSuffix() { - byte[] data = [0x41]; - byte[] rsrc = [0x42]; - var archive = BuildDdFile("file", 0, data, 1u, 0, rsrc, 1u); - using var ms = new MemoryStream(archive); - var r = new DiskDoublerReader(ms); - Assert.That(r.Entries[1].Name, Does.EndWith(".rsrc")); + public void Reader_MacBinaryWrapped_IsFound() { + var file = new byte[128].Concat(BuildDdFile(0, "wrapped"u8.ToArray(), 7)).ToArray(); + Assert.That(DiskDoublerReader.IsDiskDoubler(file), Is.True); + using var r = Open(file); + Assert.That(r.Extract(r.Entries[0]), Is.EqualTo("wrapped"u8.ToArray())); } [Test, Category("EdgeCase")] - public void Parse_EmptyName_NoException() { - var archive = BuildStoredFile("", [0x41]); - using var ms = new MemoryStream(archive); - var r = new DiskDoublerReader(ms); - Assert.That(r.Entries[0].Name, Is.EqualTo(string.Empty)); + public void Reader_ZeroHeaderCrc_AcceptedAsOldFile() { + using var r = Open(BuildDdFile(0, [9], 1, zeroHeaderCrc: true)); + Assert.That(r.Extract(r.Entries[0]), Is.EqualTo(new byte[] { 9 })); } - // ── Extraction ──────────────────────────────────────────────────────────────── + [Test, Category("Exception")] + public void Reader_WrongHeaderCrc_Throws() { + var file = BuildDdFile(0, [9], 1); + file[40] ^= 0x01; // Finder flags + Assert.Throws(() => _ = Open(file)); + } - [Test, Category("HappyPath"), Category("RoundTrip")] - public void Extract_StoredEntry_ReturnsOriginalData() { - var original = "Hello, DiskDoubler!"u8.ToArray(); - var archive = BuildStoredFile("hello.txt", original); - using var ms = new MemoryStream(archive); - var r = new DiskDoublerReader(ms); - var result = r.Extract(r.Entries[0]); - Assert.That(result, Is.EqualTo(original)); + [Test, Category("Exception")] + [TestCase(new byte[0])] + [TestCase(new byte[] { 0xAB, 0xCD, 0x00, 0x54 })] + [TestCase(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 })] + public void Reader_NotDiskDoubler_Throws(byte[] bytes) => + Assert.Throws(() => _ = Open(bytes)); + + [Test, Category("Exception")] + public void Reader_ForksPastEndOfFile_Throws() { + var file = BuildDdFile(0, new byte[100], 100); + Assert.Throws(() => _ = Open(file[..^1])); } - [Test, Category("HappyPath"), Category("RoundTrip")] - public void Extract_BinaryData_RoundTrips() { - var original = new byte[256]; - for (var i = 0; i < 256; ++i) original[i] = (byte)i; - var archive = BuildStoredFile("all-bytes.bin", original); - using var ms = new MemoryStream(archive); - var r = new DiskDoublerReader(ms); - Assert.That(r.Extract(r.Entries[0]), Is.EqualTo(original)); + // ── Methods ─────────────────────────────────────────────────────────────────── + + [Test, Category("RoundTrip")] + public void Method1_Compress_DecodesAndChecksSum() { + var data = System.Text.Encoding.ASCII.GetBytes(string.Concat(Enumerable.Repeat("DiskDoubler compress fork. ", 50))); + var (fork, checksum) = CompressFork(data); + using var r = Open(BuildDdFile(1, fork, (uint)data.Length, dataChecksum: checksum)); + Assert.That(r.Extract(r.Entries[0]), Is.EqualTo(data)); } - [Test, Category("EdgeCase"), Category("RoundTrip")] - public void Extract_EmptyDataFork_ReturnsEmpty() { - var archive = BuildDdFile("empty", 0, [], 0u, 0, [], 0u); - using var ms = new MemoryStream(archive); - var r = new DiskDoublerReader(ms); - // No entries expected (both forks are zero-sized). - Assert.That(r.Entries, Is.Empty); + [Test, Category("Exception")] + public void Method1_WrongChecksum_Throws() { + var data = "some bytes to compress"u8.ToArray(); + var (fork, checksum) = CompressFork(data); + using var r = Open(BuildDdFile(1, fork, (uint)data.Length, dataChecksum: (ushort)(checksum + 1))); + Assert.Throws(() => r.Extract(r.Entries[0])); } [Test, Category("HappyPath")] - public void Extract_UnsupportedMethod_ReturnsRawBytes() { - // Method 3 (LZC) is proprietary — reader returns raw compressed bytes. - var compressed = new byte[] { 0xAB, 0xCD, 0xEF }; - var archive = BuildDdFile("file.bin", 3, compressed, 100u, 0, [], 0u); - using var ms = new MemoryStream(archive); - var r = new DiskDoublerReader(ms); - var result = r.Extract(r.Entries[0]); - Assert.That(result, Is.EqualTo(compressed)); + [TestCase((byte)6)] + [TestCase((byte)9)] + public void MethodAd_StoredAndCompressedBlocks(byte method) { + // Block 1 stored: "ABC". Block 2 compressed: literal 'x', literal 'y', match distance 2 length 4 + // ("xyxy" overlapping), literal 'z' → "xyxyxyz". + var block2 = PackBits( + "0 01111000" + // literal 'x' + "0 01111001" + // literal 'y' + "1 0 00000010 101" + // match, near (8-bit) distance 2, length 4 + "0 01111010"); // literal 'z' + var fork = AdBlockHeader(3, 3, 0x01).Concat("ABC"u8.ToArray()) + .Concat(AdBlockHeader(block2.Length, 7, 0x08)).Concat(block2).ToArray(); + + using var r = Open(BuildDdFile(method, fork, 10)); + Assert.That(r.Extract(r.Entries[0]), Is.EqualTo("ABCxyxyxyz"u8.ToArray())); + } + + [Test, Category("Boundary")] + [TestCase("1 0 00000000 0", Description = "distance 0")] + [TestCase("1 0 00000001 0", Description = "distance before the block start")] + public void MethodAd_MatchOutsideBlock_Throws(string bits) { + var body = PackBits(bits); + var fork = AdBlockHeader(body.Length, 2, 0x08).Concat(body).ToArray(); + using var r = Open(BuildDdFile(6, fork, 2)); + Assert.Throws(() => r.Extract(r.Entries[0])); + } + + [Test, Category("Exception")] + public void MethodAd_BadBlockHeaderXor_Throws() { + var fork = AdBlockHeader(1, 1, 0x01).Concat(new byte[] { 0x41 }).ToArray(); + fork[11] ^= 0xFF; + using var r = Open(BuildDdFile(6, fork, 1)); + Assert.Throws(() => r.Extract(r.Entries[0])); } - // ── Error handling ──────────────────────────────────────────────────────────── + [Test, Category("Exception")] + [TestCase((byte)2)] + [TestCase((byte)3)] + [TestCase((byte)4)] + [TestCase((byte)5)] + [TestCase((byte)7)] + [TestCase((byte)8)] + [TestCase((byte)10)] + [TestCase((byte)11)] + public void UndecodedMethods_AreRefusedNotReturnedRaw(byte method) { + using var r = Open(BuildDdFile(method, [1, 2, 3, 4], 100)); + Assert.That(r.Entries[0].IsSupported, Is.False); + Assert.Throws(() => r.Extract(r.Entries[0])); + } - [Test, Category("ErrorHandling")] - public void Ctor_StreamTooShort_Throws() { - var tooShort = new byte[10]; - using var ms = new MemoryStream(tooShort); - Assert.Throws(() => _ = new DiskDoublerReader(ms)); + [Test, Category("Exception")] + public void DeltaPreprocessing_IsRefused() { + using var r = Open(BuildDdFile(0, [1, 2, 3], 3, dataDelta: 1)); + Assert.Throws(() => r.Extract(r.Entries[0])); + } + + [Test, Category("HappyPath")] + public void Method_HighBitFlag_IsMasked() { + using var r = Open(BuildDdFile(0x80, [7], 1)); + Assert.That(r.Entries[0].Method, Is.Zero); + Assert.That(r.Extract(r.Entries[0]), Is.EqualTo(new byte[] { 7 })); } - [Test, Category("ErrorHandling")] + // ── Descriptor ──────────────────────────────────────────────────────────────── + + [Test, Category("Exception")] public void Extract_NullEntry_Throws() { - var archive = BuildStoredFile("x", [0x41]); - using var ms = new MemoryStream(archive); - var r = new DiskDoublerReader(ms); + using var r = Open(BuildDdFile(0, [1], 1)); Assert.Throws(() => r.Extract(null!)); } - [Test, Category("ErrorHandling")] + [Test, Category("Exception")] public void Dispose_ThenExtract_Throws() { - var archive = BuildStoredFile("f.txt", [0x41]); - using var ms = new MemoryStream(archive); - var r = new DiskDoublerReader(ms); + var r = Open(BuildDdFile(0, [1], 1)); var entry = r.Entries[0]; r.Dispose(); Assert.Throws(() => r.Extract(entry)); } - // ── leaveOpen ───────────────────────────────────────────────────────────────── - - [Test, Category("HappyPath")] - public void LeaveOpen_True_StreamRemainsUsable() { - var archive = BuildStoredFile("f.txt", [0x41]); - using var ms = new MemoryStream(archive); - using (var r = new DiskDoublerReader(ms, leaveOpen: true)) { - _ = r.Entries; - } - Assert.That(ms.CanRead, Is.True); - } - - [Test, Category("HappyPath")] - public void Descriptor_ReportsWormCapability() { - var d = new DiskDoublerFormatDescriptor(); - Assert.That(d.Capabilities.HasFlag(Compression.Registry.FormatCapabilities.CanCreate), Is.True); - } - [Test, Category("HappyPath"), Category("RoundTrip")] - public void Writer_Stored_RoundTrips() { - var payload = "hello diskdoubler"u8.ToArray(); - var w = new DiskDoublerWriter(); - w.SetFile("test.txt", payload); + public void Descriptor_Create_ThenList() { + var d = new DiskDoublerFormatDescriptor(); using var ms = new MemoryStream(); - w.WriteTo(ms); + ((Compression.Registry.IArchiveCreatable)d).Create( + ms, + [Compression.Registry.ArchiveInputInfo.InMemory("payload.bin", "dd descriptor"u8.ToArray())], + new Compression.Registry.FormatCreateOptions()); ms.Position = 0; - - var r = new DiskDoublerReader(ms); - Assert.That(r.Entries, Has.Count.EqualTo(1)); - Assert.That(r.Entries[0].Name, Is.EqualTo("test.txt")); - Assert.That(r.Entries[0].OriginalSize, Is.EqualTo(payload.Length)); - Assert.That(r.Extract(r.Entries[0]), Is.EqualTo(payload)); - } - - [Test, Category("HappyPath"), Category("RoundTrip")] - public void Descriptor_Create_RoundTrips() { - var tmp = Path.GetTempFileName(); - try { - File.WriteAllBytes(tmp, "dd descriptor"u8.ToArray()); - var d = new DiskDoublerFormatDescriptor(); - using var ms = new MemoryStream(); - ((Compression.Registry.IArchiveCreatable)d).Create( - ms, - [new Compression.Registry.ArchiveInputInfo(tmp, "payload.bin", false)], - new Compression.Registry.FormatCreateOptions()); - ms.Position = 0; - var entries = d.List(ms, null); - Assert.That(entries, Has.Count.EqualTo(1)); - Assert.That(entries[0].Name, Is.EqualTo("payload.bin")); - } finally { - File.Delete(tmp); - } + var entries = d.List(ms, null); + Assert.That(entries, Has.Count.EqualTo(1)); + Assert.That(entries[0].OriginalSize, Is.EqualTo(13)); + Assert.That(entries[0].Method, Is.EqualTo("None")); } } diff --git a/Compression.Tests/PackIt/PackItInPlaceModifyTests.cs b/Compression.Tests/PackIt/PackItInPlaceModifyTests.cs index 78715570f..57feb2179 100644 --- a/Compression.Tests/PackIt/PackItInPlaceModifyTests.cs +++ b/Compression.Tests/PackIt/PackItInPlaceModifyTests.cs @@ -6,14 +6,15 @@ namespace Compression.Tests.PackIt; /// /// Locks PackIt's modifier as TRUE in-place at the byte level: /// -/// Add: appends a new entry at EOF. The entire pre-existing byte range -/// [0, oldLength) must be byte-identical at the original offsets. +/// Add: writes the new entry over the "PEnd" marker and re-appends the +/// marker. Every byte before the marker, [0, oldLength - 4), must be +/// byte-identical at the original offsets. /// Remove of last entry: the surviving prefix at [0, removedEntryOffset) -/// must be byte-identical at the original offsets. +/// must be byte-identical at the original offsets, followed by the marker. /// /// These are the contract that distinguishes a real random-access modifier -/// from a rebuild-disguised one. PackIt has no central directory and no -/// trailing footer, so an EOF-append is a clean in-place mutation. +/// from a rebuild-disguised one. PackIt has no central directory; its only +/// trailer is the four-byte "PEnd" marker. /// [TestFixture] public class PackItInPlaceModifyTests { @@ -36,9 +37,11 @@ public void AddFile_PrefixBytesAreByteIdenticalAtOriginalOffsets() { var after = ms.ToArray(); Assert.That(after.Length, Is.GreaterThan(oldLength), "Append must grow the archive."); - Assert.That(after.AsSpan(0, oldLength).SequenceEqual(before), + Assert.That(after.AsSpan(0, oldLength - 4).SequenceEqual(before.AsSpan(0, oldLength - 4)), Is.True, - "Pre-existing bytes [0, oldLength) must be byte-identical at original offsets."); + "Pre-existing entries [0, oldLength - 4) must be byte-identical at original offsets."); + Assert.That(after.AsSpan(after.Length - 4).SequenceEqual("PEnd"u8), Is.True, + "The end marker must follow the appended entry."); } [Test, Category("Contract")] @@ -50,9 +53,9 @@ public void AddFile_AppendsNewEntryAtEofOnly() { ms.Write(seed); PackItModifier.AddFile(ms, "added.bin", new byte[42]); - // The added entry occupies exactly [oldLength, newLength). - // 87-byte header + 42-byte data = 129 byte entry. - Assert.That(ms.Length, Is.EqualTo(oldLength + 87 + 42)); + // The added entry occupies exactly [oldLength - 4, newLength - 4): + // 4-byte magic + 94-byte header + 42-byte data fork + 2-byte fork CRC. + Assert.That(ms.Length, Is.EqualTo(oldLength + 4 + 94 + 42 + 2)); } [Test, Category("Contract")] @@ -73,11 +76,12 @@ public void RemoveLastEntry_PrefixBytesAreByteIdenticalAtOriginalOffsets() { Assert.That(removed, Is.True); var after = ms.ToArray(); - Assert.That(after.Length, Is.EqualTo(droppedEntryOffset), - "Removing the last entry must truncate to exactly the dropped entry's start offset."); - Assert.That(after.AsSpan().SequenceEqual(seed.AsSpan(0, (int)droppedEntryOffset)), + Assert.That(after.Length, Is.EqualTo(droppedEntryOffset + 4), + "Removing the last entry must leave exactly the survivors plus the end marker."); + Assert.That(after.AsSpan(0, (int)droppedEntryOffset).SequenceEqual(seed.AsSpan(0, (int)droppedEntryOffset)), Is.True, "Survivor prefix [0, removedEntryOffset) must be byte-identical at original offsets."); + Assert.That(after.AsSpan((int)droppedEntryOffset).SequenceEqual("PEnd"u8), Is.True); } [Test, Category("Contract")] @@ -114,17 +118,14 @@ private static byte[] BuildSeedPit(params (string Name, byte[] Data)[] files) { /// /// Walks the PackIt entry chain on a buffer copy and returns the byte - /// offset of the named entry's "PMag"/"PMa4" magic (i.e. the entry's - /// 87-byte fixed header begins at this offset). + /// offset of the named entry's "PMag"/"PMa4" magic. /// private static long FindEntryOffset(byte[] data, string targetName) { using var ms = new MemoryStream(data); using var r = new PackItReader(ms, leaveOpen: true); foreach (var e in r.Entries) { if (!string.Equals(e.Name, targetName, StringComparison.OrdinalIgnoreCase)) continue; - // The reader exposes the data-fork offset; the entry's header magic is - // 87 bytes earlier (PackItReader.EntryHeaderSize). - return e.DataOffset - PackItReader.EntryHeaderSize; + return e.EntryOffset; } throw new InvalidOperationException($"Entry '{targetName}' not found in seed archive."); } diff --git a/Compression.Tests/PackIt/PackItOracleTests.cs b/Compression.Tests/PackIt/PackItOracleTests.cs new file mode 100644 index 000000000..a134475cf --- /dev/null +++ b/Compression.Tests/PackIt/PackItOracleTests.cs @@ -0,0 +1,108 @@ +#pragma warning disable CS1591 +using System.Security.Cryptography; +using FileFormat.PackIt; + +namespace Compression.Tests.PackIt; + +/// +/// PackIt against The Unarchiver (unar, XADMaster), the maintained reader of the format: +/// what we write it must read, what it reads we must read the same way. +/// +[TestFixture] +[Category("ExternalInterop")] +public class PackItOracleTests { + private string _dir = null!; + + [SetUp] + public void SetUp() { + this._dir = Path.Combine(Path.GetTempPath(), "cwb_pit_" + Guid.NewGuid().ToString("N")[..8]); + Directory.CreateDirectory(this._dir); + } + + [TearDown] + public void TearDown() { + try { Directory.Delete(this._dir, true); } catch { /* best effort */ } + } + + private static void RequireUnar() { + if (!FsInteropToolbox.WslAvailable) + Assert.Ignore("WSL not available."); + if (!FsInteropToolbox.WslHasTool("unar")) + Assert.Ignore("unar is not installed. Run inside WSL / on Linux: `sudo apt install -y unar`."); + } + + /// Extracts with unar (data forks only) and returns file name → bytes. + private Dictionary Unar(string archive, bool requireSuccess = true) { + var into = Path.Combine(this._dir, "unar_" + Path.GetFileNameWithoutExtension(archive)); + Directory.CreateDirectory(into); + var r = FsInteropToolbox.RunWsl( + $"unar -q -f -k skip -D -o {FsInteropToolbox.WinToWsl(into)} {FsInteropToolbox.WinToWsl(archive)}"); + if (requireSuccess) + Assert.That(r.ExitCode, Is.EqualTo(0), $"unar failed:\n{r.StdOut}\n{r.StdErr}"); + return Directory.EnumerateFiles(into, "*", SearchOption.AllDirectories) + .ToDictionary(f => Path.GetFileName(f), File.ReadAllBytes, StringComparer.Ordinal); + } + + [Test] + public void ArchiveWeWrote_ExtractsInUnar() { + RequireUnar(); + var text = "a short line of text\n"u8.ToArray(); + var random = new byte[5000]; + new Random(17).NextBytes(random); + var path = Path.Combine(this._dir, "ours.pit"); + using (var fs = File.Create(path)) + using (var w = new PackItWriter(fs)) { + w.AddFile("A.TXT", text); + w.AddFile("B.BIN", random, "BINA", "????", resourceFork: "rsrc"u8.ToArray()); + w.AddFile("EMPTY", []); + } + + var got = this.Unar(path); + Assert.That(got["A.TXT"], Is.EqualTo(text)); + Assert.That(got["B.BIN"], Is.EqualTo(random)); + Assert.That(got["EMPTY"], Is.Empty); + } + + [Test] + public void HuffmanEntry_DecodesTheSameInUnarAndHere() { + RequireUnar(); + var data = System.Text.Encoding.ASCII.GetBytes(string.Concat(Enumerable.Repeat("PackIt II Huffman, oracle check. ", 60))); + var path = Path.Combine(this._dir, "huff.pit"); + using (var fs = File.Create(path)) { + PackItTestEncoder.WriteCompressedEntry(fs, "HUFF.TXT", data, []); + fs.Write("PEnd"u8); + } + + Assert.That(this.Unar(path)["HUFF.TXT"], Is.EqualTo(data), "unar's reading of the stream"); + using var r = new PackItReader(File.OpenRead(path)); + Assert.That(r.Extract(r.Entries.Single()), Is.EqualTo(data), "our reading of the same stream"); + } + + /// + /// Real PackIt archives, dropped into test-corpus/packit/ (not committed: their redistribution + /// terms are unknown). The dexvert sample set (sembiance.com/fileFormatSamples/archive/packIt) holds + /// nine, seven of them Huffman-coded. Every data fork must match what unar extracts. + /// + [Test] + public void RealArchives_InTestCorpus_MatchUnar() { + var corpus = Path.GetFullPath(Path.Combine(TestContext.CurrentContext.TestDirectory, "..", "..", "..", "..", "test-corpus", "packit")); + if (!Directory.Exists(corpus) || !Directory.EnumerateFiles(corpus, "*.pit").Any()) + Assert.Ignore($"No real .pit files under {corpus}."); + RequireUnar(); + + var checkedForks = 0; + foreach (var file in Directory.EnumerateFiles(corpus, "*.pit").Order()) { + // unar cannot open a resource-fork-only entry while skipping forks; every data fork still lands. + var expected = this.Unar(file, requireSuccess: false); + using var r = new PackItReader(File.OpenRead(file)); + Assert.That(r.IsIncomplete, Is.False, file); + foreach (var e in r.Entries.Where(e => e.DataForkSize > 0)) { + var ours = r.Extract(e); + Assert.That(expected.TryGetValue(e.Name, out var theirs), Is.True, $"{file}: unar produced no '{e.Name}'"); + Assert.That(SHA256.HashData(ours), Is.EqualTo(SHA256.HashData(theirs!)), $"{file}: '{e.Name}'"); + ++checkedForks; + } + } + Assert.That(checkedForks, Is.GreaterThan(0)); + } +} diff --git a/Compression.Tests/PackIt/PackItTests.cs b/Compression.Tests/PackIt/PackItTests.cs index b43dc542c..903e4a7e1 100644 --- a/Compression.Tests/PackIt/PackItTests.cs +++ b/Compression.Tests/PackIt/PackItTests.cs @@ -1,5 +1,6 @@ using System.Buffers.Binary; using System.Text; +using Compression.Core.Checksums; using FileFormat.PackIt; namespace Compression.Tests.PackIt; @@ -7,294 +8,293 @@ namespace Compression.Tests.PackIt; [TestFixture] public class PackItTests { - // ── Archive builder helper ──────────────────────────────────────────────────── + // ── Helpers ─────────────────────────────────────────────────────────────────── - /// - /// Manually assembles a single PackIt entry in a stream, using the given magic, - /// name, file type, creator, data fork, and resource fork. - /// - private static void WriteRawEntry( - MemoryStream ms, - byte[] magic, - string name, - string fileType, - string creator, - byte[] dataFork, - byte[] rsrcFork) { - - ms.Write(magic); - - // 63-byte Pascal filename field - var nameBytes = Encoding.Latin1.GetBytes(name); - var nameLen = Math.Min(nameBytes.Length, 62); - var nameField = new byte[63]; - nameField[0] = (byte)nameLen; - Array.Copy(nameBytes, 0, nameField, 1, nameLen); - ms.Write(nameField); - - // file type (4) + creator (4) - var typeBytes = PadOrTrunc(Encoding.ASCII.GetBytes(fileType), 4); - var creatorBytes = PadOrTrunc(Encoding.ASCII.GetBytes(creator), 4); - ms.Write(typeBytes); - ms.Write(creatorBytes); - - // Finder flags (2) + locked (1) + pad (1) - ms.Write(new byte[4]); - - // data fork size (uint32 BE) + resource fork size (uint32 BE) - var sizes = new byte[8]; - BinaryPrimitives.WriteUInt32BigEndian(sizes.AsSpan(0, 4), (uint)dataFork.Length); - BinaryPrimitives.WriteUInt32BigEndian(sizes.AsSpan(4, 4), (uint)rsrcFork.Length); - ms.Write(sizes); - - ms.Write(dataFork); - ms.Write(rsrcFork); - } - - private static byte[] PadOrTrunc(byte[] src, int len) { - var dest = new byte[len]; - Array.Copy(src, dest, Math.Min(src.Length, len)); - for (var i = src.Length; i < len; ++i) - dest[i] = (byte)' '; - return dest; - } - - /// Builds a single-entry stored archive. - private static byte[] BuildStoredArchive(string name, byte[] data) { + private static byte[] Write(params (string Name, byte[] Data)[] files) { using var ms = new MemoryStream(); - WriteRawEntry(ms, "PMag"u8.ToArray(), name, "TEXT", "CWIE", data, []); + using (var w = new PackItWriter(ms, leaveOpen: true)) + foreach (var (name, data) in files) + w.AddFile(name, data); return ms.ToArray(); } - // ── Header / entry parsing ──────────────────────────────────────────────────── + private static PackItReader Open(byte[] archive) => new(new MemoryStream(archive)); + + private static readonly DateTime SampleTime = new(1990, 6, 15, 12, 30, 0, DateTimeKind.Utc); + + // ── Layout ──────────────────────────────────────────────────────────────────── [Test, Category("HappyPath")] - public void Parse_SingleStoredEntry_OneEntry() { - var archive = BuildStoredArchive("hello.txt", "hello"u8.ToArray()); - using var ms = new MemoryStream(archive); - var r = new PackItReader(ms); - Assert.That(r.Entries, Has.Count.EqualTo(1)); + public void Writer_Layout_MagicHeaderForksCrcAndEndMarker() { + var data = "Hello, PackIt!"u8.ToArray(); + var archive = Write(("hello.txt", data)); + + Assert.That(archive.AsSpan(0, 4).SequenceEqual("PMag"u8), Is.True); + var header = archive.AsSpan(4, 94); + Assert.That(header[0], Is.EqualTo(9)); + Assert.That(Encoding.Latin1.GetString(header.Slice(1, 9)), Is.EqualTo("hello.txt")); + Assert.That(Encoding.Latin1.GetString(header.Slice(64, 4)), Is.EqualTo("TEXT")); + Assert.That(Encoding.Latin1.GetString(header.Slice(68, 4)), Is.EqualTo("CWIE")); + Assert.That(BinaryPrimitives.ReadUInt32BigEndian(header[76..]), Is.EqualTo((uint)data.Length)); + Assert.That(BinaryPrimitives.ReadUInt32BigEndian(header[80..]), Is.Zero); + Assert.That(BinaryPrimitives.ReadUInt16BigEndian(header[92..]), Is.EqualTo(Crc16Ccitt.Compute(header[..92])), + "header CRC is CRC-16/XMODEM over the 92 bytes before it"); + Assert.That(archive.AsSpan(98, data.Length).SequenceEqual(data), Is.True); + Assert.That(BinaryPrimitives.ReadUInt16BigEndian(archive.AsSpan(98 + data.Length)), Is.EqualTo(Crc16Ccitt.Compute(data))); + Assert.That(archive.AsSpan(archive.Length - 4).SequenceEqual("PEnd"u8), Is.True); + Assert.That(archive, Has.Length.EqualTo(4 + 94 + data.Length + 2 + 4)); } [Test, Category("HappyPath")] - public void Parse_Entry_Name() { - var archive = BuildStoredArchive("readme.txt", [0x41]); - using var ms = new MemoryStream(archive); - var r = new PackItReader(ms); - Assert.That(r.Entries[0].Name, Is.EqualTo("readme.txt")); + public void Writer_NoFiles_IsJustTheEndMarker() { + var archive = Write(); + Assert.That(archive, Is.EqualTo("PEnd"u8.ToArray())); + using var r = Open(archive); + Assert.That(r.Entries, Is.Empty); + Assert.That(r.IsIncomplete, Is.False); } - [Test, Category("HappyPath")] - public void Parse_Entry_DataForkSize() { - var data = new byte[128]; - var archive = BuildStoredArchive("data.bin", data); - using var ms = new MemoryStream(archive); - var r = new PackItReader(ms); - Assert.That(r.Entries[0].DataForkSize, Is.EqualTo(128)); + [Test, Category("HappyPath"), Category("RoundTrip")] + public void Writer_Reader_RoundTrip_MetadataAndForks() { + var data = new byte[3000]; + new Random(5).NextBytes(data); + var rsrc = "resource fork bytes"u8.ToArray(); + using var ms = new MemoryStream(); + using (var w = new PackItWriter(ms, leaveOpen: true)) + w.AddFile("doc", data, "APPL", "ttxt", rsrc, SampleTime); + + ms.Position = 0; + using var r = new PackItReader(ms); + var e = r.Entries.Single(); + Assert.That((e.Name, e.FileType, e.Creator), Is.EqualTo(("doc", "APPL", "ttxt"))); + Assert.That((e.DataForkSize, e.ResourceForkSize), Is.EqualTo((3000L, (long)rsrc.Length))); + Assert.That(e.Modified, Is.EqualTo(SampleTime)); + Assert.That(e.IsCompressed, Is.False); + Assert.That(r.Extract(e), Is.EqualTo(data)); + Assert.That(r.ExtractResourceFork(e), Is.EqualTo(rsrc)); } - [Test, Category("HappyPath")] - public void Parse_Entry_FileTypeAndCreator() { - var archive = BuildStoredArchive("doc.txt", [0x41]); - using var ms = new MemoryStream(archive); - var r = new PackItReader(ms); - Assert.That(r.Entries[0].FileType, Is.EqualTo("TEXT")); - Assert.That(r.Entries[0].Creator, Is.EqualTo("CWIE")); + [Test, Category("RoundTrip")] + public void Writer_Reader_MultipleFiles_InOrder() { + var archive = Write(("a.txt", "Hello"u8.ToArray()), ("b.txt", []), ("c.bin", new byte[700])); + using var r = Open(archive); + Assert.That(r.Entries.Select(e => e.Name), Is.EqualTo(new[] { "a.txt", "b.txt", "c.bin" })); + Assert.That(r.Extract(r.Entries[0]), Is.EqualTo("Hello"u8.ToArray())); + Assert.That(r.Extract(r.Entries[1]), Is.Empty); + Assert.That(r.Extract(r.Entries[2]), Is.EqualTo(new byte[700])); } - [Test, Category("HappyPath")] - public void Parse_StoredEntry_IsCompressedFalse() { - var archive = BuildStoredArchive("f.txt", [0x41]); - using var ms = new MemoryStream(archive); - var r = new PackItReader(ms); - Assert.That(r.Entries[0].IsCompressed, Is.False); + [Test, Category("Boundary")] + [TestCase(63, 63)] + [TestCase(64, 63)] + [TestCase(1, 1)] + public void Writer_Name_IsCutAtSixtyThreeBytes(int length, int kept) { + var name = new string('n', length); + using var r = Open(Write((name, [1]))); + Assert.That(r.Entries[0].Name, Is.EqualTo(name[..kept])); } + // ── Huffman ("PMa4") entries ───────────────────────────────────────────────── + [Test, Category("HappyPath")] - public void Parse_CompressedEntry_IsCompressedTrue() { - // Build an entry with "PMa4" magic. + public void Compressed_Entry_DecodesBothForksAndFindsNextEntry() { + var data = Encoding.ASCII.GetBytes(string.Concat(Enumerable.Repeat("the quick brown fox ", 40))); + var rsrc = new byte[] { 0, 0, 1, 2, 3, 0, 0, 0 }; using var ms = new MemoryStream(); - WriteRawEntry(ms, "PMa4"u8.ToArray(), "compressed.bin", "TEXT", "CWIE", [0xAB, 0xCD], []); + PackItTestEncoder.WriteCompressedEntry(ms, "fox.txt", data, rsrc); + PackItTestEncoder.WriteStoredEntry(ms, "after.txt", "tail"u8.ToArray(), []); + ms.Write("PEnd"u8); + ms.Position = 0; - var r = new PackItReader(ms); + using var r = new PackItReader(ms); + Assert.That(r.Entries, Has.Count.EqualTo(2)); Assert.That(r.Entries[0].IsCompressed, Is.True); + Assert.That(r.Entries[0].EntryLength, Is.LessThan(4 + 94 + data.Length + rsrc.Length + 2), + "the test encoder must actually compress, or the boundary walk is not exercised"); + Assert.That(r.Extract(r.Entries[0]), Is.EqualTo(data)); + Assert.That(r.ExtractResourceFork(r.Entries[0]), Is.EqualTo(rsrc)); + Assert.That(r.Extract(r.Entries[1]), Is.EqualTo("tail"u8.ToArray())); + Assert.That(r.IsIncomplete, Is.False); } - [Test, Category("HappyPath")] - public void Parse_MultipleEntries_AllFound() { + [Test, Category("Exception")] + public void Compressed_ForkCrcMismatch_Throws() { using var ms = new MemoryStream(); - WriteRawEntry(ms, "PMag"u8.ToArray(), "first.txt", "TEXT", "CWIE", "first"u8.ToArray(), []); - WriteRawEntry(ms, "PMag"u8.ToArray(), "second.txt", "TEXT", "CWIE", "second"u8.ToArray(), []); - WriteRawEntry(ms, "PMag"u8.ToArray(), "third.txt", "TEXT", "CWIE", "third"u8.ToArray(), []); + PackItTestEncoder.WriteCompressedEntry(ms, "bad.txt", "payload"u8.ToArray(), [], corruptForkCrc: true); + ms.Write("PEnd"u8); ms.Position = 0; - var r = new PackItReader(ms); - Assert.That(r.Entries, Has.Count.EqualTo(3)); - Assert.That(r.Entries[0].Name, Is.EqualTo("first.txt")); - Assert.That(r.Entries[1].Name, Is.EqualTo("second.txt")); - Assert.That(r.Entries[2].Name, Is.EqualTo("third.txt")); + using var r = new PackItReader(ms); + Assert.Throws(() => r.Extract(r.Entries[0])); } - [Test, Category("EdgeCase")] - public void Parse_UnknownMagic_StopsEarly() { - using var ms = new MemoryStream(); - WriteRawEntry(ms, "PMag"u8.ToArray(), "valid.txt", "TEXT", "CWIE", [0x41], []); - ms.Write("XXXX"u8); // garbage magic — should stop parsing - ms.Position = 0; - var r = new PackItReader(ms); - Assert.That(r.Entries, Has.Count.EqualTo(1)); - } + // ── Integrity ───────────────────────────────────────────────────────────────── - [Test, Category("EdgeCase")] - public void Parse_EmptyStream_NoEntries() { - using var ms = new MemoryStream(); - var r = new PackItReader(ms); - Assert.That(r.Entries, Is.Empty); + [Test, Category("Exception")] + public void Stored_ForkCrcMismatch_Throws() { + var archive = Write(("x.txt", "abc"u8.ToArray())); + archive[98] ^= 0x01; // first data byte + using var r = Open(archive); + Assert.Throws(() => r.Extract(r.Entries[0])); } - // ── Extraction ──────────────────────────────────────────────────────────────── + [Test, Category("Exception")] + public void HeaderCrcMismatch_ListsButRefusesExtraction() { + var archive = Write(("x.txt", "abc"u8.ToArray())); + archive[4 + 72] ^= 0x01; // Finder flags: covered by the header CRC, changes nothing else + using var r = Open(archive); + Assert.That(r.Entries[0].HeaderCrcValid, Is.False); + Assert.Throws(() => r.Extract(r.Entries[0])); + } - [Test, Category("HappyPath"), Category("RoundTrip")] - public void Extract_StoredEntry_ReturnsOriginalData() { - var original = "Hello, PackIt!"u8.ToArray(); - var archive = BuildStoredArchive("hello.txt", original); - using var ms = new MemoryStream(archive); - var r = new PackItReader(ms); - var result = r.Extract(r.Entries[0]); - Assert.That(result, Is.EqualTo(original)); + [Test, Category("Exception")] + public void EncryptedEntry_ListedButRefused() { + using var ms = new MemoryStream(); + PackItTestEncoder.WriteStoredEntry(ms, "plain.txt", "ok"u8.ToArray(), []); + ms.Write("PMa5"u8); + ms.Write(new byte[40]); + ms.Position = 0; + using var r = new PackItReader(ms); + Assert.That(r.Entries, Has.Count.EqualTo(2)); + Assert.That(r.Entries[1].IsEncrypted, Is.True); + Assert.That(r.IsIncomplete, Is.True); + Assert.That(r.Extract(r.Entries[0]), Is.EqualTo("ok"u8.ToArray())); + Assert.Throws(() => r.Extract(r.Entries[1])); } - [Test, Category("HappyPath"), Category("RoundTrip")] - public void Extract_BinaryData_RoundTrips() { - var original = new byte[256]; - for (var i = 0; i < 256; ++i) original[i] = (byte)i; - var archive = BuildStoredArchive("allbytes.bin", original); - using var ms = new MemoryStream(archive); - var r = new PackItReader(ms); - Assert.That(r.Extract(r.Entries[0]), Is.EqualTo(original)); + // ── Damaged and foreign input ───────────────────────────────────────────────── + + [Test, Category("EdgeCase")] + public void MissingEndMarker_IsTolerated() { + var archive = Write(("a.txt", "x"u8.ToArray())); + using var r = Open(archive[..^4]); + Assert.That(r.Entries, Has.Count.EqualTo(1)); + Assert.That(r.IsIncomplete, Is.False); } - [Test, Category("EdgeCase"), Category("RoundTrip")] - public void Extract_EmptyDataFork_ReturnsEmpty() { - var archive = BuildStoredArchive("empty.txt", []); - using var ms = new MemoryStream(archive); - var r = new PackItReader(ms); - Assert.That(r.Extract(r.Entries[0]), Is.Empty); + [Test, Category("EdgeCase")] + public void TruncatedSecondEntry_KeepsFirstAndFlagsIncomplete() { + var archive = Write(("a.txt", "first"u8.ToArray()), ("b.txt", new byte[100])); + using var r = Open(archive[..^50]); + Assert.That(r.Entries.Select(e => e.Name), Is.EqualTo(new[] { "a.txt" })); + Assert.That(r.IsIncomplete, Is.True); } - [Test, Category("HappyPath"), Category("RoundTrip")] - public void Extract_MultipleEntries_CorrectData() { - var data1 = "Hello"u8.ToArray(); - var data2 = "World"u8.ToArray(); - using var ms = new MemoryStream(); - WriteRawEntry(ms, "PMag"u8.ToArray(), "a.txt", "TEXT", "CWIE", data1, []); - WriteRawEntry(ms, "PMag"u8.ToArray(), "b.txt", "TEXT", "CWIE", data2, []); - ms.Position = 0; - var r = new PackItReader(ms); - Assert.That(r.Extract(r.Entries[0]), Is.EqualTo(data1)); - Assert.That(r.Extract(r.Entries[1]), Is.EqualTo(data2)); + [Test, Category("EdgeCase")] + public void UnknownMagicAfterEntries_StopsAndFlagsIncomplete() { + var archive = Write(("valid.txt", [0x41])); + var damaged = archive[..^4].Concat("XXXX"u8.ToArray()).ToArray(); + using var r = Open(damaged); + Assert.That(r.Entries, Has.Count.EqualTo(1)); + Assert.That(r.IsIncomplete, Is.True); } - // ── Error handling ──────────────────────────────────────────────────────────── + [Test, Category("Exception")] + [TestCase(new byte[0])] + [TestCase(new byte[] { (byte)'P', (byte)'K', 3, 4, 0, 0 })] + public void NotAPackItArchive_Throws(byte[] bytes) => + Assert.Throws(() => _ = Open(bytes)); - [Test, Category("ErrorHandling")] + [Test, Category("Exception")] public void Extract_NullEntry_Throws() { - var archive = BuildStoredArchive("x.txt", [0x41]); - using var ms = new MemoryStream(archive); - var r = new PackItReader(ms); + using var r = Open(Write(("x.txt", [0x41]))); Assert.Throws(() => r.Extract(null!)); } - [Test, Category("ErrorHandling")] + [Test, Category("Exception")] public void Dispose_ThenExtract_Throws() { - var archive = BuildStoredArchive("f.txt", [0x41]); - using var ms = new MemoryStream(archive); - var r = new PackItReader(ms); + var r = Open(Write(("f.txt", [0x41]))); var entry = r.Entries[0]; r.Dispose(); Assert.Throws(() => r.Extract(entry)); } - // ── Writer round-trip ───────────────────────────────────────────────────────── - - [Test, Category("HappyPath"), Category("RoundTrip")] - public void Writer_RoundTrip_SingleFile() { - var data = "Hello, PackIt Writer!"u8.ToArray(); - using var ms = new MemoryStream(); - using (var w = new PackItWriter(ms, leaveOpen: true)) - w.AddFile("hello.txt", data); - - ms.Position = 0; - var r = new PackItReader(ms); - Assert.That(r.Entries, Has.Count.EqualTo(1)); - Assert.That(r.Entries[0].Name, Is.EqualTo("hello.txt")); - Assert.That(r.Extract(r.Entries[0]), Is.EqualTo(data)); - } - - [Test, Category("HappyPath"), Category("RoundTrip")] - public void Writer_RoundTrip_MultipleFiles() { - var data1 = new byte[256]; - var data2 = new byte[128]; - Random.Shared.NextBytes(data1); - Random.Shared.NextBytes(data2); - - using var ms = new MemoryStream(); - using (var w = new PackItWriter(ms, leaveOpen: true)) { - w.AddFile("file1.bin", data1); - w.AddFile("file2.bin", data2); - } - - ms.Position = 0; - var r = new PackItReader(ms); - Assert.That(r.Entries, Has.Count.EqualTo(2)); - Assert.That(r.Extract(r.Entries[0]), Is.EqualTo(data1)); - Assert.That(r.Extract(r.Entries[1]), Is.EqualTo(data2)); + [Test, Category("HappyPath")] + public void Reader_LeaveOpen_True_StreamRemainsUsable() { + using var ms = new MemoryStream(Write(("f.txt", [0x41]))); + using (var r = new PackItReader(ms, leaveOpen: true)) + _ = r.Entries; + Assert.That(ms.CanRead, Is.True); } +} - [Test, Category("HappyPath"), Category("RoundTrip")] - public void Writer_RoundTrip_EmptyFile() { - using var ms = new MemoryStream(); - using (var w = new PackItWriter(ms, leaveOpen: true)) - w.AddFile("empty.txt", []); - - ms.Position = 0; - var r = new PackItReader(ms); - Assert.That(r.Entries, Has.Count.EqualTo(1)); - Assert.That(r.Extract(r.Entries[0]), Is.Empty); +/// +/// Writes PackIt entries for tests, including "PMa4" Huffman entries, which the product writer does not +/// produce. The Huffman tree is built from byte frequencies; any prefix tree is valid PackIt. +/// +internal static class PackItTestEncoder { + public static void WriteStoredEntry(Stream output, string name, byte[] data, byte[] rsrc) { + output.Write("PMag"u8); + output.Write(Body(name, data, rsrc, corruptForkCrc: false)); } - [Test, Category("HappyPath")] - public void Writer_Magic_IsPMag() { - using var ms = new MemoryStream(); - using (var w = new PackItWriter(ms, leaveOpen: true)) - w.AddFile("x", [1, 2, 3]); - - ms.Position = 0; - Span magic = stackalloc byte[4]; - ms.ReadExactly(magic); - Assert.That(magic.ToArray(), Is.EqualTo("PMag"u8.ToArray())); + public static void WriteCompressedEntry(Stream output, string name, byte[] data, byte[] rsrc, bool corruptForkCrc = false) { + var body = Body(name, data, rsrc, corruptForkCrc); + output.Write("PMa4"u8); + output.Write(Huffman(body)); } - [Test, Category("HappyPath")] - public void Writer_StoredEntry_IsCompressedFalse() { - var data = "test"u8.ToArray(); - using var ms = new MemoryStream(); - using (var w = new PackItWriter(ms, leaveOpen: true)) - w.AddFile("t.txt", data); - - ms.Position = 0; - var r = new PackItReader(ms); - Assert.That(r.Entries[0].IsCompressed, Is.False); + private static byte[] Body(string name, byte[] data, byte[] rsrc, bool corruptForkCrc) { + var header = new byte[94]; + var nameBytes = Encoding.Latin1.GetBytes(name); + header[0] = (byte)nameBytes.Length; + nameBytes.CopyTo(header, 1); + "TEXT"u8.CopyTo(header.AsSpan(64)); + "ttxt"u8.CopyTo(header.AsSpan(68)); + BinaryPrimitives.WriteUInt32BigEndian(header.AsSpan(76), (uint)data.Length); + BinaryPrimitives.WriteUInt32BigEndian(header.AsSpan(80), (uint)rsrc.Length); + BinaryPrimitives.WriteUInt16BigEndian(header.AsSpan(92), Crc16Ccitt.Compute(header.AsSpan(0, 92))); + var forks = data.Concat(rsrc).ToArray(); + var crc = Crc16Ccitt.Compute(forks); + if (corruptForkCrc) crc ^= 0x5555; + var trailer = new byte[2]; + BinaryPrimitives.WriteUInt16BigEndian(trailer, crc); + return [.. header, .. forks, .. trailer]; } - // ── leaveOpen ───────────────────────────────────────────────────────────────── - - [Test, Category("HappyPath")] - public void Reader_LeaveOpen_True_StreamRemainsUsable() { - var archive = BuildStoredArchive("f.txt", [0x41]); - using var ms = new MemoryStream(archive); - using (var r = new PackItReader(ms, leaveOpen: true)) { - _ = r.Entries; + private sealed record Node(int Weight, int Symbol, Node? Zero, Node? One); + + /// Huffman-codes as PackIt does: pre-order tree, then MSB-first codes, byte-padded. + public static byte[] Huffman(byte[] body) { + var counts = new int[256]; + foreach (var b in body) ++counts[b]; + var queue = new PriorityQueue(); + var order = 0; + for (var s = 0; s < 256; ++s) + if (counts[s] > 0) + queue.Enqueue(new Node(counts[s], s, null, null), (counts[s], order++)); + if (queue.Count == 1) { + var only = queue.Dequeue(); + queue.Enqueue(new Node(only.Weight, -1, only, new Node(0, (only.Symbol + 1) & 0xFF, null, null)), (only.Weight, order++)); } - Assert.That(ms.CanRead, Is.True); + while (queue.Count > 1) { + var a = queue.Dequeue(); + var b = queue.Dequeue(); + queue.Enqueue(new Node(a.Weight + b.Weight, -1, a, b), (a.Weight + b.Weight, order++)); + } + var root = queue.Dequeue(); + + var codes = new (uint Bits, int Length)[256]; + var bits = new List(); + void Emit(Node n, uint code, int length) { + if (n.Zero is null) { + bits.Add(true); + for (var i = 7; i >= 0; --i) bits.Add(((n.Symbol >> i) & 1) != 0); + codes[n.Symbol] = (code, length); + return; + } + bits.Add(false); + Emit(n.Zero, code << 1, length + 1); + Emit(n.One!, (code << 1) | 1, length + 1); + } + Emit(root, 0, 0); + foreach (var b in body) { + var (code, length) = codes[b]; + for (var i = length - 1; i >= 0; --i) bits.Add(((code >> i) & 1) != 0); + } + + var result = new byte[(bits.Count + 7) / 8]; + for (var i = 0; i < bits.Count; ++i) + if (bits[i]) result[i >> 3] |= (byte)(0x80 >> (i & 7)); + return result; } } diff --git a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.DiskDoubler/DiskDoublerAdDecoder.cs b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.DiskDoubler/DiskDoublerAdDecoder.cs new file mode 100644 index 000000000..e44fee711 --- /dev/null +++ b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.DiskDoubler/DiskDoublerAdDecoder.cs @@ -0,0 +1,109 @@ +using System.Buffers.Binary; + +namespace FileFormat.DiskDoubler; + +/// +/// Decodes DiskDoubler's "AD" methods (6 = ADS/AD2, 9 = AD1): a sequence of independent LZSS blocks +/// of at most 8 KiB each. +/// +/// +/// Each block opens with a 12-byte header: +/// +/// [0..2] compressed size (bytes after this header) [2..4] uncompressed size (≤ 0x2000) +/// [4..8] unused here [8] data check (unverified) [9] flags (bit 0 = stored block) +/// [10] unused here [11] XOR of header bytes 0..11 (bytes 0-1 and 2-3 folded as 16-bit values) +/// +/// A compressed block is an MSB-first bit stream of tokens: 0 + 8 bits is a literal; 1 is a match, +/// followed by a "far" bit selecting a 12- or 8-bit distance, then the length: 0 → 2, 100 → 3, +/// 101 → 4, 11 + 4 bits → 5-20. Matches never reach outside their own block. +/// Sourcing: converted from The Unarchiver's XADMaster (XADDiskDoublerADnHandle.m, +/// LGPL-2.1-or-later); checked against real DiskDoubler files, decoding them as unar does. +/// +internal static class DiskDoublerAdDecoder { + private const int HeaderSize = 12; + private const int MaxBlock = 0x2000; + + public static byte[] Decode(ReadOnlySpan input, long originalSize) { + var output = new byte[checked((int)originalSize)]; + var outPos = 0; + var inPos = 0; + + while (outPos < output.Length) { + if (input.Length - inPos < HeaderSize) + throw new InvalidDataException("DiskDoubler AD stream ends inside a block header."); + var header = input.Slice(inPos, HeaderSize); + var compSize = BinaryPrimitives.ReadUInt16BigEndian(header); + var uncompSize = BinaryPrimitives.ReadUInt16BigEndian(header[2..]); + var check = (byte)(compSize ^ (compSize >> 8) ^ uncompSize ^ (uncompSize >> 8)); + for (var i = 4; i < 11; ++i) + check ^= header[i]; + if (check != header[11]) + throw new InvalidDataException("DiskDoubler AD block header fails its XOR check."); + if (uncompSize > MaxBlock) + throw new InvalidDataException($"DiskDoubler AD block claims {uncompSize} bytes; blocks hold at most {MaxBlock}."); + if (uncompSize > output.Length - outPos) + throw new InvalidDataException("DiskDoubler AD block runs past the fork's declared size."); + + inPos += HeaderSize; + if (compSize > input.Length - inPos) + throw new InvalidDataException("DiskDoubler AD block runs past the end of the fork."); + var block = input.Slice(inPos, compSize); + var target = output.AsSpan(outPos, uncompSize); + + if ((header[9] & 1) != 0) { + if (compSize < uncompSize) + throw new InvalidDataException("DiskDoubler AD stored block is shorter than its size."); + block[..uncompSize].CopyTo(target); + } else + DecodeBlock(block, target); + + outPos += uncompSize; + inPos += compSize; + } + + return output; + } + + private static void DecodeBlock(ReadOnlySpan block, Span output) { + var bitPos = 0; + var pos = 0; + + int Bit(ReadOnlySpan data) { + var index = bitPos >> 3; + if (index >= data.Length) + throw new InvalidDataException("DiskDoubler AD block ends before its output is complete."); + var bit = (data[index] >> (7 - (bitPos & 7))) & 1; + ++bitPos; + return bit; + } + + int Bits(ReadOnlySpan data, int count) { + var value = 0; + for (var i = 0; i < count; ++i) + value = (value << 1) | Bit(data); + return value; + } + + while (pos < output.Length) { + if (Bit(block) == 0) { + output[pos++] = (byte)Bits(block, 8); + continue; + } + + var offset = Bits(block, Bit(block) != 0 ? 12 : 8); + int length; + if (Bit(block) == 0) + length = 2; + else if (Bit(block) == 0) + length = Bit(block) == 0 ? 3 : 4; + else + length = Bits(block, 4) + 5; + + if (offset == 0 || offset > pos) + throw new InvalidDataException("DiskDoubler AD match reaches before the start of its block."); + length = Math.Min(length, output.Length - pos); + for (var i = 0; i < length; ++i, ++pos) + output[pos] = output[pos - offset]; + } + } +} diff --git a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.DiskDoubler/DiskDoublerConstants.cs b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.DiskDoubler/DiskDoublerConstants.cs index d0312b2dc..6f02d94b1 100644 --- a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.DiskDoubler/DiskDoublerConstants.cs +++ b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.DiskDoubler/DiskDoublerConstants.cs @@ -4,15 +4,33 @@ namespace FileFormat.DiskDoubler; /// Constants for the DiskDoubler compressed file format (Salient Software, 1989-1993). /// internal static class DiskDoublerConstants { - /// Size of the fixed-length DiskDoubler file header in bytes. - public const int HeaderSize = 82; + /// Magic at the start of a single compressed file (and of every file header inside a DiskDoubler archive). + public const uint Magic = 0xABCD0054; + + /// Size of the single-file header in bytes, magic and header CRC included. + public const int HeaderSize = 84; + + /// Bytes covered by the header CRC (everything before it). + public const int HeaderCrcSpan = 82; + + /// Size of the MacBinary header that commonly wraps a DiskDoubler file. + public const int MacBinaryHeaderSize = 128; /// Compression method: stored (no compression). - public const byte MethodStored = 0; + public const int MethodStored = 0; + + /// Compression method: Unix compress LZW. + public const int MethodCompress = 1; + + /// Compression method: "AD" / "ADS" LZSS blocks (DiskDoubler 3.x/4.x, AutoDoubler). + public const int MethodAds = 6; + + /// Compression method: Compact Pro (LZH + RLE). + public const int MethodCompactPro = 8; - /// Compression method: simple RLE. - public const byte MethodRle = 1; + /// Compression method: "AD1" LZSS blocks, same block format as . + public const int MethodAd1 = 9; - /// Compression method: LZC variant (proprietary). - public const byte MethodLzc = 3; + /// Seconds between the Macintosh epoch (1904-01-01) and the Unix epoch. + public const long MacEpochOffsetSeconds = 2_082_844_800; } diff --git a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.DiskDoubler/DiskDoublerEntry.cs b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.DiskDoubler/DiskDoublerEntry.cs index 64070cac5..c2cafce94 100644 --- a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.DiskDoubler/DiskDoublerEntry.cs +++ b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.DiskDoubler/DiskDoublerEntry.cs @@ -1,11 +1,15 @@ namespace FileFormat.DiskDoubler; -/// Represents a single compressed file stored in a DiskDoubler file. +/// Represents one fork of a DiskDoubler compressed file. public sealed class DiskDoublerEntry { - /// Gets the original filename. + /// Gets the entry name: the file's name for the data fork, with ".rsrc" appended for the resource fork. public string Name { get; init; } = string.Empty; - /// Gets the compression method ID (0 = stored, 1 = RLE, 3 = LZC variant, others = proprietary). + /// + /// Gets the compression method: 0 stored, 1 Unix compress, 2 and 5 DiskDoubler Huffman variants, + /// 3 RLE, 4 Huffman, 6 and 9 "AD" LZSS, 7 Stac LZS, 8 Compact Pro, 10 "DD". The high bit is a flag + /// and is masked off. + /// public int Method { get; init; } /// Gets the original (uncompressed) size in bytes. @@ -19,4 +23,27 @@ public sealed class DiskDoublerEntry { /// Gets whether this entry represents the data fork (as opposed to the resource fork). public bool IsDataFork { get; init; } + + /// Gets the fork's checksum field (its meaning depends on the method). + public ushort Checksum { get; init; } + + /// Gets the fork's delta pre-processing type (0 = none). + public int DeltaType { get; init; } + + /// Gets the Mac file type code. + public string FileType { get; init; } = string.Empty; + + /// Gets the Mac creator code. + public string Creator { get; init; } = string.Empty; + + /// Gets the modification time, or when the header holds zero. + public DateTime? Modified { get; init; } + + /// Gets whether this reader can decode the entry's method. + public bool IsSupported => this.DeltaType == 0 && this.Method is + DiskDoublerConstants.MethodStored or DiskDoublerConstants.MethodCompress or + DiskDoublerConstants.MethodAds or DiskDoublerConstants.MethodAd1; + + /// The XOR key the encoder applied to compress output (0 or 0x5A), from the header's version bytes. + internal byte XorKey { get; init; } } diff --git a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.DiskDoubler/DiskDoublerFormatDescriptor.cs b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.DiskDoubler/DiskDoublerFormatDescriptor.cs index bb8f9e4d1..9247b6464 100644 --- a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.DiskDoubler/DiskDoublerFormatDescriptor.cs +++ b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.DiskDoubler/DiskDoublerFormatDescriptor.cs @@ -55,12 +55,10 @@ public IEnumerable EnumerateLayout(Stream archive) { /// Gets the compound extensions. /// public IReadOnlyList CompoundExtensions => []; - // DiskDoubler has no universally reliable magic bytes; detection is primarily by extension. - // The header version identifier at offset 0 varies by version so we use a low-confidence hint. /// - /// Gets the magic signatures. + /// Gets the magic signatures: a single DiskDoubler file starts with 0xABCD0054. /// - public IReadOnlyList MagicSignatures => []; + public IReadOnlyList MagicSignatures => [new([0xAB, 0xCD, 0x00, 0x54], Confidence: 0.8)]; /// /// Gets the methods. /// @@ -89,10 +87,10 @@ public List List(Stream stream, string? password) { e.Name, e.OriginalSize, e.CompressedSize, - $"Method {e.Method}", + DiskDoublerReader.MethodName(e.Method), false, false, - DateTime.MinValue)) + e.Modified ?? DateTime.MinValue)) .ToList(); } @@ -141,11 +139,11 @@ public byte[] ExtractEntryToMemory(Stream archive, string entryName, string? pas /// public void Create(Stream output, IReadOnlyList inputs, FormatCreateOptions options) { // WORM: store the first input as a DiskDoubler data fork (method 0 = stored). - // DiskDoubler wraps a single Macintosh file, not a multi-file archive. + // DiskDoubler wraps a single Macintosh file, not a multi-file archive, and records no name. var w = new DiskDoublerWriter(); foreach (var i in inputs) { if (i.IsDirectory) continue; - w.SetFile(Path.GetFileName(i.ArchiveName), i.ReadContent()); + w.SetFile(i.ReadContent()); break; } w.WriteTo(output); diff --git a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.DiskDoubler/DiskDoublerReader.cs b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.DiskDoubler/DiskDoublerReader.cs index eeefea0f0..799ec9f3b 100644 --- a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.DiskDoubler/DiskDoublerReader.cs +++ b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.DiskDoubler/DiskDoublerReader.cs @@ -1,46 +1,38 @@ using System.Buffers.Binary; using System.Text; +using Compression.Core.Checksums; +using Compression.Core.Streams; +using FileFormat.Compress; namespace FileFormat.DiskDoubler; /// -/// Reads the header of a DiskDoubler compressed file and provides access to -/// its data and resource fork entries. +/// Reads a single DiskDoubler compressed file and decodes its forks. /// /// -/// DiskDoubler (Salient Software, 1989-1993) compressed individual files using a -/// proprietary 82-byte header followed by the compressed data fork and an optional -/// compressed resource fork. This reader reconstructs the entry list from the header -/// and supports extraction of stored (method 0) entries; for all other compression -/// methods the raw compressed bytes are returned unchanged. -/// -/// Header layout (82 bytes): +/// DiskDoubler (Salient Software, 1989-1993) replaced a file by a compressed one with this 84-byte +/// header (all big-endian), followed by the compressed data fork and then the compressed resource fork: /// -/// [0..4] format version identifier (uint32 BE) -/// [4..8] Mac file type code (4 ASCII bytes) -/// [8..12] Mac creator code (4 ASCII bytes) -/// [12..14] Finder flags (uint16 BE) -/// [14..16] padding / reserved -/// [16..20] data fork original size (uint32 BE) -/// [20..24] data fork compressed size (uint32 BE) -/// [24..28] resource fork original size (uint32 BE) -/// [28..32] resource fork compressed size (uint32 BE) -/// [32] data fork compression method -/// [33] resource fork compression method -/// [34..82] filename (Pascal string: byte 34 = length, bytes 35..82 = name) +/// [0..4] magic 0xABCD0054 +/// [4..8] data fork size [8..12] data fork compressed size +/// [12..16] resource fork size [16..20] resource fork compressed size +/// [20] data fork method [21] resource fork method [22] version byte +/// [24..28] modified [28..32] created (seconds since 1904) +/// [32..36] file type [36..40] creator [40..42] Finder flags +/// [48..50] data fork checksum [50..52] resource fork checksum [52] second version byte +/// [54..56] data fork delta type [56..58] resource fork delta type +/// [82..84] CRC-16/XMODEM of bytes 0..82 (very old files store 0000) /// -/// Compressed data follows immediately after the 82-byte header. +/// The file may sit inside a MacBinary wrapper, in which case the header follows its 128 bytes. +/// The header holds no name: the entries take the name of the file being read, without ".dd". +/// Decoded methods: 0 (stored), 1 (Unix compress, with its own header XOR-masked by 0x5A and +/// a 16-bit byte-sum check), 6 and 9 ("AD" LZSS blocks, see ). Every +/// other method — the DiskDoubler Huffman variants, RLE, Stac LZS, Compact Pro and "DD" — and any delta +/// pre-processing is refused with rather than returned undecoded. +/// Sourcing: layout and methods from The Unarchiver's XADMaster (XADDiskDoublerParser.m, +/// LGPL-2.1-or-later); verified on real DiskDoubler files against unar's output. /// public sealed class DiskDoublerReader : IDisposable { - /// Size of the fixed-length DiskDoubler file header in bytes. - public const int HeaderSize = 82; - - /// Byte offset of the filename Pascal string within the header. - private const int FileNameOffset = 34; - - /// Maximum bytes available for the filename (Pascal length byte + 47 name bytes). - private const int FileNameFieldLength = 48; - private readonly Stream _stream; private readonly bool _leaveOpen; private readonly List _entries = []; @@ -53,46 +45,64 @@ public sealed class DiskDoublerReader : IDisposable { /// /// to leave open after this reader is disposed. /// + /// The name to give the entries; defaults to the stream's file name without ".dd". /// Thrown when is null. - /// Thrown when the stream is too short to contain a valid header. - public DiskDoublerReader(Stream stream, bool leaveOpen = false) { + /// The stream holds no DiskDoubler header, or its header CRC is wrong. + public DiskDoublerReader(Stream stream, bool leaveOpen = false, string? name = null) { this._stream = stream ?? throw new ArgumentNullException(nameof(stream)); this._leaveOpen = leaveOpen; - this.ParseHeader(); + this.ParseHeader(name ?? DefaultName(stream)); } - /// Gets the list of entries parsed from the DiskDoubler file header. - /// - /// A DiskDoubler file contains at most two entries: one for the data fork and one for the - /// resource fork. The resource fork entry is omitted when the resource fork is empty. - /// + /// Gets the forks parsed from the header: the data fork (if any), then the resource fork (if any). public IReadOnlyList Entries => this._entries; - /// - /// Extracts the data for the specified entry. - /// - /// An entry obtained from . - /// - /// The uncompressed bytes for method 0 (stored), or the raw compressed bytes for all - /// other methods. - /// - /// Thrown when is null. - /// Thrown when this reader has been disposed. + /// Returns whether starts a DiskDoubler file (directly or behind MacBinary). + public static bool IsDiskDoubler(ReadOnlySpan header) => + StartsWithHeader(header) || + (header.Length >= DiskDoublerConstants.MacBinaryHeaderSize && StartsWithHeader(header[DiskDoublerConstants.MacBinaryHeaderSize..])); + + /// Decodes the specified fork. + /// The fork uses a method (or delta step) this reader does not decode. + /// The compressed data is damaged or fails its check. public byte[] Extract(DiskDoublerEntry entry) { ArgumentNullException.ThrowIfNull(entry); ObjectDisposedException.ThrowIf(this._disposed, this); - if (entry.CompressedSize == 0) - return []; + if (entry.DeltaType != 0) + throw new NotSupportedException($"DiskDoubler fork '{entry.Name}' uses delta pre-processing type {entry.DeltaType}, which is not supported."); this._stream.Position = entry.DataOffset; - var compressed = this.ReadExact((int)entry.CompressedSize); - - return entry.Method == DiskDoublerConstants.MethodStored - ? compressed - : compressed; // raw compressed bytes for unsupported methods + var compressed = new byte[entry.CompressedSize]; + this._stream.ReadExactly(compressed); + + return entry.Method switch { + DiskDoublerConstants.MethodStored => entry.OriginalSize <= compressed.LongLength + ? compressed[..(int)entry.OriginalSize] + : throw new InvalidDataException($"DiskDoubler stored fork '{entry.Name}' is shorter than its size."), + DiskDoublerConstants.MethodCompress => DecodeCompress(entry, compressed), + DiskDoublerConstants.MethodAds or DiskDoublerConstants.MethodAd1 => DiskDoublerAdDecoder.Decode(compressed, entry.OriginalSize), + _ => throw new NotSupportedException( + $"DiskDoubler fork '{entry.Name}' uses method {entry.Method} ({MethodName(entry.Method)}), which is not supported."), + }; } + /// Names a method as the format's tools do. + public static string MethodName(int method) => method switch { + 0 => "None", + 1 => "Compress", + 2 => "Method 2", + 3 => "RLE", + 4 => "Huffman", + 5 => "Method 5", + 6 => "ADS/AD2", + 7 => "Stac LZS", + 8 => "Compact Pro", + 9 => "AD/AD1", + 10 => "DDn", + _ => $"Method {method}", + }; + /// public void Dispose() { if (!this._disposed) { @@ -102,78 +112,109 @@ public void Dispose() { } } - // ── Parsing ─────────────────────────────────────────────────────────────────── - - private void ParseHeader() { - if (this._stream.Length - this._stream.Position < HeaderSize) - throw new InvalidDataException( - $"Stream is too short to contain a DiskDoubler header (need {HeaderSize} bytes)."); - - var hdr = this.ReadExact(HeaderSize); - - // [4..8] Mac file type — informational only - // [8..12] Mac creator — informational only - // [16..20] data fork original size - var dataOrigSize = BinaryPrimitives.ReadUInt32BigEndian(hdr.AsSpan(16, 4)); - // [20..24] data fork compressed size - var dataCompSize = BinaryPrimitives.ReadUInt32BigEndian(hdr.AsSpan(20, 4)); - // [24..28] resource fork original size - var rsrcOrigSize = BinaryPrimitives.ReadUInt32BigEndian(hdr.AsSpan(24, 4)); - // [28..32] resource fork compressed size - var rsrcCompSize = BinaryPrimitives.ReadUInt32BigEndian(hdr.AsSpan(28, 4)); - // [32] data fork method, [33] resource fork method - var dataMethod = hdr[32]; - var rsrcMethod = hdr[33]; - - // [34] Pascal string length, [35..81] name bytes - var nameLength = hdr[FileNameOffset]; - if (nameLength > FileNameFieldLength - 1) - nameLength = (byte)(FileNameFieldLength - 1); - var name = nameLength > 0 - ? Encoding.Latin1.GetString(hdr, FileNameOffset + 1, nameLength) - : string.Empty; - - // Compressed data follows the header immediately. - var dataStart = this._stream.Position; // = HeaderSize from stream start - - if (dataOrigSize > 0 || dataCompSize > 0) { - this._entries.Add(new DiskDoublerEntry { - Name = name, - Method = dataMethod, - OriginalSize = dataOrigSize, - CompressedSize = dataCompSize, - DataOffset = dataStart, - IsDataFork = true, - }); + // ── Decoding ────────────────────────────────────────────────────────────────── + + private static byte[] DecodeCompress(DiskDoublerEntry entry, byte[] compressed) { + if (compressed.Length < 3) + throw new InvalidDataException($"DiskDoubler compress fork '{entry.Name}' is shorter than its header."); + // The three header bytes are a .Z header (1F 9D flags), masked like the output. + var header = new byte[] { (byte)(compressed[0] ^ entry.XorKey), (byte)(compressed[1] ^ entry.XorKey), (byte)(compressed[2] ^ entry.XorKey) }; + if (header[0] != 0x1F || header[1] != 0x9D) + throw new InvalidDataException($"DiskDoubler compress fork '{entry.Name}' lacks the compress magic."); + + var unmasked = (byte[])compressed.Clone(); + header.CopyTo(unmasked, 0); + + var output = new byte[checked((int)entry.OriginalSize)]; + using (var source = new MemoryStream(unmasked, writable: false)) + using (var lzw = new CompressStream(source, CompressionStreamMode.Decompress, leaveOpen: true)) { + var total = 0; + while (total < output.Length) { + var read = lzw.Read(output, total, output.Length - total); + if (read == 0) + throw new InvalidDataException($"DiskDoubler compress fork '{entry.Name}' ends after {total} of {output.Length} bytes."); + total += read; + } } - if (rsrcOrigSize > 0 || rsrcCompSize > 0) { - this._entries.Add(new DiskDoublerEntry { - Name = name + ".rsrc", - Method = rsrcMethod, - OriginalSize = rsrcOrigSize, - CompressedSize = rsrcCompSize, - DataOffset = dataStart + dataCompSize, - IsDataFork = false, - }); + var sum = 0; + for (var i = 0; i < output.Length; ++i) { + output[i] ^= entry.XorKey; + sum += output[i]; } + // The stored check is the byte sum of the output plus the three (unmasked) header bytes. + var expected = (entry.Checksum - header[0] - header[1] - header[2]) & 0xFFFF; + if ((sum & 0xFFFF) != expected) + throw new InvalidDataException($"DiskDoubler compress fork '{entry.Name}' fails its checksum."); + return output; } - // ── Stream helpers ──────────────────────────────────────────────────────────── + // ── Parsing ─────────────────────────────────────────────────────────────────── + + private static bool StartsWithHeader(ReadOnlySpan data) => + data.Length >= DiskDoublerConstants.HeaderSize && + BinaryPrimitives.ReadUInt32BigEndian(data) == DiskDoublerConstants.Magic; - private byte[] ReadExact(int count) { - var buf = new byte[count]; - this.ReadExact(buf.AsSpan()); - return buf; + private static string DefaultName(Stream stream) { + var path = (stream as FileStream)?.Name; + if (string.IsNullOrEmpty(path)) + return "file"; + var name = Path.GetFileName(path); + return name.EndsWith(".dd", StringComparison.OrdinalIgnoreCase) && name.Length > 3 ? name[..^3] : name; } - private void ReadExact(Span buffer) { - var total = 0; - while (total < buffer.Length) { - var read = this._stream.Read(buffer[total..]); - if (read == 0) - throw new EndOfStreamException("Unexpected end of stream reading DiskDoubler data."); - total += read; - } + private void ParseHeader(string name) { + var origin = this._stream.Position; + var probe = new byte[DiskDoublerConstants.MacBinaryHeaderSize + DiskDoublerConstants.HeaderSize]; + var got = this._stream.ReadAtLeast(probe, probe.Length, throwOnEndOfStream: false); + var span = probe.AsSpan(0, got); + + int headerAt; + if (StartsWithHeader(span)) + headerAt = 0; + else if (got >= DiskDoublerConstants.MacBinaryHeaderSize && StartsWithHeader(span[DiskDoublerConstants.MacBinaryHeaderSize..])) + headerAt = DiskDoublerConstants.MacBinaryHeaderSize; + else + throw new InvalidDataException("Stream does not start with a DiskDoubler header (magic 0xABCD0054)."); + + var hdr = span.Slice(headerAt, DiskDoublerConstants.HeaderSize); + var storedCrc = BinaryPrimitives.ReadUInt16BigEndian(hdr[82..]); + if (storedCrc != 0 && storedCrc != Crc16Ccitt.Compute(hdr[..DiskDoublerConstants.HeaderCrcSpan])) + throw new InvalidDataException("DiskDoubler header fails its CRC-16."); + + var dataSize = BinaryPrimitives.ReadUInt32BigEndian(hdr[4..]); + var dataCompSize = BinaryPrimitives.ReadUInt32BigEndian(hdr[8..]); + var rsrcSize = BinaryPrimitives.ReadUInt32BigEndian(hdr[12..]); + var rsrcCompSize = BinaryPrimitives.ReadUInt32BigEndian(hdr[16..]); + var info1 = hdr[22]; + var info2 = hdr[52]; + var xorKey = info1 >= 0x2A && (info2 & 0x80) == 0 ? (byte)0x5A : (byte)0; + var modified = BinaryPrimitives.ReadUInt32BigEndian(hdr[24..]); + var fileType = Encoding.Latin1.GetString(hdr.Slice(32, 4)); + var creator = Encoding.Latin1.GetString(hdr.Slice(36, 4)); + + var dataStart = origin + headerAt + DiskDoublerConstants.HeaderSize; + if (dataStart + (long)dataCompSize + rsrcCompSize > this._stream.Length) + throw new InvalidDataException("DiskDoubler file is shorter than its header's fork sizes."); + + DiskDoublerEntry Fork(string entryName, uint size, uint compSize, int method, int checksumAt, int deltaAt, long offset, bool isData) => new() { + Name = entryName, + Method = method & 0x7F, + OriginalSize = size, + CompressedSize = compSize, + DataOffset = offset, + IsDataFork = isData, + Checksum = BinaryPrimitives.ReadUInt16BigEndian(probe.AsSpan(headerAt + checksumAt)), + DeltaType = BinaryPrimitives.ReadUInt16BigEndian(probe.AsSpan(headerAt + deltaAt)), + FileType = fileType, + Creator = creator, + Modified = modified == 0 ? null : DateTime.UnixEpoch.AddSeconds(modified - DiskDoublerConstants.MacEpochOffsetSeconds), + XorKey = xorKey, + }; + + if (dataSize > 0 || rsrcSize == 0) + this._entries.Add(Fork(name, dataSize, dataCompSize, hdr[20], 48, 54, dataStart, true)); + if (rsrcSize > 0) + this._entries.Add(Fork(name + ".rsrc", rsrcSize, rsrcCompSize, hdr[21], 50, 56, dataStart + dataCompSize, false)); } } diff --git a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.DiskDoubler/DiskDoublerWriter.cs b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.DiskDoubler/DiskDoublerWriter.cs index 9b5929ef2..a66b8eaf7 100644 --- a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.DiskDoubler/DiskDoublerWriter.cs +++ b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.DiskDoubler/DiskDoublerWriter.cs @@ -1,66 +1,51 @@ #pragma warning disable CS1591 using System.Buffers.Binary; using System.Text; +using Compression.Core.Checksums; namespace FileFormat.DiskDoubler; /// -/// Writes a DiskDoubler stored (method 0) file. Takes a single data fork -/// payload; resource fork is empty. Roundtrips through -/// . +/// Writes a single DiskDoubler file with a stored (method 0) data fork and no resource fork, in the +/// layout documents. The Unarchiver (unar) reads it back. /// public sealed class DiskDoublerWriter { - private const int HeaderSize = 82; - private const int FileNameOffset = 34; - private const int MaxNameLength = 47; // 82 - 34(offset) - 1(pascal len byte) = 47 - - private string _name = "file"; private byte[] _data = []; + private string _fileType = "????"; + private string _creator = "????"; - /// - /// Sets the file. - /// - public void SetFile(string name, byte[] data) { - ArgumentNullException.ThrowIfNull(name); + /// Sets the data fork. DiskDoubler stores no name; the file's own name is the entry's name. + public void SetFile(byte[] data, string fileType = "????", string creator = "????") { ArgumentNullException.ThrowIfNull(data); - _name = name; - _data = data; + ArgumentNullException.ThrowIfNull(fileType); + ArgumentNullException.ThrowIfNull(creator); + this._data = data; + this._fileType = fileType; + this._creator = creator; } - /// - /// Writes the to to the supplied output. - /// + /// Writes the header and the stored data fork to . public void WriteTo(Stream output) { ArgumentNullException.ThrowIfNull(output); - var hdr = new byte[HeaderSize]; - - // [0..4] version — reader doesn't validate; use zero. - // [4..8] Mac file type — "????" placeholder. - Encoding.ASCII.GetBytes("????").CopyTo(hdr, 4); - // [8..12] Mac creator — "????" placeholder. - Encoding.ASCII.GetBytes("????").CopyTo(hdr, 8); - // [12..14] Finder flags — 0. - // [14..16] reserved — 0. - - // [16..20] data fork original size (BE). - BinaryPrimitives.WriteUInt32BigEndian(hdr.AsSpan(16), (uint)_data.Length); - // [20..24] data fork compressed size (BE) — same as original (stored). - BinaryPrimitives.WriteUInt32BigEndian(hdr.AsSpan(20), (uint)_data.Length); - // [24..28] resource fork original size — 0. - // [28..32] resource fork compressed size — 0. - // [32] data fork compression method — 0 (stored). - hdr[32] = DiskDoublerConstants.MethodStored; - // [33] resource fork compression method — 0. - hdr[33] = DiskDoublerConstants.MethodStored; - - // [34] Pascal string: length byte + name bytes (Latin-1, up to 47 chars). - var nameBytes = Encoding.Latin1.GetBytes(_name); - var nameLen = Math.Min(nameBytes.Length, MaxNameLength); - hdr[FileNameOffset] = (byte)nameLen; - nameBytes.AsSpan(0, nameLen).CopyTo(hdr.AsSpan(FileNameOffset + 1)); + var hdr = new byte[DiskDoublerConstants.HeaderSize]; + BinaryPrimitives.WriteUInt32BigEndian(hdr, DiskDoublerConstants.Magic); + BinaryPrimitives.WriteUInt32BigEndian(hdr.AsSpan(4), (uint)this._data.Length); + BinaryPrimitives.WriteUInt32BigEndian(hdr.AsSpan(8), (uint)this._data.Length); + // [12..20] resource fork sizes, [20..22] methods: zero = empty and stored. + hdr[22] = 0x2A; // the version byte real files carry + WriteFourCc(hdr.AsSpan(32, 4), this._fileType); + WriteFourCc(hdr.AsSpan(36, 4), this._creator); + BinaryPrimitives.WriteUInt16BigEndian(hdr.AsSpan(82), Crc16Ccitt.Compute(hdr.AsSpan(0, DiskDoublerConstants.HeaderCrcSpan))); output.Write(hdr); - output.Write(_data); + output.Write(this._data); + } + + private static void WriteFourCc(Span dest, string value) { + var bytes = Encoding.Latin1.GetBytes(value); + var length = Math.Min(bytes.Length, 4); + bytes.AsSpan(0, length).CopyTo(dest); + dest[length..].Fill((byte)' '); } } diff --git a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.DiskDoubler/THIRD-PARTY-NOTICE.XADMaster.txt b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.DiskDoubler/THIRD-PARTY-NOTICE.XADMaster.txt new file mode 100644 index 000000000..267cdbd8e --- /dev/null +++ b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.DiskDoubler/THIRD-PARTY-NOTICE.XADMaster.txt @@ -0,0 +1,34 @@ +XADMaster (DiskDoubler) - converted to C# + +Source: https://github.com/MacPaw/XADMaster (The Unarchiver's unarchiving engine) +Revision: 7cb9ee0abbb163f261e4cb74501e15067032319c (master, 2026-09-21) +Authors: Dag Ågren (original author); MacPaw Way Ltd. (2017-present); + parts descend from libxad (Dirk Stöcker and contributors) +Licence: GNU Lesser General Public License, version 2.1 or (at your option) + any later version. This repository is LGPL-3.0-or-later, which the + "or any later version" grant permits; the full LGPL text is in the + repository's LICENSE file. + +Upstream notice, as carried in each XADMaster source file: + + Copyright (c) 2017-present, MacPaw Way Ltd. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + +Files converted: + + XADDiskDoublerParser.m -> DiskDoublerReader.cs (header layout, method + numbering, compress-method XOR and checksum) + XADDiskDoublerADnHandle.m -> DiskDoublerAdDecoder.cs + +Methods 2-5, 7, 8 and 10 and delta pre-processing are refused rather than +decoded. The header CRC is CRC-16/XMODEM over the 82 bytes before it (XADMaster +computes the same value through a bit-reversed table). diff --git a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.PackIt/PackItConstants.cs b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.PackIt/PackItConstants.cs index 08fd8ba05..133139ea5 100644 --- a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.PackIt/PackItConstants.cs +++ b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.PackIt/PackItConstants.cs @@ -10,9 +10,24 @@ internal static class PackItConstants { /// Entry magic for a Huffman-compressed entry: "PMa4". public static ReadOnlySpan MagicCompressed => "PMa4"u8; - /// Size of the per-entry header in bytes (magic + filename field + metadata). - public const int EntryHeaderSize = 87; + /// Entry magic for a Huffman-compressed entry XOR-encrypted with the password: "PMa5". + public static ReadOnlySpan MagicXorEncrypted => "PMa5"u8; - /// Maximum filename length in a PackIt entry (Pascal string capacity minus length byte). - public const int FileNameMaxLength = 62; + /// Entry magic for a Huffman-compressed entry DES-encrypted with the password: "PMa6". + public static ReadOnlySpan MagicDesEncrypted => "PMa6"u8; + + /// End-of-archive marker that follows the last entry: "PEnd". + public static ReadOnlySpan MagicEnd => "PEnd"u8; + + /// Size of the entry header that follows the magic, in bytes. + public const int HeaderSize = 94; + + /// Bytes of the header covered by the header CRC (everything before the CRC itself). + public const int HeaderCrcSpan = 92; + + /// Capacity of the filename field (after its length byte). + public const int FileNameMaxLength = 63; + + /// Seconds between the Macintosh epoch (1904-01-01) and the Unix epoch. + public const long MacEpochOffsetSeconds = 2_082_844_800; } diff --git a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.PackIt/PackItEntry.cs b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.PackIt/PackItEntry.cs index c2cfe8f36..be31e2cb3 100644 --- a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.PackIt/PackItEntry.cs +++ b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.PackIt/PackItEntry.cs @@ -11,15 +11,39 @@ public sealed class PackItEntry { /// Gets the Mac four-character creator code (e.g. "CWIE"). public string Creator { get; init; } = string.Empty; + /// Gets the Finder flags. + public ushort FinderFlags { get; init; } + /// Gets the uncompressed data fork size in bytes. public long DataForkSize { get; init; } /// Gets the uncompressed resource fork size in bytes. public long ResourceForkSize { get; init; } - /// Gets whether this entry uses Huffman compression ("PMa4"). + /// Gets the creation time, or when the header holds zero. + public DateTime? Created { get; init; } + + /// Gets the modification time, or when the header holds zero. + public DateTime? Modified { get; init; } + + /// Gets whether this entry is Huffman-compressed ("PMa4", "PMa5", "PMa6"). public bool IsCompressed { get; init; } - /// Gets the absolute byte offset where the data fork begins in the source stream. + /// Gets whether this entry is password-encrypted ("PMa5" XOR, "PMa6" DES). + public bool IsEncrypted { get; init; } + + /// Gets whether the header matched its CRC-16 (always for encrypted entries, which cannot be checked). + public bool HeaderCrcValid { get; init; } = true; + + /// + /// Gets the absolute offset where the data fork begins for a stored entry; for a compressed entry, + /// the offset of its Huffman bit stream (the byte after the magic). + /// public long DataOffset { get; init; } + + /// Gets the absolute offset of the entry's magic. + public long EntryOffset { get; init; } + + /// Gets the number of bytes the entry occupies, magic included. + public long EntryLength { get; init; } } diff --git a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.PackIt/PackItFormatDescriptor.cs b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.PackIt/PackItFormatDescriptor.cs index 54eaa0f5f..760de7dd1 100644 --- a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.PackIt/PackItFormatDescriptor.cs +++ b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.PackIt/PackItFormatDescriptor.cs @@ -10,27 +10,36 @@ namespace FileFormat.PackIt; /// References: /// /// https://github.com/MacPaw/XADMaster — XADMaster (The Unarchiver) — maintained implementation including PackIt -/// No formal specification — format known from PackIt itself and later community documentation +/// No formal specification; header CRC measured on real PackIt II archives (see PackItReader) /// /// public sealed class PackItFormatDescriptor : IFormatDescriptor, IArchiveFormatOperations, IArchiveCreatable, IArchiveModifiable, IArchiveDefragmentable, IArchiveLayoutMap { - /// Rebuild-based defrag: extracts then re-creates the PackIt archive in listing order. + /// Rebuild-based defrag: re-chains the entries in listing order. public void Defragment(Stream archive) => this.Defragment(archive, new DefragOptions { Mode = DefragMode.ConsolidateAtStart }); - /// Rebuild-based defrag: extracts then re-creates the PackIt archive per the requested mode. + /// + /// Rebuild-based defrag. Each entry is carried over as its raw bytes (magic to fork CRC), so resource + /// forks, Finder info, dates and Huffman coding survive unchanged; only the chain is rewritten. + /// public void Defragment(Stream archive, DefragOptions options) { DefragRebuilder.Rebuild(archive, options, readEntries: stream => { var r = new PackItReader(stream, leaveOpen: true); - return r.Entries.Select(e => (e.Name, r.Extract(e))); + if (r.IsIncomplete) + throw new InvalidDataException("PackIt archive cannot be walked to its end; refusing to rebuild it."); + return r.Entries.Select(e => { + var raw = new byte[e.EntryLength]; + stream.Position = e.EntryOffset; + stream.ReadExactly(raw); + return (e.Name, raw); + }).ToList(); }, - buildImage: files => { + buildImage: entries => { using var ms = new MemoryStream(); - using (var w = new PackItWriter(ms, leaveOpen: true)) { - foreach (var (n, d) in files) w.AddFile(n, d); - } + foreach (var (_, raw) in entries) ms.Write(raw); + ms.Write("PEnd"u8); return ms.ToArray(); }); } @@ -40,11 +49,8 @@ public void Defragment(Stream archive, DefragOptions options) { public IEnumerable EnumerateLayout(Stream archive) { archive.Position = 0; var r = new PackItReader(archive); - foreach (var e in r.Entries) { - var totalSize = e.DataForkSize + e.ResourceForkSize; - if (totalSize > 0) - yield return new DefragBlockInfo(e.DataOffset, totalSize, DefragBlockKind.Used, FileName: e.Name); - } + foreach (var e in r.Entries) + yield return new DefragBlockInfo(e.EntryOffset, e.EntryLength, DefragBlockKind.Used, FileName: e.Name); } /// @@ -103,6 +109,8 @@ public void Remove(Stream archive, string[] entryNames) { public IReadOnlyList MagicSignatures => [ new([(byte)'P', (byte)'M', (byte)'a', (byte)'g'], Confidence: 0.85), new([(byte)'P', (byte)'M', (byte)'a', (byte)'4'], Confidence: 0.85), + new([(byte)'P', (byte)'M', (byte)'a', (byte)'5'], Confidence: 0.85), + new([(byte)'P', (byte)'M', (byte)'a', (byte)'6'], Confidence: 0.85), ]; /// /// Gets the methods. @@ -131,11 +139,11 @@ public List List(Stream stream, string? password) { i, e.Name, e.DataForkSize, - e.DataForkSize, - e.IsCompressed ? "Huffman" : "Stored", - false, + e.EntryLength, + e.IsEncrypted ? "Huffman+Encrypted" : e.IsCompressed ? "Huffman" : "Stored", false, - DateTime.MinValue)) + e.IsEncrypted, + e.Modified ?? DateTime.MinValue)) .ToList(); } diff --git a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.PackIt/PackItHuffmanDecoder.cs b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.PackIt/PackItHuffmanDecoder.cs new file mode 100644 index 000000000..c1983cf95 --- /dev/null +++ b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.PackIt/PackItHuffmanDecoder.cs @@ -0,0 +1,85 @@ +namespace FileFormat.PackIt; + +/// +/// Decodes the Huffman stream of a "PMa4" PackIt entry. +/// +/// +/// The stream is MSB-first. It opens with the code tree in pre-order: a 1 bit is a leaf followed +/// by its 8-bit byte value; a 0 bit is a branch, followed by its 0-subtree and then its 1-subtree. +/// Every byte after that is one code walked from the root (0 = first subtree). The tree is sent once per +/// entry and the entry ends on a byte boundary. +/// Sourcing: the scheme is StuffIt's method 3, which PackIt II introduced; behaviour taken from +/// The Unarchiver's XADMaster (XADStuffItHuffmanHandle.m, XADPackItParser.m, +/// LGPL-2.1-or-later) and checked against real PackIt II archives, whose header and fork CRC-16s +/// the decoded bytes must match. +/// +internal sealed class PackItHuffmanDecoder { + // Node i: Left/Right children; a negative child is ~byteValue (a leaf). + private readonly List<(int Zero, int One)> _nodes = []; + private readonly int _rootLeaf = int.MinValue; + private readonly Stream _stream; + private int _current; + private int _bitsLeft; + + /// Gets the number of bytes consumed from the stream so far. + public long BytesConsumed { get; private set; } + + /// Reads the code tree from , positioned just after the entry magic. + /// The tree is deeper than 256 levels or the stream ends inside it. + public PackItHuffmanDecoder(Stream stream) { + this._stream = stream; + var root = this.ParseTree(0); + if (root < 0) + this._rootLeaf = ~root; // degenerate one-symbol tree: every code is empty + } + + /// Decodes .Length bytes. + public void Decode(Span output) { + if (this._rootLeaf != int.MinValue) { + output.Fill((byte)this._rootLeaf); + return; + } + for (var i = 0; i < output.Length; ++i) { + var node = 0; + while (true) { + var (zero, one) = this._nodes[node]; + var next = this.ReadBit() == 0 ? zero : one; + if (next < 0) { + output[i] = (byte)~next; + break; + } + node = next; + } + } + } + + private int ParseTree(int depth) { + if (depth > 256) + throw new InvalidDataException("PackIt Huffman tree is deeper than any 256-symbol code."); + if (this.ReadBit() == 1) { + var value = 0; + for (var i = 0; i < 8; ++i) + value = (value << 1) | this.ReadBit(); + return ~value; + } + var index = this._nodes.Count; + this._nodes.Add((0, 0)); + var zero = this.ParseTree(depth + 1); + var one = this.ParseTree(depth + 1); + this._nodes[index] = (zero, one); + return index; + } + + private int ReadBit() { + if (this._bitsLeft == 0) { + var b = this._stream.ReadByte(); + if (b < 0) + throw new InvalidDataException("PackIt Huffman stream ends before the entry is complete."); + ++this.BytesConsumed; + this._current = b; + this._bitsLeft = 8; + } + --this._bitsLeft; + return (this._current >> this._bitsLeft) & 1; + } +} diff --git a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.PackIt/PackItModifier.cs b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.PackIt/PackItModifier.cs index 41a2db9a1..3febc62ec 100644 --- a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.PackIt/PackItModifier.cs +++ b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.PackIt/PackItModifier.cs @@ -1,193 +1,78 @@ #pragma warning disable CS1591 -using System.Buffers.Binary; -using System.Text; namespace FileFormat.PackIt; /// -/// Random-access in-place modifier for PackIt (.pit) classic Macintosh -/// archives. PackIt has no explicit end-of-archive marker — readers stop -/// when the next 4 bytes are not "PMag" or "PMa4". Add appends a new -/// stored entry at EOF; Remove walks the entry chain, locates the target, -/// and shifts trailing bytes forward to compact (no central directory). +/// In-place edits of a PackIt archive. PackIt has no directory: entries are a chain ended by +/// "PEnd", so an add rewrites the marker after the new entry and a remove shifts the tail. /// public static class PackItModifier { /// - /// Appends a stored ("PMag") entry at the end of the archive. Walks the - /// existing entry chain to find the EOF (first non-magic 4 bytes), then - /// writes the new entry there and truncates. I/O cost is one full - /// sequential entry walk plus the new entry's bytes. + /// Appends a stored ("PMag") entry after the last entry and rewrites the "PEnd" marker behind it. /// + /// The archive cannot be walked to its end (truncated or encrypted entry). public static void AddFile(Stream pit, string name, byte[] data, string fileType = "TEXT", string creator = "CWIE") { ArgumentNullException.ThrowIfNull(pit); ArgumentNullException.ThrowIfNull(name); ArgumentNullException.ThrowIfNull(data); - var eofOffset = FindEofOffset(pit); - pit.Position = eofOffset; - WriteStoredEntry(pit, name, data, fileType, creator); + var end = FindEntriesEnd(pit); + pit.Position = end; + PackItWriter.WriteStoredEntry(pit, name, data, [], fileType, creator, modified: null); + pit.Write(PackItConstants.MagicEnd); pit.SetLength(pit.Position); } /// - /// Removes the named entry. Returns true if found. Walks the chain to - /// locate the entry, then shifts trailing bytes forward to compact. + /// Removes the named entry. Returns true if found. Locates the entry by walking the chain, then shifts + /// the trailing bytes forward over it. /// public static bool RemoveFile(Stream pit, string name, bool wipeData = true) { ArgumentNullException.ThrowIfNull(pit); ArgumentNullException.ThrowIfNull(name); - var locator = LocateEntry(pit, name); - if (!locator.Found) return false; + if (pit.Length == 0) + return false; + pit.Position = 0; + PackItEntry? target; + using (var reader = new PackItReader(pit, leaveOpen: true)) + target = reader.Entries.FirstOrDefault(e => !e.IsEncrypted && string.Equals(e.Name, name, StringComparison.OrdinalIgnoreCase)); + if (target is null) + return false; if (wipeData) - ZeroRange(pit, locator.EntryOffset, locator.EntrySize); + ZeroRange(pit, target.EntryOffset, target.EntryLength); - var afterEntry = locator.EntryOffset + locator.EntrySize; + var afterEntry = target.EntryOffset + target.EntryLength; var bytesToShift = pit.Length - afterEntry; - if (bytesToShift > 0) { - var buf = new byte[64 * 1024]; - var src = afterEntry; - var dst = locator.EntryOffset; - while (bytesToShift > 0) { - var chunk = (int)Math.Min(buf.Length, bytesToShift); - pit.Position = src; - var read = 0; - while (read < chunk) { - var n = pit.Read(buf, read, chunk - read); - if (n <= 0) break; - read += n; - } - pit.Position = dst; - pit.Write(buf, 0, read); - src += read; - dst += read; - bytesToShift -= read; - } + var buf = new byte[64 * 1024]; + var src = afterEntry; + var dst = target.EntryOffset; + while (bytesToShift > 0) { + var chunk = (int)Math.Min(buf.Length, bytesToShift); + pit.Position = src; + pit.ReadExactly(buf, 0, chunk); + pit.Position = dst; + pit.Write(buf, 0, chunk); + src += chunk; + dst += chunk; + bytesToShift -= chunk; } - pit.SetLength(pit.Length - locator.EntrySize); + pit.SetLength(pit.Length - target.EntryLength); return true; } - // ── Entry walking ───────────────────────────────────────────────────── - // PackIt entry layout (87-byte fixed header + variable forks): - // bytes 0..3: magic ("PMag" or "PMa4") - // bytes 4..66: filename Pascal string (1 length byte + 62 name bytes) - // bytes 67..70: Mac file type (4 ASCII) - // bytes 71..74: Mac creator code (4 ASCII) - // bytes 75..76: Finder flags (uint16 BE) - // byte 77: locked flag - // byte 78: zero padding - // bytes 79..82: data fork size (uint32 BE) - // bytes 83..86: resource fork size (uint32 BE) - // bytes 87..: data fork bytes, then resource fork bytes - // Total entry = 87 + dataForkSize + resourceForkSize. - - private const int EntryHeaderSize = PackItReader.EntryHeaderSize; // 87 - private const int DataForkSizeOffset = 79; - private const int ResourceForkSizeOffset = 83; - - private static long FindEofOffset(Stream pit) { + /// Returns the offset just past the last entry, which is where "PEnd" sits (or belongs). + private static long FindEntriesEnd(Stream pit) { + if (pit.Length == 0) + return 0; pit.Position = 0; - Span hdr = stackalloc byte[EntryHeaderSize]; - while (pit.Position + EntryHeaderSize <= pit.Length) { - var entryStart = pit.Position; - var read = ReadFully(pit, hdr); - if (read < EntryHeaderSize) return entryStart; - - if (!IsKnownMagic(hdr)) return entryStart; - - var dataForkSize = BinaryPrimitives.ReadUInt32BigEndian(hdr.Slice(DataForkSizeOffset, 4)); - var rsrcForkSize = BinaryPrimitives.ReadUInt32BigEndian(hdr.Slice(ResourceForkSizeOffset, 4)); - pit.Position = entryStart + EntryHeaderSize + dataForkSize + rsrcForkSize; - } - return pit.Length; - } - - private readonly record struct EntryLocator(bool Found, long EntryOffset, long EntrySize); - - private static EntryLocator LocateEntry(Stream pit, string targetName) { - pit.Position = 0; - Span hdr = stackalloc byte[EntryHeaderSize]; - while (pit.Position + EntryHeaderSize <= pit.Length) { - var entryStart = pit.Position; - var read = ReadFully(pit, hdr); - if (read < EntryHeaderSize) break; - if (!IsKnownMagic(hdr)) break; - - var nameLength = hdr[4]; - if (nameLength > 62) nameLength = 62; - var name = nameLength > 0 - ? Encoding.Latin1.GetString(hdr.Slice(5, nameLength)) - : string.Empty; - - var dataForkSize = BinaryPrimitives.ReadUInt32BigEndian(hdr.Slice(DataForkSizeOffset, 4)); - var rsrcForkSize = BinaryPrimitives.ReadUInt32BigEndian(hdr.Slice(ResourceForkSizeOffset, 4)); - var entrySize = (long)EntryHeaderSize + dataForkSize + rsrcForkSize; - - if (string.Equals(name, targetName, StringComparison.OrdinalIgnoreCase)) - return new EntryLocator(true, entryStart, entrySize); - - pit.Position = entryStart + entrySize; - } - return new EntryLocator(false, 0, 0); - } - - private static bool IsKnownMagic(ReadOnlySpan hdr) => - hdr[..4].SequenceEqual(PackItConstants.MagicStored) || - hdr[..4].SequenceEqual(PackItConstants.MagicCompressed); - - // ── Entry writing ───────────────────────────────────────────────────── - - private static void WriteStoredEntry(Stream pit, string name, byte[] data, string fileType, string creator) { - Span header = stackalloc byte[EntryHeaderSize]; - header.Clear(); - - // Magic "PMag" (stored). - PackItConstants.MagicStored.CopyTo(header); - - // Pascal filename: 1 length byte + up to 62 name bytes at offsets 4..66. - var nameBytes = Encoding.Latin1.GetBytes(name); - var nameLen = Math.Min(nameBytes.Length, PackItConstants.FileNameMaxLength); // 62 - header[4] = (byte)nameLen; - nameBytes.AsSpan(0, nameLen).CopyTo(header[5..]); - - // File type at 67..70. - WriteAscii4(header.Slice(67, 4), fileType); - // Creator at 71..74. - WriteAscii4(header.Slice(71, 4), creator); - // Finder flags (75..76), locked (77), padding (78) — already zeroed. - - // Data fork size (BE) at 79..82, resource fork size (BE) at 83..86. - BinaryPrimitives.WriteUInt32BigEndian(header.Slice(DataForkSizeOffset, 4), (uint)data.Length); - BinaryPrimitives.WriteUInt32BigEndian(header.Slice(ResourceForkSizeOffset, 4), 0u); - - pit.Write(header); - if (data.Length > 0) - pit.Write(data); - // Resource fork: zero-length, nothing to write. - } - - private static void WriteAscii4(Span dest, string value) { - var bytes = Encoding.ASCII.GetBytes(value); - var len = Math.Min(bytes.Length, 4); - bytes.AsSpan(0, len).CopyTo(dest); - for (var i = len; i < 4; ++i) - dest[i] = (byte)' '; - } - - // ── Helpers ─────────────────────────────────────────────────────────── - - private static int ReadFully(Stream s, Span buf) { - var read = 0; - while (read < buf.Length) { - var n = s.Read(buf[read..]); - if (n <= 0) break; - read += n; - } - return read; + using var reader = new PackItReader(pit, leaveOpen: true); + if (reader.IsIncomplete) + throw new InvalidDataException("PackIt archive cannot be walked to its end; refusing to append after an unknown tail."); + return reader.Entries.Count == 0 ? 0 : reader.Entries[^1].EntryOffset + reader.Entries[^1].EntryLength; } private static void ZeroRange(Stream s, long offset, long length) { diff --git a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.PackIt/PackItReader.cs b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.PackIt/PackItReader.cs index 9d38d182e..989b28fc7 100644 --- a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.PackIt/PackItReader.cs +++ b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.PackIt/PackItReader.cs @@ -1,5 +1,6 @@ using System.Buffers.Binary; using System.Text; +using Compression.Core.Checksums; namespace FileFormat.PackIt; @@ -7,49 +8,27 @@ namespace FileFormat.PackIt; /// Reads entries from a PackIt (.pit) classic Macintosh archive. /// /// -/// PackIt (Harry Chesley, 1984) was one of the earliest Mac archive formats. -/// Each entry in the archive begins with a 4-byte magic word ("PMag" for stored, -/// "PMa4" for Huffman-compressed), followed by the entry header and inline file data. -/// -/// Per-entry layout: +/// PackIt (Harry Chesley, 1984-86) has no archive header: entries follow one another, each +/// opened by a four-byte magic, and the archive ends with "PEnd". /// -/// [0..4] magic: "PMag" (stored) or "PMa4" (Huffman compressed) -/// [4..67] filename field: 63 bytes, Pascal string (byte 4 = length, bytes 5..67 = name) -/// [67..71] Mac file type (4 ASCII bytes) -/// [71..75] Mac creator code (4 ASCII bytes) -/// [75..77] Finder flags (uint16 BE) -/// [77] locked flag (1 byte) -/// [78] zero padding (1 byte) -/// [79..83] data fork size (uint32 BE) -/// [83..87] resource fork size (uint32 BE) -/// [87..] data fork bytes (data fork size bytes) -/// resource fork bytes (resource fork size bytes) +/// magic "PMag" stored · "PMa4" Huffman · "PMa5" Huffman + XOR · "PMa6" Huffman + DES +/// [0] filename length +/// [1..64] filename (63 bytes) +/// [64..68] file type [68..72] creator [72..74] Finder flags [74..76] unused +/// [76..80] data fork size [80..84] resource fork size +/// [84..88] modified [88..92] created (seconds since 1904, big-endian) +/// [92..94] CRC-16/XMODEM of bytes 0..92 +/// data fork, resource fork, CRC-16/XMODEM of both forks /// -/// This reader supports stored ("PMag") entries with full extraction and lists -/// Huffman-compressed ("PMa4") entries for informational purposes only. +/// In a compressed entry everything after the magic — the 94-byte header, both forks and the fork +/// CRC — is one Huffman stream (see ), padded to a byte boundary. +/// Nothing records its compressed length, so listing decodes each compressed entry to find the next. +/// Encrypted entries cannot be walked without the password; listing stops at the first one. +/// Sourcing: layout from The Unarchiver's XADMaster (XADPackItParser.m, +/// LGPL-2.1-or-later); the header CRC (not checked by XADMaster) was identified by measurement on +/// real PackIt II archives, every header of which it matches. /// public sealed class PackItReader : IDisposable { - /// Size of the fixed per-entry header in bytes (magic + filename field + metadata). - public const int EntryHeaderSize = 87; - - /// Offset of the Pascal filename field within the entry header. - private const int FileNameOffset = 4; - - /// Maximum length of the filename field in bytes (63 bytes: 1 length + 62 name). - private const int FileNameFieldSize = 63; - - /// Offset of the Mac file type within the entry header. - private const int FileTypeOffset = 67; - - /// Offset of the Mac creator code within the entry header. - private const int CreatorOffset = 71; - - /// Offset of the data fork size within the entry header. - private const int DataForkSizeOffset = 79; - - /// Offset of the resource fork size within the entry header. - private const int ResourceForkSizeOffset = 83; - private readonly Stream _stream; private readonly bool _leaveOpen; private readonly List _entries = []; @@ -63,6 +42,7 @@ public sealed class PackItReader : IDisposable { /// to leave open after this reader is disposed. /// /// Thrown when is null. + /// The stream does not start with a PackIt entry or end marker. public PackItReader(Stream stream, bool leaveOpen = false) { this._stream = stream ?? throw new ArgumentNullException(nameof(stream)); this._leaveOpen = leaveOpen; @@ -72,25 +52,50 @@ public PackItReader(Stream stream, bool leaveOpen = false) { /// Gets the list of entries found in the archive. public IReadOnlyList Entries => this._entries; - /// - /// Extracts the data fork of the specified entry. - /// - /// An entry obtained from . - /// - /// The raw data fork bytes. For stored entries the bytes are the original file data. - /// For Huffman-compressed entries the raw compressed bytes are returned. - /// - /// Thrown when is null. - /// Thrown when this reader has been disposed. - public byte[] Extract(PackItEntry entry) { + /// Gets whether listing stopped early (truncated archive or an entry it cannot walk past). + public bool IsIncomplete { get; private set; } + + /// Extracts the data fork of the specified entry. + /// The entry is encrypted. + /// The header or fork CRC-16 does not match. + public byte[] Extract(PackItEntry entry) => this.ExtractForks(entry).Data; + + /// Extracts the resource fork of the specified entry. + /// The entry is encrypted. + /// The header or fork CRC-16 does not match. + public byte[] ExtractResourceFork(PackItEntry entry) => this.ExtractForks(entry).Resource; + + /// Decodes both forks of and checks every CRC. + public (byte[] Data, byte[] Resource) ExtractForks(PackItEntry entry) { ArgumentNullException.ThrowIfNull(entry); ObjectDisposedException.ThrowIf(this._disposed, this); - if (entry.DataForkSize == 0) - return []; + if (entry.IsEncrypted) + throw new NotSupportedException($"PackIt entry '{entry.Name}' is password-encrypted; decryption is not supported."); + if (!entry.HeaderCrcValid) + throw new InvalidDataException($"PackIt entry '{entry.Name}' has a header that fails its CRC-16."); + + var forkBytes = checked((int)(entry.DataForkSize + entry.ResourceForkSize)); + var body = new byte[forkBytes + 2]; + if (entry.IsCompressed) { + this._stream.Position = entry.DataOffset; + var decoder = new PackItHuffmanDecoder(this._stream); + Span header = stackalloc byte[PackItConstants.HeaderSize]; + decoder.Decode(header); + decoder.Decode(body); + } else { + this._stream.Position = entry.DataOffset; + this._stream.ReadExactly(body); + } + + var stored = BinaryPrimitives.ReadUInt16BigEndian(body.AsSpan(forkBytes)); + var actual = Crc16Ccitt.Compute(body.AsSpan(0, forkBytes)); + if (stored != actual) + throw new InvalidDataException( + $"PackIt entry '{entry.Name}' fails its fork CRC-16: stored 0x{stored:X4}, computed 0x{actual:X4}."); - this._stream.Position = entry.DataOffset; - return this.ReadExact((int)entry.DataForkSize); + var dataLength = (int)entry.DataForkSize; + return (body.AsSpan(0, dataLength).ToArray(), body.AsSpan(dataLength, forkBytes - dataLength).ToArray()); } /// @@ -105,80 +110,114 @@ public void Dispose() { // ── Parsing ─────────────────────────────────────────────────────────────────── private void ParseArchive() { - Span magicBuf = stackalloc byte[4]; - - while (this._stream.Position < this._stream.Length) { - // Try to read 4-byte magic. If fewer than 4 bytes remain, stop. - var remaining = (int)(this._stream.Length - this._stream.Position); - if (remaining < EntryHeaderSize) - break; - - this.ReadExact(magicBuf); - - var isStored = magicBuf.SequenceEqual(PackItConstants.MagicStored); - var isCompressed = !isStored && magicBuf.SequenceEqual(PackItConstants.MagicCompressed); - - if (!isStored && !isCompressed) - break; // no more recognized entries - - // Read the rest of the entry header (EntryHeaderSize - 4 bytes already consumed). - var hdrRemainder = this.ReadExact(EntryHeaderSize - 4); - - // Filename: Pascal string at hdrRemainder[0..63] (relative to hdrRemainder). - // hdrRemainder[0] = name length, [1..62] = name bytes - var nameLength = hdrRemainder[0]; - if (nameLength > FileNameFieldSize - 1) - nameLength = (byte)(FileNameFieldSize - 1); - var name = nameLength > 0 - ? Encoding.Latin1.GetString(hdrRemainder, 1, nameLength) - : string.Empty; - - // File type and creator are 4 ASCII bytes each. - // Offsets within hdrRemainder = (absolute offset) - 4 (magic already consumed). - var fileTypeOff = FileTypeOffset - 4; // 63 - var creatorOff = CreatorOffset - 4; // 67 - var dataOff = DataForkSizeOffset - 4; // 75 - var rsrcOff = ResourceForkSizeOffset - 4; // 79 - - var fileType = Encoding.ASCII.GetString(hdrRemainder, fileTypeOff, 4); - var creator = Encoding.ASCII.GetString(hdrRemainder, creatorOff, 4); - - var dataForkSize = BinaryPrimitives.ReadUInt32BigEndian(hdrRemainder.AsSpan(dataOff, 4)); - var rsrcForkSize = BinaryPrimitives.ReadUInt32BigEndian(hdrRemainder.AsSpan(rsrcOff, 4)); - - // Data fork bytes start immediately after the header. - var dataForkStart = this._stream.Position; - - this._entries.Add(new PackItEntry { - Name = name, - FileType = fileType, - Creator = creator, - DataForkSize = dataForkSize, - ResourceForkSize = rsrcForkSize, - IsCompressed = isCompressed, - DataOffset = dataForkStart, - }); - - // Skip past data fork + resource fork to reach the next entry. - this._stream.Position = dataForkStart + dataForkSize + rsrcForkSize; + Span magic = stackalloc byte[4]; + Span header = stackalloc byte[PackItConstants.HeaderSize]; + var first = true; + + while (true) { + var entryOffset = this._stream.Position; + if (this._stream.Length - entryOffset < 4) { + // A missing "PEnd" is tolerated (some transfers drop it); a missing first entry is not a PackIt file. + if (first) + throw new InvalidDataException("Stream is too short to be a PackIt archive."); + this.IsIncomplete = this._stream.Length != entryOffset; + return; + } + this._stream.ReadExactly(magic); + + if (magic.SequenceEqual(PackItConstants.MagicEnd)) + return; + + var stored = magic.SequenceEqual(PackItConstants.MagicStored); + var compressed = magic.SequenceEqual(PackItConstants.MagicCompressed); + var encrypted = magic.SequenceEqual(PackItConstants.MagicXorEncrypted) || magic.SequenceEqual(PackItConstants.MagicDesEncrypted); + if (!stored && !compressed && !encrypted) { + if (first) + throw new InvalidDataException("Stream does not start with a PackIt entry magic."); + this.IsIncomplete = true; + return; + } + first = false; + + if (encrypted) { + // The header itself is encrypted; without the password neither it nor the entry's length is known. + this._entries.Add(new PackItEntry { + Name = "(encrypted)", IsCompressed = true, IsEncrypted = true, + EntryOffset = entryOffset, DataOffset = entryOffset + 4, EntryLength = this._stream.Length - entryOffset, + }); + this.IsIncomplete = true; + return; + } + + var bodyStart = this._stream.Position; + try { + PackItHuffmanDecoder? decoder = null; + if (stored) + this._stream.ReadExactly(header); + else { + decoder = new PackItHuffmanDecoder(this._stream); + decoder.Decode(header); + } + + var dataSize = BinaryPrimitives.ReadUInt32BigEndian(header[76..]); + var rsrcSize = BinaryPrimitives.ReadUInt32BigEndian(header[80..]); + var crcValid = BinaryPrimitives.ReadUInt16BigEndian(header[92..]) + == Crc16Ccitt.Compute(header[..PackItConstants.HeaderCrcSpan]); + var forkBytes = (long)dataSize + rsrcSize; + + long end; + if (stored) { + end = bodyStart + PackItConstants.HeaderSize + forkBytes + 2; + if (end > this._stream.Length) + throw new EndOfStreamException(); + } else { + // Walk the forks and their CRC to find where the next entry starts. + var scratch = new byte[64 * 1024]; + for (var left = forkBytes + 2; left > 0;) { + var chunk = (int)Math.Min(left, scratch.Length); + decoder!.Decode(scratch.AsSpan(0, chunk)); + left -= chunk; + } + end = bodyStart + decoder!.BytesConsumed; + } + + this._entries.Add(new PackItEntry { + Name = ReadName(header), + FileType = Encoding.Latin1.GetString(header[64..68]), + Creator = Encoding.Latin1.GetString(header[68..72]), + FinderFlags = BinaryPrimitives.ReadUInt16BigEndian(header[72..]), + DataForkSize = dataSize, + ResourceForkSize = rsrcSize, + Modified = FromMacTime(BinaryPrimitives.ReadUInt32BigEndian(header[84..])), + Created = FromMacTime(BinaryPrimitives.ReadUInt32BigEndian(header[88..])), + IsCompressed = compressed, + HeaderCrcValid = crcValid, + EntryOffset = entryOffset, + DataOffset = stored ? bodyStart + PackItConstants.HeaderSize : bodyStart, + EntryLength = end - entryOffset, + }); + this._stream.Position = end; + } catch (Exception ex) when (ex is EndOfStreamException or InvalidDataException) { + if (this._entries.Count == 0) + throw new InvalidDataException("PackIt archive is truncated inside its first entry.", ex); + this.IsIncomplete = true; + return; + } } } - // ── Stream helpers ──────────────────────────────────────────────────────────── - - private byte[] ReadExact(int count) { - var buf = new byte[count]; - this.ReadExact(buf.AsSpan()); - return buf; + private static string ReadName(ReadOnlySpan header) { + var length = Math.Min((int)header[0], PackItConstants.FileNameMaxLength); + return Encoding.Latin1.GetString(header.Slice(1, length)); } - private void ReadExact(Span buffer) { - var total = 0; - while (total < buffer.Length) { - var read = this._stream.Read(buffer[total..]); - if (read == 0) - throw new EndOfStreamException("Unexpected end of stream reading PackIt data."); - total += read; - } + internal static DateTime? FromMacTime(uint seconds) => + seconds == 0 ? null : DateTime.UnixEpoch.AddSeconds(seconds - PackItConstants.MacEpochOffsetSeconds); + + internal static uint ToMacTime(DateTime? time) { + if (time is null) + return 0; + var seconds = (long)(time.Value.ToUniversalTime() - DateTime.UnixEpoch).TotalSeconds + PackItConstants.MacEpochOffsetSeconds; + return seconds is < 0 or > uint.MaxValue ? 0 : (uint)seconds; } } diff --git a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.PackIt/PackItWriter.cs b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.PackIt/PackItWriter.cs index 0ca897ee3..b2a92f8da 100644 --- a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.PackIt/PackItWriter.cs +++ b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.PackIt/PackItWriter.cs @@ -1,16 +1,16 @@ using System.Buffers.Binary; using System.Text; +using Compression.Core.Checksums; namespace FileFormat.PackIt; /// -/// Creates a PackIt (.pit) classic Macintosh archive. +/// Creates a PackIt (.pit) classic Macintosh archive of stored ("PMag") entries. /// /// -/// Produces archives using the stored ("PMag") entry format only. All entries are -/// written with an empty resource fork. The archive is written incrementally on each -/// call, so the stream must remain writable until -/// is called. +/// Entries are written as they are added; writes the "PEnd" marker, so the +/// stream must stay writable until then. The layout is the one documents, +/// header and fork CRC-16s included; The Unarchiver (unar) reads it back. /// public sealed class PackItWriter : IDisposable { private readonly Stream _stream; @@ -31,28 +31,33 @@ public PackItWriter(Stream stream, bool leaveOpen = false) { /// /// Appends a stored file entry to the archive. /// - /// The filename (up to 62 characters, Latin-1 encoded). - /// The uncompressed data fork bytes. + /// The filename (up to 63 characters, Latin-1 encoded; longer names are cut). + /// The data fork bytes. /// The four-character Mac file type code (default "TEXT"). /// The four-character Mac creator code (default "CWIE"). + /// The resource fork bytes, or for none. + /// The modification time, or to record none. /// Thrown when or is null. /// Thrown when this writer has been disposed. public void AddFile( string name, byte[] data, string fileType = "TEXT", - string creator = "CWIE") { + string creator = "CWIE", + byte[]? resourceFork = null, + DateTime? modified = null) { ArgumentNullException.ThrowIfNull(name); ArgumentNullException.ThrowIfNull(data); ObjectDisposedException.ThrowIf(this._disposed, this); - this.WriteEntry(name, data, fileType, creator); + WriteStoredEntry(this._stream, name, data, resourceFork ?? [], fileType, creator, modified); } /// public void Dispose() { if (!this._disposed) { this._disposed = true; + this._stream.Write(PackItConstants.MagicEnd); this._stream.Flush(); if (!this._leaveOpen) this._stream.Dispose(); @@ -61,51 +66,43 @@ public void Dispose() { // ── Serialisation ───────────────────────────────────────────────────────────── - private void WriteEntry(string name, byte[] data, string fileType, string creator) { - // Magic "PMag" = stored. - this._stream.Write(PackItConstants.MagicStored); + /// Writes one complete "PMag" entry (magic, header, forks, fork CRC). + internal static void WriteStoredEntry(Stream output, string name, ReadOnlySpan data, ReadOnlySpan resourceFork, + string fileType, string creator, DateTime? modified) { + Span header = stackalloc byte[PackItConstants.HeaderSize]; + header.Clear(); - // Filename field: 63 bytes (Pascal string: 1 length byte + up to 62 name bytes). var nameBytes = Encoding.Latin1.GetBytes(name); - var nameLen = Math.Min(nameBytes.Length, PackItConstants.FileNameMaxLength); - Span nameField = stackalloc byte[PackItConstants.FileNameMaxLength + 1]; // 63 bytes - nameField.Clear(); - nameField[0] = (byte)nameLen; - nameBytes.AsSpan(0, nameLen).CopyTo(nameField[1..]); - this._stream.Write(nameField); + var nameLength = Math.Min(nameBytes.Length, PackItConstants.FileNameMaxLength); + header[0] = (byte)nameLength; + nameBytes.AsSpan(0, nameLength).CopyTo(header[1..]); + WriteFourCc(header.Slice(64, 4), fileType); + WriteFourCc(header.Slice(68, 4), creator); + // [72..74] Finder flags and [74..76] are left zero. + BinaryPrimitives.WriteUInt32BigEndian(header[76..], (uint)data.Length); + BinaryPrimitives.WriteUInt32BigEndian(header[80..], (uint)resourceFork.Length); + var macTime = PackItReader.ToMacTime(modified); + BinaryPrimitives.WriteUInt32BigEndian(header[84..], macTime); + BinaryPrimitives.WriteUInt32BigEndian(header[88..], macTime); + BinaryPrimitives.WriteUInt16BigEndian(header[92..], Crc16Ccitt.Compute(header[..PackItConstants.HeaderCrcSpan])); - // File type (4 bytes) + creator (4 bytes). - Span typeField = stackalloc byte[4]; - Span creatorField = stackalloc byte[4]; - WriteAscii4(typeField, fileType); - WriteAscii4(creatorField, creator); - this._stream.Write(typeField); - this._stream.Write(creatorField); + var crc = new Crc16Ccitt(); + crc.Update(data); + crc.Update(resourceFork); + Span trailer = stackalloc byte[2]; + BinaryPrimitives.WriteUInt16BigEndian(trailer, (ushort)crc.Value); - // Finder flags (2 bytes) + locked (1 byte) + zero padding (1 byte) = 4 bytes. - Span meta = stackalloc byte[4]; - meta.Clear(); - this._stream.Write(meta); - - // Data fork size (uint32 BE) + resource fork size (uint32 BE) = 8 bytes. - Span sizes = stackalloc byte[8]; - BinaryPrimitives.WriteUInt32BigEndian(sizes, (uint)data.Length); - BinaryPrimitives.WriteUInt32BigEndian(sizes[4..], 0u); // resource fork = empty - this._stream.Write(sizes); - - // Data fork bytes. - if (data.Length > 0) - this._stream.Write(data); - // Resource fork bytes: zero bytes (already accounted for by size = 0). + output.Write(PackItConstants.MagicStored); + output.Write(header); + output.Write(data); + output.Write(resourceFork); + output.Write(trailer); } - // ── Helpers ─────────────────────────────────────────────────────────────────── - - private static void WriteAscii4(Span dest, string value) { - var bytes = Encoding.ASCII.GetBytes(value); - var len = Math.Min(bytes.Length, 4); - bytes.AsSpan(0, len).CopyTo(dest); - for (var i = len; i < 4; ++i) - dest[i] = (byte)' '; + private static void WriteFourCc(Span dest, string value) { + var bytes = Encoding.Latin1.GetBytes(value); + var length = Math.Min(bytes.Length, 4); + bytes.AsSpan(0, length).CopyTo(dest); + dest[length..].Fill((byte)' '); } } diff --git a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.PackIt/THIRD-PARTY-NOTICE.XADMaster.txt b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.PackIt/THIRD-PARTY-NOTICE.XADMaster.txt new file mode 100644 index 000000000..f729d680a --- /dev/null +++ b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.PackIt/THIRD-PARTY-NOTICE.XADMaster.txt @@ -0,0 +1,34 @@ +XADMaster (PackIt) - converted to C# + +Source: https://github.com/MacPaw/XADMaster (The Unarchiver's unarchiving engine) +Revision: 7cb9ee0abbb163f261e4cb74501e15067032319c (master, 2026-09-21) +Authors: Dag Ågren (original author); MacPaw Way Ltd. (2017-present); + parts descend from libxad (Dirk Stöcker and contributors) +Licence: GNU Lesser General Public License, version 2.1 or (at your option) + any later version. This repository is LGPL-3.0-or-later, which the + "or any later version" grant permits; the full LGPL text is in the + repository's LICENSE file. + +Upstream notice, as carried in each XADMaster source file: + + Copyright (c) 2017-present, MacPaw Way Ltd. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + +Files converted: + + XADPackItParser.m -> PackItReader.cs (entry layout) + XADStuffItHuffmanHandle.m -> PackItHuffmanDecoder.cs + +The header CRC (which XADMaster reads but does not check) was identified by +measurement: CRC-16/XMODEM over the 92 header bytes before it matched every +header of nine real PackIt archives. Encrypted entries (PMa5/PMa6) are listed +and refused rather than decrypted. diff --git a/Hawkynt.FileFormats.Archives/README.md b/Hawkynt.FileFormats.Archives/README.md index 3affa6901..19f330c8f 100644 --- a/Hawkynt.FileFormats.Archives/README.md +++ b/Hawkynt.FileFormats.Archives/README.md @@ -117,7 +117,7 @@ Every State, Test, Maintenance, Compress/Decompress and Demux/Mux/Remux cell is | [Compact Pro](https://en.wikipedia.org/wiki/Compact_Pro) | `CompactPro` | `.cpt` | R/W | ✅ | defrag · wipe | | [GitHub](https://github.com/MacPaw/XADMaster) | | [CPIO](https://en.wikipedia.org/wiki/Cpio) | `Cpio` | `.cpio` | R/W | ✅ | defrag · wipe | Reads and writes all four header variants — binary (both byte orders), `odc`, `newc` and `crc`, the last with its payload checksum verified; the `Format` option picks one and edits keep the archive's own | [pubs.opengroup.org](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html) | | [DAR (Disk ARchive)](https://en.wikipedia.org/wiki/Dar_(disk_archiver)) | `Dar` | `.dar` | WORM | ✅ | — | Reads formats 9–11.3 (verified on 11.3): single and multi-slice, tape marks, gzip / bzip2 / xz / zstd / lz4 / lzo in streaming or block mode, sparse files, hard links; encrypted archives are refused. Writes single-slice 11.3 archives (stored, gzip, bzip2, xz, zstd, lz4) that `dar -t` accepts and `dar -x` restores byte for byte. Layout: [DAR-ON-DISK.md](https://github.com/Hawkynt/CompressionWorkbench/blob/main/docs/DAR-ON-DISK.md) | [dar.linux.free.fr](http://dar.linux.free.fr) | -| [DiskDoubler](https://en.wikipedia.org/wiki/DiskDoubler) | `DiskDoubler` | `.dd` `.sea` | WORM | — | wipe | Single-fork compressor: one payload per file | [GitHub](https://github.com/MacPaw/XADMaster) | +| [DiskDoubler](https://en.wikipedia.org/wiki/DiskDoubler) | `DiskDoubler` | `.dd` `.sea` | WORM | — | wipe | Single-fork compressor: one payload per file. Reads methods 0, 1, 6 and 9, refuses the rest; writes stored | [GitHub](https://github.com/MacPaw/XADMaster) | | [DMS](https://en.wikipedia.org/wiki/Disk_Masher_System) | `Dms` | `.dms` | WORM | ✅ | defrag | | [GitHub](https://github.com/markrabjohn/xDMS) | | [EGG (ALZip)](https://en.wikipedia.org/wiki/EGG_(file_format)) | `Egg` | `.egg` | WORM | ✅ | defrag | | [GitHub](https://github.com/alkegi/docs/blob/master/egg.md) | | [ESD](https://en.wikipedia.org/wiki/Windows_Imaging_Format) | `Esd` | `.esd` | WORM | ✅ | wipe | Solid LZMS WIM; created images carry a metadata resource but entries re-list as resources | [Microsoft Learn](https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/wim-and-esd-windows-image-files-overview) | @@ -132,7 +132,7 @@ Every State, Test, Maintenance, Compress/Decompress and Demux/Mux/Remux cell is | [LZX (Amiga)](https://en.wikipedia.org/wiki/LZX) | `LzxAmiga` | `.lzx` | R/W | ✅ | defrag · wipe | | [Aminet](https://aminet.net) | | [mtree](https://man.freebsd.org/cgi/man.cgi?query=mtree&sektion=5) | `Mtree` | `.mtree` | WORM | ✅ | — | Filesystem metadata manifest; does not embed file bodies, so CWB does not dereference `contents=` host paths | [FreeBSD mtree(5)](https://man.freebsd.org/cgi/man.cgi?query=mtree&sektion=5) | | [NuFX / ShrinkIt](https://en.wikipedia.org/wiki/ShrinkIt) | `NuFx` | `.shk` `.sdk` `.bxy` | R/W | ✅ | defrag · shrink · wipe | | [nulib.com](https://nulib.com/library/FTN.e08002.htm) | -| PackIt | `PackIt` | `.pit` | R/W | ✅ | defrag · wipe | | [GitHub](https://github.com/MacPaw/XADMaster) | +| PackIt | `PackIt` | `.pit` | R/W | ✅ | defrag · wipe | Reads stored and Huffman entries, writes stored; encrypted entries are refused | [GitHub](https://github.com/MacPaw/XADMaster) | | [RAR](https://en.wikipedia.org/wiki/RAR_(file_format)) | `Rar` | `.rar` | R/W | ✅ | wipe | v1–v5 readers; creation and edits emit RAR4/RAR5 without claiming WinRAR encoder parity | [rarlab.com](https://www.rarlab.com/technote.htm) | | [7z](https://en.wikipedia.org/wiki/7z) | `SevenZip` | `.7z` | R/W | ✅ | compress · wipe | | [7-zip.org](https://www.7-zip.org/7z.html) | | [SHAR](https://en.wikipedia.org/wiki/Shar) | `Shar` | `.shar` `.sh` | R/W | ✅ | defrag | Add appends in place; Remove re-emits the script from the survivors | [gnu.org](https://www.gnu.org/software/sharutils/) | diff --git a/Hawkynt.FileFormats.Archives/REFERENCE.md b/Hawkynt.FileFormats.Archives/REFERENCE.md index abb0035eb..08ea63067 100644 --- a/Hawkynt.FileFormats.Archives/REFERENCE.md +++ b/Hawkynt.FileFormats.Archives/REFERENCE.md @@ -13715,16 +13715,22 @@ Implements `IArchiveFormatOperations`, `IArchiveInMemoryExtract`, `IFormatDescri #### `DiskDoublerEntry` -Represents a single compressed file stored in a DiskDoubler file. +Represents one fork of a DiskDoubler compressed file. | Member | Signature | Summary | | --- | --- | --- | | `DiskDoublerEntry` | `DiskDoublerEntry()` | | +| `Checksum` | `ushort Checksum { get; init; }` | Gets the fork's checksum field (its meaning depends on the method). | | `CompressedSize` | `long CompressedSize { get; init; }` | Gets the compressed size in bytes. | +| `Creator` | `string Creator { get; init; }` | Gets the Mac creator code. | | `DataOffset` | `long DataOffset { get; init; }` | Gets the absolute byte offset of the compressed data within the source stream. | +| `DeltaType` | `int DeltaType { get; init; }` | Gets the fork's delta pre-processing type (0 = none). | +| `FileType` | `string FileType { get; init; }` | Gets the Mac file type code. | | `IsDataFork` | `bool IsDataFork { get; init; }` | Gets whether this entry represents the data fork (as opposed to the resource fork). | -| `Method` | `int Method { get; init; }` | Gets the compression method ID (0 = stored, 1 = RLE, 3 = LZC variant, others = proprietary). | -| `Name` | `string Name { get; init; }` | Gets the original filename. | +| `IsSupported` | `bool IsSupported { get; }` | Gets whether this reader can decode the entry's method. | +| `Method` | `int Method { get; init; }` | Gets the compression method: 0 stored, 1 Unix compress, 2 and 5 DiskDoubler Huffman variants, 3 RLE, 4 Huffman, 6 and 9 "AD" LZSS, 7 Stac LZS, 8 Compact Pro, 10 "DD". The high bit is a flag and is masked off. | +| `Modified` | `DateTime? Modified { get; init; }` | Gets the modification time, or `null` when the header holds zero. | +| `Name` | `string Name { get; init; }` | Gets the entry name: the file's name for the data fork, with ".rsrc" appended for the resource fork. | | `OriginalSize` | `long OriginalSize { get; init; }` | Gets the original (uncompressed) size in bytes. | #### `DiskDoublerFormatDescriptor` @@ -13745,7 +13751,7 @@ Implements `IArchiveCreatable`, `IArchiveFormatOperations`, `IArchiveLayoutMap`, | `Extensions` | `IReadOnlyList Extensions { get; }` | Gets the extensions. | | `Family` | `AlgorithmFamily Family { get; }` | Gets the family. | | `Id` | `string Id { get; }` | Gets the id. | -| `MagicSignatures` | `IReadOnlyList MagicSignatures { get; }` | Gets the magic signatures. | +| `MagicSignatures` | `IReadOnlyList MagicSignatures { get; }` | Gets the magic signatures: a single DiskDoubler file starts with 0xABCD0054. | | `Methods` | `IReadOnlyList Methods { get; }` | Gets the methods. | | `TarCompressionFormatId` | `string TarCompressionFormatId { get; }` | Gets the tar compression format id. | | `Create` | `void Create(Stream output, IReadOnlyList inputs, FormatCreateOptions options)` | Performs the create operation. | @@ -13757,27 +13763,28 @@ Implements `IArchiveCreatable`, `IArchiveFormatOperations`, `IArchiveLayoutMap`, #### `DiskDoublerReader` -Reads the header of a DiskDoubler compressed file and provides access to its data and resource fork entries. +Reads a single DiskDoubler compressed file and decodes its forks. Implements `IDisposable`. | Member | Signature | Summary | | --- | --- | --- | -| `DiskDoublerReader` | `DiskDoublerReader(Stream stream, bool leaveOpen = false)` | Opens a DiskDoubler compressed file from the given stream and parses its header. | -| `HeaderSize` | `const int HeaderSize` | Size of the fixed-length DiskDoubler file header in bytes. | -| `Entries` | `IReadOnlyList Entries { get; }` | Gets the list of entries parsed from the DiskDoubler file header. | +| `DiskDoublerReader` | `DiskDoublerReader(Stream stream, bool leaveOpen = false, string name = null)` | Opens a DiskDoubler compressed file from the given stream and parses its header. | +| `Entries` | `IReadOnlyList Entries { get; }` | Gets the forks parsed from the header: the data fork (if any), then the resource fork (if any). | | `Dispose` | `void Dispose()` | | -| `Extract` | `byte[] Extract(DiskDoublerEntry entry)` | Extracts the data for the specified entry. | +| `Extract` | `byte[] Extract(DiskDoublerEntry entry)` | Decodes the specified fork. | +| `IsDiskDoubler` | `static bool IsDiskDoubler(ReadOnlySpan header)` | Returns whether `header` starts a DiskDoubler file (directly or behind MacBinary). | +| `MethodName` | `static string MethodName(int method)` | Names a method as the format's tools do. | #### `DiskDoublerWriter` -Writes a DiskDoubler stored (method 0) file. Takes a single data fork payload; resource fork is empty. Roundtrips through `DiskDoublerReader`. +Writes a single DiskDoubler file with a stored (method 0) data fork and no resource fork, in the layout `DiskDoublerReader` documents. The Unarchiver (`unar`) reads it back. | Member | Signature | Summary | | --- | --- | --- | | `DiskDoublerWriter` | `DiskDoublerWriter()` | | -| `SetFile` | `void SetFile(string name, byte[] data)` | Sets the file. | -| `WriteTo` | `void WriteTo(Stream output)` | Writes the to to the supplied output. | +| `SetFile` | `void SetFile(byte[] data, string fileType = "????", string creator = "????")` | Sets the data fork. DiskDoubler stores no name; the file's own name is the entry's name. | +| `WriteTo` | `void WriteTo(Stream output)` | Writes the header and the stored data fork to `output`. | ### Namespace `FileFormat.Dls` @@ -24034,17 +24041,24 @@ Represents a file entry in a PackIt (.pit) archive. | Member | Signature | Summary | | --- | --- | --- | | `PackItEntry` | `PackItEntry()` | | +| `Created` | `DateTime? Created { get; init; }` | Gets the creation time, or `null` when the header holds zero. | | `Creator` | `string Creator { get; init; }` | Gets the Mac four-character creator code (e.g. "CWIE"). | | `DataForkSize` | `long DataForkSize { get; init; }` | Gets the uncompressed data fork size in bytes. | -| `DataOffset` | `long DataOffset { get; init; }` | Gets the absolute byte offset where the data fork begins in the source stream. | +| `DataOffset` | `long DataOffset { get; init; }` | Gets the absolute offset where the data fork begins for a stored entry; for a compressed entry, the offset of its Huffman bit stream (the byte after the magic). | +| `EntryLength` | `long EntryLength { get; init; }` | Gets the number of bytes the entry occupies, magic included. | +| `EntryOffset` | `long EntryOffset { get; init; }` | Gets the absolute offset of the entry's magic. | | `FileType` | `string FileType { get; init; }` | Gets the Mac four-character file type code (e.g. "TEXT"). | -| `IsCompressed` | `bool IsCompressed { get; init; }` | Gets whether this entry uses Huffman compression ("PMa4"). | +| `FinderFlags` | `ushort FinderFlags { get; init; }` | Gets the Finder flags. | +| `HeaderCrcValid` | `bool HeaderCrcValid { get; init; }` | Gets whether the header matched its CRC-16 (always `true` for encrypted entries, which cannot be checked). | +| `IsCompressed` | `bool IsCompressed { get; init; }` | Gets whether this entry is Huffman-compressed ("PMa4", "PMa5", "PMa6"). | +| `IsEncrypted` | `bool IsEncrypted { get; init; }` | Gets whether this entry is password-encrypted ("PMa5" XOR, "PMa6" DES). | +| `Modified` | `DateTime? Modified { get; init; }` | Gets the modification time, or `null` when the header holds zero. | | `Name` | `string Name { get; init; }` | Gets the filename (up to 63 characters, Mac Roman / Latin-1 encoded). | | `ResourceForkSize` | `long ResourceForkSize { get; init; }` | Gets the uncompressed resource fork size in bytes. | #### `PackItFormatDescriptor` -PackIt classic Macintosh archive (.pit; Harry Chesley, 1984) — sequential PMag/PMa4 records bundling data and resource forks. References: `https://github.com/MacPaw/XADMaster` — XADMaster (The Unarchiver) — maintained implementation including PackItNo formal specification — format known from PackIt itself and later community documentation +PackIt classic Macintosh archive (.pit; Harry Chesley, 1984) — sequential PMag/PMa4 records bundling data and resource forks. References: `https://github.com/MacPaw/XADMaster` — XADMaster (The Unarchiver) — maintained implementation including PackItNo formal specification; header CRC measured on real PackIt II archives (see PackItReader) Implements `IArchiveCreatable`, `IArchiveDefragmentable`, `IArchiveFormatOperations`, `IArchiveLayoutMap`, `IArchiveModifiable`, `IArchivePurgeable`, `IFormatDescriptor`, `IWipeEmpty`. @@ -24065,8 +24079,8 @@ Implements `IArchiveCreatable`, `IArchiveDefragmentable`, `IArchiveFormatOperati | `TarCompressionFormatId` | `string TarCompressionFormatId { get; }` | Gets the tar compression format id. | | `Add` | `void Add(Stream archive, IReadOnlyList inputs)` | Adds (or replaces by name) files inside an existing PackIt archive. Uses `PackItModifier` — Add appends Stored at EOF; Remove walks the entry chain and shifts trailing bytes (no central directory). | | `Create` | `void Create(Stream output, IReadOnlyList inputs, FormatCreateOptions options)` | Performs the create operation. | -| `Defragment` | `void Defragment(Stream archive)` | Rebuild-based defrag: extracts then re-creates the PackIt archive in listing order. | -| `Defragment` | `void Defragment(Stream archive, DefragOptions options)` | Rebuild-based defrag: extracts then re-creates the PackIt archive per the requested mode. | +| `Defragment` | `void Defragment(Stream archive)` | Rebuild-based defrag: re-chains the entries in listing order. | +| `Defragment` | `void Defragment(Stream archive, DefragOptions options)` | Rebuild-based defrag. Each entry is carried over as its raw bytes (magic to fork CRC), so resource forks, Finder info, dates and Huffman coding survive unchanged; only the chain is rewritten. | | `EnumerateLayout` | `IEnumerable EnumerateLayout(Stream archive)` | | | `ExtractEntryToMemory` | `byte[] ExtractEntryToMemory(Stream archive, string entryName, string password)` | Native in-memory single-entry extraction routed through the bounded `OpenEntry`. | | `Extract` | `void Extract(Stream stream, string outputDir, string password, string[] files)` | Decodes the supplied input. | @@ -24076,12 +24090,12 @@ Implements `IArchiveCreatable`, `IArchiveDefragmentable`, `IArchiveFormatOperati #### `PackItModifier` -Random-access in-place modifier for PackIt (.pit) classic Macintosh archives. PackIt has no explicit end-of-archive marker — readers stop when the next 4 bytes are not "PMag" or "PMa4". Add appends a new stored entry at EOF; Remove walks the entry chain, locates the target, and shifts trailing bytes forward to compact (no central directory). +In-place edits of a PackIt archive. PackIt has no directory: entries are a chain ended by `"PEnd"`, so an add rewrites the marker after the new entry and a remove shifts the tail. | Member | Signature | Summary | | --- | --- | --- | -| `AddFile` | `static void AddFile(Stream pit, string name, byte[] data, string fileType = "TEXT", string creator = "CWIE")` | Appends a stored ("PMag") entry at the end of the archive. Walks the existing entry chain to find the EOF (first non-magic 4 bytes), then writes the new entry there and truncates. I/O cost is one full sequential entry walk plus the new entry's bytes. | -| `RemoveFile` | `static bool RemoveFile(Stream pit, string name, bool wipeData = true)` | Removes the named entry. Returns true if found. Walks the chain to locate the entry, then shifts trailing bytes forward to compact. | +| `AddFile` | `static void AddFile(Stream pit, string name, byte[] data, string fileType = "TEXT", string creator = "CWIE")` | Appends a stored ("PMag") entry after the last entry and rewrites the `"PEnd"` marker behind it. | +| `RemoveFile` | `static bool RemoveFile(Stream pit, string name, bool wipeData = true)` | Removes the named entry. Returns true if found. Locates the entry by walking the chain, then shifts the trailing bytes forward over it. | #### `PackItReader` @@ -24092,21 +24106,23 @@ Implements `IDisposable`. | Member | Signature | Summary | | --- | --- | --- | | `PackItReader` | `PackItReader(Stream stream, bool leaveOpen = false)` | Opens a PackIt archive from the given stream and reads all entries. | -| `EntryHeaderSize` | `const int EntryHeaderSize` | Size of the fixed per-entry header in bytes (magic + filename field + metadata). | | `Entries` | `IReadOnlyList Entries { get; }` | Gets the list of entries found in the archive. | +| `IsIncomplete` | `bool IsIncomplete { get; }` | Gets whether listing stopped early (truncated archive or an entry it cannot walk past). | | `Dispose` | `void Dispose()` | | +| `ExtractForks` | `ValueTuple ExtractForks(PackItEntry entry)` | Decodes both forks of `entry` and checks every CRC. | +| `ExtractResourceFork` | `byte[] ExtractResourceFork(PackItEntry entry)` | Extracts the resource fork of the specified entry. | | `Extract` | `byte[] Extract(PackItEntry entry)` | Extracts the data fork of the specified entry. | #### `PackItWriter` -Creates a PackIt (.pit) classic Macintosh archive. +Creates a PackIt (.pit) classic Macintosh archive of stored ("PMag") entries. Implements `IDisposable`. | Member | Signature | Summary | | --- | --- | --- | | `PackItWriter` | `PackItWriter(Stream stream, bool leaveOpen = false)` | Initialises a new `PackItWriter`. | -| `AddFile` | `void AddFile(string name, byte[] data, string fileType = "TEXT", string creator = "CWIE")` | Appends a stored file entry to the archive. | +| `AddFile` | `void AddFile(string name, byte[] data, string fileType = "TEXT", string creator = "CWIE", byte[] resourceFork = null, DateTime? modified = null)` | Appends a stored file entry to the archive. | | `Dispose` | `void Dispose()` | | ### Namespace `FileFormat.Paf` diff --git a/docs/catalogue/GAPS.md b/docs/catalogue/GAPS.md index fb9d0648c..04f73884c 100644 --- a/docs/catalogue/GAPS.md +++ b/docs/catalogue/GAPS.md @@ -180,9 +180,9 @@ running real-world samples. The file named at the end of each row is where the l | ZPAQ | `Zpaq` | 3 | reads only the stored blocks our own writer emits; ZPAQL-compressed blocks (every real zpaq level) throw (ZpaqReader.cs) | | Btrfs | `Btrfs` | 2 | zlib/lzo/zstd compressed extents are skipped (BtrfsReader.cs) | | Compact Pro | `CompactPro` | 2 | stored and RLE only; LZ+RLE and LZ+Huffman unsupported (CompactProReader.cs) | -| DiskDoubler | `DiskDoubler` | 2 | stored only; compressed members are returned as raw compressed bytes without an error (DiskDoublerReader.cs) | +| DiskDoubler | `DiskDoubler` | 2 | single files only (no DDAR/DDA2 archives); methods 0, 1, 6, 9 decoded and checked against unar on real files; 2-5, 7, 8, 10 and delta refused (DiskDoublerReader.cs) | | EnCase / Expert Witness (E01) | `Ewf` | 2 | single segment only (no .E02+), no L01 logical evidence, no Ex01 (EwfReader.cs) | -| PackIt | `PackIt` | 2 | PMag (stored) only; PMa4 Huffman members are returned raw (PackItReader.cs) | +| PackIt | `PackIt` | 2 | encrypted PMa5/PMa6 entries refused; stored and Huffman entries verified on real archives against unar (PackItReader.cs) | | UHARC | `Uharc` | 2 | stored and LZP only (UharcReader.cs) | | VHDX | `Vhdx` | 2 | differencing / partially-present blocks read as zeros (VhdxStream.cs) | | Comic Book Archive | `Cbz`, `Cbr`, `Cb7` | 1 | .cbt (tar) and .cba (ACE) have no descriptor | diff --git a/docs/catalogue/reference-formats.csv b/docs/catalogue/reference-formats.csv index bd05e5eee..516676341 100644 --- a/docs/catalogue/reference-formats.csv +++ b/docs/catalogue/reference-formats.csv @@ -45,9 +45,9 @@ ALZip,alz,archive,,,R,,R,,RW,3,https://en.wikipedia.org/wiki/ALZip,AlZip,Y,Y,Y,P EGG (ALZip),egg,archive,,,R,,,,,1,https://en.wikipedia.org/wiki/ALZip,Egg,Y,Y,,PARTIAL,,"store and deflate only; bzip2/LZMA/AZO, encryption and split archives unsupported (EggReader.cs)" StuffIt (classic + 5),sit;sea,archive,,,R,,R,fmt/1459;fmt/1460;fmt/639,RW,4,https://en.wikipedia.org/wiki/StuffIt,StuffIt,Y,Y,Y,PARTIAL,,store and RLE only; LZW/Huffman/LZAH/Arsenic/DES methods throw (StuffItReader.cs) StuffIt X,sitx,archive,,,R,,R,fmt/399,RW,4,https://en.wikipedia.org/wiki/StuffIt,StuffItX,Y,Y,,PARTIAL,,stored and deflate only; Brimstone/Cyanide/Darkhorse/Iron throw (StuffItXReader.cs) -DiskDoubler,dd,archive,,,R,,,,Y,2,https://en.wikipedia.org/wiki/DiskDoubler,DiskDoubler,Y,Y,,PARTIAL,,stored only; compressed members are returned as raw compressed bytes without an error (DiskDoublerReader.cs) +DiskDoubler,dd,archive,,,R,,,,Y,2,https://en.wikipedia.org/wiki/DiskDoubler,DiskDoubler,Y,Y,,PARTIAL,,"single files only (no DDAR/DDA2 archives); methods 0, 1, 6, 9 decoded and checked against unar on real files; 2-5, 7, 8, 10 and delta refused (DiskDoublerReader.cs)" Compact Pro,cpt,archive,,,R,,,,Y,2,https://en.wikipedia.org/wiki/Compact_Pro,CompactPro,Y,Y,Y,PARTIAL,,stored and RLE only; LZ+RLE and LZ+Huffman unsupported (CompactProReader.cs) -PackIt,pit,archive,,,R,,,,Y,2,https://en.wikipedia.org/wiki/PackIt,PackIt,Y,Y,Y,PARTIAL,,PMag (stored) only; PMa4 Huffman members are returned raw (PackItReader.cs) +PackIt,pit,archive,,,R,,,,Y,2,https://en.wikipedia.org/wiki/PackIt,PackIt,Y,Y,Y,PARTIAL,,"real layout (PMag/PMa4, header and fork CRC-16) verified on real archives against unar; encrypted PMa5/PMa6 entries refused (PackItReader.cs)" Now Compress,now,archive,,,R,,,,,1,,,,,,MISSING,, LZX (Amiga),lzx,archive,,,R,,R,,Y,3,https://en.wikipedia.org/wiki/LZX,LzxAmiga,Y,Y,Y,OK,, NuFX / ShrinkIt,shk;sdk;bxy,archive,,,,,,,Y,1,https://wiki.preterhuman.net/NuFX,NuFx,Y,Y,Y,OK,, @@ -185,7 +185,7 @@ StuffIt X Darkhorse,,codec,,,R,,,,,1,,,,,,MISSING,, StuffIt X Iron,,codec,,,R,,,,,1,,,,,,MISSING,, StuffIt X English dictionary preprocessor,,codec,,,R,,,,,1,,,,,,MISSING,, StuffIt X x86 filter,,filter,,,R,,,,,1,,,,,,MISSING,, -DiskDoubler methods (ADn/DDn/Method2),,codec,,,R,,,,,1,,,,,,MISSING,, +DiskDoubler methods (ADn/DDn/Method2),,codec,,,R,,,,,1,,DiskDoubler,Y,Y,,PARTIAL,,"ADn (6, 9) decoded; DDn and Method 2 refused (DiskDoublerAdDecoder.cs)" Compact Pro LZH/RLE,,codec,,,R,,,,,1,,,,,,MISSING,, Squeeze (Huffman),,codec,,,R,,,,,1,,Squeeze,Y,Y,,OK,, Crunch CP/M (LZW),,codec,,,R,,,,,1,,Crunch,Y,Y,,OK,, From 2350d2a66c545ce842875522d4792342e18572cf Mon Sep 17 00:00:00 2001 From: Hawkynt Date: Mon, 26 Oct 2026 13:04:16 +0100 Subject: [PATCH 10/34] * regenerate references --- docs/catalogue/GAPS.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/catalogue/GAPS.md b/docs/catalogue/GAPS.md index 04f73884c..5ac828100 100644 --- a/docs/catalogue/GAPS.md +++ b/docs/catalogue/GAPS.md @@ -16,16 +16,16 @@ exists. It warns when a capability letter has drifted from the live descriptor. | Catalogue rows | 983 | | … formats listed by at least one source | 525 | | … registry ids no source lists (OURS-ONLY) | 458 | -| Source-listed rows mapped to something here | 284 / 525 (54.1 %) | +| Source-listed rows mapped to something here | 285 / 525 (54.3 %) | | … of rows listed by ≥ 2 sources | 122 / 138 (88 %) | | … of rows listed by ≥ 3 sources | 66 / 67 (PEA is the one missing) | | … of rows listed by ≥ 4 sources | 31 / 31 | | Gap type | Rows | Meaning | | --- | ---: | --- | -| `MISSING` | 241 | a source lists it; no descriptor or building block covers it | +| `MISSING` | 240 | a source lists it; no descriptor or building block covers it | | `READ-ONLY-BUT-SOURCES-WRITE` | 23 | we only read it; a source or the format's own tool writes it | -| `PARTIAL` | 40 | covered, but the reader handles only part of the format (see the table below) | +| `PARTIAL` | 41 | covered, but the reader handles only part of the format (see the table below) | | `OK` | 221 | covered with no known gap against what the sources list | | `OURS-ONLY` | 458 | in our registry, listed by none of the seven sources | @@ -38,7 +38,7 @@ read-only rows. | --- | ---: | ---: | ---: | ---: | | 7-Zip (formats + codecs) | 96 | 89 | 92.7 | 72 | | libarchive (formats + filters) | 28 | 27 | 96.4 | 23 | -| XADMaster / The Unarchiver | 126 | 94 | 74.6 | 78 | +| XADMaster / The Unarchiver | 126 | 95 | 75.4 | 78 | | binwalk v3 | 79 | 48 | 60.8 | 36 | | Universal Extractor 2 | 124 | 71 | 57.3 | 48 | | PRONOM | 87 | 63 | 72.4 | 43 | From fd7e239e91db14dbf05f346dd9b7eef7a4a5c7eb Mon Sep 17 00:00:00 2001 From: Hawkynt Date: Mon, 26 Oct 2026 13:04:19 +0100 Subject: [PATCH 11/34] * FreeArc: real FreeArc 0.67 archives, read and write * FreeArc: real FreeArc 0.67 archives, read and write Symptom: FreeArcReader/FreeArcWriter used a private layout of our own ("ArC\1" + flags + typed blocks). Not one real FreeArc archive could be read, and FreeArc rejected everything we wrote, but the descriptor still claimed create and modify. Root cause: the format was never taken from FreeArc. A real archive is read from its end. The footer descriptor sits in the last 4 KiB and lists the control blocks. Each directory block lists its solid blocks with offsets relative to itself and the per-file columns. Every descriptor and control block is CRC-32 checked. Integers use FreeArc's own variable-length form, and the directory and footer are normally lzma-compressed. Change: the reader and writer are rewritten from docs/FREEARC-ON-DISK.md and the private layout is removed with no backward compatibility. - Reading: the full container (SFX prefix, comment, directories, UTF-8 names, several solid blocks). storing and lzma (raw LZMA1, dictionary and lc/lp/pb from the method string) decode. tornado, ppmd, lzp, grzip, rep, delta, exe, 4x4 and encryption are listed but refused with NotSupportedException. Archives whose directory cannot be read (encrypted headers) list as FULL.arc + metadata.ini (parse_status=partial). - Writing: FreeArc's layout with a storing or lzma solid block. For storing input in FreeArc's order it is byte-identical to "arc a -m0 -dmstoring". Oracle: FreeArc 0.67 (2014-03-15) Arc.exe and unarc.exe, run as black boxes. "unarc t/x" and "arc t/lb/x" extract our storing and lzma archives byte-identically, and we extract arc's -m0/-mlzma/-mlzma:lc4 archives byte-identically (FreeArcOracleTests). Nine archives Arc.exe wrote are checked in as fixtures, with provenance. Create/modify stay because FreeArc itself accepts our output. Sourcing: rung 2. FreeArc's sources are GPL-2.0 and the 0.67 binaries freeware, so neither can be absorbed. The sources (svn2github/freearc: Unarc/ArcStructure.h, ArhiveStructure.hs, ArhiveDirectory.hs, ByteStream.hs) were read to write docs/FREEARC-ON-DISK.md, every field was confirmed against hex dumps of Arc.exe's output, and the code was written from that document. Rung 1 does not apply: there is no permissively licensed FreeArc implementation. * FreeArc: regenerate the Archives API reference --- Compression.Tests/Compression.Tests.csproj | 4 + .../FreeArc/FreeArcOracleTests.cs | 168 +++++++ Compression.Tests/FreeArc/FreeArcTests.cs | 434 ++++++++++------- .../FreeArc/ReferenceVectors/README.md | 31 ++ .../FreeArc/ReferenceVectors/comment.arc | Bin 0 -> 448 bytes .../FreeArc/ReferenceVectors/dirs.arc | Bin 0 -> 326 bytes .../FreeArc/ReferenceVectors/encrypted.arc | Bin 0 -> 369 bytes .../FreeArc/ReferenceVectors/hdrenc.arc | Bin 0 -> 735 bytes .../FreeArc/ReferenceVectors/lzma-lc4.arc | Bin 0 -> 447 bytes .../FreeArc/ReferenceVectors/lzma.arc | Bin 0 -> 431 bytes .../FreeArc/ReferenceVectors/ppmd.arc | Bin 0 -> 386 bytes .../FreeArc/ReferenceVectors/storing.arc | Bin 0 -> 2287 bytes .../FreeArc/ReferenceVectors/tornado.arc | Bin 0 -> 524 bytes .../FileFormat.FreeArc/FreeArcEncoding.cs | 121 +++++ .../FileFormat.FreeArc/FreeArcEntry.cs | 32 +- .../FreeArcFormatDescriptor.cs | 105 +++-- .../FileFormat.FreeArc/FreeArcMethods.cs | 151 ++++++ .../FileFormat.FreeArc/FreeArcReader.cs | 440 ++++++++++-------- .../FileFormat.FreeArc/FreeArcWriter.cs | 222 ++++++--- Hawkynt.FileFormats.Archives/README.md | 2 +- Hawkynt.FileFormats.Archives/REFERENCE.md | 56 ++- docs/FREEARC-ON-DISK.md | 130 ++++++ docs/catalogue/GAPS.md | 6 +- docs/catalogue/reference-formats.csv | 2 +- 24 files changed, 1408 insertions(+), 496 deletions(-) create mode 100644 Compression.Tests/FreeArc/FreeArcOracleTests.cs create mode 100644 Compression.Tests/FreeArc/ReferenceVectors/README.md create mode 100644 Compression.Tests/FreeArc/ReferenceVectors/comment.arc create mode 100644 Compression.Tests/FreeArc/ReferenceVectors/dirs.arc create mode 100644 Compression.Tests/FreeArc/ReferenceVectors/encrypted.arc create mode 100644 Compression.Tests/FreeArc/ReferenceVectors/hdrenc.arc create mode 100644 Compression.Tests/FreeArc/ReferenceVectors/lzma-lc4.arc create mode 100644 Compression.Tests/FreeArc/ReferenceVectors/lzma.arc create mode 100644 Compression.Tests/FreeArc/ReferenceVectors/ppmd.arc create mode 100644 Compression.Tests/FreeArc/ReferenceVectors/storing.arc create mode 100644 Compression.Tests/FreeArc/ReferenceVectors/tornado.arc create mode 100644 Hawkynt.FileFormats.Archives/FileFormats/FileFormat.FreeArc/FreeArcEncoding.cs create mode 100644 Hawkynt.FileFormats.Archives/FileFormats/FileFormat.FreeArc/FreeArcMethods.cs create mode 100644 docs/FREEARC-ON-DISK.md diff --git a/Compression.Tests/Compression.Tests.csproj b/Compression.Tests/Compression.Tests.csproj index 67f6c0399..59dbe59f1 100644 --- a/Compression.Tests/Compression.Tests.csproj +++ b/Compression.Tests/Compression.Tests.csproj @@ -79,6 +79,10 @@ CscVectors.%(Filename)%(Extension) + + + FreeArcVectors.%(Filename)%(Extension) + CvfVectors.%(Filename)%(Extension) diff --git a/Compression.Tests/FreeArc/FreeArcOracleTests.cs b/Compression.Tests/FreeArc/FreeArcOracleTests.cs new file mode 100644 index 000000000..940c72ccb --- /dev/null +++ b/Compression.Tests/FreeArc/FreeArcOracleTests.cs @@ -0,0 +1,168 @@ +using System.Diagnostics; +using System.Text; +using FileFormat.FreeArc; + +namespace Compression.Tests.FreeArc; + +/// +/// Live round trips against FreeArc 0.67 itself: its unarc and arc must test and +/// extract what our writer makes byte-identically, and we must extract what arc makes. +/// +/// +/// The tools are located as $FREEARC_UNARC / $FREEARC_ARC (paths), else as +/// unarc.exe / Arc.exe on the Windows PATH. Both ship in the bin folder of +/// FreeArc 0.67 (2014-03-15), e.g. the self-extracting FreeArc-console-0.67-alpha-win32.exe +/// (run it with -x -y -d<dir>). FreeArc is freeware without published sources for +/// 0.67, so it is used only as a black box. Without the tools every test here is ignored. +/// +[TestFixture] +[Category("ExternalInterop")] +public sealed class FreeArcOracleTests { + private static readonly Lazy _unarc = new(() => Locate("FREEARC_UNARC", "unarc.exe")); + private static readonly Lazy _arc = new(() => Locate("FREEARC_ARC", "Arc.exe")); + private string _tmp = null!; + + [SetUp] + public void Setup() { + this._tmp = Path.Combine(Path.GetTempPath(), $"cwb_freearc_oracle_{Guid.NewGuid():N}"); + Directory.CreateDirectory(this._tmp); + } + + [TearDown] + public void Teardown() { + try { Directory.Delete(this._tmp, recursive: true); } catch { /* best effort */ } + } + + private static (string Name, byte[] Data)[] Files() { + var big = new byte[300_000]; + for (var i = 0; i < big.Length; ++i) + big[i] = (byte)((i * 2654435761u >> 13) % 7 == 0 ? i : i >> 4); + return [ + ("hello.txt", FreeArcTests.Hello), + ("sub/lines.txt", FreeArcTests.Lines()), + ("empty.bin", []), + ("dир/файл.txt", "abc"u8.ToArray()), + ("a/b/big.bin", big), + ]; + } + + [TestCase("storing")] + [TestCase("lzma")] + [TestCase("lzma:1mb:lc4:lp1:pb1")] + public void OurArchive_UnarcTestsAndExtractsByteIdentical(string method) { + var unarc = Require(_unarc, "unarc"); + var archive = this.WriteOurs(method); + + var test = Run(unarc, ["t", archive]); + Assert.That(test.ExitCode, Is.Zero, test.Output); + Assert.That(test.Output, Does.Contain("All OK").Or.Contain("Testing"), test.Output); + + var outDir = Path.Combine(this._tmp, "x"); + var extract = Run(unarc, ["x", "-o+", $"-dp{outDir}", archive]); + Assert.That(extract.ExitCode, Is.Zero, extract.Output); + this.AssertExtracted(outDir); + } + + [TestCase("storing")] + [TestCase("lzma")] + public void OurArchive_ArcListsTestsAndExtractsByteIdentical(string method) { + var arc = Require(_arc, "Arc"); + var archive = this.WriteOurs(method); + + var test = Run(arc, ["t", "-cfg-", archive]); + Assert.That(test.ExitCode, Is.Zero, test.Output); + var list = Run(arc, ["lb", "-cfg-", archive]); + Assert.That(list.ExitCode, Is.Zero, list.Output); + Assert.That(list.Output, Does.Contain("big.bin")); + + var outDir = Path.Combine(this._tmp, "x"); + var extract = Run(arc, ["x", "-cfg-", "-y", $"-dp{outDir}", archive]); + Assert.That(extract.ExitCode, Is.Zero, extract.Output); + this.AssertExtracted(outDir); + } + + [TestCase("-m0")] + [TestCase("-mlzma")] + [TestCase("-mlzma:lc4:lp1:pb1")] + [TestCase("-m5x")] + public void ArcArchive_WeExtractByteIdentical(string method) { + var arc = Require(_arc, "Arc"); + var src = Path.Combine(this._tmp, "src"); + foreach (var (name, data) in Files()) { + var path = Path.Combine(src, name.Replace('/', Path.DirectorySeparatorChar)); + Directory.CreateDirectory(Path.GetDirectoryName(path)!); + File.WriteAllBytes(path, data); + } + Directory.CreateDirectory(Path.Combine(src, "emptydir")); + + var archive = Path.Combine(this._tmp, "theirs.arc"); + var create = Run(arc, ["a", "-cfg-", "-r", method, archive, "*"], src); + Assert.That(create.ExitCode, Is.Zero, create.Output); + + using var r = new FreeArcReader(File.OpenRead(archive)); + if (r.Entries.Any(e => !e.IsDirectory && !FreeArcMethods.IsSupported(e.Method))) + Assert.Inconclusive($"{method} chose methods we do not decode: {string.Join(", ", r.Entries.Select(e => e.Method).Distinct())}"); + foreach (var (name, data) in Files()) + Assert.That(r.Extract(r.Entries.Single(e => e.Name == name)), Is.EqualTo(data), name); + Assert.That(r.Entries.Single(e => e.Name == "emptydir").IsDirectory, Is.True); + } + + private string WriteOurs(string method) { + var w = new FreeArcWriter(method); + w.AddDirectory("emptydir"); + w.AddDirectory("dир"); + foreach (var (name, data) in Files()) + w.AddFile(name, data); + var path = Path.Combine(this._tmp, "ours.arc"); + File.WriteAllBytes(path, w.Build()); + return path; + } + + private void AssertExtracted(string outDir) { + Assert.Multiple(() => { + foreach (var (name, data) in Files()) + Assert.That(File.ReadAllBytes(Path.Combine(outDir, name.Replace('/', Path.DirectorySeparatorChar))), Is.EqualTo(data), name); + Assert.That(Directory.Exists(Path.Combine(outDir, "emptydir")), Is.True); + }); + } + + private static string Require(Lazy tool, string name) { + if (tool.Value is null) + Assert.Ignore($"FreeArc's {name} not found: set FREEARC_{name.ToUpperInvariant()} or put it on PATH (see the class remarks)."); + return tool.Value!; + } + + private static string? Locate(string variable, string executable) { + var configured = Environment.GetEnvironmentVariable(variable); + if (!string.IsNullOrWhiteSpace(configured) && File.Exists(configured)) + return configured; + if (!OperatingSystem.IsWindows()) + return null; + foreach (var dir in (Environment.GetEnvironmentVariable("PATH") ?? "").Split(Path.PathSeparator)) { + var candidate = Path.Combine(dir.Trim(), executable); + if (File.Exists(candidate)) + return candidate; + } + return null; + } + + private static (int ExitCode, string Output) Run(string executable, string[] arguments, string? workingDirectory = null) { + var start = new ProcessStartInfo(executable) { + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + WorkingDirectory = workingDirectory ?? Path.GetDirectoryName(executable)!, + StandardOutputEncoding = Encoding.UTF8, + }; + foreach (var argument in arguments) + start.ArgumentList.Add(argument); + using var process = Process.Start(start)!; + var stdOut = process.StandardOutput.ReadToEndAsync(); + var stdErr = process.StandardError.ReadToEndAsync(); + if (!process.WaitForExit(120_000)) { + process.Kill(entireProcessTree: true); + return (-1, "timed out"); + } + return (process.ExitCode, stdOut.Result + stdErr.Result); + } +} diff --git a/Compression.Tests/FreeArc/FreeArcTests.cs b/Compression.Tests/FreeArc/FreeArcTests.cs index 20bb554fe..8f41bc2a1 100644 --- a/Compression.Tests/FreeArc/FreeArcTests.cs +++ b/Compression.Tests/FreeArc/FreeArcTests.cs @@ -1,242 +1,332 @@ +using System.Reflection; +using System.Text; +using Compression.Core.Checksums; +using Compression.Registry; using FileFormat.FreeArc; namespace Compression.Tests.FreeArc; +/// +/// FreeArc against archives FreeArc 0.67 wrote (see ReferenceVectors/README.md) and against +/// our own writer. The live oracle runs are in . +/// [TestFixture] public class FreeArcTests { + // The fixture inputs were touched to 2024-01-02 03:04:05 UTC, but FreeArc 0.67 (run on Windows + // while daylight saving was in effect) recorded them one hour later; the archives hold this value. + internal static readonly DateTime FixtureTime = new(2024, 1, 2, 4, 4, 5, DateTimeKind.Utc); - // ── Helper ──────────────────────────────────────────────────────────────── + internal static byte[] Hello => "Hello, FreeArc!\n"u8.ToArray(); - private static MemoryStream BuildArchive(Action populate) { - var w = new FreeArcWriter(); - populate(w); - return new MemoryStream(w.Build()); + internal static byte[] Lines() { + var sb = new StringBuilder(); + for (var i = 1; i <= 40; ++i) + sb.Append("line ").Append(i).Append(" the quick brown fox jumps over the lazy dog\n"); + return Encoding.ASCII.GetBytes(sb.ToString()); } - // ── Reader round-trip tests ─────────────────────────────────────────────── - - [Test, Category("RoundTrip")] - public void Read_SyntheticArchive_SingleFile() { - var data = "Hello FreeArc!"u8.ToArray(); - using var ms = BuildArchive(w => w.AddFile("hello.txt", data)); - using var r = new FreeArcReader(ms); - - Assert.That(r.Entries, Has.Count.EqualTo(1)); - Assert.That(r.Entries[0].Name, Is.EqualTo("hello.txt")); - Assert.That(r.Entries[0].Size, Is.EqualTo(data.Length)); - Assert.That(r.Entries[0].Method, Is.EqualTo("storing")); - Assert.That(r.Extract(r.Entries[0]), Is.EqualTo(data)); + internal static byte[] Vector(string name) { + using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream($"FreeArcVectors.{name}.arc") + ?? throw new InvalidOperationException($"Missing FreeArc reference vector {name}.arc"); + using var ms = new MemoryStream(); + stream.CopyTo(ms); + return ms.ToArray(); } - [Test, Category("RoundTrip")] - public void Read_SyntheticArchive_MultipleFiles() { - var file1 = "First file data"u8.ToArray(); - var file2 = "Second file data"u8.ToArray(); - var file3 = "Third file data"u8.ToArray(); - - using var ms = BuildArchive(w => { - w.AddFile("a.txt", file1); - w.AddFile("b.txt", file2); - w.AddFile("c.txt", file3); + private static FreeArcReader Open(byte[] archive) => new(new MemoryStream(archive)); + + private static FreeArcEntry Entry(FreeArcReader r, string name) => r.Entries.Single(e => e.Name == name); + + // ── Real archives: supported methods ─────────────────────────────────────── + + [TestCase("storing", "storing")] + [TestCase("lzma", "lzma:32kb:normal:32")] + [TestCase("lzma-lc4", "lzma:32kb:normal:32:pb1:lc4:lp1")] + [Category("RoundTrip")] + public void RealArchive_ThreeFiles_ExtractByteIdentical(string vector, string method) { + using var r = Open(Vector(vector)); + + Assert.That(r.Entries.Select(e => e.Name), Is.EquivalentTo(new[] { "hello.txt", "empty.bin", "sub/lines.txt" })); + Assert.Multiple(() => { + Assert.That(r.Extract(Entry(r, "hello.txt")), Is.EqualTo(Hello)); + Assert.That(r.Extract(Entry(r, "sub/lines.txt")), Is.EqualTo(Lines())); + Assert.That(r.Extract(Entry(r, "empty.bin")), Is.Empty); + Assert.That(r.Entries.Select(e => e.Method).Distinct(), Is.EqualTo(new[] { method })); + Assert.That(r.Entries.All(e => !e.IsDirectory)); + Assert.That(r.Entries.All(e => e.LastModified == FixtureTime)); + Assert.That(Entry(r, "hello.txt").Crc, Is.EqualTo(0x2B27C831u)); + Assert.That(r.Entries.Sum(e => e.CompressedSize), Is.LessThanOrEqualTo(Vector(vector).Length)); + Assert.That(r.SfxSize, Is.Zero); }); - using var r = new FreeArcReader(ms); - - Assert.That(r.Entries, Has.Count.EqualTo(3)); - Assert.That(r.Entries[0].Name, Is.EqualTo("a.txt")); - Assert.That(r.Entries[1].Name, Is.EqualTo("b.txt")); - Assert.That(r.Entries[2].Name, Is.EqualTo("c.txt")); - Assert.That(r.Extract(r.Entries[0]), Is.EqualTo(file1)); - Assert.That(r.Extract(r.Entries[1]), Is.EqualTo(file2)); - Assert.That(r.Extract(r.Entries[2]), Is.EqualTo(file3)); } [Test, Category("RoundTrip")] - public void Read_SyntheticArchive_EmptyFile() { - using var ms = BuildArchive(w => w.AddFile("empty.bin", [])); - using var r = new FreeArcReader(ms); + public void RealArchive_DirectoriesUnicodeAndSeveralSolidBlocks() { + using var r = Open(Vector("dirs")); + + Assert.Multiple(() => { + Assert.That(Entry(r, "emptydir").IsDirectory, Is.True); + Assert.That(Entry(r, "dир").IsDirectory, Is.True); + Assert.That(Entry(r, "dир/файл.txt").IsDirectory, Is.False); + Assert.That(r.Extract(Entry(r, "dир/файл.txt")), Is.EqualTo("abc"u8.ToArray())); + Assert.That(r.Extract(Entry(r, "hello.txt")), Is.EqualTo(Hello)); + Assert.That(r.Extract(Entry(r, "emptydir")), Is.Empty); + }); + } - Assert.That(r.Entries, Has.Count.EqualTo(1)); - Assert.That(r.Entries[0].Size, Is.EqualTo(0)); - Assert.That(r.Extract(r.Entries[0]), Is.Empty); + [Test] + public void RealArchive_Comment() { + using var r = Open(Vector("comment")); + Assert.That(r.Comment, Is.EqualTo("A FreeArc comment")); + Assert.That(r.Extract(Entry(r, "sub/lines.txt")), Is.EqualTo(Lines())); } - [Test, Category("RoundTrip")] - public void Read_SyntheticArchive_BinaryData() { - // Deterministic non-trivial binary payload — never Random.Shared.NextBytes. - var data = new byte[512]; - for (var i = 0; i < data.Length; i++) data[i] = (byte)(i * 7 + 13); + // ── Real archives: methods we refuse ─────────────────────────────────────── - using var ms = BuildArchive(w => w.AddFile("binary.bin", data)); - using var r = new FreeArcReader(ms); + [TestCase("ppmd", "ppmd:10:48mb")] + [TestCase("tornado", "rep:3kb:256+4x4:tor:3:3kb:h16kb")] + public void RealArchive_UnsupportedMethod_ListsButRefusesExtraction(string vector, string method) { + using var r = Open(Vector(vector)); - Assert.That(r.Extract(r.Entries[0]), Is.EqualTo(data)); + Assert.That(r.Entries.Select(e => e.Name), Is.EquivalentTo(new[] { "hello.txt", "sub/lines.txt" })); + Assert.That(r.Entries[0].Method, Is.EqualTo(method)); + Assert.That(() => r.Extract(Entry(r, "hello.txt")), Throws.TypeOf()); } - [Test, Category("RoundTrip")] - public void Read_SyntheticArchive_Utf8FileName() { - var data = "content"u8.ToArray(); - using var ms = BuildArchive(w => w.AddFile("dossier/fichier.txt", data)); - using var r = new FreeArcReader(ms); - - Assert.That(r.Entries[0].Name, Is.EqualTo("dossier/fichier.txt")); - Assert.That(r.Extract(r.Entries[0]), Is.EqualTo(data)); + [Test] + public void RealArchive_EncryptedData_ListsButRefusesExtraction() { + using var r = Open(Vector("encrypted")); + var entry = Entry(r, "hello.txt"); + Assert.That(entry.Method, Does.Contain("+aes-256/ctr:")); + Assert.That(() => r.Extract(entry), Throws.TypeOf().With.Message.Contains("encrypted")); } - [Test, Category("RoundTrip")] - public void Read_SyntheticArchive_NoFiles() { - using var ms = BuildArchive(_ => { }); - using var r = new FreeArcReader(ms); + [Test] + public void RealArchive_EncryptedHeaders_ReaderRefuses_DescriptorFallsBack() { + var archive = Vector("hdrenc"); + Assert.That(() => Open(archive), Throws.TypeOf()); - Assert.That(r.Entries, Is.Empty); + var listed = new FreeArcFormatDescriptor().List(new MemoryStream(archive), null); + Assert.That(listed.Select(e => e.Name), Is.EqualTo(new[] { "FULL.arc", "metadata.ini" })); + Assert.That(listed[0].OriginalSize, Is.EqualTo(archive.Length)); } - [Test, Category("RoundTrip")] - public void Read_SyntheticArchive_LargeFile() { - var data = new byte[65536]; - for (var i = 0; i < data.Length; i++) data[i] = (byte)(i & 0xFF); - - using var ms = BuildArchive(w => w.AddFile("large.dat", data)); - using var r = new FreeArcReader(ms); - - Assert.That(r.Entries[0].Size, Is.EqualTo(65536)); - Assert.That(r.Extract(r.Entries[0]), Is.EqualTo(data)); + [Test] + public void RealArchive_SfxPrefix_IsSkipped() { + var archive = Vector("lzma"); + var prefixed = new byte[1000 + archive.Length]; + archive.CopyTo(prefixed, 1000); + using var r = Open(prefixed); + Assert.That(r.SfxSize, Is.EqualTo(1000)); + Assert.That(r.Extract(Entry(r, "sub/lines.txt")), Is.EqualTo(Lines())); } - [Test, Category("RoundTrip")] - public void Read_SyntheticArchive_CompressedSizeMatchesStored() { - var data = "stored data"u8.ToArray(); - using var ms = BuildArchive(w => w.AddFile("f.txt", data)); - using var r = new FreeArcReader(ms); + // ── Corruption and boundaries ────────────────────────────────────────────── - // Writer stores without compression so CompressedSize == Size. - Assert.That(r.Entries[0].CompressedSize, Is.EqualTo(r.Entries[0].Size)); + [Test] + public void Corrupt_FooterDescriptorCrc_Throws() { + var archive = Vector("storing"); + archive[^1] ^= 0x55; + Assert.That(() => Open(archive), Throws.TypeOf()); } - // ── Descriptor property tests ───────────────────────────────────────────── - [Test] - public void Descriptor_Properties() { - var d = new FreeArcFormatDescriptor(); - Assert.That(d.Id, Is.EqualTo("FreeArc")); - Assert.That(d.DisplayName, Is.EqualTo("FreeArc")); - Assert.That(d.DefaultExtension, Is.EqualTo(".arc")); - Assert.That(d.Extensions, Contains.Item(".arc")); - Assert.That(d.Category, Is.EqualTo(Compression.Registry.FormatCategory.Archive)); - Assert.That(d.Family, Is.EqualTo(Compression.Registry.AlgorithmFamily.Archive)); - Assert.That(d.Description, Is.EqualTo("FreeArc compressed archive")); - Assert.That(d.TarCompressionFormatId, Is.Null); + public void Corrupt_Truncated_Throws() { + var archive = Vector("lzma"); + Assert.That(() => Open(archive[..^3]), Throws.TypeOf()); } [Test] - public void Descriptor_Id_IsDistinctFromArc() { - // Ensure we don't accidentally share the legacy ARC format id. - var d = new FreeArcFormatDescriptor(); - Assert.That(d.Id, Is.Not.EqualTo("Arc")); - Assert.That(d.Id, Is.Not.EqualTo("arc")); + public void Corrupt_DirectoryBlockByte_FailsCrc() { + var archive = Vector("storing"); + // Data block is 2087 bytes at 31; the stored directory follows it. + archive[31 + 2087 + 5] ^= 0x01; + Assert.That(() => Open(archive), Throws.TypeOf()); } [Test] - public void Descriptor_Capabilities() { - var d = new FreeArcFormatDescriptor(); - Assert.That(d.Capabilities.HasFlag(Compression.Registry.FormatCapabilities.CanList)); - Assert.That(d.Capabilities.HasFlag(Compression.Registry.FormatCapabilities.CanExtract)); - Assert.That(d.Capabilities.HasFlag(Compression.Registry.FormatCapabilities.CanCreate)); - Assert.That(d.Capabilities.HasFlag(Compression.Registry.FormatCapabilities.CanTest)); - Assert.That(d.Capabilities.HasFlag(Compression.Registry.FormatCapabilities.SupportsMultipleEntries)); + public void Corrupt_FileDataByte_FailsCrcOnExtract() { + var archive = Vector("storing"); + archive[31 + 2087 - 1] ^= 0x01; // last byte of sub/lines.txt + using var r = Open(archive); + Assert.That(r.Extract(Entry(r, "hello.txt")), Is.EqualTo(Hello)); + Assert.That(() => r.Extract(Entry(r, "sub/lines.txt")), Throws.TypeOf().With.Message.Contains("CRC")); } - [Test] - public void Descriptor_MagicSignature_Correct() { - var d = new FreeArcFormatDescriptor(); - var sig = d.MagicSignatures[0]; - Assert.That(sig.Bytes, Is.EqualTo(new byte[] { (byte)'A', (byte)'r', (byte)'C', 0x01 })); - Assert.That(sig.Confidence, Is.EqualTo(0.95).Within(0.001)); + [TestCase(0)] + [TestCase(2)] + [TestCase(15)] + public void TooSmall_Throws(int length) { + var data = new byte[length]; + "ArC\x01"u8[..Math.Min(4, length)].CopyTo(data); + Assert.That(() => Open(data), Throws.TypeOf()); } [Test] - public void Descriptor_Create_ViaInterface() { - var data = "create test"u8.ToArray(); - var tmpFile = Path.GetTempFileName(); - try { - File.WriteAllBytes(tmpFile, data); - var d = new FreeArcFormatDescriptor(); - using var ms = new MemoryStream(); - ((Compression.Registry.IArchiveCreatable)d).Create(ms, - [new Compression.Registry.ArchiveInputInfo(tmpFile, "test.txt", false)], - new Compression.Registry.FormatCreateOptions()); - ms.Position = 0; - using var r = new FreeArcReader(ms); - Assert.That(r.Entries, Has.Count.EqualTo(1)); - Assert.That(r.Entries[0].Name, Is.EqualTo("test.txt")); - Assert.That(r.Extract(r.Entries[0]), Is.EqualTo(data)); - } finally { File.Delete(tmpFile); } + public void SignatureOnly_NoFooter_Throws() { + var data = new byte[64]; + "ArC\x01"u8.CopyTo(data); + Assert.That(() => Open(data), Throws.TypeOf()); } [Test] - public void Descriptor_List_ViaInterface() { - var data = "descriptor list test"u8.ToArray(); - using var ms = BuildArchive(w => w.AddFile("test.txt", data)); - var d = new FreeArcFormatDescriptor(); - var entries = d.List(ms, null); + public void LegacyArcBytes_Throw() { + var data = new byte[64]; + data[0] = 0x1A; + data[1] = 0x08; + Assert.That(() => Open(data), Throws.TypeOf()); + } - Assert.That(entries, Has.Count.EqualTo(1)); - Assert.That(entries[0].Name, Is.EqualTo("test.txt")); - Assert.That(entries[0].OriginalSize, Is.EqualTo(data.Length)); + [TestCase(0UL, 1)] + [TestCase(127UL, 1)] + [TestCase(128UL, 2)] + [TestCase((1UL << 14) - 1, 2)] + [TestCase(1UL << 14, 3)] + [TestCase((1UL << 28) - 1, 4)] + [TestCase(1UL << 28, 5)] + [TestCase((1UL << 56) - 1, 8)] + [TestCase(1UL << 56, 9)] + [TestCase(ulong.MaxValue, 9)] + public void VarInt_RoundTrip_AtLengthBoundaries(ulong value, int expectedLength) { + using var ms = new MemoryStream(); + FreeArcEncoding.WriteVarInt(ms, value); + var bytes = ms.ToArray(); + var p = 0; + Assert.That(bytes, Has.Length.EqualTo(expectedLength)); + Assert.That(FreeArcEncoding.ReadVarInt(bytes, ref p), Is.EqualTo(value)); + Assert.That(p, Is.EqualTo(expectedLength)); } - // ── Magic distinction ────────────────────────────────────────────────────── + [Test] + public void VarInt_KnownEncodings_FromFreeArcOutput() { + // 10508 was written by FreeArc as 31 A4, 85 as AA, 8 as 10. + var p = 0; + Assert.That(FreeArcEncoding.ReadVarInt([0x31, 0xA4], ref p), Is.EqualTo(10508)); + p = 0; + Assert.That(FreeArcEncoding.ReadVarInt([0xAA], ref p), Is.EqualTo(85)); + p = 0; + Assert.That(FreeArcEncoding.ReadVarInt([0x10], ref p), Is.EqualTo(8)); + } [Test] - public void MagicDistinction_FreeArcVsLegacyArc() { - // Legacy ARC uses 0x1A as the first byte. - // FreeArc uses 'A' (0x41), 'r', 'C', 0x01. - var freeArcMagic = FreeArcReader.Magic; - Assert.That(freeArcMagic[0], Is.Not.EqualTo(0x1A), - "FreeArc magic must not collide with legacy ARC 0x1A marker."); - Assert.That(freeArcMagic[0], Is.EqualTo((byte)'A')); - Assert.That(freeArcMagic[1], Is.EqualTo((byte)'r')); - Assert.That(freeArcMagic[2], Is.EqualTo((byte)'C')); - Assert.That(freeArcMagic[3], Is.EqualTo(0x01)); + public void VarInt_Truncated_Throws() { + var p = 0; + Assert.That(() => FreeArcEncoding.ReadVarInt([0x01], ref p), Throws.TypeOf()); } - // ── Error handling ──────────────────────────────────────────────────────── + [TestCase("storing", true)] + [TestCase("lzma:1mb:normal:bt4:32", true)] + [TestCase("ppmd:10:48mb", false)] + [TestCase("delta+lzma:32kb", false)] + [TestCase("4x4:tor:3", false)] + [TestCase("lzma+aes-256/ctr:n1000", false)] + public void Methods_IsSupported(string method, bool supported) => + Assert.That(FreeArcMethods.IsSupported(method) && !FreeArcMethods.IsEncrypted(method), Is.EqualTo(supported)); - [Test] - public void BadMagic_Throws_InvalidDataException() { - var bad = new byte[] { 0x1A, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; - using var ms = new MemoryStream(bad); - Assert.Throws(() => _ = new FreeArcReader(ms)); + // ── Writer ───────────────────────────────────────────────────────────────── + + [Test, Category("RoundTrip")] + public void Writer_Storing_ReproducesFreeArcByteForByte() { + // FreeArc 0.67 wrote storing.arc with -m0 -dmstoring; given the same files in the same order + // and timestamps our writer must produce the identical archive. + var w = new FreeArcWriter(); + w.AddFile("empty.bin", [], FixtureTime); + w.AddFile("hello.txt", Hello, FixtureTime); + w.AddFile("sub/lines.txt", Lines(), FixtureTime); + Assert.That(w.Build(), Is.EqualTo(Vector("storing"))); + } + + [TestCase("storing")] + [TestCase("lzma")] + [TestCase("lzma:1mb")] + [TestCase("lzma:64kb:lc4:lp1:pb1")] + [Category("RoundTrip")] + public void Writer_RoundTrip(string method) { + var big = new byte[200_000]; + for (var i = 0; i < big.Length; ++i) + big[i] = (byte)(i * 31 % 251 ^ (i >> 9)); + + var w = new FreeArcWriter(method); + w.AddDirectory("emptydir", FixtureTime); + w.AddDirectory("dир", FixtureTime); + w.AddFile("dир/файл.txt", "abc"u8.ToArray(), FixtureTime); + w.AddFile("hello.txt", Hello, FixtureTime); + w.AddFile("a/b/empty.bin", [], FixtureTime); + w.AddFile("big.bin", big, FixtureTime); + + using var r = Open(w.Build()); + Assert.Multiple(() => { + Assert.That(r.Entries, Has.Count.EqualTo(6)); + Assert.That(Entry(r, "emptydir").IsDirectory, Is.True); + Assert.That(Entry(r, "dир").IsDirectory, Is.True); + Assert.That(r.Extract(Entry(r, "dир/файл.txt")), Is.EqualTo("abc"u8.ToArray())); + Assert.That(r.Extract(Entry(r, "hello.txt")), Is.EqualTo(Hello)); + Assert.That(r.Extract(Entry(r, "a/b/empty.bin")), Is.Empty); + Assert.That(r.Extract(Entry(r, "big.bin")), Is.EqualTo(big)); + Assert.That(Entry(r, "big.bin").Crc, Is.EqualTo(Crc32.Compute(big))); + Assert.That(r.Entries.All(e => e.LastModified == FixtureTime)); + }); } [Test] - public void TooSmall_Throws_InvalidDataException() { - var small = new byte[] { (byte)'A', (byte)'r' }; // only 2 bytes - using var ms = new MemoryStream(small); - Assert.Throws(() => _ = new FreeArcReader(ms)); + public void Writer_NoEntries_IsReadableAndEmpty() { + using var r = Open(new FreeArcWriter().Build()); + Assert.That(r.Entries, Is.Empty); } + [TestCase("ppmd")] + [TestCase("tor:3")] + [TestCase("delta+lzma")] + public void Writer_UnsupportedMethod_Throws(string method) => + Assert.That(() => new FreeArcWriter(method), Throws.TypeOf()); + + // ── Descriptor ───────────────────────────────────────────────────────────── + [Test] - public void EmptyStream_Throws_InvalidDataException() { - using var ms = new MemoryStream(); - Assert.Throws(() => _ = new FreeArcReader(ms)); + public void Descriptor_Capabilities_AndIdentity() { + var d = new FreeArcFormatDescriptor(); + Assert.Multiple(() => { + Assert.That(d.Id, Is.EqualTo("FreeArc")); + Assert.That(d.DefaultExtension, Is.EqualTo(".arc")); + Assert.That(d.MagicSignatures[0].Bytes, Is.EqualTo("ArC\x01"u8.ToArray())); + Assert.That(d.Capabilities.HasFlag(FormatCapabilities.CanCreate), Is.True); + Assert.That(d.Capabilities.HasFlag(FormatCapabilities.CanModify), Is.True); + Assert.That(d.Capabilities.HasFlag(FormatCapabilities.SupportsDirectories), Is.True); + }); } [Test] - public void CorrectMagicButTruncatedBody_ProducesEmptyEntries() { - // 4-byte valid magic + 4-byte flags but then nothing — the reader - // treats EOF on the block-type byte as end-of-archive (no entries). - var buf = new byte[8]; - buf[0] = (byte)'A'; buf[1] = (byte)'r'; buf[2] = (byte)'C'; buf[3] = 0x01; - using var ms = new MemoryStream(buf); - var r = new FreeArcReader(ms); - Assert.That(r.Entries, Has.Count.EqualTo(0)); + public void Descriptor_ListAndExtract_RealArchive() { + var d = new FreeArcFormatDescriptor(); + var listed = d.List(new MemoryStream(Vector("dirs")), null); + Assert.That(listed.Single(e => e.Name == "emptydir").IsDirectory, Is.True); + + var dir = Path.Combine(Path.GetTempPath(), $"cwb_freearc_{Guid.NewGuid():N}"); + try { + d.Extract(new MemoryStream(Vector("dirs")), dir, null, null); + Assert.That(File.ReadAllBytes(Path.Combine(dir, "dир", "файл.txt")), Is.EqualTo("abc"u8.ToArray())); + Assert.That(Directory.Exists(Path.Combine(dir, "emptydir")), Is.True); + } finally { + try { Directory.Delete(dir, recursive: true); } catch { /* best effort */ } + } } [Test] - public void WrongMagicByte4_Throws_InvalidDataException() { - // 'A','r','C' but 0x02 instead of 0x01 - var bad = new byte[] { (byte)'A', (byte)'r', (byte)'C', 0x02, 0, 0, 0, 0, 0 }; - using var ms = new MemoryStream(bad); - Assert.Throws(() => _ = new FreeArcReader(ms)); + public void Descriptor_Create_WritesRealLayout() { + var tmp = Path.GetTempFileName(); + try { + File.WriteAllBytes(tmp, Hello); + using var ms = new MemoryStream(); + ((IArchiveCreatable)new FreeArcFormatDescriptor()).Create(ms, + [new ArchiveInputInfo(tmp, "x/hello.txt", false), new ArchiveInputInfo("", "x", true)], + new FormatCreateOptions()); + ms.Position = 0; + using var r = new FreeArcReader(ms); + Assert.That(r.Extract(Entry(r, "x/hello.txt")), Is.EqualTo(Hello)); + Assert.That(Entry(r, "x").IsDirectory, Is.True); + Assert.That(ms.ToArray()[..4], Is.EqualTo("ArC\x01"u8.ToArray())); + } finally { + File.Delete(tmp); + } } } diff --git a/Compression.Tests/FreeArc/ReferenceVectors/README.md b/Compression.Tests/FreeArc/ReferenceVectors/README.md new file mode 100644 index 000000000..6d8f8736c --- /dev/null +++ b/Compression.Tests/FreeArc/ReferenceVectors/README.md @@ -0,0 +1,31 @@ +# FreeArc reference archives + +Every `.arc` file here was written by FreeArc 0.67 (2014-03-15) `Arc.exe`, taken from the `bin` +folder of `FreeArc-console-0.67-alpha-win32.exe` (Wayback Machine copy of +`freearc.org/download/testing/`), on Windows 11. FreeArc is GPL/freeware and is used only as a black +box; see `docs/FREEARC-ON-DISK.md`. + +Inputs (all files and directories touched to 2024-01-02 03:04:05 UTC; FreeArc, run on Windows during +daylight saving time, recorded them as 04:04:05 UTC, and that is what the archives hold): + +| Name | Content | +|------|---------| +| `hello.txt` | `Hello, FreeArc!\n` (16 bytes) | +| `empty.bin` | 0 bytes | +| `sub/lines.txt` | `line N the quick brown fox jumps over the lazy dog\n` for N = 1..40 (2071 bytes) | +| `dир/файл.txt` | `abc` | +| `emptydir/` | empty directory | + +Each archive was made with `Arc.exe a -i0 -cfg- `: + +| File | Switches | Files | What it covers | +|------|----------|-------|----------------| +| `storing.arc` | `-m0 -dmstoring` | hello, empty, lines | everything uncompressed; our writer reproduces it byte for byte | +| `lzma.arc` | `-mlzma` | hello, empty, lines | `lzma:32kb:normal:32` data, lzma-compressed directory and footer | +| `lzma-lc4.arc` | `-mlzma:lc4:lp1:pb1` | hello, empty, lines | non-default literal/position bits | +| `dirs.arc` | `-mlzma -s- -r` | emptydir, dир (recursive), hello | directory entries, UTF-8 names, three solid blocks | +| `comment.arc` | `lzma.arc`, then `Arc.exe c comment.arc "--archive-comment=A FreeArc comment"` | | archive comment in the footer | +| `ppmd.arc` | `-mppmd` | hello, lines | `ppmd:10:48mb` — refused | +| `tornado.arc` | `-m1` | hello, lines | `rep:…+4x4:tor:…` — refused | +| `encrypted.arc` | `-mlzma -pSecret` | hello | `lzma…+aes-256/ctr:…` data — refused | +| `hdrenc.arc` | `-mlzma -hpSecret` | hello | encrypted directory — listed as `FULL.arc` + `metadata.ini` | diff --git a/Compression.Tests/FreeArc/ReferenceVectors/comment.arc b/Compression.Tests/FreeArc/ReferenceVectors/comment.arc new file mode 100644 index 0000000000000000000000000000000000000000..e446f98922ad87bc169ccbc04b742f2515ad1d17 GIT binary patch literal 448 zcmZ=Pa%N;;U}JX#(oDrA`9+y|=?nq_{`(8s)48jTGN?#;&d3*IYWs6pdwad0U!Tae z+#)-R331ow`a8(JH^{m5e@}I3^Ns^EuCHY3P>9gz{wbFd_pinCh+%L=%S_=*F*j~U z+;?T;n^(59bFY>BHvtp2&H2nio2G=hKX@;o^)_7eM@;pr1w4%UQVEh_n_eG3e73xY zOW}v%gvUaE(l!*Du9&%a#mvjUy*oDTm@V$q!FcM1@05lM>E@5-M74fd`Ty@zhhzpu z#tU!%@|uQxo*J{Q{H(lHS+#oU?qk<_^6un|J#kcGFyE8-&F=rcz>*JJ8{WR%`*4-l z?1y_UJv(;6|h*^X@Jde{y8u7vAtQzm#P=_D_jcIL=c1|No!JISinXVaut? zO|&x1O|r_%FUn2Ku}Ug2u`)JdIJD>6(FPaUIECX3ObqiFxh6*LxjV7(#6jo#36<^- z8EU#7$8W4#W5zhqP$APQZ^89B4ik7*W^aEdx3NrXS$y8jP1*g9&tB%={$h5F(^vSD h?8QCt%M0(a#s5o}0^7@h!`{9nHczcoCKOxi004t(&DsC} literal 0 HcmV?d00001 diff --git a/Compression.Tests/FreeArc/ReferenceVectors/dirs.arc b/Compression.Tests/FreeArc/ReferenceVectors/dirs.arc new file mode 100644 index 0000000000000000000000000000000000000000..c2245e0a3ecdeaf7f71b9456c755b24c0873b08f GIT binary patch literal 326 zcmZ=Pa%N;;U}JX#(oDrA`9+y|=?nq_{`(8s)48jTGN?#;&d3*IYWs6pdwad0U!VBV zsr>){zTd#WU@)U&iC5qJ|KE}u7#NrtWe@KD@cZWz-%mxQ>_@l8b*wgwmb0Jp z7v(1ASS6L1SQ#5Jgfcy0U~uf~WSU#Xz{D_*k!xb)p1Tv9SZ)Lc`23sA9Is@)d}5r} zlbu!{?6-JWJKo=|ILFUs-+@~%rkz<661q&~aug>=tn%0RKO1TqK=yFpuqUtSOf84^ I!_=pS02rc=ApigX literal 0 HcmV?d00001 diff --git a/Compression.Tests/FreeArc/ReferenceVectors/encrypted.arc b/Compression.Tests/FreeArc/ReferenceVectors/encrypted.arc new file mode 100644 index 0000000000000000000000000000000000000000..1f86897aeb343231d07ac57016fa50589314ac57 GIT binary patch literal 369 zcmZ=Pa%N;;U}JX#(oDrA`9+y|=?nq_{`(8s)48jT-eYH!exQ1&gwarcq4qV~iniFv z3zhD>hA7)GFea|~lJ_c`W0LhNZ_ehI6MYY;BpERa&ON5Jdz#r>=Z7!8)_aIZ@2<8e zdhU8{yMKV?WxMkXK7mZ@pRMVu4;4CC+dOwe?|+3|S<~K6x**%I_Jb#v>WPyUyI5Rr z^l!}HQDXZ!=;f8%oRoX9rinWpw+NP1a9-i4e}5_Ixkk(@t0S@N+TUA!yQ^BiSbA2* z10|(MVXH~LYi{tVnK$p^mt`=PTmApf^mz>+f3f9M^QC BpGp7# literal 0 HcmV?d00001 diff --git a/Compression.Tests/FreeArc/ReferenceVectors/hdrenc.arc b/Compression.Tests/FreeArc/ReferenceVectors/hdrenc.arc new file mode 100644 index 0000000000000000000000000000000000000000..20150ff1968cd41e61ae083e91894b080c8d4130 GIT binary patch literal 735 zcmb7=*-KPW0LI4|m&}2L1T`{B?7=KL_uO+=iky6jaK#*P$-4L4d(GTuMo}9IMO-4( z!Ue_58ZtF2OCKCkCKcCELwy*xGA$P>!$>NkO#g(QzVGGx!S~Y{f*e#TXBXX0s*R>} zLsD9z%H2K8mQkGOmN#JcDd~DDr5D<^4@VuvD+BRqI{)+NP>nsIAU!ckznPrybwFTHn-f)dqd3 zK!-eHzeNUzMo%()_C`&NUYB)#%5UxBf%}RRA8t4NirAh?4urjQygX!c@)-&A{CwK7 zd(Al|FIC`)RFO|hH>3(Fyl6r^2?GS#cmh)NNo+FkX#@a(Hvry<8G;0mMJ&aL7(<|7 z03kr+PNR`Pa|qG`L<%4nR+1=M;#iL46q>{=QZQCH9zzTvpM+Qi0+NM5#ycY#ahL@h z0GNWT#4t!!^{89a&}_4*6gn+wfjHl8(FxVpg&u$sgUB@R+>zU-i>`@ zJqZzUx2%5tl^@sqTqauGL(9B828NfO1(vOBB283e#7y4wY+M7`d1TvK`Tc`kcbj)> z&y5aAo_sjmXs%x+Ci@#g%Ntft)#EKyQPrIJ%Cc$w=bw=wWmoE+|B}iQf+Q;}g9(bJ z0l^9wGLWNbSwfJKSegSO5=l;`fFLR|MR15>5CVXqupm+rB eiURNsj8G_A0F*!>g;Oo4t?HNI>#Xm=Fv~xsb1EwU literal 0 HcmV?d00001 diff --git a/Compression.Tests/FreeArc/ReferenceVectors/lzma-lc4.arc b/Compression.Tests/FreeArc/ReferenceVectors/lzma-lc4.arc new file mode 100644 index 0000000000000000000000000000000000000000..8ba6afc93479f1a4f4f8347eda2610f792c3649d GIT binary patch literal 447 zcmZ=Pa%N;;U}JX#(oDrA`9+y|=?nq_{`(8s)48jTGN?#;u@?MtYCEj|e4WWI34=u% z=WhQJVp%lh@HK13WB#Al?=(@nRsM6n#Gad}Uuw#-F8bT<^1hY-FUY#-RlAkTq+Q|_ z@7CEKRJxVL*EHnw)R=AMXXUNRs?|$(-{X>ae$VL)*YUQNIo>B0l}ww=7Mmin>O+4| z+pZa}B}-l|?6xXXOx&dL=9R6ks=4vM%No`XA80k2&gvB35mdEUiC!#iRzpTxH0TfbfIaRrdR))DrR(bhFxrsSeNhKy$#zqXcu4s6AFczp?TgAY{FprUI zV&tB?6B|Fi(z$0>w@|?RVw}X;P0PwnV}mt0xx>D0ol?ZasP{){`OB1hMVzJ&Hz#-= dU3dG`|NoD7bbzhlz+p{JlgF%CZDysCA^-sV!hiq( literal 0 HcmV?d00001 diff --git a/Compression.Tests/FreeArc/ReferenceVectors/lzma.arc b/Compression.Tests/FreeArc/ReferenceVectors/lzma.arc new file mode 100644 index 0000000000000000000000000000000000000000..bf6b31aa79470d43c1f93cc0dcb6242080d69da0 GIT binary patch literal 431 zcmZ=Pa%N;;U}JX#(oDrA`9+y|=?nq_{`(8s)48jTGN?#;&d3*IYWs6pdwad0U!Tae z+#)-R331ow`a8(JH^{m5e@}I3^Ns^EuCHY3P>9gz{wbFd_pinCh+%L=%S_=*F*j~U z+;?T;n^(59bFY>BHvtp2&H2nio2G=hKX@;o^)_7eM@;pr1w4%UQVEh_n_eG3e73xY zOW}v%gvUaE(l!*Du9&%a#mvjUy*oDTm@V$q!FcM1@05lM>E@5-M74fd`Ty@zhhzpu z#tU!%@|uQxo*J{Q{H(lHS+#oU?qk<_^6un|J#kcGFyE8-&F=rcz>*JJ8{WR%`*4-l z?1y_UJv(;6|h*^X@Jde{y8u7vAtQzm#P=_D_jcIL=c1|No!JISinXVaut? zO|&x1O|r_%FUn2Ku}Ug2u`)JdIJD>6(FPaUIECX3ObqiFxh6*LxjV7(#6jo#36<^- z8EU#7$8W4#W5zhqP$APQZ^89B4ik7*W^aEdx3NrXS$y8jO*!Rj_x%5VgYN*?8V(%R P?*OOE`FcUK%72ektAKZfe0^u))V6TFB!@_g;EBr~ zGA=yb6sU}SXo!W;jD_u_|DRgG2GdW-WLx)*N0zbnq= z4)+|Xj)NC29tl+1u!6UxFK)Z^Tc5Re9&+A(7A>wiqv_P+mI+4t9>*^dlmEZ!4Fdzn zw`@68xrtVWxk*-e`9-;jIaWy}CRWBq3|-UKh*(z6;5he+fr()rBiF>pJ$ENI{we&r zsqd5Yk$u-p5?V6~YwB8ZD-`5kpDLQsaZYjX#$B6MJ^AbQB>3{&mbz_TZU6rtR|x>y T!hyqv-tq)@m#G&FV=+M+NjZJfD9%|Y*;Na*cbaSe! zyOW|XKyeg&BbtU}zh59Ekoz77`3!$6JXv9^wrc%9D^aXN*LPUGe(@6AIvcasGv#@D zk4{6Stgzo$_gr5Qr+A=foVfiV*`dDi=}M<`os5Er^hAZr&t7jfrAzJPi7-#3dD1dZ z+UCiDd2(o;9GNG_<_X^i8sPtP000LBaDV^@3~&Ge2NZCC0S6oiz<~f92*7~=90aEv|XPKvLT`wr!mXMhiLi6ScOrS+IRP=xLJPj_nqD0<7djUk)+&rvCvx&leeF* i=W02od~D}~O1bDeUzP2X#cRc(I-TzC=6U0(H~s@kyCMVt literal 0 HcmV?d00001 diff --git a/Compression.Tests/FreeArc/ReferenceVectors/tornado.arc b/Compression.Tests/FreeArc/ReferenceVectors/tornado.arc new file mode 100644 index 0000000000000000000000000000000000000000..e48877fb7bd5fc486dcf8a87079dc50338b86775 GIT binary patch literal 524 zcmZ=Pa%N;;U}JX#(oDrA`9+y|=?nq_{`(8s)48jTG5~=!2Lr<|AkECe(7?cO>&3!e zv$WTXe77cS(z~@{>rq$pE4y;r49#{vUlf&nxAj1G05c>de3=ro4aZN(p zyqtT}Y8QBz>HA|r_WowL(Kdzt)uF4y)<$j3x|+4P*X-7cv~U0a|NTF=j)9TUQGHv% z=OEv;Ulw*7ecH3$L2d2-nZJJP554j_e`#9Re%t^5-y~iDTf>3Fnw+LfzRSBElB=Tt DMHS_@ literal 0 HcmV?d00001 diff --git a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.FreeArc/FreeArcEncoding.cs b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.FreeArc/FreeArcEncoding.cs new file mode 100644 index 000000000..7296547f7 --- /dev/null +++ b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.FreeArc/FreeArcEncoding.cs @@ -0,0 +1,121 @@ +using System.Buffers.Binary; +using System.Numerics; +using System.Text; + +namespace FileFormat.FreeArc; + +/// +/// The primitive encodings of the FreeArc container: the variable-length integer, the C string +/// and the block-type numbers. See docs/FREEARC-ON-DISK.md. +/// +internal static class FreeArcEncoding { + /// "ArC\x01" read as a little-endian 32-bit value; opens the archive and every block descriptor. + public const uint Signature = 0x01437241; + + /// The version word FreeArc 0.67 writes after the signature in the header block. + public const uint ArchiveVersion = 0x07060000; + + /// How far from the end of the archive the footer descriptor may start. + public const int FooterScanWindow = 4096; + + public const int BlockHeader = 1; + public const int BlockData = 2; + public const int BlockDirectory = 3; + public const int BlockFooter = 4; + public const int BlockRecovery = 5; + + /// Reads one variable-length integer: the count of low one-bits in the first byte tells how many more bytes follow. + public static ulong ReadVarInt(ReadOnlySpan buffer, ref int position) { + if (position >= buffer.Length) + throw new InvalidDataException("FreeArc: integer runs past the end of the block."); + + var first = buffer[position]; + var extra = BitOperations.TrailingZeroCount(~(uint)first); + if (extra >= 8) { + // A first byte of all ones announces a plain 64-bit value in the next eight bytes. + if (position + 9 > buffer.Length) + throw new InvalidDataException("FreeArc: integer runs past the end of the block."); + var wide = BinaryPrimitives.ReadUInt64LittleEndian(buffer.Slice(position + 1, 8)); + position += 9; + return wide; + } + + var length = extra + 1; + if (position + length > buffer.Length) + throw new InvalidDataException("FreeArc: integer runs past the end of the block."); + + ulong value = 0; + for (var i = length - 1; i >= 0; --i) + value = (value << 8) | buffer[position + i]; + position += length; + return value >> length; + } + + /// Reads a variable-length integer that must fit a non-negative . + public static int ReadCount(ReadOnlySpan buffer, ref int position) { + var value = ReadVarInt(buffer, ref position); + if (value > int.MaxValue) + throw new InvalidDataException("FreeArc: count out of range."); + return (int)value; + } + + /// Reads a variable-length integer that must fit a non-negative . + public static long ReadSize(ReadOnlySpan buffer, ref int position) { + var value = ReadVarInt(buffer, ref position); + if (value > long.MaxValue) + throw new InvalidDataException("FreeArc: size out of range."); + return (long)value; + } + + /// Writes one variable-length integer in the shortest form. + public static void WriteVarInt(Stream output, ulong value) { + Span buffer = stackalloc byte[9]; + for (var length = 1; length <= 8; ++length) { + if (value >= 1UL << (7 * length)) + continue; + + var encoded = (value << length) | ((1UL << (length - 1)) - 1); + for (var i = 0; i < length; ++i) + buffer[i] = (byte)(encoded >> (8 * i)); + output.Write(buffer[..length]); + return; + } + + buffer[0] = 0xFF; + BinaryPrimitives.WriteUInt64LittleEndian(buffer[1..], value); + output.Write(buffer); + } + + /// Reads a NUL-terminated UTF-8 string. + public static string ReadString(ReadOnlySpan buffer, ref int position) { + var rest = buffer[Math.Min(position, buffer.Length)..]; + var end = rest.IndexOf((byte)0); + if (end < 0) + throw new InvalidDataException("FreeArc: unterminated string."); + var text = Encoding.UTF8.GetString(rest[..end]); + position += end + 1; + return text; + } + + /// Writes a NUL-terminated UTF-8 string. + public static void WriteString(Stream output, string text) { + output.Write(Encoding.UTF8.GetBytes(text)); + output.WriteByte(0); + } + + /// Reads a little-endian 32-bit value. + public static uint ReadUInt32(ReadOnlySpan buffer, ref int position) { + if (position + 4 > buffer.Length) + throw new InvalidDataException("FreeArc: field runs past the end of the block."); + var value = BinaryPrimitives.ReadUInt32LittleEndian(buffer.Slice(position, 4)); + position += 4; + return value; + } + + /// Writes a little-endian 32-bit value. + public static void WriteUInt32(Stream output, uint value) { + Span buffer = stackalloc byte[4]; + BinaryPrimitives.WriteUInt32LittleEndian(buffer, value); + output.Write(buffer); + } +} diff --git a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.FreeArc/FreeArcEntry.cs b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.FreeArc/FreeArcEntry.cs index b7103a984..3abc3e7df 100644 --- a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.FreeArc/FreeArcEntry.cs +++ b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.FreeArc/FreeArcEntry.cs @@ -1,19 +1,37 @@ namespace FileFormat.FreeArc; -/// Represents a single file entry within a FreeArc archive. +/// Represents a single file or directory recorded in a FreeArc directory block. public sealed class FreeArcEntry { - /// The file name as stored in the archive directory block. + /// The full stored path, with / as separator. public string Name { get; init; } = ""; /// The uncompressed size of the file in bytes. public long Size { get; init; } - /// The compressed size of the file data in bytes. + /// + /// The compressed size of the solid block that holds this file, reported on the block's first + /// file only (FreeArc does not record per-file compressed sizes); zero on the others. + /// public long CompressedSize { get; init; } - /// The compression method string (e.g. "storing", "lzma", "freearc"). - public string Method { get; init; } = "storing"; + /// The method string of the solid block holding this file, e.g. lzma:32kb:normal:32. + public string Method { get; init; } = FreeArcMethods.Storing; - /// The byte offset within the data payload where this file's data begins. - internal long DataOffset { get; init; } + /// Whether this entry is a directory. + public bool IsDirectory { get; init; } + + /// The modification time (stored as 32-bit Unix seconds), in UTC. + public DateTime? LastModified { get; init; } + + /// The CRC-32 of the uncompressed file contents. + public uint Crc { get; init; } + + /// The solid data block holding this file. + internal FreeArcDataBlock Block { get; init; } = null!; + + /// The byte offset of this file inside the decoded solid block. + internal long OffsetInBlock { get; init; } } + +/// A solid data block: one compressed stream holding the concatenated contents of consecutive files. +internal sealed record FreeArcDataBlock(string Compressor, long Position, long CompressedSize, long OriginalSize); diff --git a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.FreeArc/FreeArcFormatDescriptor.cs b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.FreeArc/FreeArcFormatDescriptor.cs index 2f870b213..80712a35e 100644 --- a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.FreeArc/FreeArcFormatDescriptor.cs +++ b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.FreeArc/FreeArcFormatDescriptor.cs @@ -1,19 +1,26 @@ #pragma warning disable CS1591 +using System.Text; using Compression.Registry; using static Compression.Registry.FormatHelpers; namespace FileFormat.FreeArc; /// -/// Format descriptor for FreeArc compressed archives (.arc). +/// Format descriptor for FreeArc archives (.arc, signature ArC\x01). /// /// References: /// +/// docs/FREEARC-ON-DISK.md — the layout this implementation follows, derived from FreeArc's sources and checked against FreeArc 0.67's output /// https://github.com/Bulat-Ziganshin/FA — FreeArc'Next by FreeArc's author, Bulat Ziganshin (the original freearc.org site is defunct) /// https://en.wikipedia.org/wiki/FreeArc — Wikipedia overview -/// No formal specification — the archive layout is defined by the original FreeArc sources /// /// +/// +/// Reads real FreeArc archives; extracts solid blocks compressed with storing or lzma +/// and refuses every other method (tornado, ppmd, lzp, grzip, rep, delta, exe, ...) and encryption. +/// Writes storing/lzma archives that FreeArc 0.67's unarc and arc +/// test and extract byte-identically. +/// public sealed class FreeArcFormatDescriptor : IFormatDescriptor, IArchiveFormatOperations, IArchiveCreatable, IArchiveModifiable, IArchiveDefragmentable, IArchiveLayoutMap { /// Rebuild-based defrag: extracts then re-creates the FreeArc archive in listing order. @@ -25,10 +32,10 @@ public void Defragment(Stream archive, DefragOptions options) { DefragRebuilder.Rebuild(archive, options, readEntries: stream => { var r = new FreeArcReader(stream, leaveOpen: true); - return r.Entries.Select(e => (e.Name, r.Extract(e))); + return r.Entries.Where(e => !e.IsDirectory).Select(e => (e.Name, r.Extract(e))).ToList(); }, buildImage: files => { - var w = new FreeArcWriter(); + var w = new FreeArcWriter("lzma"); foreach (var (n, d) in files) w.AddFile(n, d); return w.Build(); }); @@ -37,11 +44,10 @@ public void Defragment(Stream archive, DefragOptions options) { /// public IEnumerable EnumerateLayout(Stream archive) { archive.Position = 0; - var r = new FreeArcReader(archive); - foreach (var e in r.Entries) { + var r = new FreeArcReader(archive, leaveOpen: true); + foreach (var e in r.Entries) if (e.CompressedSize > 0) - yield return new DefragBlockInfo(e.DataOffset, e.CompressedSize, DefragBlockKind.Used, FileName: e.Name); - } + yield return new DefragBlockInfo(e.Block.Position, e.CompressedSize, DefragBlockKind.Used, FileName: e.Name); } /// @@ -53,17 +59,13 @@ public IEnumerable EnumerateLayout(Stream archive) { /// public FormatCategory Category => FormatCategory.Archive; + // R/W: Add/Replace/Remove go through the verified extract -> edit -> re-create rebuild + // (default IArchiveModifiable). The writer's output is accepted by FreeArc 0.67 itself. /// - // R/W: a mutable archive. Add/Replace/Remove go through the verified extract -> - // edit -> re-create rebuild (default IArchiveModifiable); relayouting the container - // on edit is honest R/W. See FormatCapabilities.cs (WORM vs R/W). - /// - /// Gets the capabilities. - /// public FormatCapabilities Capabilities => FormatCapabilities.CanList | FormatCapabilities.CanExtract | FormatCapabilities.CanCreate | - FormatCapabilities.CanModify | - FormatCapabilities.CanTest | FormatCapabilities.SupportsMultipleEntries; + FormatCapabilities.CanModify | FormatCapabilities.CanTest | + FormatCapabilities.SupportsMultipleEntries | FormatCapabilities.SupportsDirectories; /// public string DefaultExtension => ".arc"; @@ -75,17 +77,12 @@ public IEnumerable EnumerateLayout(Stream archive) { public IReadOnlyList CompoundExtensions => []; /// - // "ArC\x01" — the four-byte signature that opens every FreeArc archive. - // Confidence is set high (0.95) because the combination of 'r', 'C' and 0x01 - // makes accidental collisions with the legacy ARC format (which uses 0x1A) extremely unlikely. - /// - /// Gets the magic signatures. - /// + // "ArC\x01" opens every FreeArc archive; the legacy SEA ARC format starts with 0x1A instead. public IReadOnlyList MagicSignatures => [new([(byte)'A', (byte)'r', (byte)'C', 0x01], Confidence: 0.95)]; /// - public IReadOnlyList Methods => [new("freearc", "FreeArc")]; + public IReadOnlyList Methods => [new("lzma", "LZMA"), new(FreeArcMethods.Storing, "Storing")]; /// public string? TarCompressionFormatId => null; @@ -94,30 +91,66 @@ public IEnumerable EnumerateLayout(Stream archive) { public AlgorithmFamily Family => AlgorithmFamily.Archive; /// - public string Description => "FreeArc compressed archive"; + public string Description => "FreeArc archive"; /// public List List(Stream stream, string? password) { - using var r = new FreeArcReader(stream, leaveOpen: true); - return r.Entries.Select((e, i) => - new ArchiveEntryInfo(i, e.Name, e.Size, e.CompressedSize, e.Method, false, false, null)) - .ToList(); + try { + using var r = new FreeArcReader(stream, leaveOpen: true); + return r.Entries.Select((e, i) => new ArchiveEntryInfo(i, e.Name, e.Size, e.CompressedSize, e.Method, + e.IsDirectory, FreeArcMethods.IsEncrypted(e.Method), e.LastModified)).ToList(); + } catch (Exception ex) when (ex is NotSupportedException or InvalidDataException) { + // Encrypted headers or a directory compressed with a method we lack: expose the raw archive. + return [ + new(0, "FULL.arc", stream.Length, stream.Length, FreeArcMethods.Storing, false, false, null), + new(1, "metadata.ini", Metadata(ex).Length, Metadata(ex).Length, FreeArcMethods.Storing, false, false, null), + ]; + } } /// public void Extract(Stream stream, string outputDir, string? password, string[]? files) { - using var r = new FreeArcReader(stream, leaveOpen: true); - foreach (var e in r.Entries) { - if (files != null && !MatchesFilter(e.Name, files)) continue; - WriteFile(outputDir, e.Name, r.Extract(e)); + FreeArcReader r; + try { + r = new FreeArcReader(stream, leaveOpen: true); + } catch (Exception ex) when (ex is NotSupportedException or InvalidDataException) { + stream.Position = 0; + using var raw = new MemoryStream(); + stream.CopyTo(raw); + if (files == null || MatchesFilter("FULL.arc", files)) WriteFile(outputDir, "FULL.arc", raw.ToArray()); + if (files == null || MatchesFilter("metadata.ini", files)) WriteFile(outputDir, "metadata.ini", Metadata(ex)); + return; + } + + using (r) { + foreach (var e in r.Entries) { + if (files != null && !MatchesFilter(e.Name, files)) continue; + if (e.IsDirectory) + Directory.CreateDirectory(Path.Combine(outputDir, e.Name.Replace('/', Path.DirectorySeparatorChar))); + else + WriteFile(outputDir, e.Name, r.Extract(e)); + } } } /// public void Create(Stream output, IReadOnlyList inputs, FormatCreateOptions options) { - var w = new FreeArcWriter(); - foreach (var (name, data) in FormatHelpers.FilesOnly(inputs)) - w.AddFile(name, data); - output.Write(w.Build()); + var method = options.MethodName?.Trim().ToLowerInvariant() switch { + null or "" or "lzma" => options.DictSize > 0 ? $"lzma:{Math.Max(4096, options.DictSize)}b" : "lzma", + var m => m, + }; + var w = new FreeArcWriter(method); + foreach (var input in inputs) { + var modified = input.Metadata?.LastWriteTimeUtc?.UtcDateTime + ?? (input.InMemoryContent == null && File.Exists(input.FullPath) ? File.GetLastWriteTimeUtc(input.FullPath) : (DateTime?)null); + if (input.IsDirectory) + w.AddDirectory(input.ArchiveName, modified); + else + w.AddFile(input.ArchiveName, input.ReadContent(), modified); + } + w.WriteTo(output); } + + private static byte[] Metadata(Exception ex) => + Encoding.UTF8.GetBytes($"format=FreeArc\nparse_status=partial\nreason={ex.Message.ReplaceLineEndings(" ")}\n"); } diff --git a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.FreeArc/FreeArcMethods.cs b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.FreeArc/FreeArcMethods.cs new file mode 100644 index 000000000..e005149b3 --- /dev/null +++ b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.FreeArc/FreeArcMethods.cs @@ -0,0 +1,151 @@ +using System.Globalization; +using Compression.Core.Dictionary.Lzma; + +namespace FileFormat.FreeArc; + +/// +/// FreeArc compression method strings: name:param:param... stages joined by +, applied +/// left to right when compressing and therefore undone right to left when extracting. +/// +/// +/// Only the stages our own codecs reproduce exactly are decoded: storing and lzma +/// (a raw LZMA1 stream with no properties header; dictionary and lc/lp/pb come from the method +/// string). Everything else — tornado, lzp, ppmd, grzip, rep, dict, delta, exe, mm, external +/// compressors and encryption — is refused with . +/// +public static class FreeArcMethods { + /// The method name of an uncompressed stage. + public const string Storing = "storing"; + + private static readonly string[] EncryptionNames = ["aes", "blowfish", "serpent", "twofish"]; + + /// Splits a method string into its stages. + public static string[] Stages(string compressor) => compressor.Split('+'); + + /// Gets whether every stage of can be decoded. + public static bool IsSupported(string compressor) => + Stages(compressor).All(stage => StageName(stage) is Storing or "lzma"); + + /// Gets whether contains an encryption stage. + public static bool IsEncrypted(string compressor) => + Stages(compressor).Any(stage => EncryptionNames.Any(e => StageName(stage).StartsWith(e, StringComparison.OrdinalIgnoreCase))); + + /// Decodes produced by into exactly bytes. + /// A stage is encrypted or uses a codec this library does not implement. + /// The stream does not decode to the expected size. + public static byte[] Decode(string compressor, byte[] input, long outputSize) { + if (IsEncrypted(compressor)) + throw new NotSupportedException($"FreeArc: encrypted data (method \"{compressor}\") is not supported."); + + var stages = Stages(compressor); + foreach (var stage in stages) + if (StageName(stage) is not (Storing or "lzma")) + throw new NotSupportedException($"FreeArc: compression method \"{StageName(stage)}\" (in \"{compressor}\") is not supported."); + + var data = input; + for (var i = stages.Length - 1; i >= 0; --i) + data = DecodeStage(stages[i], data, i == 0 ? outputSize : -1); + + if (data.LongLength != outputSize) + throw new InvalidDataException($"FreeArc: \"{compressor}\" decoded to {data.LongLength} bytes, expected {outputSize}."); + return data; + } + + /// Encodes with a single supported stage. + public static byte[] Encode(string compressor, ReadOnlySpan input) { + var stages = Stages(compressor); + if (stages.Length != 1) + throw new NotSupportedException($"FreeArc: writing chained method \"{compressor}\" is not supported."); + + switch (StageName(stages[0])) { + case Storing: + return input.ToArray(); + case "lzma": { + var p = LzmaParameters.Parse(stages[0], input.Length); + var encoder = new LzmaEncoder((int)Math.Clamp(p.Dictionary, 4096, 1L << 30), p.Lc, p.Lp, p.Pb); + using var output = new MemoryStream(); + encoder.Encode(output, input, writeEndMarker: true); + return output.ToArray(); + } + default: + throw new NotSupportedException($"FreeArc: writing method \"{compressor}\" is not supported."); + } + } + + /// + /// Gives a bare lzma stage an explicit dictionary (the next power of two covering the + /// input, 64 KiB to 64 MiB) so FreeArc's decoder does not reserve its much larger default. + /// + public static string WithDictionary(string compressor, long inputLength) { + if (StageName(compressor) != "lzma" || compressor.Split(':').Skip(1).Any(t => LzmaParameters.IsSize(t.Trim().ToLowerInvariant()))) + return compressor; + var dictionary = 1L << 16; + while (dictionary < inputLength && dictionary < 1L << 26) + dictionary <<= 1; + var size = dictionary >= 1L << 20 ? $"{dictionary >> 20}mb" : $"{dictionary >> 10}kb"; + var colon = compressor.IndexOf(':'); + return colon < 0 ? $"lzma:{size}" : $"lzma:{size}{compressor[colon..]}"; + } + + private static string StageName(string stage) { + var colon = stage.IndexOf(':'); + return (colon < 0 ? stage : stage[..colon]).Trim().ToLowerInvariant(); + } + + private static byte[] DecodeStage(string stage, byte[] input, long outputSize) { + if (StageName(stage) == Storing) + return input; + + var p = LzmaParameters.Parse(stage, outputSize); + using var source = new MemoryStream(input, writable: false); + var dictionary = (int)Math.Clamp(Math.Max(p.Dictionary, outputSize), 4096, int.MaxValue); + return new LzmaDecoder(source, p.Lc, p.Lp, p.Pb, dictionary, outputSize).Decode(); + } + + /// lzma stage parameters: tokens with a size suffix are the dictionary, lcN/lpN/pbN the literal and position bits; mode, match finder, fast bytes and mcN only steer the encoder. + private readonly record struct LzmaParameters(long Dictionary, int Lc, int Lp, int Pb) { + public static LzmaParameters Parse(string stage, long fallbackDictionary) { + long dictionary = -1; + int lc = 3, lp = 0, pb = 2; + foreach (var raw in stage.Split(':').Skip(1)) { + var token = raw.Trim().ToLowerInvariant(); + if (TryBits(token, "lc", 8, ref lc) || TryBits(token, "lp", 4, ref lp) || TryBits(token, "pb", 4, ref pb)) + continue; + if (TrySize(token, out var size)) + dictionary = size; + } + + return new(dictionary > 0 ? dictionary : Math.Max(fallbackDictionary, 1 << 16), lc, lp, pb); + } + + private static bool TryBits(string token, string prefix, int max, ref int value) { + if (!token.StartsWith(prefix, StringComparison.Ordinal) + || !int.TryParse(token.AsSpan(prefix.Length), NumberStyles.None, CultureInfo.InvariantCulture, out var parsed)) + return false; + if (parsed > max) + throw new InvalidDataException($"FreeArc: lzma parameter \"{token}\" out of range."); + value = parsed; + return true; + } + + public static bool IsSize(string token) => TrySize(token, out _); + + private static bool TrySize(string token, out long size) { + size = 0; + var (digits, multiplier) = token switch { + _ when token.EndsWith("gb", StringComparison.Ordinal) => (token[..^2], 1L << 30), + _ when token.EndsWith("mb", StringComparison.Ordinal) => (token[..^2], 1L << 20), + _ when token.EndsWith("kb", StringComparison.Ordinal) => (token[..^2], 1L << 10), + _ when token.EndsWith('g') => (token[..^1], 1L << 30), + _ when token.EndsWith('m') => (token[..^1], 1L << 20), + _ when token.EndsWith('k') => (token[..^1], 1L << 10), + _ when token.EndsWith('b') => (token[..^1], 1L), + _ => ("", 0L), + }; + if (multiplier == 0 || !long.TryParse(digits, NumberStyles.None, CultureInfo.InvariantCulture, out var value)) + return false; + size = value * multiplier; + return true; + } + } +} diff --git a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.FreeArc/FreeArcReader.cs b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.FreeArc/FreeArcReader.cs index 97cfa1b7f..413d63f92 100644 --- a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.FreeArc/FreeArcReader.cs +++ b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.FreeArc/FreeArcReader.cs @@ -1,225 +1,281 @@ -#pragma warning disable CS1591 -using System.Buffers.Binary; -using System.Text; +using Compression.Core.Checksums; +using static FileFormat.FreeArc.FreeArcEncoding; namespace FileFormat.FreeArc; /// -/// Reads FreeArc archives (.arc). -/// -/// This implementation parses a well-defined binary subset of the FreeArc container -/// format that is produced by and understood by this reader. -/// -/// -/// Binary layout (all integers are little-endian): -/// -/// 4 bytes — magic "ArC\x01" -/// 4 bytes — uint32 archive flags (reserved, currently 0) -/// One or more blocks, each preceded by a 1-byte block type: -/// -/// 0x01 = directory block -/// 0x02 = data block -/// 0x00 = end-of-archive marker -/// -/// -/// -/// -/// -/// Directory block payload: -/// -/// uint32 — number of file entries -/// Per entry: uint16 nameLen + UTF-8 name + uint64 size + uint64 compressedSize + uint64 dataOffset + uint16 methodLen + ASCII method -/// -/// -/// -/// Data block payload: -/// -/// uint32 — payload length in bytes -/// payload bytes (concatenated raw file data) -/// -/// +/// Reads real FreeArc archives (.arc, signature ArC\x01) as written by FreeArc 0.4x–0.67. /// +/// +/// The archive is located from its end: the footer block's descriptor sits in the last 4 KiB, the +/// footer lists the control blocks (header, directories, recovery) and each directory block lists +/// its solid data blocks and files. Every control block and descriptor is CRC-checked; file data is +/// checked against the per-file CRC-32 on extraction. Layout in docs/FREEARC-ON-DISK.md. +/// Data whose method this library cannot decode (see ) is listed but +/// refused on extraction. +/// public sealed class FreeArcReader : IDisposable { - /// Magic bytes at the start of every FreeArc archive. - public static readonly byte[] Magic = [(byte)'A', (byte)'r', (byte)'C', 0x01]; - - private const byte BlockTypeEnd = 0x00; - private const byte BlockTypeDir = 0x01; - private const byte BlockTypeData = 0x02; - private readonly Stream _stream; - private readonly bool _leaveOpen; - private readonly byte[] _dataBuf; // full concatenated data payload - private bool _disposed; + private readonly bool _leaveOpen; + private readonly List _entries = []; + private FreeArcDataBlock? _cachedBlock; + private byte[]? _cachedData; - /// Gets all file entries found in the archive directory. - public IReadOnlyList Entries { get; } + /// Gets all files and directories recorded in the archive's directory blocks, in archive order. + public IReadOnlyList Entries => this._entries; - /// - /// Initialises a new and parses the archive. - /// - /// A readable stream positioned at the start of a FreeArc archive. - /// - /// to leave open after this reader is disposed; - /// to close it. - /// - /// Thrown when is . - /// Thrown when the archive signature is missing or the structure is malformed. - public FreeArcReader(Stream stream, bool leaveOpen = false) { - _stream = stream ?? throw new ArgumentNullException(nameof(stream)); - _leaveOpen = leaveOpen; - - // Validate magic. - Span hdr = stackalloc byte[4]; - ReadExact(hdr); - if (hdr[0] != 'A' || hdr[1] != 'r' || hdr[2] != 'C' || hdr[3] != 0x01) - throw new InvalidDataException("Invalid FreeArc magic bytes."); - - // Archive flags (reserved). - Span flagBuf = stackalloc byte[4]; - ReadExact(flagBuf); - // flags currently unused - - // Read blocks sequentially. - var entries = new List(); - var dataBufs = new List(); - - while (true) { - var b = _stream.ReadByte(); - if (b < 0 || b == BlockTypeEnd) break; - - switch ((byte)b) { - case BlockTypeDir: - ParseDirectoryBlock(entries); - break; - case BlockTypeData: - dataBufs.Add(ReadDataBlock()); - break; - default: - throw new InvalidDataException($"Unknown FreeArc block type 0x{b:X2}."); - } - } + /// Gets the size of the data preceding the archive proper (an SFX module), or zero. + public long SfxSize { get; private set; } - // Merge all data blocks into a single flat buffer so Extract() can index by offset. - var totalData = dataBufs.Sum(d => d.Length); - _dataBuf = new byte[totalData]; - var pos = 0; - foreach (var chunk in dataBufs) { - chunk.CopyTo(_dataBuf, pos); - pos += chunk.Length; - } + /// Gets whether the archive is locked against further modification. + public bool IsLocked { get; private set; } - Entries = entries; - } + /// Gets the archive comment, or an empty string. + public string Comment { get; private set; } = ""; - // ── Extraction ─────────────────────────────────────────────────────────── + /// Opens a FreeArc archive and reads its footer and directory blocks. + /// A readable, seekable stream holding the archive. + /// to leave open on dispose. + /// The stream is not a FreeArc archive or its structure is corrupt. + /// A directory block is encrypted or compressed with an unsupported method. + public FreeArcReader(Stream stream, bool leaveOpen = false) { + ArgumentNullException.ThrowIfNull(stream); + if (!stream.CanRead || !stream.CanSeek) + throw new ArgumentException("FreeArc needs a readable, seekable stream.", nameof(stream)); + this._stream = stream; + this._leaveOpen = leaveOpen; + this.ReadStructure(); + } - /// - /// Extracts and returns the raw (uncompressed) bytes for the specified entry. - /// - /// The to extract. - /// - /// The file data as a byte array, or an empty array when the entry has zero size. - /// - /// Thrown when is . - /// Thrown when the data offset or size is out of range. + /// Extracts the contents of , verifying its CRC-32. + /// The entry's solid block uses an unsupported or encrypted method. + /// The data is corrupt. public byte[] Extract(FreeArcEntry entry) { ArgumentNullException.ThrowIfNull(entry); + if (entry.IsDirectory || entry.Size == 0) + return []; - if (entry.Size == 0) return []; + var block = this.DecodeBlock(entry.Block); + if (entry.OffsetInBlock + entry.Size > block.LongLength) + throw new InvalidDataException($"FreeArc: \"{entry.Name}\" lies outside its solid block."); - var offset = entry.DataOffset; - var size = entry.Size; + var data = block.AsSpan((int)entry.OffsetInBlock, (int)entry.Size).ToArray(); + if (Crc32.Compute(data) != entry.Crc) + throw new InvalidDataException($"FreeArc: \"{entry.Name}\" failed its CRC check."); + return data; + } - if (offset < 0 || offset + size > _dataBuf.Length) - throw new InvalidDataException( - $"Entry '{entry.Name}': data offset {offset} + size {size} exceeds data buffer ({_dataBuf.Length} bytes)."); + /// + public void Dispose() { + if (!this._leaveOpen) + this._stream.Dispose(); + } - var result = new byte[size]; - _dataBuf.AsSpan((int)offset, (int)size).CopyTo(result); - return result; + private byte[] DecodeBlock(FreeArcDataBlock block) { + if (ReferenceEquals(block, this._cachedBlock)) + return this._cachedData!; + + var compressed = this.ReadAt(block.Position, block.CompressedSize); + var data = FreeArcMethods.Decode(block.Compressor, compressed, block.OriginalSize); + this._cachedBlock = block; + this._cachedData = data; + return data; } - // ── Block parsers ──────────────────────────────────────────────────────── - - private void ParseDirectoryBlock(List entries) { - // Hoist fixed-size scratch buffers outside the loop to satisfy CA2014. - Span buf2 = stackalloc byte[2]; - Span buf4 = stackalloc byte[4]; - Span buf8 = stackalloc byte[8]; - - // uint32 LE — entry count - ReadExact(buf4); - var count = BinaryPrimitives.ReadUInt32LittleEndian(buf4); - - for (var i = 0; i < count; i++) { - // uint16 LE name length - ReadExact(buf2); - var nameLen = BinaryPrimitives.ReadUInt16LittleEndian(buf2); - if (nameLen > 4096) throw new InvalidDataException("FreeArc directory: name length too large."); - - var nameBytes = new byte[nameLen]; - ReadExact(nameBytes); - var name = Encoding.UTF8.GetString(nameBytes); - - // uint64 LE uncompressed size - ReadExact(buf8); - var size = (long)BinaryPrimitives.ReadUInt64LittleEndian(buf8); - - // uint64 LE compressed size - ReadExact(buf8); - var compressedSize = (long)BinaryPrimitives.ReadUInt64LittleEndian(buf8); - - // uint64 LE data offset (byte offset within the concatenated data payload) - ReadExact(buf8); - var dataOffset = (long)BinaryPrimitives.ReadUInt64LittleEndian(buf8); - - // uint16 LE method length + ASCII method string - ReadExact(buf2); - var methodLen = BinaryPrimitives.ReadUInt16LittleEndian(buf2); - var methodBytes = new byte[methodLen]; - ReadExact(methodBytes); - var method = Encoding.ASCII.GetString(methodBytes); - - entries.Add(new FreeArcEntry { - Name = name, - Size = size, - CompressedSize = compressedSize, - DataOffset = dataOffset, - Method = method, - }); + private void ReadStructure() { + var footer = this.FindFooter(); + var footerData = this.ReadControlBlock(footer); + + var p = 0; + var count = ReadCount(footerData, ref p); + var controls = new List(count); + for (var i = 0; i < count; ++i) { + var type = ReadCount(footerData, ref p); + var compressor = ReadString(footerData, ref p); + var relative = ReadSize(footerData, ref p); + var original = ReadSize(footerData, ref p); + var compressed = ReadSize(footerData, ref p); + var crc = ReadUInt32(footerData, ref p); + var position = footer.Position - relative; + if (position < 0 || position + compressed > footer.Position) + throw new InvalidDataException("FreeArc: footer lists a block outside the archive."); + controls.Add(new(type, compressor, position, original, compressed, crc)); } - } - private byte[] ReadDataBlock() { - // uint32 LE payload length - Span buf4 = stackalloc byte[4]; - ReadExact(buf4); - var len = BinaryPrimitives.ReadUInt32LittleEndian(buf4); + if (p < footerData.Length) + this.IsLocked = footerData[p++] != 0; + if (p < footerData.Length) { + // Pre-0.60 comment: a list of UCS-4 code points. + var legacyLength = ReadCount(footerData, ref p); + var chars = new System.Text.StringBuilder(); + for (var i = 0; i < legacyLength; ++i) + chars.Append(char.ConvertFromUtf32((int)ReadUInt32(footerData, ref p))); + this.Comment = chars.ToString(); + } + if (p < footerData.Length) + _ = ReadString(footerData, ref p); // recovery-record setting + if (p < footerData.Length) { + var commentLength = ReadCount(footerData, ref p); + if (p + commentLength > footerData.Length) + throw new InvalidDataException("FreeArc: comment runs past the footer."); + if (commentLength > 0) + this.Comment = System.Text.Encoding.UTF8.GetString(footerData, p, commentLength); + } - var data = new byte[len]; - ReadExact(data); - return data; + this.SfxSize = controls.Count > 0 ? Math.Max(0, controls.Min(c => c.Position)) : 0; + + foreach (var control in controls.Where(c => c.Type == BlockDirectory)) + this.ReadDirectory(control, this.ReadControlBlock(control)); } - // ── Low-level I/O ──────────────────────────────────────────────────────── + private void ReadDirectory(ControlBlock directory, byte[] dir) { + var p = 0; + var blockCount = ReadCount(dir, ref p); + var filesPerBlock = new int[blockCount]; + for (var i = 0; i < blockCount; ++i) + filesPerBlock[i] = ReadCount(dir, ref p); + var compressors = new string[blockCount]; + for (var i = 0; i < blockCount; ++i) + compressors[i] = ReadString(dir, ref p); + var offsets = new long[blockCount]; + for (var i = 0; i < blockCount; ++i) + offsets[i] = ReadSize(dir, ref p); + var compSizes = new long[blockCount]; + for (var i = 0; i < blockCount; ++i) + compSizes[i] = ReadSize(dir, ref p); + + var dirCount = ReadCount(dir, ref p); + var dirNames = new string[dirCount]; + for (var i = 0; i < dirCount; ++i) + dirNames[i] = ReadString(dir, ref p).Replace('\\', '/'); + + long total = 0; + foreach (var n in filesPerBlock) + total += n; + if (total > dir.Length) + throw new InvalidDataException("FreeArc: directory claims more files than it can hold."); + var files = (int)total; + + var names = new string[files]; + for (var i = 0; i < files; ++i) + names[i] = ReadString(dir, ref p).Replace('\\', '/'); + var dirNumbers = new int[files]; + for (var i = 0; i < files; ++i) { + dirNumbers[i] = ReadCount(dir, ref p); + if (dirNumbers[i] >= dirCount) + throw new InvalidDataException("FreeArc: file refers to a directory name that does not exist."); + } + var sizes = new long[files]; + for (var i = 0; i < files; ++i) + sizes[i] = ReadSize(dir, ref p); + var times = new uint[files]; + for (var i = 0; i < files; ++i) + times[i] = ReadUInt32(dir, ref p); + if (p + files > dir.Length) + throw new InvalidDataException("FreeArc: directory flags run past the block."); + var isDir = new bool[files]; + for (var i = 0; i < files; ++i) + isDir[i] = dir[p++] != 0; + var crcs = new uint[files]; + for (var i = 0; i < files; ++i) + crcs[i] = ReadUInt32(dir, ref p); + // Optional tagged fields follow, ending with tag 0; FreeArc 0.67 writes none. + + var file = 0; + for (var b = 0; b < blockCount; ++b) { + long blockSize = 0; + for (var k = 0; k < filesPerBlock[b]; ++k) + blockSize += sizes[file + k]; + + var position = directory.Position - offsets[b]; + if (position < 0 || position + compSizes[b] > directory.Position) + throw new InvalidDataException("FreeArc: directory lists a data block outside the archive."); + var block = new FreeArcDataBlock(compressors[b], position, compSizes[b], blockSize); + + long offset = 0; + for (var k = 0; k < filesPerBlock[b]; ++k, ++file) { + var folder = dirNames[dirNumbers[file]]; + this._entries.Add(new() { + Name = folder.Length == 0 ? names[file] : folder.TrimEnd('/') + "/" + names[file], + Size = sizes[file], + CompressedSize = k == 0 ? compSizes[b] : 0, + Method = compressors[b], + IsDirectory = isDir[file], + LastModified = DateTime.UnixEpoch.AddSeconds(times[file]), + Crc = crcs[file], + Block = block, + OffsetInBlock = offset, + }); + offset += sizes[file]; + } + } + } - private void ReadExact(Span buf) { - var read = 0; - while (read < buf.Length) { - var n = _stream.Read(buf[read..]); - if (n == 0) - throw new InvalidDataException( - $"FreeArc: unexpected end of stream (needed {buf.Length} bytes, got {read})."); - read += n; + /// Scans the last 4 KiB backwards for a descriptor that checks out as the footer's. + private ControlBlock FindFooter() { + var length = this._stream.Length; + if (length < 16) + throw new InvalidDataException("FreeArc: too small to be an archive."); + + var windowStart = Math.Max(0, length - FooterScanWindow); + var window = this.ReadAt(windowStart, length - windowStart); + for (var at = window.Length - 4; at >= 0; --at) { + if (window[at] != 'A' || window[at + 1] != 'r' || window[at + 2] != 'C' || window[at + 3] != 1) + continue; + if (TryParseDescriptor(window.AsSpan(at), windowStart + at, out var block) && block.Type == BlockFooter) + return block; } + + throw new InvalidDataException("FreeArc: no footer block descriptor in the last 4 KiB — not a FreeArc archive or truncated."); } - /// - public void Dispose() { - if (!_disposed) { - _disposed = true; - if (!_leaveOpen) _stream.Dispose(); + /// + /// A descriptor: signature, type, compressor, original size, compressed size, CRC of the block's + /// decoded contents, then the CRC of the descriptor itself. The block precedes its descriptor. + /// The footer's descriptor runs to the end of the archive. + /// + private static bool TryParseDescriptor(ReadOnlySpan descriptor, long position, out ControlBlock block) { + block = default; + if (descriptor.Length < 12) + return false; + var body = descriptor[..^4]; + if (Crc32.Compute(body) != System.Buffers.Binary.BinaryPrimitives.ReadUInt32LittleEndian(descriptor[^4..])) + return false; + + try { + var p = 4; + var type = ReadCount(body, ref p); + var compressor = ReadString(body, ref p); + var original = ReadSize(body, ref p); + var compressed = ReadSize(body, ref p); + var crc = ReadUInt32(body, ref p); + if (p != body.Length || compressed > position) + return false; + block = new(type, compressor, position - compressed, original, compressed, crc); + return true; + } catch (InvalidDataException) { + return false; } } + + private byte[] ReadControlBlock(ControlBlock block) { + if (block.Original > int.MaxValue || block.Compressed > int.MaxValue) + throw new InvalidDataException("FreeArc: control block too large."); + var compressed = this.ReadAt(block.Position, block.Compressed); + var data = FreeArcMethods.Decode(block.Compressor, compressed, block.Original); + if (Crc32.Compute(data) != block.Crc) + throw new InvalidDataException($"FreeArc: control block at {block.Position} failed its CRC check."); + return data; + } + + private byte[] ReadAt(long position, long count) { + if (position < 0 || count < 0 || position + count > this._stream.Length || count > int.MaxValue) + throw new InvalidDataException("FreeArc: block lies outside the archive."); + var buffer = new byte[count]; + this._stream.Position = position; + this._stream.ReadExactly(buffer); + return buffer; + } + + private readonly record struct ControlBlock(int Type, string Compressor, long Position, long Original, long Compressed, uint Crc); } diff --git a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.FreeArc/FreeArcWriter.cs b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.FreeArc/FreeArcWriter.cs index e7a4691a7..b20499d46 100644 --- a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.FreeArc/FreeArcWriter.cs +++ b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.FreeArc/FreeArcWriter.cs @@ -1,88 +1,172 @@ -#pragma warning disable CS1591 -using System.Buffers.Binary; -using System.Text; +using Compression.Core.Checksums; +using static FileFormat.FreeArc.FreeArcEncoding; namespace FileFormat.FreeArc; /// -/// Builds minimal FreeArc archives (.arc) that can be read by . -/// -/// All files are stored without compression (method "storing"). The writer produces -/// the exact binary layout described in : magic, archive flags, -/// a single directory block, a single data block, and the end-of-archive marker. -/// +/// Writes real FreeArc archives that FreeArc 0.67's arc and unarc list, test and extract. /// +/// +/// The layout is FreeArc's own (docs/FREEARC-ON-DISK.md): a header block, the solid data +/// blocks, one directory block and the footer block, each control block followed by its CRC-checked +/// descriptor. Directories go into an empty storing block of their own, as FreeArc does; all +/// files share one solid block compressed with the chosen method — storing or lzma. +/// The directory and footer blocks are stored uncompressed. +/// public sealed class FreeArcWriter { - private readonly List<(string Name, byte[] Data)> _files = []; - - /// Adds a file to the archive being built. - /// The filename as it will appear in the archive directory. - /// The raw file data to store. - public void AddFile(string name, byte[] data) => _files.Add((name, data)); - - /// - /// Serialises the archive and returns it as a byte array. - /// - /// The complete FreeArc archive bytes. - public byte[] Build() { - using var ms = new MemoryStream(); - - // Magic + archive flags (zero). - ms.Write(FreeArcReader.Magic); - ms.Write(stackalloc byte[4]); // flags = 0 - - // Compute per-file data offsets within the concatenated payload. - var offsets = new long[_files.Count]; - long dataPos = 0; - for (var i = 0; i < _files.Count; i++) { - offsets[i] = dataPos; - dataPos += _files[i].Data.Length; - } - - // --- Directory block --- - ms.WriteByte(0x01); // BlockTypeDir - - Span buf4 = stackalloc byte[4]; - Span buf2 = stackalloc byte[2]; - Span buf8 = stackalloc byte[8]; - - BinaryPrimitives.WriteUInt32LittleEndian(buf4, (uint)_files.Count); - ms.Write(buf4); + private readonly List<(string Name, byte[] Data, DateTime Modified)> _files = []; + private readonly List<(string Name, DateTime Modified)> _directories = []; + + /// Creates a writer that compresses the file block with . + /// storing, or lzma with optional FreeArc parameters such as lzma:1mb. + public FreeArcWriter(string method = FreeArcMethods.Storing) { + var name = method.Split(':')[0].Trim().ToLowerInvariant(); + if (method.Contains('+') || name is not (FreeArcMethods.Storing or "lzma")) + throw new NotSupportedException($"FreeArc: writing method \"{method}\" is not supported (storing or lzma only)."); + this.Method = method; + } - for (var i = 0; i < _files.Count; i++) { - var (name, data) = _files[i]; - var nameBytes = Encoding.UTF8.GetBytes(name); - var methodBytes = Encoding.ASCII.GetBytes("storing"); + /// Gets the method string used for the file block. + public string Method { get; } - BinaryPrimitives.WriteUInt16LittleEndian(buf2, (ushort)nameBytes.Length); - ms.Write(buf2); - ms.Write(nameBytes); + /// Adds a file. + public void AddFile(string name, byte[] data, DateTime? modified = null) { + ArgumentNullException.ThrowIfNull(name); + ArgumentNullException.ThrowIfNull(data); + this._files.Add((Normalize(name), data, modified ?? DateTime.UtcNow)); + } - BinaryPrimitives.WriteUInt64LittleEndian(buf8, (ulong)data.Length); - ms.Write(buf8); // uncompressed size + /// Adds a directory entry. + public void AddDirectory(string name, DateTime? modified = null) { + ArgumentNullException.ThrowIfNull(name); + this._directories.Add((Normalize(name), modified ?? DateTime.UtcNow)); + } - BinaryPrimitives.WriteUInt64LittleEndian(buf8, (ulong)data.Length); - ms.Write(buf8); // compressed size (same — stored) + /// Builds the archive. + public byte[] Build() { + using var output = new MemoryStream(); + this.WriteTo(output); + return output.ToArray(); + } - BinaryPrimitives.WriteUInt64LittleEndian(buf8, (ulong)offsets[i]); - ms.Write(buf8); // data offset + /// Writes the archive to , which must be positioned at its start. + public void WriteTo(Stream output) { + ArgumentNullException.ThrowIfNull(output); + using var archive = new MemoryStream(); + + // Header block: signature + version, then its descriptor. + var header = new MemoryStream(); + WriteUInt32(header, Signature); + WriteUInt32(header, ArchiveVersion); + var headerBlock = WriteControlBlock(archive, BlockHeader, header.ToArray()); + + // Data blocks: directories in an empty storing block, files in one solid block. + var blocks = new List<(int Files, string Compressor, long Position, long Size)>(); + var entries = new List<(string Name, long Size, DateTime Modified, bool IsDir, uint Crc)>(); + if (this._directories.Count > 0) { + blocks.Add((this._directories.Count, FreeArcMethods.Storing, archive.Position, 0)); + foreach (var (name, modified) in this._directories) + entries.Add((name, 0, modified, true, 0)); + } + if (this._files.Count > 0) { + using var solid = new MemoryStream(); + foreach (var (name, data, modified) in this._files) { + solid.Write(data); + entries.Add((name, data.LongLength, modified, false, Crc32.Compute(data))); + } + var method = FreeArcMethods.WithDictionary(this.Method, solid.Length); + var compressed = FreeArcMethods.Encode(method, solid.ToArray()); + blocks.Add((this._files.Count, method, archive.Position, compressed.LongLength)); + archive.Write(compressed); + } - BinaryPrimitives.WriteUInt16LittleEndian(buf2, (ushort)methodBytes.Length); - ms.Write(buf2); - ms.Write(methodBytes); + var directoryPosition = archive.Position; + var directoryBlock = WriteControlBlock(archive, BlockDirectory, BuildDirectory(blocks, entries, directoryPosition)); + + var footerPosition = archive.Position; + var footer = new MemoryStream(); + WriteVarInt(footer, 2); + foreach (var block in new[] { headerBlock, directoryBlock }) { + WriteVarInt(footer, (ulong)block.Type); + WriteString(footer, FreeArcMethods.Storing); + WriteVarInt(footer, (ulong)(footerPosition - block.Position)); + WriteVarInt(footer, (ulong)block.Size); + WriteVarInt(footer, (ulong)block.Size); + WriteUInt32(footer, block.Crc); } + footer.WriteByte(0); // not locked + WriteVarInt(footer, 0); // no legacy (UCS-4) comment + WriteString(footer, ""); // no recovery-record setting + WriteVarInt(footer, 0); // no comment + WriteControlBlock(archive, BlockFooter, footer.ToArray()); + + archive.Position = 0; + archive.CopyTo(output); + } - // --- Data block --- - ms.WriteByte(0x02); // BlockTypeData + private static byte[] BuildDirectory( + List<(int Files, string Compressor, long Position, long Size)> blocks, + List<(string Name, long Size, DateTime Modified, bool IsDir, uint Crc)> entries, + long directoryPosition) { + var dir = new MemoryStream(); + WriteVarInt(dir, (ulong)blocks.Count); + foreach (var b in blocks) WriteVarInt(dir, (ulong)b.Files); + foreach (var b in blocks) WriteString(dir, b.Compressor); + foreach (var b in blocks) WriteVarInt(dir, (ulong)(directoryPosition - b.Position)); + foreach (var b in blocks) WriteVarInt(dir, (ulong)b.Size); + + var folders = new List(); + var folderIndex = new Dictionary(StringComparer.Ordinal); + var numbers = new List(); + var baseNames = new List(); + foreach (var e in entries) { + var slash = e.Name.LastIndexOf('/'); + var folder = slash < 0 ? "" : e.Name[..slash]; + baseNames.Add(slash < 0 ? e.Name : e.Name[(slash + 1)..]); + if (!folderIndex.TryGetValue(folder, out var index)) { + index = folders.Count; + folderIndex[folder] = index; + folders.Add(folder); + } + numbers.Add(index); + } - BinaryPrimitives.WriteUInt32LittleEndian(buf4, (uint)dataPos); - ms.Write(buf4); - foreach (var (_, data) in _files) - ms.Write(data); + WriteVarInt(dir, (ulong)folders.Count); + foreach (var f in folders) WriteString(dir, f); + foreach (var n in baseNames) WriteString(dir, n); + foreach (var n in numbers) WriteVarInt(dir, (ulong)n); + foreach (var e in entries) WriteVarInt(dir, (ulong)e.Size); + foreach (var e in entries) WriteUInt32(dir, ToUnixTime(e.Modified)); + foreach (var e in entries) dir.WriteByte(e.IsDir ? (byte)1 : (byte)0); + foreach (var e in entries) WriteUInt32(dir, e.Crc); + WriteVarInt(dir, 0); // end of optional fields + return dir.ToArray(); + } - // --- End-of-archive marker --- - ms.WriteByte(0x00); + /// Writes a stored control block followed by its descriptor and the descriptor's CRC. + private static (int Type, long Position, long Size, uint Crc) WriteControlBlock(Stream archive, int type, byte[] data) { + var position = archive.Position; + archive.Write(data); + var crc = Crc32.Compute(data); + + var descriptor = new MemoryStream(); + WriteUInt32(descriptor, Signature); + WriteVarInt(descriptor, (ulong)type); + WriteString(descriptor, FreeArcMethods.Storing); + WriteVarInt(descriptor, (ulong)data.LongLength); + WriteVarInt(descriptor, (ulong)data.LongLength); + WriteUInt32(descriptor, crc); + var body = descriptor.ToArray(); + archive.Write(body); + WriteUInt32(archive, Crc32.Compute(body)); + return (type, position, data.LongLength, crc); + } - return ms.ToArray(); + private static uint ToUnixTime(DateTime time) { + var utc = time.Kind == DateTimeKind.Local ? time.ToUniversalTime() : time; + var seconds = (long)(utc - DateTime.UnixEpoch).TotalSeconds; + return (uint)Math.Clamp(seconds, 0, uint.MaxValue); } + + private static string Normalize(string name) => name.Replace('\\', '/').Trim('/'); } diff --git a/Hawkynt.FileFormats.Archives/README.md b/Hawkynt.FileFormats.Archives/README.md index 19f330c8f..c3d21dc19 100644 --- a/Hawkynt.FileFormats.Archives/README.md +++ b/Hawkynt.FileFormats.Archives/README.md @@ -121,7 +121,7 @@ Every State, Test, Maintenance, Compress/Decompress and Demux/Mux/Remux cell is | [DMS](https://en.wikipedia.org/wiki/Disk_Masher_System) | `Dms` | `.dms` | WORM | ✅ | defrag | | [GitHub](https://github.com/markrabjohn/xDMS) | | [EGG (ALZip)](https://en.wikipedia.org/wiki/EGG_(file_format)) | `Egg` | `.egg` | WORM | ✅ | defrag | | [GitHub](https://github.com/alkegi/docs/blob/master/egg.md) | | [ESD](https://en.wikipedia.org/wiki/Windows_Imaging_Format) | `Esd` | `.esd` | WORM | ✅ | wipe | Solid LZMS WIM; created images carry a metadata resource but entries re-list as resources | [Microsoft Learn](https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/wim-and-esd-windows-image-files-overview) | -| [FreeArc](https://en.wikipedia.org/wiki/FreeArc) | `FreeArc` | `.arc` | R/W | ✅ | defrag · wipe | | [GitHub](https://github.com/Bulat-Ziganshin/FA) | +| [FreeArc](https://en.wikipedia.org/wiki/FreeArc) | `FreeArc` | `.arc` | R/W | ✅ | defrag · wipe | Reads FreeArc 0.67 archives; decodes storing and lzma blocks only (tornado, ppmd, lzp, grzip, rep, delta, exe and encryption are refused). Writes storing/lzma archives that FreeArc 0.67 extracts byte-identically. Layout: [FREEARC-ON-DISK.md](https://github.com/Hawkynt/CompressionWorkbench/blob/main/docs/FREEARC-ON-DISK.md) | [GitHub](https://github.com/Bulat-Ziganshin/FA) | | HA | `Ha` | `.ha` | R/W | ✅ | defrag · wipe | | [Archive Team](http://fileformats.archiveteam.org/wiki/HA) | | [IFF CDAF](https://en.wikipedia.org/wiki/Interchange_File_Format) | `IffCdaf` | `.cdaf` | R/W | ✅ | defrag · wipe | | [Aminet](https://aminet.net) | | [LBR](https://en.wikipedia.org/wiki/LBR_(file_format)) | `Lbr` | `.lbr` | R/W | ✅ | defrag · wipe | | [gaby.de](http://www.gaby.de/cpm/manuals/archive/lbr.txt) | diff --git a/Hawkynt.FileFormats.Archives/REFERENCE.md b/Hawkynt.FileFormats.Archives/REFERENCE.md index 08ea63067..0df10d396 100644 --- a/Hawkynt.FileFormats.Archives/REFERENCE.md +++ b/Hawkynt.FileFormats.Archives/REFERENCE.md @@ -16762,30 +16762,33 @@ Implements `IArchiveFormatOperations`, `IFormatDescriptor`. ### Namespace `FileFormat.FreeArc` -[`FreeArcEntry`](#freearcentry) · [`FreeArcFormatDescriptor`](#freearcformatdescriptor) · [`FreeArcReader`](#freearcreader) · [`FreeArcWriter`](#freearcwriter) +[`FreeArcEntry`](#freearcentry) · [`FreeArcFormatDescriptor`](#freearcformatdescriptor) · [`FreeArcMethods`](#freearcmethods) · [`FreeArcReader`](#freearcreader) · [`FreeArcWriter`](#freearcwriter) #### `FreeArcEntry` -Represents a single file entry within a FreeArc archive. +Represents a single file or directory recorded in a FreeArc directory block. | Member | Signature | Summary | | --- | --- | --- | | `FreeArcEntry` | `FreeArcEntry()` | | -| `CompressedSize` | `long CompressedSize { get; init; }` | The compressed size of the file data in bytes. | -| `Method` | `string Method { get; init; }` | The compression method string (e.g. "storing", "lzma", "freearc"). | -| `Name` | `string Name { get; init; }` | The file name as stored in the archive directory block. | +| `CompressedSize` | `long CompressedSize { get; init; }` | The compressed size of the solid block that holds this file, reported on the block's first file only (FreeArc does not record per-file compressed sizes); zero on the others. | +| `Crc` | `uint Crc { get; init; }` | The CRC-32 of the uncompressed file contents. | +| `IsDirectory` | `bool IsDirectory { get; init; }` | Whether this entry is a directory. | +| `LastModified` | `DateTime? LastModified { get; init; }` | The modification time (stored as 32-bit Unix seconds), in UTC. | +| `Method` | `string Method { get; init; }` | The method string of the solid block holding this file, e.g. `lzma:32kb:normal:32`. | +| `Name` | `string Name { get; init; }` | The full stored path, with `/` as separator. | | `Size` | `long Size { get; init; }` | The uncompressed size of the file in bytes. | #### `FreeArcFormatDescriptor` -Format descriptor for FreeArc compressed archives (.arc). References: `https://github.com/Bulat-Ziganshin/FA` — FreeArc'Next by FreeArc's author, Bulat Ziganshin (the original freearc.org site is defunct)`https://en.wikipedia.org/wiki/FreeArc` — Wikipedia overviewNo formal specification — the archive layout is defined by the original FreeArc sources +Format descriptor for FreeArc archives (.arc, signature `ArC\x01`). References: `docs/FREEARC-ON-DISK.md` — the layout this implementation follows, derived from FreeArc's sources and checked against FreeArc 0.67's output`https://github.com/Bulat-Ziganshin/FA` — FreeArc'Next by FreeArc's author, Bulat Ziganshin (the original freearc.org site is defunct)`https://en.wikipedia.org/wiki/FreeArc` — Wikipedia overview Implements `IArchiveCreatable`, `IArchiveDefragmentable`, `IArchiveFormatOperations`, `IArchiveLayoutMap`, `IArchiveModifiable`, `IArchivePurgeable`, `IFormatDescriptor`, `IWipeEmpty`. | Member | Signature | Summary | | --- | --- | --- | | `FreeArcFormatDescriptor` | `FreeArcFormatDescriptor()` | | -| `Capabilities` | `FormatCapabilities Capabilities { get; }` | Gets the capabilities. | +| `Capabilities` | `FormatCapabilities Capabilities { get; }` | | | `Category` | `FormatCategory Category { get; }` | | | `CompoundExtensions` | `IReadOnlyList CompoundExtensions { get; }` | | | `DefaultExtension` | `string DefaultExtension { get; }` | | @@ -16794,7 +16797,7 @@ Implements `IArchiveCreatable`, `IArchiveDefragmentable`, `IArchiveFormatOperati | `Extensions` | `IReadOnlyList Extensions { get; }` | | | `Family` | `AlgorithmFamily Family { get; }` | | | `Id` | `string Id { get; }` | | -| `MagicSignatures` | `IReadOnlyList MagicSignatures { get; }` | Gets the magic signatures. | +| `MagicSignatures` | `IReadOnlyList MagicSignatures { get; }` | | | `Methods` | `IReadOnlyList Methods { get; }` | | | `TarCompressionFormatId` | `string TarCompressionFormatId { get; }` | | | `Create` | `void Create(Stream output, IReadOnlyList inputs, FormatCreateOptions options)` | | @@ -16804,29 +16807,48 @@ Implements `IArchiveCreatable`, `IArchiveDefragmentable`, `IArchiveFormatOperati | `Extract` | `void Extract(Stream stream, string outputDir, string password, string[] files)` | | | `List` | `List List(Stream stream, string password)` | | +#### `FreeArcMethods` + +FreeArc compression method strings: `name:param:param...` stages joined by `+`, applied left to right when compressing and therefore undone right to left when extracting. + +| Member | Signature | Summary | +| --- | --- | --- | +| `Storing` | `const string Storing` | The method name of an uncompressed stage. | +| `Decode` | `static byte[] Decode(string compressor, byte[] input, long outputSize)` | Decodes `input` produced by `compressor` into exactly `outputSize` bytes. | +| `Encode` | `static byte[] Encode(string compressor, ReadOnlySpan input)` | Encodes `input` with a single supported stage. | +| `IsEncrypted` | `static bool IsEncrypted(string compressor)` | Gets whether `compressor` contains an encryption stage. | +| `IsSupported` | `static bool IsSupported(string compressor)` | Gets whether every stage of `compressor` can be decoded. | +| `Stages` | `static string[] Stages(string compressor)` | Splits a method string into its stages. | +| `WithDictionary` | `static string WithDictionary(string compressor, long inputLength)` | Gives a bare `lzma` stage an explicit dictionary (the next power of two covering the input, 64 KiB to 64 MiB) so FreeArc's decoder does not reserve its much larger default. | + #### `FreeArcReader` -Reads FreeArc archives (.arc). This implementation parses a well-defined binary subset of the FreeArc container format that is produced by `FreeArcWriter` and understood by this reader. Binary layout (all integers are little-endian): 4 bytes — magic "ArC\x01"4 bytes — uint32 archive flags (reserved, currently 0)One or more blocks, each preceded by a 1-byte block type: 0x01 = directory block0x02 = data block0x00 = end-of-archive marker Directory block payload: uint32 — number of file entriesPer entry: uint16 nameLen + UTF-8 name + uint64 size + uint64 compressedSize + uint64 dataOffset + uint16 methodLen + ASCII method Data block payload: uint32 — payload length in bytespayload bytes (concatenated raw file data) +Reads real FreeArc archives (`.arc`, signature `ArC\x01`) as written by FreeArc 0.4x–0.67. Implements `IDisposable`. | Member | Signature | Summary | | --- | --- | --- | -| `FreeArcReader` | `FreeArcReader(Stream stream, bool leaveOpen = false)` | Initialises a new `FreeArcReader` and parses the archive. | -| `Magic` | `static readonly byte[] Magic` | Magic bytes at the start of every FreeArc archive. | -| `Entries` | `IReadOnlyList Entries { get; }` | Gets all file entries found in the archive directory. | +| `FreeArcReader` | `FreeArcReader(Stream stream, bool leaveOpen = false)` | Opens a FreeArc archive and reads its footer and directory blocks. | +| `Comment` | `string Comment { get; }` | Gets the archive comment, or an empty string. | +| `Entries` | `IReadOnlyList Entries { get; }` | Gets all files and directories recorded in the archive's directory blocks, in archive order. | +| `IsLocked` | `bool IsLocked { get; }` | Gets whether the archive is locked against further modification. | +| `SfxSize` | `long SfxSize { get; }` | Gets the size of the data preceding the archive proper (an SFX module), or zero. | | `Dispose` | `void Dispose()` | | -| `Extract` | `byte[] Extract(FreeArcEntry entry)` | Extracts and returns the raw (uncompressed) bytes for the specified entry. | +| `Extract` | `byte[] Extract(FreeArcEntry entry)` | Extracts the contents of `entry`, verifying its CRC-32. | #### `FreeArcWriter` -Builds minimal FreeArc archives (.arc) that can be read by `FreeArcReader`. All files are stored without compression (method "storing"). The writer produces the exact binary layout described in `FreeArcReader`: magic, archive flags, a single directory block, a single data block, and the end-of-archive marker. +Writes real FreeArc archives that FreeArc 0.67's `arc` and `unarc` list, test and extract. | Member | Signature | Summary | | --- | --- | --- | -| `FreeArcWriter` | `FreeArcWriter()` | | -| `AddFile` | `void AddFile(string name, byte[] data)` | Adds a file to the archive being built. | -| `Build` | `byte[] Build()` | Serialises the archive and returns it as a byte array. | +| `FreeArcWriter` | `FreeArcWriter(string method = "storing")` | Creates a writer that compresses the file block with `method`. | +| `Method` | `string Method { get; }` | Gets the method string used for the file block. | +| `AddDirectory` | `void AddDirectory(string name, DateTime? modified = null)` | Adds a directory entry. | +| `AddFile` | `void AddFile(string name, byte[] data, DateTime? modified = null)` | Adds a file. | +| `Build` | `byte[] Build()` | Builds the archive. | +| `WriteTo` | `void WriteTo(Stream output)` | Writes the archive to `output`, which must be positioned at its start. | ### Namespace `FileFormat.Freeze` diff --git a/docs/FREEARC-ON-DISK.md b/docs/FREEARC-ON-DISK.md new file mode 100644 index 000000000..b096b1751 --- /dev/null +++ b/docs/FREEARC-ON-DISK.md @@ -0,0 +1,130 @@ +# FreeArc on disk + +The container layout of FreeArc archives (`.arc`, signature `ArC\x01`) as FreeArc 0.4x–0.67 writes +them, and what `FileFormat.FreeArc` does with each part. + +## 🔎 Provenance + +- **Licence.** FreeArc's sources (Bulat Ziganshin, 2004–2014) are GPL-2.0; the 0.67 binaries carry + a freeware usage licence. Neither can be absorbed into this LGPL-3.0-or-later repository, so this + is rung 2 of the sourcing ladder: the sources were *read* to write this document, the + implementation was written from this document, and FreeArc 0.67 is the oracle. No FreeArc code, + file layout or identifier names were carried across. +- **Sources read.** The SVN snapshot mirrored at `github.com/svn2github/freearc`: `Unarc/ArcStructure.h` + (the C++ reader of footer and directory), `ArhiveStructure.hs`, `ArhiveDirectory.hs`, + `ByteStream.hs` and `Compression.hs` (the Haskell writer). +- **Oracle.** FreeArc 0.67 (2014-03-15) `Arc.exe` and `unarc.exe`, from the self-extracting + `FreeArc-console-0.67-alpha-win32.exe` (Wayback Machine copy of freearc.org/download/testing), + run on Windows 11. Every structure below was checked against hex dumps of archives it wrote; the + checked-in fixtures are in `Compression.Tests/FreeArc/ReferenceVectors/`. + +## 🧱 Primitives + +All fixed-width integers are little-endian. + +| Primitive | Encoding | +|-----------|----------| +| integer | Variable length, 1–9 bytes. The number of trailing one-bits in the first byte, *n*, says the value occupies *n*+1 bytes: read those bytes little-endian and shift right by *n*+1. So `xxxxxxx0` holds 7 bits, `…01` 14 bits, `…011` 21 bits, up to `01111111` + 7 bytes = 56 bits. A first byte `0xFF` is followed by a plain 64-bit value. Writers use the shortest form. Measured: `10` = 8, `AA` = 85, `31 A4` = 10508. | +| string | UTF-8 bytes, NUL-terminated. | +| list of *T* | An integer count, then the elements — except where a count is implied (see the directory block). | +| bool | One byte, 0 or 1. Lists of bools are *not* bit-packed. | +| CRC | CRC-32 (the zlib/PKZIP polynomial `0xEDB88320`, initial and final XOR `0xFFFFFFFF`), 4 bytes. `"Hello, FreeArc!\n"` → `2B27C831`. | +| time | 32-bit Unix seconds, UTC. | + +## 🗂️ Archive structure + +An archive is a sequence of blocks. Data blocks hold file contents; every other block is a +*control block* and is immediately followed by its *descriptor*: + +``` +signature "ArC\x01" 4 bytes +type integer 0 descriptor, 1 header, 2 data, 3 directory, 4 footer, 5 recovery +compressor string method that encoded the block +origsize integer decoded size +compsize integer encoded size; the block starts compsize bytes before the descriptor +crc CRC of the decoded block +dcrc CRC of all descriptor bytes before it +``` + +FreeArc 0.67 lays an archive out as: + +| Offset | Content | +|--------|---------| +| 0 | Header block: `ArC\x01` + version word `00 00 06 07` (8 bytes, `storing`), then its descriptor. Anything before the header block is an SFX module. | +| … | Data blocks (solid blocks), no descriptors. | +| … | Directory block (compressed with `-dm`, default `lzma:1mb:normal:bt4:32`), then its descriptor. | +| … | Footer block (same method), then its descriptor, which runs to the end of the file. | + +A reader starts at the end: the footer's descriptor begins within the last 4096 bytes. FreeArc takes +the last `ArC\x01` in that window; our reader scans backwards and takes the first candidate whose +descriptor CRC checks out and whose type is footer, which tolerates the signature appearing inside +compressed data. + +### Footer block + +``` +blocks list of (type integer, compressor string, relpos integer, origsize integer, compsize integer, crc CRC) +locked bool archive locked against modification +oldcomment list of 32-bit code points pre-UTF-8 comment, written empty by 0.67 +recovery string recovery-record setting (optional, absent in old archives) +comment list of bytes UTF-8 comment (optional) +``` + +`relpos` is the distance from the start of *this footer block* back to the listed block. FreeArc +0.67 lists the header block and every directory block (and the recovery block when there is one). +The trailing fields are optional: a reader stops at the end of the block. + +### Directory block + +The positions of data blocks are relative to the start of *this directory block*. The per-block and +per-file lists carry no counts of their own; their lengths are the block count and the file total. + +``` +nblocks integer +files[nblocks] integer each files in each solid block +methods[nblocks] string each compressor of each solid block +relpos[nblocks] integer each distance back from the directory block +compsize[nblocks] integer each +ndirs integer +dirs[ndirs] string each directory names, "/"-separated; "" for the root +-- nfiles = sum(files) -- +names[nfiles] string each base names +dirnum[nfiles] integer each index into dirs +size[nfiles] integer each +time[nfiles] time each +isdir[nfiles] bool each +crc[nfiles] CRC each 0 for directories +tag integer optional tagged fields would follow; 0 ends them (0.67 writes only 0) +``` + +Files belong to solid blocks in order: the first `files[0]` files are the contents of block 0 +concatenated, and so on. A data block's decoded size is therefore the sum of its files' sizes; it is +not stored anywhere. FreeArc puts directory entries (size 0) into a zero-length `storing` block of +their own. The full name of a file is `dirs[dirnum] + "/" + name` (or just `name` for the root). +A directory entry `a/b` is stored as name `b` in directory `a`. + +## 🧪 Methods + +A compressor string is one or more stages joined by `+`, each `name:param:param…`, applied left to +right on compression; extraction undoes them right to left. Seen from 0.67: + +| Method string | Meaning | Here | +|---------------|---------|------| +| `storing` | Uncompressed. | ✅ read + write | +| `lzma:32kb:normal:32[:pb1:lc4:lp1][:mc4]` | Raw LZMA1 range-coded stream, **no properties header**; the dictionary is the token with a size suffix, `lcN`/`lpN`/`pbN` override the defaults 3/0/2, everything else steers only the encoder. FreeArc shrinks the dictionary to the data size. | ✅ read + write (we write an end marker) | +| `ppmd:10:48mb` | PPMd var. I (order, memory). Our PPMd I model is not verified against FreeArc's stream. | ❌ refused | +| `tor:…`, `4x4:…`, `rep:…`, `lzp:…`, `grzip:…`, `dict:…`, `delta`, `exe`, `mm`, external compressors | FreeArc's own codecs and filters, no compatible implementation here. | ❌ refused | +| `…+aes-256/ctr:n1000:r0:i…:s…:c…` (also blowfish, serpent, twofish) | Encryption stage (`-p`, or `-hp` for headers too). | ❌ refused | + +A refused method still lists: only extraction of that block fails, with `NotSupportedException`. An +archive whose directory or footer is unreadable (`-hp`, or an unsupported `-dm` method) lists as +`FULL.arc` + `metadata.ini` with `parse_status=partial`. + +## ✍️ What our writer emits + +Header block, a zero-length `storing` block for directory entries (when there are any), one solid +block for all files (`storing` or `lzma:[:lcN:lpN:pbN]`), then a `storing` directory block +and a `storing` footer listing the header and directory blocks. For `storing` input in FreeArc's +order with FreeArc's timestamps this is byte-identical to `arc a -m0 -dmstoring` (pinned by +`Writer_Storing_ReproducesFreeArcByteForByte`). FreeArc 0.67's `unarc t/x` and `arc t/lb/x` accept +our `storing` and `lzma` archives and extract them byte-identically (`FreeArcOracleTests`). diff --git a/docs/catalogue/GAPS.md b/docs/catalogue/GAPS.md index 5ac828100..e32d9b445 100644 --- a/docs/catalogue/GAPS.md +++ b/docs/catalogue/GAPS.md @@ -169,7 +169,7 @@ running real-world samples. The file named at the end of each row is where the l | ALZip | `AlZip` | 3 | store and deflate only (AlZipReader.cs) | | APFS | `Apfs` | 3 | no decmpfs (LZVN/LZFSE) compressed files, no encryption (ApfsReader.cs) | | Compiled HTML Help (CHM) | `Chm` | 3 | content sections above 1 unsupported (ChmReader.cs) | -| FreeArc | `FreeArc` | 3 | reader/writer use a simplified private layout with no codecs; real FreeArc archives are not readable (FreeArcReader.cs) | +| FreeArc | `FreeArc` | 3 | only storing and lzma blocks decode; tornado/ppmd/lzp/grzip/rep/delta/exe and encryption are refused (FreeArcMethods.cs) | | NSIS installer | `Nsis` | 3 | solid installers cannot be extracted per entry (NsisReader.cs) | | NTFS | `Ntfs` | 3 | WOF (XPRESS/LZX) compressed files not decoded; LZNT1 is | | QCOW / QCOW2 | `Qcow2` | 3 | no encryption, backing files, zstd clusters or extended L2 (Qcow2Structures.cs) | @@ -212,6 +212,10 @@ running real-world samples. The file named at the end of each row is where the l writer and reader agree with each other. - **Stale comments.** The "(not supported)" comments in `ArcConstants.cs` describe methods the reader does decode. +- **Capability claims against real files.** `Zpaq` declares `CanCreate | CanModify`, but does not + read an archive written by the real tool. Its round-trip only proves that our writer and reader + agree with each other. (`FreeArc` now reads FreeArc 0.67's archives and FreeArc accepts ours; see + `docs/FREEARC-ON-DISK.md`.) ## ➕ Ours only diff --git a/docs/catalogue/reference-formats.csv b/docs/catalogue/reference-formats.csv index 516676341..56c5deac3 100644 --- a/docs/catalogue/reference-formats.csv +++ b/docs/catalogue/reference-formats.csv @@ -55,7 +55,7 @@ Spark (Acorn),spk;arc,archive,,,,,,,Y,1,https://en.wikipedia.org/wiki/Spark_(arc HA,ha,archive,,,,,,,Y,1,https://en.wikipedia.org/wiki/HA_(file_format),Ha,Y,Y,Y,PARTIAL,,stored only; HSC and ASC throw (HaReader.cs) SQX,sqx,archive,,,,,,,Y,1,https://en.wikipedia.org/wiki/SQX,Sqx,Y,Y,Y,OK,, UHARC,uha,archive,,,,,R,,Y,2,https://en.wikipedia.org/wiki/UHarc,Uharc,Y,Y,Y,PARTIAL,,stored and LZP only (UharcReader.cs) -FreeArc,arc;cdx,archive,,,,,R,fmt/1096,Y,3,https://en.wikipedia.org/wiki/FreeArc,FreeArc,Y,Y,Y,PARTIAL,,reader/writer use a simplified private layout with no codecs; real FreeArc archives are not readable (FreeArcReader.cs) +FreeArc,arc;cdx,archive,,,,,R,fmt/1096,Y,3,https://en.wikipedia.org/wiki/FreeArc,FreeArc,Y,Y,Y,PARTIAL,,only storing and lzma blocks decode; tornado/ppmd/lzp/grzip/rep/delta/exe and encryption are refused (FreeArcMethods.cs) ZPAQ,zpaq,archive,,,,,R,fmt/1097,RW,3,https://mattmahoney.net/dc/zpaq206.pdf,Zpaq,Y,Y,Y,PARTIAL,,reads only the stored blocks our own writer emits; ZPAQL-compressed blocks (every real zpaq level) throw (ZpaqReader.cs) PAQ (paq6/7/8 family),paq6;paq7;paq8;paq8l,archive,,,,,,,Y,1,https://mattmahoney.net/dc/paq.html,Paq8,Y,Y,,PARTIAL,,a single PAQ8 variant (.paq8l); the other PAQ versions are mutually incompatible KGB Archiver,kgb;kge,archive,,,,,R,,RW,2,,,,,,MISSING,, From dd3a154bb6417b99655613cbb9f85c1ef45f1086 Mon Sep 17 00:00:00 2001 From: Hawkynt Date: Mon, 26 Oct 2026 13:04:58 +0100 Subject: [PATCH 12/34] * regenerate references --- Hawkynt.FileFormats.Archives/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Hawkynt.FileFormats.Archives/README.md b/Hawkynt.FileFormats.Archives/README.md index c3d21dc19..fe0e8de9c 100644 --- a/Hawkynt.FileFormats.Archives/README.md +++ b/Hawkynt.FileFormats.Archives/README.md @@ -544,7 +544,7 @@ Use it when a .NET process needs to enumerate, extract, test, create, edit or in -Every public and protected member of all 2571 types, generated from the built assembly and its XML documentation, is in [REFERENCE.md](https://github.com/Hawkynt/CompressionWorkbench/blob/main/Hawkynt.FileFormats.Archives/REFERENCE.md). +Every public and protected member of all 2572 types, generated from the built assembly and its XML documentation, is in [REFERENCE.md](https://github.com/Hawkynt/CompressionWorkbench/blob/main/Hawkynt.FileFormats.Archives/REFERENCE.md). From 685d6276d0ff97617b32c946dac3ef8b314b2aac Mon Sep 17 00:00:00 2001 From: Hawkynt Date: Mon, 26 Oct 2026 13:05:02 +0100 Subject: [PATCH 13/34] * NSIS: real installer format, solid and non-solid, read-only The reader and writer used an invented layout: compression taken from the first-header flags, the block compression bit inverted, no script header. Real installers never parsed; the "writer" produced files only our reader accepted. Both are replaced. The reader follows NSIS's fileform.h: first header at a 512-byte boundary, compressor recognised from the data (zlib, bzip2, LZMA, stored), solid streams split on the fly, file names recovered by replaying SetOutPath/File from the script header, NSIS 2 and 3 string escapes, Unicode and ANSI, 32- and 64-bit headers, file times. Deflate gains the NSIS 2 stored-block rule (LEN without NLEN); bzip2 gains NSIS's header-less framing. Detection scans aligned first headers instead of a tail search. Creation, defrag and the fake writer are removed: nothing but makensis can produce a runnable installer. Oracle: 11 installers compiled by makensis 2.51 and 3.10 (zlib/bzip2/LZMA, solid and not, Unicode and ANSI); names, sizes and SHA-256 of every file match what 7-Zip 26.03 extracts. Sourcing: rung 1. NSIS sources (zlib/libpng) for layout and constants; 7-Zip's NSIS handler (LGPL-2.1+) for the signature table and dialect checks. --- .../Deflate/DeflateDecompressor.cs | 18 +- Compression.Core/REFERENCE.md | 2 + .../Streams/Bzip2/Bzip2Decompressor.cs | 60 +- Compression.Core/Streams/Bzip2/Bzip2Stream.cs | 17 + Compression.Lib/FormatDetector.cs | 33 +- Compression.Tests/Compression.Tests.csproj | 4 + .../ConversionMatrix/ConversionMatrixTests.cs | 7 +- .../Nsis/NsisReferenceVectorTests.cs | 153 +++ Compression.Tests/Nsis/NsisWriterTests.cs | 63 -- .../Nsis/ReferenceVectors/README.md | 20 + .../Nsis/ReferenceVectors/make-fixtures.py | 36 + .../ReferenceVectors/nsis2-bzip2-solid.exe.gz | Bin 0 -> 25279 bytes .../nsis2-lzma-nonsolid.exe.gz | Bin 0 -> 23913 bytes .../nsis2-zlib-nonsolid.exe.gz | Bin 0 -> 24982 bytes .../ReferenceVectors/nsis2-zlib-solid.exe.gz | Bin 0 -> 25380 bytes .../nsis3-bzip2-nonsolid.exe.gz | Bin 0 -> 26743 bytes .../ReferenceVectors/nsis3-bzip2-solid.exe.gz | Bin 0 -> 27487 bytes .../nsis3-lzma-nonsolid.exe.gz | Bin 0 -> 26174 bytes .../nsis3-lzma-solid-ansi.exe.gz | Bin 0 -> 25683 bytes .../ReferenceVectors/nsis3-lzma-solid.exe.gz | Bin 0 -> 26445 bytes .../nsis3-zlib-nonsolid.exe.gz | Bin 0 -> 27212 bytes .../ReferenceVectors/nsis3-zlib-solid.exe.gz | Bin 0 -> 27598 bytes .../Nsis/ReferenceVectors/oracle.tsv | 45 + .../FileFormat.Nsis/NsisCompression.cs | 10 +- .../FileFormat.Nsis/NsisConstants.cs | 80 +- .../FileFormats/FileFormat.Nsis/NsisEntry.cs | 33 +- .../FileFormat.Nsis/NsisFormatDescriptor.cs | 172 ++-- .../FileFormats/FileFormat.Nsis/NsisReader.cs | 954 ++++++++++++------ .../FileFormats/FileFormat.Nsis/NsisWriter.cs | 54 - .../THIRD-PARTY-NOTICE.NSIS.txt | 29 + Hawkynt.FileFormats.Archives/README.md | 2 +- Hawkynt.FileFormats.Archives/REFERENCE.md | 98 +- docs/catalogue/GAPS.md | 4 +- docs/catalogue/our-registry.csv | 2 +- docs/catalogue/reference-formats.csv | 2 +- 35 files changed, 1225 insertions(+), 673 deletions(-) create mode 100644 Compression.Tests/Nsis/NsisReferenceVectorTests.cs delete mode 100644 Compression.Tests/Nsis/NsisWriterTests.cs create mode 100644 Compression.Tests/Nsis/ReferenceVectors/README.md create mode 100644 Compression.Tests/Nsis/ReferenceVectors/make-fixtures.py create mode 100644 Compression.Tests/Nsis/ReferenceVectors/nsis2-bzip2-solid.exe.gz create mode 100644 Compression.Tests/Nsis/ReferenceVectors/nsis2-lzma-nonsolid.exe.gz create mode 100644 Compression.Tests/Nsis/ReferenceVectors/nsis2-zlib-nonsolid.exe.gz create mode 100644 Compression.Tests/Nsis/ReferenceVectors/nsis2-zlib-solid.exe.gz create mode 100644 Compression.Tests/Nsis/ReferenceVectors/nsis3-bzip2-nonsolid.exe.gz create mode 100644 Compression.Tests/Nsis/ReferenceVectors/nsis3-bzip2-solid.exe.gz create mode 100644 Compression.Tests/Nsis/ReferenceVectors/nsis3-lzma-nonsolid.exe.gz create mode 100644 Compression.Tests/Nsis/ReferenceVectors/nsis3-lzma-solid-ansi.exe.gz create mode 100644 Compression.Tests/Nsis/ReferenceVectors/nsis3-lzma-solid.exe.gz create mode 100644 Compression.Tests/Nsis/ReferenceVectors/nsis3-zlib-nonsolid.exe.gz create mode 100644 Compression.Tests/Nsis/ReferenceVectors/nsis3-zlib-solid.exe.gz create mode 100644 Compression.Tests/Nsis/ReferenceVectors/oracle.tsv delete mode 100644 Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Nsis/NsisWriter.cs create mode 100644 Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Nsis/THIRD-PARTY-NOTICE.NSIS.txt diff --git a/Compression.Core/Deflate/DeflateDecompressor.cs b/Compression.Core/Deflate/DeflateDecompressor.cs index 640504f1d..ca2d93897 100644 --- a/Compression.Core/Deflate/DeflateDecompressor.cs +++ b/Compression.Core/Deflate/DeflateDecompressor.cs @@ -104,6 +104,16 @@ private DeflateDecompressor(byte[] compressed, int outputCapacity) { /// Gets a value indicating whether the final block has been decoded. public bool IsFinished => this._state == State.Done; + /// + /// Gets or sets a value indicating whether stored blocks carry only LEN and no NLEN. + /// + /// + /// The inflate built into NSIS 2.x installers reads a stored block's 16-bit length and goes + /// straight to the data; its compiler writes no one's-complement copy. Set this before the + /// first read to decode such streams. RFC 1951 streams leave it . + /// + public bool StoredBlocksOmitComplement { get; set; } + /// /// Decompresses all data from the stream. /// @@ -320,9 +330,11 @@ private void ReadBlockHeader() { this._bitCnt -= drop; var len = this.GetBits(16); - var nlen = this.GetBits(16); - if ((len ^ nlen) != 0xFFFF) - throw new InvalidDataException("Invalid uncompressed block: LEN/NLEN mismatch."); + if (!this.StoredBlocksOmitComplement) { + var nlen = this.GetBits(16); + if ((len ^ nlen) != 0xFFFF) + throw new InvalidDataException("Invalid uncompressed block: LEN/NLEN mismatch."); + } this._storedLeft = (int)len; this._state = State.Stored; diff --git a/Compression.Core/REFERENCE.md b/Compression.Core/REFERENCE.md index 02761f7a1..ac9593fae 100644 --- a/Compression.Core/REFERENCE.md +++ b/Compression.Core/REFERENCE.md @@ -384,6 +384,7 @@ Decompresses data in the DEFLATE format (RFC 1951). | --- | --- | --- | | `DeflateDecompressor` | `DeflateDecompressor(Stream input)` | Initializes a new `DeflateDecompressor` for streaming decompression. | | `IsFinished` | `bool IsFinished { get; }` | Gets a value indicating whether the final block has been decoded. | +| `StoredBlocksOmitComplement` | `bool StoredBlocksOmitComplement { get; set; }` | Gets or sets a value indicating whether stored blocks carry only LEN and no NLEN. | | `UnconsumedBytes` | `int UnconsumedBytes { get; }` | Gets the number of whole bytes read from the underlying stream but not consumed by the DEFLATE data. Once the stream has ended on a seekable input this is zero, because the read-ahead has already been handed back by seeking. | | `DecompressAll` | `byte[] DecompressAll()` | Decompresses all data from the stream. | | `Decompress` | `int Decompress(Span output)` | Decompresses data into `output`, filling it unless the DEFLATE stream ends first. | @@ -9242,6 +9243,7 @@ Inherits `CompressionStream`. Implements `IAsyncDisposable`, `IDisposable`. | `CompressBlock` | `protected override void CompressBlock(byte[] buffer, int offset, int count)` | Compresses data from the provided buffer and writes it to the inner stream. | | `DecompressBlock` | `protected override int DecompressBlock(byte[] buffer, int offset, int count)` | Decompresses data from the inner stream into the provided buffer. | | `FinishCompression` | `protected override void FinishCompression()` | Called when the stream is being closed in Compress mode. Implementations should flush any remaining compressed data. | +| `OpenNsisDecoder` | `static Bzip2Stream OpenNsisDecoder(Stream stream, bool leaveOpen = false)` | Opens a decoder for the header-less bzip2 dialect embedded in NSIS installers: no "BZh" stream header, a single 0x31 byte before each block, 0x17 at the end, no CRCs. | ### Namespace `FileFormat.Gzip` diff --git a/Compression.Core/Streams/Bzip2/Bzip2Decompressor.cs b/Compression.Core/Streams/Bzip2/Bzip2Decompressor.cs index 0954bd3e9..40f187d3a 100644 --- a/Compression.Core/Streams/Bzip2/Bzip2Decompressor.cs +++ b/Compression.Core/Streams/Bzip2/Bzip2Decompressor.cs @@ -13,6 +13,7 @@ internal sealed class Bzip2Decompressor { private int _currentBlockPos; private bool _finished; private uint _combinedCrc; + private readonly bool _nsisVariant; /// /// Gets whether all blocks have been read. @@ -28,8 +29,13 @@ internal sealed class Bzip2Decompressor { /// Initializes a new bzip2 decompressor. /// /// The bit buffer for input (MSB-first). - public Bzip2Decompressor(BitBuffer bits) { + /// + /// Decode the header-less dialect NSIS installers carry: a block starts with the single byte + /// 0x31 and the stream ends with 0x17, with no CRCs and no randomisation bit. + /// + public Bzip2Decompressor(BitBuffer bits, bool nsisVariant = false) { this._bits = bits; + this._nsisVariant = nsisVariant; } /// @@ -61,6 +67,9 @@ public int Read(byte[] buffer, int offset, int count) { } private bool ReadNextBlock() { + if (this._nsisVariant) + return this.ReadNextNsisBlock(); + // Read 48-bit magic var high = this._bits.ReadBits(24); var low = this._bits.ReadBits(24); @@ -87,6 +96,41 @@ private bool ReadNextBlock() { if (randomized != 0) throw new InvalidDataException("Randomized bzip2 blocks are not supported."); + var blockData = this.DecodeBlockBody(); + + // Verify block CRC + var computedCrc = Crc32Bzip2(blockData); + if (computedCrc != blockCrc) + throw new InvalidDataException( + $"Bzip2 block CRC mismatch: expected 0x{blockCrc:X8}, computed 0x{computedCrc:X8}."); + + this._combinedCrc = ((this._combinedCrc << 1) | (this._combinedCrc >> 31)) ^ blockCrc; + + this._currentBlock = blockData; + this._currentBlockPos = 0; + return true; + } + + private const uint NsisBlockMarker = 0x31; + private const uint NsisEndMarker = 0x17; + + // NSIS keeps bzip2's entropy coding but trims the framing: one marker byte per block, no + // checksums and no randomised blocks (Source/bzip2/decompress.c in the NSIS tree). + private bool ReadNextNsisBlock() { + switch (this._bits.ReadBits(8)) { + case NsisEndMarker: + this._finished = true; + return false; + case NsisBlockMarker: + this._currentBlock = this.DecodeBlockBody(); + this._currentBlockPos = 0; + return true; + case var other: + throw new InvalidDataException($"Invalid NSIS bzip2 block marker: 0x{other:X2}"); + } + } + + private byte[] DecodeBlockBody() { // BWT original pointer (24 bits) var bwtIndex = (int)this._bits.ReadBits(24); @@ -189,19 +233,7 @@ private bool ReadNextBlock() { var rle1Data = BurrowsWheelerTransform.Inverse(bwtData, bwtIndex); // RLE1 decode - var blockData = Bzip2Compressor.Rle1Decode(rle1Data); - - // Verify block CRC - var computedCrc = Crc32Bzip2(blockData); - if (computedCrc != blockCrc) - throw new InvalidDataException( - $"Bzip2 block CRC mismatch: expected 0x{blockCrc:X8}, computed 0x{computedCrc:X8}."); - - this._combinedCrc = ((this._combinedCrc << 1) | (this._combinedCrc >> 31)) ^ blockCrc; - - this._currentBlock = blockData; - this._currentBlockPos = 0; - return true; + return Bzip2Compressor.Rle1Decode(rle1Data); } private bool[] ReadSymbolBitmap() { diff --git a/Compression.Core/Streams/Bzip2/Bzip2Stream.cs b/Compression.Core/Streams/Bzip2/Bzip2Stream.cs index 9b3429f7a..d50645024 100644 --- a/Compression.Core/Streams/Bzip2/Bzip2Stream.cs +++ b/Compression.Core/Streams/Bzip2/Bzip2Stream.cs @@ -33,8 +33,25 @@ public Bzip2Stream(Stream stream, CompressionStreamMode mode, this._blockSize100k = blockSize100k; } + private bool _nsisVariant; + + /// + /// Opens a decoder for the header-less bzip2 dialect embedded in NSIS installers: no "BZh" + /// stream header, a single 0x31 byte before each block, 0x17 at the end, no CRCs. + /// + /// The stream positioned at the first block marker. + /// Whether to leave the inner stream open. + /// A read-only decompressing stream. + public static Bzip2Stream OpenNsisDecoder(Stream stream, bool leaveOpen = false) + => new(stream, CompressionStreamMode.Decompress, 9, leaveOpen) { _nsisVariant = true }; + /// protected override int DecompressBlock(byte[] buffer, int offset, int count) { + if (!this._headerRead && this._nsisVariant) { + this._decompressor = new Bzip2Decompressor(new BitBuffer(InnerStream), nsisVariant: true); + this._headerRead = true; + } + if (!this._headerRead) { ReadHeader(); this._headerRead = true; diff --git a/Compression.Lib/FormatDetector.cs b/Compression.Lib/FormatDetector.cs index c97d2a111..25df6b418 100644 --- a/Compression.Lib/FormatDetector.cs +++ b/Compression.Lib/FormatDetector.cs @@ -504,10 +504,12 @@ private static Format DetectInstaller(string path) { var buf = new byte[2]; var isPe = fs.Read(buf, 0, 2) >= 2 && buf[0] == 'M' && buf[1] == 'Z'; - // NSIS/InnoSetup markers can live anywhere in the file (our own stub-less - // NSIS writer puts the "NullsoftInst" signature in a leading overlay - // header, not a PE overlay), so scan the whole file when it is small and - // the tail otherwise — independent of the MZ check, so a stub-less + // An NSIS first header sits right after the exe stub, at a multiple of 512 bytes (the + // exehead itself only looks there), so a large installer keeps it far from the tail. + if (HasNsisFirstHeader(fs)) return Format.Nsis; + + // Other installer markers can live anywhere in the file, so scan the whole file when it + // is small and the tail otherwise — independent of the MZ check, so a stub-less // installer image still re-detects as its own format. var scanLen = (int)Math.Min(fs.Length, 65536L); if (scanLen > 0) { @@ -516,7 +518,6 @@ private static Format DetectInstaller(string path) { var tail = new byte[scanLen]; var bytesRead = fs.Read(tail, 0, tail.Length); var tailSpan = tail.AsSpan(0, bytesRead); - if (ContainsBytes(tailSpan, "NullsoftInst"u8)) return Format.Nsis; if (ContainsBytes(tailSpan, "Inno Setup"u8)) return Format.InnoSetup; // PyInstaller "onefile" build: the CArchive MEI cookie sits near EOF, // after the PE image (and before any appended Authenticode signature). @@ -557,6 +558,28 @@ private static Format DetectInstaller(string path) { private static bool ContainsBytes(ReadOnlySpan haystack, ReadOnlySpan needle) => haystack.IndexOf(needle) >= 0; + /// + /// Looks for NSIS's firstheader (flags with only the low four bits in use, 0xDEADBEEF, + /// "NullsoftInst") at 512-byte boundaries in the first 8 MiB. + /// + private static bool HasNsisFirstHeader(Stream fs) { + const int alignment = 512; + var limit = Math.Min(fs.Length, 8L << 20); + var chunk = new byte[64 * 1024]; + for (long start = 0; start < limit; start += chunk.Length) { + fs.Position = start; + var read = fs.ReadAtLeast(chunk, chunk.Length, throwOnEndOfStream: false); + for (var i = 0; i + 20 <= read; i += alignment) + if ((chunk[i] & 0xF0) == 0 && chunk[i + 1] == 0 && chunk[i + 2] == 0 && chunk[i + 3] == 0 + && chunk[i + 4] == 0xEF && chunk[i + 5] == 0xBE && chunk[i + 6] == 0xAD && chunk[i + 7] == 0xDE + && chunk.AsSpan(i + 8, 12).SequenceEqual("NullsoftInst"u8)) + return true; + if (read < chunk.Length) + break; + } + return false; + } + /// The 8-byte PyInstaller CArchive magic cookie (MEI + version bytes). private static readonly byte[] PyInstallerCookie = [(byte)'M', (byte)'E', (byte)'I', 0x0C, 0x0B, 0x0A, 0x0B, 0x0E]; diff --git a/Compression.Tests/Compression.Tests.csproj b/Compression.Tests/Compression.Tests.csproj index 59dbe59f1..74893a303 100644 --- a/Compression.Tests/Compression.Tests.csproj +++ b/Compression.Tests/Compression.Tests.csproj @@ -71,6 +71,10 @@ BcacheFsVectors.%(Filename)%(Extension) + + + NsisVectors.%(Filename)%(Extension) + MobiVectors.%(Filename)%(Extension) diff --git a/Compression.Tests/ConversionMatrix/ConversionMatrixTests.cs b/Compression.Tests/ConversionMatrix/ConversionMatrixTests.cs index 0fa582c80..664cfe99d 100644 --- a/Compression.Tests/ConversionMatrix/ConversionMatrixTests.cs +++ b/Compression.Tests/ConversionMatrix/ConversionMatrixTests.cs @@ -37,10 +37,9 @@ public sealed record Pair(string SourceId, string TargetId) { new(StringComparer.OrdinalIgnoreCase) { "D64", "D71", "D81", "T64", "Cpm", "Atari8", "AppleDos", "ProDos", "Bbc", "ZxScl", "TrDos", "Mfs", - // Mfs1 folds names to 7-char DFS slots (HELLO.TXT -> HELLO.T); Nsis stores - // no per-block names and the reader exposes block_N. Both extract the exact - // stored bytes, so they round-trip by content, not by verbatim name. - "Mfs1", "Nsis", + // Mfs1 folds names to 7-char DFS slots (HELLO.TXT -> HELLO.T); it extracts the + // exact stored bytes, so it round-trips by content, not by verbatim name. + "Mfs1", }; // Case-folding targets (8.3 / uppercase) — compare names case-insensitively diff --git a/Compression.Tests/Nsis/NsisReferenceVectorTests.cs b/Compression.Tests/Nsis/NsisReferenceVectorTests.cs new file mode 100644 index 000000000..5f385f0c1 --- /dev/null +++ b/Compression.Tests/Nsis/NsisReferenceVectorTests.cs @@ -0,0 +1,153 @@ +using System.IO.Compression; +using System.Security.Cryptography; +using Compression.Lib; +using FileFormat.Nsis; + +namespace Compression.Tests.Nsis; + +/// +/// Real installers compiled by makensis 2.51 and 3.10 (see ReferenceVectors/README.md), checked +/// against what 7-Zip extracts from them (oracle.tsv). +/// +[TestFixture] +public class NsisReferenceVectorTests { + + private static readonly string[] Fixtures = [ + "nsis3-zlib-nonsolid.exe", "nsis3-zlib-solid.exe", "nsis3-bzip2-nonsolid.exe", "nsis3-bzip2-solid.exe", + "nsis3-lzma-nonsolid.exe", "nsis3-lzma-solid.exe", "nsis3-lzma-solid-ansi.exe", + "nsis2-zlib-nonsolid.exe", "nsis2-zlib-solid.exe", "nsis2-bzip2-solid.exe", "nsis2-lzma-nonsolid.exe", + ]; + + private static byte[] Load(string name) { + using var resource = typeof(NsisReferenceVectorTests).Assembly.GetManifestResourceStream("NsisVectors." + name + ".gz") + ?? throw new InvalidOperationException($"Missing embedded fixture {name}."); + using var gz = new GZipStream(resource, CompressionMode.Decompress); // BCL as the test-side oracle + using var ms = new MemoryStream(); + gz.CopyTo(ms); + return ms.ToArray(); + } + + private static Dictionary Oracle(string fixture) { + using var resource = typeof(NsisReferenceVectorTests).Assembly.GetManifestResourceStream("NsisVectors.oracle.tsv")!; + using var reader = new StreamReader(resource); + var result = new Dictionary(StringComparer.Ordinal); + while (reader.ReadLine() is { } line) { + if (line.StartsWith('#') || line.Length == 0) + continue; + var f = line.Split('\t'); + if (f[0] == fixture) + result[f[1]] = (long.Parse(f[2], System.Globalization.CultureInfo.InvariantCulture), f[3]); + } + return result; + } + + private static string Sha(byte[] data) => Convert.ToHexStringLower(SHA256.HashData(data)); + + [TestCaseSource(nameof(Fixtures))] + public void GivenRealInstaller_WhenListed_ThenNamesAndSizesMatch7Zip(string fixture) { + var oracle = Oracle(fixture); + Assert.That(oracle, Is.Not.Empty); + + var entries = new NsisFormatDescriptor().List(new MemoryStream(Load(fixture)), null); + + Assert.That(entries.Select(e => e.Name), Is.EquivalentTo(oracle.Keys)); + foreach (var e in entries) + Assert.That(e.OriginalSize, Is.EqualTo(oracle[e.Name].Size), e.Name); + } + + [TestCaseSource(nameof(Fixtures))] + public void GivenRealInstaller_WhenExtracted_ThenEveryFileMatches7Zip(string fixture) { + var oracle = Oracle(fixture); + var dir = Path.Combine(Path.GetTempPath(), "cwb-nsis-" + Guid.NewGuid().ToString("N")); + try { + new NsisFormatDescriptor().Extract(new MemoryStream(Load(fixture)), dir, null, null); + var written = Directory.GetFiles(dir, "*", SearchOption.AllDirectories) + .ToDictionary(p => Path.GetRelativePath(dir, p).Replace('\\', '/'), p => Sha(File.ReadAllBytes(p))); + Assert.That(written.Keys, Is.EquivalentTo(oracle.Keys)); + foreach (var (name, sha) in written) + Assert.That(sha, Is.EqualTo(oracle[name].Sha), name); + } finally { + if (Directory.Exists(dir)) + Directory.Delete(dir, recursive: true); + } + } + + [TestCase("nsis3-zlib-nonsolid.exe", NsisCompression.Zlib, false, true, true)] + [TestCase("nsis3-bzip2-solid.exe", NsisCompression.BZip2, true, true, true)] + [TestCase("nsis3-lzma-solid-ansi.exe", NsisCompression.Lzma, true, false, true)] + [TestCase("nsis2-zlib-nonsolid.exe", NsisCompression.Zlib, false, false, false)] + [TestCase("nsis2-bzip2-solid.exe", NsisCompression.BZip2, true, false, false)] + public void GivenRealInstaller_WhenOpened_ThenCompressorAndDialectAreRecognised(string fixture, NsisCompression method, bool solid, bool unicode, bool nsis3) { + using var reader = new NsisReader(new MemoryStream(Load(fixture))); + + Assert.Multiple(() => { + Assert.That(reader.Compression, Is.EqualTo(method)); + Assert.That(reader.IsSolid, Is.EqualTo(solid)); + Assert.That(reader.IsUnicode, Is.EqualTo(unicode)); + Assert.That(reader.IsNsis3, Is.EqualTo(nsis3)); + Assert.That(reader.FirstHeaderOffset % 512, Is.Zero); + }); + } + + [Test] + public void GivenSolidInstaller_WhenOnlyTheLastFileIsExtracted_ThenItMatches7Zip() { + var oracle = Oracle("nsis3-lzma-solid.exe"); + using var reader = new NsisReader(new MemoryStream(Load("nsis3-lzma-solid.exe"))); + var last = reader.Entries[^1]; + + var data = reader.Extract(last); + + Assert.That(Sha(data), Is.EqualTo(oracle[last.FileName].Sha)); + } + + [Test] + public void GivenRealInstaller_WhenListed_ThenFileTimesAreReported() { + var entries = new NsisFormatDescriptor().List(new MemoryStream(Load("nsis3-lzma-nonsolid.exe")), null); + Assert.That(entries.All(e => e.LastModified is { Year: >= 2020 }), Is.True); + } + + [Test] + public void GivenRealInstallerOnDisk_WhenDetected_ThenNsisIsChosen() { + var path = Path.Combine(Path.GetTempPath(), "cwb-nsis-" + Guid.NewGuid().ToString("N") + ".exe"); + try { + File.WriteAllBytes(path, Load("nsis3-zlib-solid.exe")); + Assert.That(FormatDetector.Detect(path), Is.EqualTo(FormatDetector.Format.Nsis)); + } finally { + File.Delete(path); + } + } + + [Test] + public void GivenInstallerCutInsideItsData_WhenListed_ThenAPartialMetadataEntryIsReturned() { + var full = Load("nsis3-lzma-solid.exe"); + using var probe = new NsisReader(new MemoryStream(full)); + var cut = full.AsSpan(0, (int)probe.FirstHeaderOffset + 40).ToArray(); + + var entries = new NsisFormatDescriptor().List(new MemoryStream(cut), null); + + Assert.That(entries.Select(e => e.Name), Is.EqualTo(new[] { "metadata.ini" })); + } + + [Test] + public void GivenFirstHeaderOffTheAlignment_WhenOpened_ThenNoInstallerIsFound() { + var full = Load("nsis3-zlib-nonsolid.exe"); + var shifted = new byte[full.Length + 1]; + full.CopyTo(shifted.AsSpan(1)); + + Assert.Throws(() => _ = new NsisReader(new MemoryStream(shifted))); + } + + [Test] + public void GivenNoStream_WhenOpened_ThenArgumentNullException() + => Assert.Throws(() => _ = new NsisReader(null!)); + + [Test] + public void GivenEmptyStream_WhenOpened_ThenInvalidDataException() + => Assert.Throws(() => _ = new NsisReader(new MemoryStream())); + + [Test] + public void GivenUnknownEntryName_WhenExtractedToMemory_ThenNoBytes() { + var bytes = new NsisFormatDescriptor().ExtractEntryToMemory(new MemoryStream(Load("nsis3-zlib-solid.exe")), "nope.txt", null); + Assert.That(bytes, Is.Empty); + } +} diff --git a/Compression.Tests/Nsis/NsisWriterTests.cs b/Compression.Tests/Nsis/NsisWriterTests.cs deleted file mode 100644 index e67057433..000000000 --- a/Compression.Tests/Nsis/NsisWriterTests.cs +++ /dev/null @@ -1,63 +0,0 @@ -using FileFormat.Nsis; - -namespace Compression.Tests.Nsis; - -[TestFixture] -public class NsisWriterTests { - - [Test, Category("HappyPath")] - public void Descriptor_ReportsWormCapability() { - var d = new NsisFormatDescriptor(); - Assert.That(d.Capabilities.HasFlag(Compression.Registry.FormatCapabilities.CanCreate), Is.True); - } - - [Test, Category("HappyPath"), Category("RoundTrip")] - public void Writer_SingleBlock_RoundTrips() { - var payload = "nsis test block"u8.ToArray(); - var w = new NsisWriter(); - w.AddFile("block_0", payload); - using var ms = new MemoryStream(); - w.WriteTo(ms); - ms.Position = 0; - - var r = new NsisReader(ms); - Assert.That(r.Entries, Has.Count.EqualTo(1)); - Assert.That(r.Extract(r.Entries[0]), Is.EqualTo(payload)); - } - - [Test, Category("HappyPath"), Category("RoundTrip")] - public void Writer_MultipleBlocks_AllRoundTrip() { - var p1 = "first"u8.ToArray(); - var p2 = new byte[] { 0xDE, 0xAD, 0xBE, 0xEF }; - var w = new NsisWriter(); - w.AddFile("a", p1); - w.AddFile("b", p2); - using var ms = new MemoryStream(); - w.WriteTo(ms); - ms.Position = 0; - - var r = new NsisReader(ms); - Assert.That(r.Entries, Has.Count.EqualTo(2)); - Assert.That(r.Extract(r.Entries[0]), Is.EqualTo(p1)); - Assert.That(r.Extract(r.Entries[1]), Is.EqualTo(p2)); - } - - [Test, Category("HappyPath"), Category("RoundTrip")] - public void Descriptor_Create_RoundTrips() { - var tmp = Path.GetTempFileName(); - try { - File.WriteAllBytes(tmp, "nsis descriptor"u8.ToArray()); - var d = new NsisFormatDescriptor(); - using var ms = new MemoryStream(); - ((Compression.Registry.IArchiveCreatable)d).Create( - ms, - [new Compression.Registry.ArchiveInputInfo(tmp, "data.bin", false)], - new Compression.Registry.FormatCreateOptions()); - ms.Position = 0; - var entries = d.List(ms, null); - Assert.That(entries, Has.Count.EqualTo(1)); - } finally { - File.Delete(tmp); - } - } -} diff --git a/Compression.Tests/Nsis/ReferenceVectors/README.md b/Compression.Tests/Nsis/ReferenceVectors/README.md new file mode 100644 index 000000000..3415297e5 --- /dev/null +++ b/Compression.Tests/Nsis/ReferenceVectors/README.md @@ -0,0 +1,20 @@ +# NSIS reference installers + +Real NSIS installers, compiled by the official NSIS compilers, never by CompressionWorkbench: + +| Compiler | Source | +| --- | --- | +| `makensis.exe` 3.10 | `nsis-3.10.zip` from https://sourceforge.net/projects/nsis/files/NSIS%203/3.10/ | +| `makensis.exe` 2.51 | `nsis-2.51.zip` from https://sourceforge.net/projects/nsis/files/NSIS%202/2.51/ | + +`make-fixtures.py` writes the payload (two copies of a short text, 3000 seeded random bytes so that +Deflate emits stored blocks, 32000 bytes of a repeated pattern) and one script per variant, then runs +the compiler: `python make-fixtures.py `. The file name says +compiler major version, `SetCompressor` and `/SOLID`; `-ansi` is `Unicode false` under NSIS 3 (NSIS 2 +is always ANSI). Each installer is stored gzip-compressed; nothing else was changed. + +`oracle.tsv` is what 7-Zip 26.03 (x64) extracts from each installer (`7z x`): path, size and SHA-256 +per file. The tests require our reader to produce exactly that set. + +NSIS is released under the zlib/libpng licence; the installers contain only the NSIS exehead stub +and the payload above. diff --git a/Compression.Tests/Nsis/ReferenceVectors/make-fixtures.py b/Compression.Tests/Nsis/ReferenceVectors/make-fixtures.py new file mode 100644 index 000000000..19c94726b --- /dev/null +++ b/Compression.Tests/Nsis/ReferenceVectors/make-fixtures.py @@ -0,0 +1,36 @@ +import os, random, subprocess, sys +W = sys.argv[1] # work dir +NSIS3 = sys.argv[2] # makensis 3 path +NSIS2 = sys.argv[3] # makensis 2 path +os.makedirs(os.path.join(W, 'payload', 'sub'), exist_ok=True) +os.chdir(W) +open('payload/readme.txt', 'wb').write(b'Hello from an NSIS fixture.\r\nLine two.\r\n') +open('payload/sub/readme-copy.txt', 'wb').write(b'Hello from an NSIS fixture.\r\nLine two.\r\n') +r = random.Random(1234) +open('payload/sub/random.bin', 'wb').write(bytes(r.getrandbits(8) for _ in range(3000))) +open('payload/sub/repeat.txt', 'w').write('abcdefgh' * 4000) + +def script(comp, solid, uni, out): + lines = [] + if uni: + lines.append(f'Unicode {uni}') + lines += ['Name "fixture"', f'OutFile "{out}"', 'RequestExecutionLevel user', 'InstallDir "$TEMP\\nsisfixture"', + f'SetCompressor /FINAL {"/SOLID " if solid else ""}{comp}', 'Page instfiles', 'Section', + 'SetOutPath "$INSTDIR"', 'File "payload\\readme.txt"', 'SetOutPath "$INSTDIR\\sub"', + 'File "payload\\sub\\random.bin"', 'File "payload\\sub\\repeat.txt"', 'File "payload\\sub\\readme-copy.txt"', + 'SectionEnd'] + return '\r\n'.join(lines) + '\r\n' + +jobs = [] +for comp in ('zlib', 'bzip2', 'lzma'): + for solid in (False, True): + tag = 'solid' if solid else 'nonsolid' + jobs.append((NSIS3, comp, solid, 'true', f'nsis3-{comp}-{tag}.exe')) + jobs.append((NSIS2, comp, solid, None, f'nsis2-{comp}-{tag}.exe')) +jobs.append((NSIS3, 'lzma', True, 'false', 'nsis3-lzma-solid-ansi.exe')) +jobs.append((NSIS3, 'zlib', False, 'false', 'nsis3-zlib-nonsolid-ansi.exe')) +for exe, comp, solid, uni, out in jobs: + nsi = out[:-4] + '.nsi' + open(nsi, 'w').write(script(comp, solid, uni, out)) + p = subprocess.run([exe, '-V1', nsi], capture_output=True, text=True) + print(out, p.returncode, os.path.getsize(out) if os.path.exists(out) else -1, p.stdout[-300:], p.stderr[-300:]) diff --git a/Compression.Tests/Nsis/ReferenceVectors/nsis2-bzip2-solid.exe.gz b/Compression.Tests/Nsis/ReferenceVectors/nsis2-bzip2-solid.exe.gz new file mode 100644 index 0000000000000000000000000000000000000000..6219154a29ac17c7592dbcfa7fddf88d57413441 GIT binary patch literal 25279 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 WGsmz0R%U%GZ1P|8>&=l5l>h+X!y>`} literal 0 HcmV?d00001 diff --git a/Compression.Tests/Nsis/ReferenceVectors/nsis2-lzma-nonsolid.exe.gz b/Compression.Tests/Nsis/ReferenceVectors/nsis2-lzma-nonsolid.exe.gz new file mode 100644 index 0000000000000000000000000000000000000000..14d0b8b7ed9c6dd7a1d4b536230440a900322dd4 GIT binary patch literal 23913 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 I^E993m6Qgs2Qm z7&3!3AdyU*j3J51-drq!q?2fd%^>x5wC!o3*dE_gdrDi2v}#O%l7Oh;Qj7&PDzENw zQjL;^0aEt&`>(wx#GcdhzR&l3-}AlC_cX9(?{)vL|N39oWybZL9h{EiI6Z%U%viPQfuk$XAit?Tz`7Qc1<@<2_KwV|>8p~lL!)-{!Nb@jaULBF+8sI%78 zS&Nr?tZVD5{BzRMQnS@IewjO~HCk43JP~eLbTM|8u|GKmYze`70L(LxUBeW|NuE!Dor+%n;H-rCAm+ZPwrd zE?la2%YsD~OtN5aM1-Cj`MiMyj#7!O22~d?MtO^aah5dE0tfmP4CY$h==(H&@OqMXmNO)Zxp;W>Zk(>v5hXZt8JL zMv{z~#V8=JTj7&47dNoB#5@OHeDWz}zyT}Dxr1`RdUgR)Gxe-T6Y^QVz>|U!H_{(i zW$U{C@O}#?F3#5PDg_$FW1gDhSzueQPh74wj_lX+OCyhHY_dIXF|wN$h;P>%ru;~; z#wZ#`y0yG+QS7>rZatqOzO%IbFmH`nM!GG+1o1uFpF^K$Z107$N(@v(X`%7R1FP=e zuczrQD-+9&n7^-UMkrsLMgZ6j>(2SYu2G;+EHS>QHnE zbTJ?{Wf>z2G@_Z{NDXS1Nk0QNDXJG{jdVMBo$MSP>DKTDJY$H&?FMQ=>}!0@)&)Qg z?>BOx=24D67ix}kd{StM(GmMhTw-kbXiMm@CcMNLdv6RkUfW3Mg0*RG0UOA=*%7zV zCu(0z5+>Q?MJGOMe{_`Bn=~zeF~9e~DrLx_LDMdx){uV`lxuhzGwKry zsZ7`z7oBn2>4Gf&Ui*hlPBD+V3>`9xSB>mX5@v}`V|y0>ocA2ZPZ3?Cwk}8LW6g;H zamnb6Ua=b~CqB0C7bYxhJ;XZxuDNFua8?cT4;5 z(%vQL9VM~kQt@qZD5pELDci*H-_jUNpuHd`=M8_dfD7b8BguJ`BxmM>5#0eTAe8y+ zEID%s@%EACCv9D;Fcs9~me^kjIx&s#gf{rrTB!!7aT>>3erFP=V3b!5={i z>>?2XQWA{5VDf#D=96K<<+-1U&VI4{j5s7-?EW;BjWxV-wEMGE-B-E`5m$dWE&oRl z46c)*?#rPIMdruzpp4|~NZ%|6=W$C@(M$AaX-F~*Vju-8nW! zl>@>JV#iJzwkF&$fTwg0@Q8p@AED&vvnVODoG>dFZKnk1KxA$^MQvT;MEjfOr@Lmj z&WgqD{o#qxdg^KJGnDI%%22st+7N~!=2c(-_BZ+DWo7ay!cv+Z^MlH5x1)v|iP4+! z5*(5ugc9KlK;Ohfleyi3jOZB)%dIB><34Fy5uQW#bNupPg;&aCv8_oR*WG!b!#P2? z3S;BbrCE1zXn;5Rq_#qiizWG_3wYWO@R^0LfU<-M5LX+}$%__&75KTboD{G|Xl57tAl zPa;$YKNN?gSLkK37?=J7FFvoF*}xj&lY^f`Q&1TIo7>`M$U$jlDM4*+``k<=rJ4n( zc{X}Bm3n9({0~K(MmNABsVQgHDh$Ebb4f9cetQYWz2SHPs;t?l+jN^(x(Y+F9mZfR5!cyJS4J5qqXpoIy%=$*>u+gf zZ_VgF-#?|xf&L0s8igdU^slh1Y+YzWV}1sF5FlgAEB)L-ydi^Gg>_WW6Cf7#Nv|wK z*X&(-X(5r{AY*;08xfz9{`5kk{QmB^&aLDX&qG_1-rVMauqfrHc!L+CcaGv+7!H0c zmEFP72(-~ckdSP6k(j5Xjxj*oVE64lgld@C&abgL(oM7{cD@ zA{e5c*j!B0E$LLQ-z~M+iA>CGTYyBn6BKxlhF&Cd`;W{t+`;Hi%#7{g7KJIL-=Icx z3ngl#Pb@TT-$xJ8AAp2|TIpqCQXS3uztfur^xkP<#2t3_bzH}%0O{s7hnec#hkEu) znB=&*V-8a6@y%KhI;l_B1FD*dT(Q}RA-1I>v)SyGW+6!z-)IQ0G>Vs6Wui@tebIkVcm}PKo4UOX{I-a7E4jVGBsud%M!iA zo5D|%NrX)HDi)qn-wj{OoJNBLol zDs~-SQk04pW9c?o<@{LEiO*u%6S6P*z(p{uMzSV=xd`CgB$wtwPKOSfqCZSyl3pxy z>~WANDK4eC_$$PkINyFi_=;kmAr|VaLWH>3DN6+Z6 zi6~C86K-Mo#--cvHsT!AU@l>y*`&J=M>>+oVk?&Bu(&bQeDeCBMktmv6IBevX~3?* z?h9J}x=8u(vN8|Ny%2XpI?e`CQ~@&@n|~#ptcloc>CdPdoP;)x;bp6FLyqq}?t%RM!1uOf_+vb@Vt480A%d$t>M2cO`bTl$4+qN7sLv)^r-e6)} zwTf~vSze`7KA8eH!|2`>fV9xS=Lm*Z`#Q-b{1+S~Gx=8)ljBxpL)R!}-pwAuQ=YP@%(-G|Sg zeupY9g6a+wGom05C0dY7?6%ybvfI5TVz;wk?+J1ynHJGlD)BYZV03_~N~cK`1%&G0 z4Pw7!qnAuf(=GHeC`}uY<*muGaF!;DDI`qR)+CB~W*kxkZWTFq@$1bI5sgNDMrt`W zfwY`A7?U2(C6)DF9!XIUmAUN>Ba?P~iI^vmh)E8br5m9jgH=QXrwX;eEgvJ7jR_VG zt;5#XGy$aGlb(P^iJ1yk4)e(>zp_Fk>Z7JqMhXoy8IsPScF0c_`{kw#pR^oR<)*A? z2ZjU@H9h)E;4Aorv&qAT5|C z%|?~r#IUnhy1~H!3OkPiS};eST;2;od3(fp5E?4(4p??L7{ujAkvs+7h1>vr|9d+L z61?AJizoWf5?3_6qH2R9;{OOjaic7<~L| zxHZ#=e1~ISd!)^T<#eD?pqClo-dZEWolzQ{$8eYC30{ysnho~cr)Q*w%olo`FVfPS zbPkS@ucIPYBSrDnQIW4fx7DHxf-fw*6mH%Vc6L^WT`x;JXA|~cfCO9ph$4lbhjxqY zE=(KxbBgmA-JC?G+{;~!IDApf*OZss#318B@xkOZ^bEKDhoW|>yZ04laWL2?jpqKZFhU%z% zfJ~4G%{XR-^b?VOlHF;H-Uo6=A3_d%agZop5z;M#WYlzk zlSSaTq|irN(1CD9`Wuy$7BG-WVMh!=?|nK-{Fo92i6(L|s3nk2ldqFb)O;+7c@Ca3 zF|Cg**~3~-naAUC`x5djGfQHc z5-UQNb%Hs-L2mmXdCp32rINXV!6Vkoq~uh^M$@U{%~5}3n1&SAo186ftSc}SmI(TMGE5!#sx<6X~RQ{Xc4No2mGQ9s2!=A<=J^mI<`qQ?FsPY%23ouh}#U z*8oopbu7uEqUlp$O))rq%4|h-pz(aPbxB96M?k;sX#WKe&0i#sf#lc#v(sO*F;BTPZog}#XMs0Zre zTu=i-8^+)jA>j`-AJqsCK;#P7w=_>k<8$zt%Fo>DJlCPWtAx4ryu&WUH+9Ae%zK^Z zU|>FgmZPJ8r=010R;0ZO{x2Z9@8x# z*J>DsQmu}eA7P-zL9y&@s z#q{TQJ<_ohLPeiXdX~Kmz!o?~q9ZIE<>w;q^^jEXft6xe;zY4TA39_#)Ahh*a_y7S z(T`5pmw%mb796Qq@h}R=W$p7Z<|NwjDH-JO8flRsne6c4F$)J~Pw?ET=E=b?R}o8R zPDK?JC*J617syKX0yWBdDh(-g1jwPqh5=XdoB}Co#kf`W0~Dj@#g^2BRz$pvDHvqpEHv912jdfjEYPWGiiyD zTHp3PT8WL_sVq9mFSoLVS!o$yh|Lt!cJ5igd1myM1t%!JJ|o9|C>4*g2*3>aQ4L#F zP?weU+~HXcZbycL`w_w`2uBc3Aq*irl8dJ%qr@MnZOW}pnhcd{Mas|fEPe1%Xj6EGv( zdZU9|gzyN$uMqa!ggO{^ry8C|cowkz0O7|7KSTHp!d`?wAao-1ARI&Z0AU2-B7(92 zOl!09+}0o`9lnhuV%BX})~DM?eBz;I?fHH&lP%%)hCVWP$My2ut=$8u26BMi(o-nk zJ|f1OlYCMSrc&%lEW@`Bn%LG+#5wA7?;_P`GTKhV=Tdiw2%e7E2CRvDY1}DpS|>BfQ9P>EjU;=z+<_#$!nxm_MGJwn=j64G z6ksO#tX-uhGate5kw zgxAdFs2Vi5cT*+B30-x74Re;qYdh@=L+8O3VQqAB6$=#;*&vae_UxX`n%G8DrXz9P z;+0ZnleHC+<#6+GxG)3b6H@o-zYjgrPab(Ex0qH;P2VCp@3@7mKyLLyPPzn7#RK8m z!DwhVAxs=HZ~qL9zUl^OiqyYMvoVwQpAEFDZyy3m<35k{bDiSY+(1&Tkcx7`qrs^W zXTQfS<>#<9&;Kz*tD23i&1b&s{N)EiO>47scU==NtZr>S+f-l83%=Iofh9t1t*|7; zNoKt=ccB4oilK`-cMjPPh0~~U^AqH&j5v)(Aqk&)p-6U)MjA%#m)A`aFUKY#O$+tF zNlQUh_u2DkG|g#b^Tnm~+u2HvrjQ!XIp02Fe!>dCY^P}*i)8UM>ft`#Qvl!Z204$4 zNpUSRl!JP=^s1Ka2EfUQ!%`Mz%fev7HG3A z9Wf7j+6xtYU+l{fXTK)QHiE^Z-T8R%NwQ%cZKW6^CB^rJB%>zxZUKG|$!ygG375dkshu74Dw>a z&Y^v|N5O=K9q@{2-*H`3Z!_q2hdz69b707GMZslWxP>}ENI*N z)SgGtz|BHNxUhwm>hjw5&}R5B@E%|Zg&p|Zy^WT6e!&cwuv0Q`dy!n!Jj%*BFP|7; ztK%9m&M(Yy4i(aYoAofnGdRJ<{7+*J23zGG69_K`tl$5pKzI@2Njh>JXr_6{)@2{! z=NC9Ff)!;4;R9h#XnTCFVtv{Qgh64BMi`Av+(i)J?Ph^Y)=;ygF}KeLj$%TAGsFCZ zo?e5&(b%-$<=8dtGN?^O0*#+z=2&U)Z!2PVi$eVG@l(L^`GJVj5`?+w8xO)ByI>K1 z!8*>Mac+4drV9(Bi7iwMQ3$9512a)w5_WkcBgQP~)B?dI7|5pA+PYS)+;8N9Y&$dC zlqg6vNcyakTTYTB`&N=9Q*UH2YgZlcO1o!L%Aamvn;~w~3a%Phhl;bata9caBCyPt@L64$#PR7K>C0Q0E=p#eo7~sg(nt=) z!?&^pMkBz9@JSljjj>>wbO5Qr3DRqL?9OJ=F`vvuIkOg(cGE(UCsLf9A6QKejVG{T z2a~#JBTxY_*y#i5%bUr^G$HYI&aselmrL*8%(hQy?Wm=Y(r(yI(p&takP4_XZrGe520 z7X$M%0rwj;Ps%=@oVgRokcuud3m`jiN%07Ogv7vd4BsbNP(iw$<(u1D$@`@lRP1Ql z`j#qTL)#BvWER0+FpWeN@A8yOf1W|!L@cHCF6#sH3wgs5`(K;R+5d{^wUF*CYMhJOF$aAluw#)~I_? zgaJu^J-I4!=5sW#%~oVBrh$k9!fGY^6Pb)p&>hVf%0xfE_Y*uYMRg(@)XM{h&tvEM?p3AH& zm+%9RPkJVscC?w!qLm|a+rN-8WRTu~S`Q|PlC+mzl7Wj~;>8|=7%T-wis7s|zywxz zaF(38gBIT9KLL>0NSoyj%>F*{%P9}>$!yxT<+JBv9_Nt{g=Z-(h{^8;jnWEg^gySU z)3@*@Z*ZmuN~#a-qUyc=4=-U6-u9|4lUYVw9pSI_iI>GY zXqqKNN6wv!cIBnMv zwq&yLBg|p!o;P;xgkbV{0@+PqMY5AYQpnET0Crr2BKgy(gm9I|7g#_=MuFV`xVJ^R zIh$=RQ(GPgUTF#DXBU|guM@q72CU_~=@>< zxC1S0Dbhr6ZJ_CsE}bS~;f+D$hP&8+1 z;4HZ%FsDR6KgDtLG*x-yts z0oJcR&9-PeM`c+&6wC2Q?_9-1a8|)IIvr$gUn0|DoOBn;MHk4d@a=dfn{ws-p@SI* zNM*l41okQ&aP~=yZXg(|&|s}3TO7x`Ny)<_T$2&>efr}4U%7_)xX$o+v(U(!kME4-76Zm zPkNnX{AEQq3pbE@Rk28J+Ael7j;-1-zKn}WWX8DJatwj0oGe=^d`9Cr5E``no}5RI z2#cfKYk$vYZnKlec*#g70ouMrM((EUY};Q#mlg?=gOkhg=Sy_hMr&=C3_=zNV|FPy zLo*Jz$Iof&r8G1nTr20r*({M`NM(0ZG&+jmg)XHF>C`0KWrd*?ouH@g)6ucHofg>4 zukhKkVb&@8u5tU{_-W-qNuJa6#vd&7sw8Q^Bn-fd@Cw56ZAD zwGY_O3v-b~YC6*=edj&6g@u8q!ND7(9}})~A()EaV=``LkAZU7=GMR%trJ*@!3-%z z0L$=5gLHmjDXk)6e1tOld%N36NV&v3f;_14g$wC`n|vy$_3eIcCwr5FQ+>NvLsCjk z9komx9sC+2+VlupcX*{=mUk0j0yC^RF^vXf`-}Kh_oqp^Jo0($Z*96O6xRs1mCNfa zlCGa|U3O4Y9x&^_39YTG;8sTEPj^LMpk?zp=C&W;86ccXW z0DiGsqGjIDCwd63)y$BkEc^Sst~{mMvU)%aiAga^ zb*GpK+5WF&odD_2T&Cl~dV?@c>CN0u>oWzn?qu)l=?tQF9HlAiDGkpppc&i#M-uZT zdapdUeGl+nEhbg#3?xQ;q({duq}cz+7|q=FB#my}>|l~xdY@y0-r^2iRLosMIndES zjsvA5X$K)Cv%H)vI|}6qK#1WnAd~d3e@f7SP!TjHvY8k+5G@@bwwXR2emRfWDSRuj z0J_*l7T~S4CM!%SxRr|WlY^-~rHvi~Wf1En#I6OtT4+&Ma?W7-+&>Kz0$wz+#UY{sVu^{)JE})X*bxYJ zWIbGBLKbscX-N{mvK6E%Vhh2d>n&rgz03~2nWPz0X`?bDtkD>%nANsYag8xRb#c+- z^_kn#NExim5Wkz-E;px(X{3@O`q$}tNKUU!QB#yke@r{k6KDdsb}RBwNjG6G@7>14HCkrYcLT@(H(mN;CsWp^q-R zC-jERl8g4IE^u`8(X_3YqFg3uOAnvl*WLeZ6q%0u!LVInmdv?S7KRpmOMkdzOxHDL*D z`jiVI(hs2-)K!{LAqh!t7fE5ku!}es@M)OPRymRuiB(#qY$>s1 z9_=WQlj=_3PTHGJzNpgK9CWs7jOrKfqFM5UooBt`ig}_Vzx$Ka)&o>q1=mN1UDG{m z#X#cIX$LE33Zn-CKopEZEz9#NRO%7qQc4E1Gc6wJM)oEod87_HTkZ?1Qj)ZV=**BC z=E5nK@(65-ECC8wp3I)_>2vUEo+ z)t~{LxqX$E?w^>*CMb;RsPt~;Q`)-ji!|?(h4{H&FuyC%Kyq4mIo$kOkF$4D353bY zqmn6$)``0!&X?Ui&VxnF)?cAGe=A6P&?u#SlT^Q5Y?;bdE%{lkm)LrNmDUY=uLkCr zPjzLXA=&4V{{3n??XiuV1_f)<6bsZxvI;FmM-M&?FQi<|?N4cxLh$=rb)<} ztZdz?dz_tt9dw~Uy7g*yNObSD%DU(F`rCd6A9bam{T-}pg!3ihO(kE9DcvZ+i{T^P|VujEhaFdjvCD4d-h>n8nQ^vMX zbPjd06Sco`4tm+4AGWgkeAsz1T;AW~j5F)P_vArc$0Ok!O=x{h&dj zXhCzFzeX(Yw;i@0TW@Jh7F{Rpy-hS&E%W?H9v5u1@VV~bC3cua`ggS!XuMw*P6kK4 z5?yhOO=K&5wB9N0nMm6a@K<*qBqJ|1m@F-L8+I|B$~fEJkiqN1&7%-v&7+XWZmBB{ z#os(Cq!kp>gb4=efl;!eDMDA16n6SBKE}JmP|YTdR~3&_Ntm)3E2J~SLI&8lS`~e# zP*5w}sy36kTs`Roi1xgdSa*O%BkAl)*bXCNvq24|LHT4j9yl@Z{CQb-NF168 z-NXe`ZO0?}7G2i~>BJZJo&QR_h&c~_=$6idlWE_~Nn*v+c0_Tzk( zccjRu;ge}mMyLOEYd5)c_XwYl-A~F_(d`qPjS#shfz))e^$5WnKTpp55%SD$^g>UW z-ynt{y7h%!LyGA~G2l2|sWak?R||$vuN=D$bP{$RjW~NL+eF!r5bUb&DbYFjz$&_k z#rk*Cg+7vezQ6@lQ=MYA8ssL6DA&XkAVF$OXt0Y^Hdz&-;gx5@&LMOA4O(^#FR0z! zf-=m#y+7P6b;I5dI>IZ5l{Khf-R`G}0#)O8Cpo5R9h4H#EkQQm9%Ng!9l(&h=q!f0te^fr@^980R-a~OqbDA$`2~0_( zprT@i*-nG;Mg4OS5I2P`S&4&&;kO^As~x_;LXCPb(&G-4CEgc?E?vLrFtneD5%E9C z4Q3K=-u71z0RNM@!K)}X12LOyJ6*u8nwhsBWCs+_M%>15`Cw#$r6nJhKHaemJ9|ZK z&?@%b7hbt%#<3pf%UVvBU)1(E_tCwMu5DJBn>P8y9;ZYpVg2uSQB<6|FE-~zJ)iCg>KIumMf+pd(1~cZm2uTkPb?o?q~4@cZpoDo znz!9cHzu1U+O{5IGeZ`0_9eF(qj?H74?~Fs4eWeI1-k(mmzE8~ISMhN8kL-gh%3)B zq9N5FOtKvgH(QES%@KBG#B?xh$#B{?nRG}>0dm@D8y7EO*D#dIMm<~VXeYrqVkiK7 zT45b>`4psPcA)^>QK zA#_+1Gs29E=|W#=+~HK|{zJ0N79(wk&-W#O?3Ro(y2CCD?B#;yVSb6|9B$oUtb&eHZZ-ID~wcTw3{l`bwNur^AI*AY903sE~MH zwFAuU1gp;{ZTTaqA`71!G)hnO(0c|yU(TZnk{7^{Z(uk?{sa`}hnpvg<$(Tsyr#%u zKd|X*Us+&DVtC<|1hbXh+LC6WSq4mjdZqI$yEh6+mpeqm8#ZtM5&6(7=q8Hj8W!~( z0Gk&lz&slHqo&9C8g2FR>5+vNyr(dJJle8GbD5~a5Y(-RO%%If--B8#ye`zG6OYM4 zSNFh#(8tCJ`^9737Yw?t(8t!$N16%yi^9%>WdUu1r6SJPAVJc`Bn}AT0K0YT99GV7 znYTT})+~LDiRetzE@l;Mu<#kMB7Sp0mdA{P2qM5K4O_@lC||?Rru$tPR^@6TG=BGh zPK^5$=@+;>-t}@Tcaar^|HaUFg^yOX%xM&^4mDe>LUVwaPx_A`nv7^WH%1$sMBBo3 ztj!k{bzjDguSj_pXad862@Je4gLYx<1=C2N^EO{6S>nuA_4>jrQM#8dbb6(q(j9~( zsTEV`^GbJ+YGw63S1z6BDKh^B^tS5$G1DP;7u}rn*iL)x2VmTfn%nl!**@AX@7w@i zK#W6kg+9{=DQLqhT{e*!ILPixnCK||6hguo#C1+%A$eR)O-S)d@0jSgKwK}RdF4E5 z%*;oURw(v(0(uy_VLIVAs_4QVXCD*4r*eA2EBj#!Mx1@_eaR@5)5FdyL2ba03rx1C zBw)V;Ips8VJV#KfccHz?`mcH?8QvP(?M*TCvzT<#moTPeJ|}d@v~;9oRKuIue)b(w z?*V3InUp2sc3BvR!7U@#i`^WOzW4&P#kL-IQR0>B&<>9moe>6YrxWTM_F}t8cDk!i z4!O=?;v>xiU?MVA;ZTHEp2Pru@e9RWD9mRYI7G>%6{2G`-H31ju@?E=z!xC8-E`fR zX7AlVoT5@9D(6woZt9v&4CevDpk8`&M3#5ctz|Op!_(NMW#WfXwyq^-_R`{vYe+$P zPA?fWo&fnAVE{{5-A4y8;F;f%Kr^O!6HHf=C!n#6se8n3yi&ljX(Fi16UerViEHM) zO(AlA=r6K7Vj$742q-uqOmUzZmRJb|NzLcFK%{<6I=2WdWUC7$_Q~!%hid(LFdO zENJaeSq&(dMl>Z%R9tU3!P1&hw)sgqsO7>uQiS;2II*5fV z9aVf*y{#*DlbF{_-JMb{bZ3!=8%XB376yr4+0A@cKDCO@SSbs!->9Di4r@@BRa2y* z32Y%CmF1dsA$-zGe>iQJyt~YkN+0Y9YN>{O%SN-&C;d=M zvfjkjZm;$!haA}bM&(R#^l7%ww$c>(L_-&;Cead34m(a}(wf_@r}JMg?pZ*0KAV$( zv(tQ9;LKCB?71~bxo5wVcEaa#LP|>^6|4{@u-joisfJziSV`AN`y=i9*!gzdhht~q z*;#d(%1HZJx>7#h9r+!-NH%IPA69V-3~^o;5u}#8<_PIqOEhg=d|=)jA*p4vmg84> zJ<{g}0Lq%S@R{I-(1329PAI1DB$3V`HCEu0HftGQPE`)-TQM#@Kgk#TN_swyUIF1b z6o^@ri`y}yFZi{z1vx6jjJT9T{^s^&G6@W$awUL$w}-xEjSP;wzd#t}T?IlMnMykj zpHymOG8AV&UO}=~(j!Mav`YQnjab^9i?fY(br6%7*nQpP0SqL81afZ}Md!uon#}d1N61GnL zc1!lRN!BV$fgMx7%}?{?f8A?zw*)FgC(5_j9JI)LX0c;d zH_?K!o(^<$!E~i<6)E&LIb55aVQ!cs8?kgvK+ei5NNO5*nkQTwp z$Bs|lZ{L)iC6otRnuvqG{u{F|zZ22l#Zuf7iNsJ(s@UVv9b%BVrEAy@5L7_9a<)=w zRd-2gm(F#16$i0g!Gvts!)(!&L?fZD)WF%NsO864U;NJ;5z}4vZc%EQu(h1)$jEWp zqm6$RF`}G9v8|Y3PsEw!b&Jj{+i8;3qL9J%mec6AS;uwXyi5St?}|80a2084+!1Gn z*R6OKS;{RpGM?c*X?P<(wY;_io=ui=(@eP}KpsbivU9nu$)PB(#Nk>+qm+p*BRIw; zw-nr}u43;={cV9@CZ_)2D{`T7~AAi_#afP7UV<|Fs zrWb{fX)<@7v!B{x2z{O#yJnt6xL*7^^y!D8%SD^64qYzUGAZNruk|81Mq=C?I-_CVLlN>DxhVNN;`y8*Wi{3uIf8gm%f(UH|K`VjPP-bN zMH+KwiZ~>`7y5Kd=#wqopC(T@hX~YW%R4RMm6G_L*c)Cs*z#6O%f-uK*HG9wY;Mn_ z>s{Ja%F)^Ou8mpMh5GOClS0kIGk8qW@EksgY@=9WuOL+T0<#NrLH(+>(;L4n9teFh zUpI}$5gKr~eV)J$I%!5<-X|RdaX}25+y8|vl@0r(ztEO_8#QxjrZ7?Y0vI{-EZsqT zBy=fT2qco|aH(x1wqjQ{TLxNU@8+`#T%-JCUl|0^?#n3Ef>KCpMsiH~6!x7tKSe!Kw;mGXCSS{46+&jqW=(}) zhN&?hrp;zug`f)^(zo0NHRqL%#~B}MOG>sTi669eiI@3;a`9GD67#07yTKh80=rAW zIMaE;B(MA@00|X;`~skq|0KqdLSJIk)+M~N643ZOiZ&MGTa)KmdHuW!hBo_$jsH_s zlbu6dv^ByAUp1iEkyElnAkAq@J#qhtooVgV)6u#10aAc&WzOw~R;@!h+Pr9a&F~>| zDxlU6L#-b!0umx&vM3^L2C7pQ7=3Eo8GDP;QjjSYb{jwOO=+eHT_!A!bRHn$p!U4)jWnW~Md4?T*@j{E8rOw_(W1l|2 zZ2C~)+(Y06uW+mFaL_1ruM+!OKGuxo=E_Ae1W59=ejC|A^afkft} zKZ(n;Eo3&gA3xu>ZU!9*^_lk;h(oQP(oK2W`EJQ5&ix%#u^lS9NBHD{^L?unH5(eF z%kZ>{V2m_aM29}?>J_)Y_(O<>ACpBEb{-WUo626g#K&Ghy1f^^dXpiiyEJALAKQtP zobIsmWb^=Cwaji01<7nY%9-2tr;zv7E4z-0Z<_aVCk9fSU9z(`bif$8q-#7aI=ezK zUH959U1%U#q;JxoFB%DblzdDIbsIwx7v7#>pp$U4mtV?zOZn1LbNfm(8hS_WNlItF zVHbpUn87DHkA@Y9i}ZBj!qw{zS@ z;|}DXfI~BzzCBWQosVVbXz8Rlk*rT@q3=w%?!KU5#kpUwZ<%0~a;Z`-RoX|D zHu3C4#dQH{S8}1sWdnh^lN#?RvFyxc&-7i?Pq-vwS1x;Ig>K}c^GMRa*^@-epFrk` zwOu*yMQ<{4>ZIIi{Ku1GVUuDzJ(G@ChF5mM#V^sD+oEKDq^^j5#w8k&=+3^(f%||uOeK$9sym_`9{itmrh*w8DzfgT#Z6QGQ%zTi{&uR4R zD_7B@L_PkV1O}?;E;u6P!$3vkF`A-fFnmX0G!bjFvlCZDOc^H2=k9S1(YO4vZ$Kc| zU7#xf#HB|r!r7;j6099|Sb{XscxfXGgOTD+lUw>gubj$%_97jF8Dh)QG67_MB7~%^ zu2F~?2p{?aiINP_dA4JT1+vGyx5UEEzGZ}oJT#2HO^FHFtNY)jg(XHt2&Eb(xy*Zw z?T3X)L=>!98Xo)N4%))W3Vmb-j+IP(PZBM7M(43ihq;YTY7K2Ck&0)Ty{l7iT|1=;|58e6YqO@c}18O&5 z9N{G<^DCv=h`s~eN8Q?Q45v){zdan2I-H&)1Jk2@q8CXyZ?{I7Ts_h>SrO_vM{?ed zY}2eh3p*7k$T1^*2W+*aT70^fc$>M%{HzaiL z-QtCqHiymY>rd==C&&GY+M7di!F|#rS|QL;^+h1;pUS^swK@B) z;PuyR1PqL485l>%XiFCNf5U7;Dl~C{qVqy=V>n5$iAkZj+5Fh;Bf!d2BZQSMo18GQ zjH-#XK_7i?gw8}nH?qf~=p%TxR6Dq*5N>|R!99u)N0=9Ia0_Z2+&P4oRy(+&2OV5B z0>8$={TAU_Kg?pJYhX62o{8A`GBZE-5!{#7C5b=Y^FzjEIUj;Y1R--kOX7W;LWH9Gd)$*qHf9Q*QX!Q=~C*g?D< z=Y9cWQamMUpgSYQ?K@=d9^vZ(VTUkTbWROfF?=gNZCz3NmUL0D+Ds?J zZXuE0N^8h9EQkDOzyl`HHG2NE-6cWihubN;$2mZ?!tJ!sXnx`x>Af;JvN*CMT;3mc zofYx_w)PQo+X4Do2c0OK6kXOOx;MkZ$pZS`=|S`Mqrh!RSQruoNp=qB2!nRPx(+3+ z^u^1^b6kT9PJC=HAA+aSe3qPB=Sf|k{q3fVSQ>_M`_?7r^cC6PZu~+lpJnTE zh?7nXgq>$1^R%t>I< zquN?-?I=&_zy!Yw$@n?Ry6y|woFmm?S1S1srf0!!uBl;-=o%E?(_!Y6u{@D4Go2?2 z1Gl`{;Yxw$9Wg}+LH4g2lMXqJU9pAYM7zt zmqjek(jDHKk>0Z39dZunJIak>c}BHpN>@M_g#ludbKYDeoUKFo3qj? zp1%jJeY#|Z5Vya~l7(ovd^ps65>5{t`et8oeO3<$WM@AJ!My!iwh&n0?Bi4D!Z=S> z1oND-|M@lh|HuCQrj_H)BAi4xh_D3V1%xLNS`caxR<$Z+ul*j!B_ZS@q$|Jtz~$o8 zjZlF=mmWJ1oUS$!fJ#MkUoU49q&Iv_zl7#gyRSU z2yujU2zycH#_w|6T!dl-H$oKxkMKQ&ClP*%uos~h;S9p(2$vC3HvvA>??c#+(1P&q z)F(m-LL_Iq+@B)GZ;d2Bl%HNK_ZB%~;1h_IG zj9~0%=?5?nPO5n5+{|%zBHV*efl!OE31K_Jj}fLK%tAPV(2wvr!bJq*7Qlxv8{v*- zj(Z7dsR%sck0Csb@NU24|a*uPFbBUMpIG2~Zi`;i}MQ(SgbFrIqFI(#N+~?tn zeJ)B~xa>an@&!d6C%1I5vuNQ$ZsFnu3yYSQQCswzFwUwPe*F9Gxkc)`?e;6_x976o zPAPVl6qPStUa_#gj&H25wYn?o{IwN#*YWWm zG_LV4u3uAG>sNj!W-dED!RfNt&%*H!?(xDdCSJHUOyX*K_~EYYa{wCX>d z`cJR^GpPUQ2WT2td94aVGWt(L&)0C*bGHFUm0T;=4Lr4KG+K>Lqt_&9j2e^1qRG-& zHMtsxX7%_!6MZH6NMd86QPH?)WHh!3oC*KZ@b8NZWn#LNCQ3Aa7oVVxFA<-$ZrpF_ zfWE5$eD3;jzx@Lqt9^vy^yA9yC3%Q)8RN<=fLw5++(F?=TD(>HJ)WU)X~p1=*+`o` z?ss>dLt3EmpECTPGW`E68P026LTzykC_ls%`)mEYzoe$tU&I~fN+7mMY8sn(C4oD` zQkM8Ps0nYg_`=%yCO-cYO^>GLHM5cA3AjijpvHov*P*X_)(wW|5zFH`c7H zvY=i-`0we=5H))v*)*014YHAYZiW;n=iH0rEdSJPNu zw-(}FfxoGU>(bRW@r`TNHlV))iT9c!=p2Sa1r~040+Y*Y*7~^u=&rR5P$y$4r?nn` zW#gKFyOIwSala$DJ%Rd0p5jZOh>ExxN-Gg+Yp*16by`>bI(24KsRvaT71uOkNcD{y zQDzmZM@4vm{Gh=50W^0_0-kTAdUc*!zrTU|km@XlqFPul)Io2rNwndqdDPD}U%@n` zsjw`dRJH#Es$mAyH*8ek`#nb!k;pkmrBw|&9@;;#p8abZ6huH-Q>*oB*8+`;YwFO> zQHJAMe;slq${ka@nKUM6V`F_IfWxdER%WANBZ|7HWN}T?!!)6uhRQXBF|9Jk<#m|x zhhYY&@x?4w&n;~9gI<>h@PGyI2(_@dvPtQIk8zRJl?w*Eo^M!{cFcHa<6mQ66a#u?Q^PXYv~vNoOM<1${L!h*SO-w z%Js{^&qdtF;N{xdWklYb%n>GQYcM@5A&pz$f2gKTt+gFeqFR{`#%CO72SwIZxiE~% zhX|&pGbe>?t=OaYV{EX`;|>ich^0HLgt1e~itDQnQK+UBMSnsmfKfu&^FREmwD?M|Fl>T16H$A@>iA8k9&8mk%uY z*F22bUm);2aqT=NO_YhD#`m%E3u^eal@O^@xP_Iqwc`o%*JIFmYno~R9=AY|v_L?^ zgY}h-;MIGS5vlQF_KS$!oO5jhzj0it5zW#Df8CXY7qrV8E7v^iu5S<;7-C?j7Bw_5 z3ak@~xK#=69xJeNJSHJW*D%fG8KZ86xF(OIca<+S>VQEYch^-hwl3msWfbIY^sg)8 zc7cML8Y=lUfh!!yY2AMR!&l#yhPut7gtr>)oA5wUWX9 z5K)zN4M|*Upt8wYT?uNdnqwt{s=m&;rc!9~H=*R3TA|9HiYKg6B3CP&jX5y#XhaIY^mfM zBc?T?IdjH_?eTj80#6{5i-8eB!_VQ@vR@J=vAMafiFxrAWM$z=U078sPU&Mk@I*a8 zKsVHrM`0xwWscQV`LN&GBmnY_^+F@dwpKGaW36ejGHYm#GB0n=cW~NL2iLv8!F{#V z!99xS9RCKk{!|Z^r{_#z&P-uu(@ZD{Zq`4T_lN#2-uIM)dl`YAJJsjY>T?4^EkcF* zELESm>N87y>Jdhtba3>WBg1%}Md(L3i0~T13+i)+`fNdfQGG=s0*R`eR&8xXT>s}Z zau13)uXCB_?xjmq>CVulTJt28OO`G!b}m!iiv+%Yp-OWUU+kl`L{)NgiWuU@W|tZ$ zstO8C{POyT{Z}TN@VU6Y{$ZhEEGYptN8(ns##tq{);LZolN?UST*?E)Y;XLNYWoLlIbdOv8Qsc>df zS}J$T{7v)j`qmA1GCBc?ToJJvPRAv4W^Ot+pW6Z}Ue?GuIaxN#)8+Z{7CPHS2S^P6 z6q)}&{?oJHg3pCBMe|&W=R{?BvV~T(JJj{UZ?5>V-#!1kbRAd2vEPz!36gBVvz`tZ z{TKBgQXc`jB@%Vck;+{nQh7B7UQp8vHjLYY^SNFAy<#)V_ zmkkYx{0*;$_as05ILqH64?X_feLs9%PUOo&8`g(keLa5uaW0Yn_zz!w|A{9y7&|&x zyrbj0ufN|xaCbB)@#NuTfe>m`RkRx4Ql)fW|L+4ys;y> zL-~80qf!kW$+GhII78ifWAYFyZDt^$z&GM3Oz|PJ8m9u^h@;35P0G-J6F194+&9Y0 z+&AOge;XeMuL^?l$H|wI<*v9!?rPxVE-SYM)Iwpt5>~4r5$ALWnl5!uWPE5*s9zID z7)7{@AXA9{H(`|1X}BvySZX}dlEmJ!G(T0-jEwDEoCnU~%5yKlYwXR>Cpvd=zy@3SX+zVA$ChOqt}yPQyc2#5zr0?vG|UB}0mzyEfP z-~E)6{2y}APdVv7$Y}^<7zBs~P?C@UevD<<-zp}lDhNOZpdbkY5bw|UcR2}w)cPVI ziQqLkfOx(2pYq@02Le=o99N0G{I~IAjF|xxfK31;fOvhl2LKIl0{9SGp#hGB%8ddb z3GGn?eKcW_0!aQX`rCx^NUZIf1dD6`;QvY0P(mqU0h}Un1rYx$YewMCXn+BszM~0N zED2jU!2cimCEmi3xIIg#cA%eeA0Pi6brRdK0~Cq51O7htzm88A`S;RGc-<%T_uq{V z$@lz$04W3wGT^WA5sxuoFwOTWORVTWEGVkn@IZk5en3qnh=iy(1Pmwe6&L}wD?u#G zwM66~U?Q!BK@mt#xDw~0sGwaA_)E6Jf$QdV#@QsX z%a=bUZ3t!z;C^rf7(rYsL7-?|Pd@^zMnS;7ZfZ`%=#?POPy`xIj6>`nyA=D&q}cy> ztgJ|+o&;ie%?&{u-2XR#-<=L>UoQfaBbdHy2G)fsK?0>@B&6i7 z$wNhCVDhpe($3dlB64s^m(LW)u}GGJg)DaTCKgP%^uS> zpsE{A6M`WfG&X(%R*1#i00J3x7^G8lc%Zv|+UJFNj7<^;gTMJXyEwb-4*QRgU^dxX z&&)-&`@DEVf>E2ZZQ)9~9B6tN&NXT}9Ka(3P~<*n*qtXmww^Yja*27Ubd*y_mBk%A zPp0&Ib&GCOzYS#-qVe@f3ForP08<|S(aBBWHq#ORo5@Nw63uI))44}AA`^8Y$MvV_ zox#CcUtT%1d0;~IGr3%W38Eog&r0T_?zg+Pc%qJhkzbuN; zsPQX&d7bn)%{$$`1~dE^sGc0+;h>eY;rhY217QkIe-340Lm)n~5f&TpPg zpOrIvl9+crLNAWz?$Ui7x4S!fGwmtDH7y?~ID5uChYpOhEhsA%ic%-o*!RPBLGiV7 zZfkuSwL;@v@ftR^_XkL;D9R2pOw-2#3+# zf65=Sj|`kHIj%v@PZ1qq3h}sG_l(fbGXtW7n~22SdzUE8Mr~;^~C2c|@qJ z14kf`U}qH40}S;9f1hx1cDq5WhoV#z#PfJyAAcm_V-qC+@CD~}j)ZA(bE7Yn{9!F4 zOS_FC5cIVhXkC;Sc+ZcINwneI=m|{F>=zZ9^4LXx+RTcx;VG1t-o(=s83vBoXsVQ~ zN%_n~l>;}!=twMh9^WjzDnvz}79vO=e}RW=-MWMQa}>`*BOi+ET4j9{BA56LK9BO_ z@O~7D*Ir(^WtBSUf0MFn?Z)(A#;mYc3l2NrrLV<_6T6OV=h%p1eLE6#b#Sm$?SX_f zTYY{1u0YDdrsf|1*vMg%>8oy36CDFw_Vh>H-cxd+Z_oCuZPXu|7;XB}q^eEO)p%@6 zvA`uB&As=L*Fo|?(*4OKwb-!1KE#}q6ZB2222NBM@MnO>@EyE#uJYDJ!_zp5o` z&^Vk1r@AtO_qx~_ECe+I&;`H^JVvVC1c*832MFk-JCtNvOABLZ{4v z7MYfMn`1~>&5?T=1V2@NM7b|h&VOW#wLva~FVK#*?HH-o1udk=T?u6@S?mW^rJ7GB zDhLLIRBu#lA92q_=jGD!MsE!(*9a|!z6h=9gpCiIaXmgxNQ}oGgip!m9*a2h^T3YrrkQA0-^dC-s*d-TJZ)|z9?3zJ$wb4dTN{Qc>O$mAyBqc z5`UDHd%J}Pv&U7js8;;^^u}VQzg^}NM_srp&f%gi=_ljVubLcjnWv^MMBhz!h!Hc@ zvm0K$)KL3C#OG*Hcn0ugFRsp2Ny7UQe)`FIo2_SQ1Ea1{K`%1X0^bCLdpAGned?IY z+E5WS=8R7Pvvu7FVvdXF>g!NbI;x|yfPL0)h_G?FJuaSovtq4c)!$^xdpVLVw*SbF zkzP_H`=iDAtCwn5r8GbSdu7I1ER<*ZD%BCDRz?TutH_4r`6{8SdvH>{jErTSg}0AP zRwr^)_(zr}By!N%-q$hXmJyo$?~yR|>f~F-DtKLiJ;8Z50rHEGTFp270|jsQ6SQ~_V22l^5+Zn)Wv!Yk zbKNQd*w5Cp;t%9U?f`)^c7tAuRWC`eBSPHGHXJBD z_Rx1H+LYn$GK77aeaB5?;(QL>Vt126x9}fr3cWPpP;#(l1qNgtM*Ujr2^P3W>9!RJ zdi+9nPW0kkpn?j`%>jsE02x`<$O4y(m^sUSd@6EIVWwh!8{; zS?20aGk<>1fKP65Px@$a81*>oRB5m4mUpD63tRjrta))7c0WeE1j_M`Zafg(l{0#fy|d=gTP@x#VSc zA6QPsT0N6Ji|tTeXX!Km(lUZZ4B~UFbE^slJaamMFrTv*7_Jmq*f`nLF@v}uXxm56 z!OYZ}Ndn}#mI?Q6Win!Vwpx)DcEdpTC1Z(Lp}dJ?g~tfte9!uj5^gA_{AOeFHKQag zbsk%;&)@3GroZ*6-@B}$FT|f!kh1&C=Zw0$!_?`8D}~*-N8O~MrzGzgiQjxvx$jgw zTUptwV|=`luyF1ArS)}GG4{+(R>3_BHHn_BFUHI+dG_`m`c6Zet`$0J4Qo5u!ahm1 zQeidgQ=-&YlYCN_3-A-Tc`n7PhAWP4YuQU`@@Dbg?~Ol{appho7it&ICW2?mYer9>DKu=Ag>t!Vp8yD6HSRhcHW@cR7y^157>b*_kR=E z&z_(<$IN@k#d*VFighO8Bm?f7G{pF-TF$}~MD^io{rh$(1sdbi(zON!&zz$izm`Yd zYB5sV`rKD7BxaI*lA_-V7jB+o=hMcCWN0hXzkeM&!1mY_vI5f#+@rWk;yMn>m#Zy` zN{sJ%Pm@Z6b+m7s38pBf22>3Jb7LyxrqcS_IId}j#=qRUsP@gEwC6qz{qr0>i-(g| zh@0OE4^R1AkDW7nJ|!#DEY*GKM9C+&sI3Pef09d{Ehmm=Qm3ghvRn~D+h%C1TUc}SFY{|M3eejNf>D+ttB)ODJ5&Xr_Qma&pd0E=fisc#V z5AJcU-evOMDSImw=&&ZciO~Do19!{JYIctkb%WR^!!?8Lw*1ZZSb4>EdZyX5+{X15 z#^=_3Fez!8m(Iqg+!>|}_`}pYs*`0a_KXU-3=^|+KfJ@f(R`e__tK*V$qf9;*SJ~k zmvhBCSM0Ut!sm(uDI)#XDG+3m?8P4qL^UEBJ^C`Y2L}Z7Zne{5_QvRkB&~dy{nFTj zABMncS9*2y=-hYY$+3}PMnyASd?D%~HLFo8X8TGisKcn(bynxM9E^*&{)NX<)TJqP zPKL_xkL|5u{mtSM5S4Y#+IJ_~47DPpxu}!!^qq{KpbHd*+j!$Bt=9M8g>D;H7XUd5Pg2+fg+kNGdn-i|S;Y&n2V^YzM|bYi!h% zRYJ8x$y|4mHn34$K~0RgE{~qQ^+l^yD#+fD+aHz0veS<6Vh<2PJ;T;>Ux`TAKiRt` zJ-1zp=}^2kM+UaG6$E^pP^8@BD}6J&T-!D!+x3Fs@|fk*LOjflMop_a|J_Ghr#>!5 za%H?q^x~-;K4y>3BL9enPc|tQBZ+U}X{V95}w*-pqRGb9qU9+ zFMFU?v!h>DsXXK8jog=oeM4ssmT75hu^SG~uDdyYU{U?x<2Y|P@|4-T`P@C-?83@i z+YsTl!FBPCjEQ(EXuZv-<<>rh&1g!q*8N8le)2x=Pd+;AArE1lT}?OPsFGE;j`t8J zuZ=bOBLk=E5CNYaLzyUvuDSP|&xJ^Mq$vDfkS>YMYW16hOS_^3H{ zgcz{*ldelA>F7fJ?cUs`?a4s;S*_1oABDF=w5Mtdm(O>c?)h3$)Ch$YyP5YNwN4o3Uu!Et}>*GZt>Ol zsa}b(33*)IrOmnQSE_jcl=J%>{?$ukhx))MUgKG=V&MK&t-i~5FqEm|VVwe5)KAZ{ zO_~hEQZz!-aCO!iAq9;GYU|RGok`tJvG}*tA$GB)p^+Sdl@0FZ`@Or|^a6kk+ zZzoiutwUP5mtv$ybw7@;s9zM|h7^a*(cEbPhTuKxR0ai)R1t1;SU#JIFe{DrXns47 z)77u_#xc>WOc9{6uXlQQD?B6(pmrwH!UO_l2xVtdzQL0Ym z7U7ama;71Q2Ww1zGfqe?_a#kSih+or-sQ0ik>QJ-IF%eLR`z67zezK=5dufmZK?MZ zr@>n!+D`GPkZLIH6mV73I!H{-#-V?Sg>95Pad7uz>(W#?%2eMor7?Rlby60WQvp1N zaAPJtwE&^%VOyVN*h*eoARj1CL{BypmbmTv-e^BfQ} zj6umzw8l2yu`Q;7Y|xR0@}ys6sNsn|O3t<*$44?^8~fS-0uAkuAOV2M0W}rm l`PI_i6H$002jNg%$t+ literal 0 HcmV?d00001 diff --git a/Compression.Tests/Nsis/ReferenceVectors/nsis2-zlib-solid.exe.gz b/Compression.Tests/Nsis/ReferenceVectors/nsis2-zlib-solid.exe.gz new file mode 100644 index 0000000000000000000000000000000000000000..101046cbd26c710ee99b53ff37d765cfd9ef65c1 GIT binary patch literal 25380 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 TE$V^MEWZB&>+RFKsFwf$fLrio literal 0 HcmV?d00001 diff --git a/Compression.Tests/Nsis/ReferenceVectors/nsis3-bzip2-nonsolid.exe.gz b/Compression.Tests/Nsis/ReferenceVectors/nsis3-bzip2-nonsolid.exe.gz new file mode 100644 index 0000000000000000000000000000000000000000..864bd9797f5bdc0dcab8ba3a675b60b23db8d715 GIT binary patch literal 26743 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 literal 0 HcmV?d00001 diff --git a/Compression.Tests/Nsis/ReferenceVectors/nsis3-bzip2-solid.exe.gz b/Compression.Tests/Nsis/ReferenceVectors/nsis3-bzip2-solid.exe.gz new file mode 100644 index 0000000000000000000000000000000000000000..167cf1f69630034d865554b60a9a644648a8bd08 GIT binary patch literal 27487 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 literal 0 HcmV?d00001 diff --git a/Compression.Tests/Nsis/ReferenceVectors/nsis3-lzma-nonsolid.exe.gz b/Compression.Tests/Nsis/ReferenceVectors/nsis3-lzma-nonsolid.exe.gz new file mode 100644 index 0000000000000000000000000000000000000000..f94ce69fcc6a19a0d961a091b0a037af7c062035 GIT binary patch literal 26174 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
    @==>jZ(6Boy4ky>JUY?(qNV2= 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 KwO?=OoB#mb6`BqJ literal 0 HcmV?d00001 diff --git a/Compression.Tests/Nsis/ReferenceVectors/nsis3-lzma-solid.exe.gz b/Compression.Tests/Nsis/ReferenceVectors/nsis3-lzma-solid.exe.gz new file mode 100644 index 0000000000000000000000000000000000000000..d50eb18033cb7fa01d1885293c1927d8f71c08ab GIT binary patch literal 26445 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 literal 0 HcmV?d00001 diff --git a/Compression.Tests/Nsis/ReferenceVectors/nsis3-zlib-nonsolid.exe.gz b/Compression.Tests/Nsis/ReferenceVectors/nsis3-zlib-nonsolid.exe.gz new file mode 100644 index 0000000000000000000000000000000000000000..7b639b7eddb160c87b280a60599ab5e0dd3afd3b GIT binary patch literal 27212 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 fiA+(i-{^#$}Q3c1NS#AFVm1{grO{D+;r#PN~ literal 0 HcmV?d00001 diff --git a/Compression.Tests/Nsis/ReferenceVectors/nsis3-zlib-solid.exe.gz b/Compression.Tests/Nsis/ReferenceVectors/nsis3-zlib-solid.exe.gz new file mode 100644 index 0000000000000000000000000000000000000000..3404a6c693f80f5e2a7c028855ff01921a39fab5 GIT binary patch literal 27598 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 -/// Identifies the compression algorithm used in an NSIS installer. +/// Identifies the compressor an NSIS installer was built with (SetCompressor). ///
public enum NsisCompression { - /// No compression — data is stored verbatim. + /// Nothing is compressed: the header and every block are stored. None = 0, - /// Zlib/Deflate compression (2-byte zlib header + raw Deflate stream). + /// "zlib": raw Deflate with no zlib wrapper (NSIS 2's inflate also omits stored-block NLEN). Zlib = 1, - /// BZip2 block-sorted compression. + /// "bzip2": NSIS's header-less bzip2 dialect. BZip2 = 2, - /// LZMA compression (5-byte properties header + raw LZMA stream). + /// "lzma": a 5-byte properties header, then range-coded data ending in an end marker. Lzma = 3, } diff --git a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Nsis/NsisConstants.cs b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Nsis/NsisConstants.cs index c72221e6e..2d1d6eeb4 100644 --- a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Nsis/NsisConstants.cs +++ b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Nsis/NsisConstants.cs @@ -1,32 +1,68 @@ namespace FileFormat.Nsis; +/// +/// Values from NSIS's Source/exehead/fileform.h (zlib/libpng licence). +/// internal static class NsisConstants { - internal static readonly byte[] Signature = [ - 0xEF, 0xBE, 0xAD, 0xDE, - (byte)'N', (byte)'u', (byte)'l', (byte)'l', - (byte)'s', (byte)'o', (byte)'f', (byte)'t', - (byte)'I', (byte)'n', (byte)'s', (byte)'t' - ]; + /// FH_SIG: the second word of firstheader. + internal const uint FirstHeaderSignature = 0xDEADBEEF; + + /// FH_INT1..3: "NullsoftInst" as three little-endian words. + internal static ReadOnlySpan Magic => "NullsoftInst"u8; + + /// flags, siginfo, nsinst[3], length_of_header, length_of_all_following_data. + internal const int FirstHeaderSize = 28; + + /// FH_FLAGS_MASK: every other flag bit must be clear. + internal const int FirstHeaderFlagsMask = 15; + + /// The exehead looks for firstheader only at multiples of 512 bytes. + internal const int FirstHeaderAlignment = 512; - internal const int SignatureOffset = 4; - internal const int SignatureLength = 16; - internal const int FirstHeaderSize = 28; // flags(4) + sig(16) + header_size(4) + archive_size(4) + /// Bit 31 of a non-solid block's length word: the block is compressed. + internal const uint CompressedFlag = 0x80000000u; - internal const int CompressionMask = 0x0F; - internal const int SolidFlag = 0x10; + /// One entry: which plus MAX_ENTRY_OFFSETS (6) parameters. + internal const int EntrySize = 28; - internal const int CompNone = 0; - internal const int CompZlib = 1; - internal const int CompBzip2 = 2; - internal const int CompLzma = 3; + // Block indices into header.blocks (NB_*). Pages are always compiled in, so entries and strings + // keep these indices whether or not the optional background-font block follows. + internal const int BlockEntries = 2; + internal const int BlockStrings = 3; + internal const int BlockLangTables = 4; - // LZMA sub-header inside the compressed stream: 5-byte properties + 8-byte uncompressed size - internal const int LzmaPropSize = 5; - internal const int LzmaHeaderSize = 13; + // Opcodes that precede every optional one in the EW_* enum, so they hold in every NSIS 2/3 build. + internal const int OpCreateDir = 11; // EW_CREATEDIR: [path, ?SetOutPath, ?restrictAcl] + internal const int OpExtractFile = 20; // EW_EXTRACTFILE: [overwrite, name, data offset, time low, time high, allow ignore] - // Zlib: 2-byte header (CMF + FLG) before raw Deflate data - internal const int ZlibHeaderSize = 2; + // String escape codes. NSIS 3 (ANSI and Unicode) uses NS_*_CODE 1..4; NSIS 2 ANSI used 252..255. + internal const int Nsis3LangCode = 1, Nsis3ShellCode = 2, Nsis3VarCode = 3, Nsis3SkipCode = 4; + internal const int Nsis2SkipCode = 252, Nsis2VarCode = 253, Nsis2ShellCode = 254, Nsis2LangCode = 255; - // Bit 31 of a block-length word means the block is stored uncompressed - internal const uint UncompressedFlag = 0x80000000u; + // Variable indices (NSIS 2.26+ and 3.x): $0-$9 are 0-9, $R0-$R9 10-19, then these. + internal const int VarInstDir = 21; + internal const int VarOutDir = 22; + internal const int VarExeDir = 23; + internal const int VarTemp = 25; + internal const int VarPluginsDir = 26; + + internal static readonly string[] InternalVariables = [ + "CMDLINE", "INSTDIR", "OUTDIR", "EXEDIR", "LANGUAGE", "TEMP", "PLUGINSDIR", "EXEPATH", "EXEFILE", + "HWNDPARENT", "_CLICK", "_OUTDIR", + ]; + + /// + /// NSIS constant names by CSIDL value, as the compiler's shell-folder table assigns them + /// (CSIDL numbering per the Windows SDK's shlobj.h). Unused slots are null. + /// + internal static readonly string?[] ShellFolders = [ + "DESKTOP", "INTERNET", "SMPROGRAMS", "CONTROLS", "PRINTERS", "DOCUMENTS", "FAVORITES", "SMSTARTUP", + "RECENT", "SENDTO", "BITBUCKET", "STARTMENU", null, "MUSIC", "VIDEOS", null, + "DESKTOP", "DRIVES", "NETWORK", "NETHOOD", "FONTS", "TEMPLATES", "STARTMENU", "SMPROGRAMS", + "SMSTARTUP", "DESKTOP", "APPDATA", "PRINTHOOD", "LOCALAPPDATA", "ALTSTARTUP", "ALTSTARTUP", "FAVORITES", + "INTERNET_CACHE", "COOKIES", "HISTORY", "APPDATA", "WINDIR", "SYSDIR", "PROGRAM_FILES", "PICTURES", + "PROFILE", "SYSTEMX86", "PROGRAM_FILESX86", "PROGRAM_FILES_COMMON", "PROGRAM_FILES_COMMONX8", "TEMPLATES", "DOCUMENTS", "ADMINTOOLS", + "ADMINTOOLS", "CONNECTIONS", null, null, null, "MUSIC", "PICTURES", "VIDEOS", + "RESOURCES", "RESOURCES_LOCALIZED", "COMMON_OEM_LINKS", "CDBURN_AREA", null, "COMPUTERSNEARME", + ]; } diff --git a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Nsis/NsisEntry.cs b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Nsis/NsisEntry.cs index 88039463f..51873163b 100644 --- a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Nsis/NsisEntry.cs +++ b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Nsis/NsisEntry.cs @@ -1,16 +1,25 @@ namespace FileFormat.Nsis; /// -/// Represents a data block embedded in an NSIS installer. +/// A file an NSIS installer extracts: one File instruction of the install script. /// -/// The entry name. Will be a generated name such as "block_0" unless the -/// installer header was successfully parsed to recover actual file names. -/// The uncompressed size in bytes, or -1 when unknown (solid stream). -/// The compressed size in bytes, or -1 when unknown (solid stream). -/// Whether the entry represents a directory. -public sealed record NsisEntry( - string FileName, - long Size, - long CompressedSize, - bool IsDirectory -); +/// +/// The target path as the script names it, with forward slashes. A leading $INSTDIR is +/// dropped, as 7-Zip does; any other variable ($PLUGINSDIR, $SYSDIR, ...) stays. +/// +/// Offset of the file's length-prefixed block within the data section. +/// The file time the compiler recorded, in UTC, or when none. +public sealed record NsisEntry(string FileName, uint DataOffset, DateTime? LastModified) { + + /// Gets the uncompressed size in bytes, or -1 until the size has been resolved. + public long Size { get; internal set; } = -1; + + /// + /// Gets the stored size of the file's block, or -1 when the block lives inside a solid stream + /// and therefore has no size of its own. + /// + public long CompressedSize { get; internal set; } = -1; + + /// Gets whether the entry is a directory; NSIS File entries never are. + public bool IsDirectory => false; +} diff --git a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Nsis/NsisFormatDescriptor.cs b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Nsis/NsisFormatDescriptor.cs index a44922962..c01ee3703 100644 --- a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Nsis/NsisFormatDescriptor.cs +++ b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Nsis/NsisFormatDescriptor.cs @@ -1,152 +1,102 @@ #pragma warning disable CS1591 +using System.Globalization; +using System.Text; using Compression.Registry; using static Compression.Registry.FormatHelpers; namespace FileFormat.Nsis; /// -/// NSIS (Nullsoft Scriptable Install System) installer archive — PE stub plus compressed installer data. +/// NSIS (Nullsoft Scriptable Install System) installer: the files its install script extracts. /// /// References: /// -/// https://nsis.sourceforge.io — official NSIS project site and sources — the installer data layout is defined there -/// https://en.wikipedia.org/wiki/Nullsoft_Scriptable_Install_System — Wikipedia -/// https://www.7-zip.org — 7-Zip — widely used independent parser of NSIS installer payloads +/// https://github.com/kichik/nsis — NSIS sources; Source/exehead/fileform.h defines the layout (zlib/libpng licence) +/// https://nsis.sourceforge.io/Docs/ — NSIS documentation +/// https://www.7-zip.org — 7-Zip's NSIS handler, the reference reader this one is checked against /// /// -public sealed class NsisFormatDescriptor : IFormatDescriptor, IArchiveFormatOperations, IArchiveCreatable, IArchiveDefragmentable { +public sealed class NsisFormatDescriptor : IFormatDescriptor, IArchiveFormatOperations { - /// Rebuild-based defrag: extracts then re-creates the NSIS data block in listing order. - public void Defragment(Stream archive) - => this.Defragment(archive, new DefragOptions { Mode = DefragMode.ConsolidateAtStart }); - - /// Rebuild-based defrag: extracts then re-creates the NSIS data block per the requested mode. - public void Defragment(Stream archive, DefragOptions options) { - DefragRebuilder.Rebuild(archive, options, - readEntries: stream => { - var r = new NsisReader(stream); - return r.Entries.Where(e => !e.IsDirectory).Select(e => (e.FileName, r.Extract(e))); - }, - buildImage: files => { - using var ms = new MemoryStream(); - var w = new NsisWriter(); - foreach (var (n, d) in files) w.AddFile(n, d); - w.WriteTo(ms); - return ms.ToArray(); - }); - } - /// - /// Gets the id. - /// public string Id => "Nsis"; - /// - /// Gets the display name. - /// public string DisplayName => "NSIS"; - /// - /// Gets the category. - /// public FormatCategory Category => FormatCategory.Archive; - /// - /// Gets the capabilities. - /// public FormatCapabilities Capabilities => - FormatCapabilities.CanList | FormatCapabilities.CanExtract | FormatCapabilities.CanCreate | - FormatCapabilities.CanTest | FormatCapabilities.SupportsMultipleEntries | - FormatCapabilities.SupportsDirectories; - /// - /// Gets the default extension. - /// + FormatCapabilities.CanList | FormatCapabilities.CanExtract | FormatCapabilities.CanTest | + FormatCapabilities.SupportsMultipleEntries | FormatCapabilities.SupportsDirectories; public string DefaultExtension => ".exe"; - /// - /// Gets the extensions. - /// + // Detection is by the "NullsoftInst" first header (FormatDetector.DetectInstaller), not by ".exe". public IReadOnlyList Extensions => []; - /// - /// Gets the compound extensions. - /// public IReadOnlyList CompoundExtensions => []; - /// - /// Gets the magic signatures. - /// public IReadOnlyList MagicSignatures => []; - /// - /// Gets the methods. - /// - public IReadOnlyList Methods => [new("nsis", "NSIS")]; - /// - /// Gets the tar compression format id. - /// + public IReadOnlyList Methods => [new("zlib", "zlib"), new("bzip2", "bzip2"), new("lzma", "LZMA"), new("stored", "Stored")]; public string? TarCompressionFormatId => null; - /// - /// Gets the family. - /// public AlgorithmFamily Family => AlgorithmFamily.Archive; - /// - /// Gets the description. - /// - public string Description => "NSIS installer archive (Nullsoft Scriptable Install System)"; + public string Description => "NSIS installer (Nullsoft Scriptable Install System): the files of its install script"; - /// - /// Lists the entries in the supplied container. - /// public List List(Stream stream, string? password) { - var r = new NsisReader(stream); - return r.Entries.Select((e, i) => new ArchiveEntryInfo(i, e.FileName, e.Size, e.CompressedSize, - "nsis", e.IsDirectory, false, null)).ToList(); + NsisReader reader; + try { + reader = new NsisReader(stream, leaveOpen: true); + reader.ResolveSizes(); + } catch (Exception ex) when (ex is InvalidDataException or NotSupportedException or EndOfStreamException) { + var note = Metadata(null, ex.Message); + return [new ArchiveEntryInfo(0, "metadata.ini", note.Length, note.Length, "stored", false, false, null, Kind: "metadata")]; + } + + var method = MethodName(reader); + return reader.Entries.Select((e, i) => new ArchiveEntryInfo(i, e.FileName, e.Size, e.CompressedSize < 0 ? e.Size : e.CompressedSize, + method, false, false, e.LastModified)).ToList(); } - /// - /// Decodes the supplied input. - /// public void Extract(Stream stream, string outputDir, string? password, string[]? files) { - var r = new NsisReader(stream); - foreach (var e in r.Entries) { - if (e.IsDirectory) continue; - if (files != null && !MatchesFilter(e.FileName, files)) continue; - WriteFile(outputDir, e.FileName, r.Extract(e)); + NsisReader reader; + try { + reader = new NsisReader(stream, leaveOpen: true); + } catch (Exception ex) when (ex is InvalidDataException or NotSupportedException or EndOfStreamException) { + WriteFile(outputDir, "metadata.ini", Metadata(null, ex.Message)); + return; } + + var wanted = reader.Entries.Where(e => files == null || MatchesFilter(e.FileName, files)).ToList(); + reader.ExtractMany(wanted, (e, data) => WriteFile(outputDir, e.FileName, data)); } - /// - /// Opens a single entry as a bounded read-only stream. The reader produces - /// the decoded bytes per entry; the matched bytes are wrapped in a - /// sized - /// to their logical length. - /// + /// Opens one entry as a bounded read-only stream. public Stream OpenEntry(Stream archive, string entryName, string? password) { ArgumentNullException.ThrowIfNull(archive); ArgumentNullException.ThrowIfNull(entryName); - if (archive.CanSeek) archive.Position = 0; - var r = new NsisReader(archive); - foreach (var e in r.Entries) { - if (e.IsDirectory) continue; - if (!string.Equals(e.FileName, entryName, StringComparison.OrdinalIgnoreCase)) continue; - var bytes = r.Extract(e); - return new Compression.Registry.Streaming.BoundedEntryStream( - new MemoryStream(bytes, writable: false), bytes.Length, leaveOpen: false); - } - return new Compression.Registry.Streaming.BoundedEntryStream( - new MemoryStream(System.Array.Empty(), writable: false), 0, leaveOpen: false); + var bytes = this.ExtractEntryToMemory(archive, entryName, password); + return new Compression.Registry.Streaming.BoundedEntryStream(new MemoryStream(bytes, writable: false), bytes.Length, leaveOpen: false); } - /// Native in-memory single-entry extraction routed through the bounded . + /// Decodes one entry into memory; an unknown name yields no bytes. public byte[] ExtractEntryToMemory(Stream archive, string entryName, string? password) { - using var s = this.OpenEntry(archive, entryName, password); - using var memoryStream = new MemoryStream(); - s.CopyTo(memoryStream); - return memoryStream.ToArray(); + ArgumentNullException.ThrowIfNull(archive); + ArgumentNullException.ThrowIfNull(entryName); + if (archive.CanSeek) + archive.Position = 0; + using var reader = new NsisReader(archive, leaveOpen: true); + var entry = reader.Entries.FirstOrDefault(e => string.Equals(e.FileName, entryName, StringComparison.OrdinalIgnoreCase)); + return entry == null ? [] : reader.Extract(entry); } - /// - /// Performs the create operation. - /// - public void Create(Stream output, IReadOnlyList inputs, FormatCreateOptions options) { - var w = new NsisWriter(); - foreach (var i in inputs) { - if (i.IsDirectory) continue; - w.AddFile(i.ArchiveName, i.ReadContent()); - } - w.WriteTo(output); + private static string MethodName(NsisReader reader) { + var name = reader.Compression switch { + NsisCompression.Zlib => "zlib", + NsisCompression.BZip2 => "bzip2", + NsisCompression.Lzma => "lzma", + _ => "stored", + }; + return reader.IsSolid ? name + ":solid" : name; + } + + private static byte[] Metadata(NsisReader? reader, string? error) { + var sb = new StringBuilder(); + sb.Append("format=nsis\n"); + sb.Append(CultureInfo.InvariantCulture, $"parse_status={(reader == null ? "partial" : "ok")}\n"); + if (error != null) + sb.Append("error=").Append(error.Replace('\n', ' ')).Append('\n'); + return Encoding.UTF8.GetBytes(sb.ToString()); } } diff --git a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Nsis/NsisReader.cs b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Nsis/NsisReader.cs index 0e1f331d6..9fdac647a 100644 --- a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Nsis/NsisReader.cs +++ b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Nsis/NsisReader.cs @@ -1,421 +1,725 @@ using System.Buffers.Binary; +using System.Text; using Compression.Core.Deflate; using Compression.Core.Dictionary.Lzma; +using FileFormat.Bzip2; namespace FileFormat.Nsis; /// -/// Reads data blocks from an NSIS (Nullsoft Scriptable Install System) installer executable. +/// Reads the files an NSIS (Nullsoft Scriptable Install System) installer carries. /// /// -/// NSIS installers are Windows PE executables that carry compressed payload data in the file -/// overlay (bytes after the last PE section). This reader locates that overlay, validates the -/// NSIS first-header signature, and enumerates the embedded data blocks. -/// -/// Full file-name recovery requires executing the compiled NSIS install script, which is out of -/// scope for a static reader. Entries are therefore named "block_N" in the order they appear in -/// the data section. -/// -/// Supported compression methods: None (store), Zlib (Deflate), BZip2, LZMA. +/// Layout, from NSIS's own Source/exehead/fileform.h and fileform.c +/// (zlib/libpng licence): the exe stub, then at a multiple of 512 bytes the 28-byte +/// firstheader (flags, 0xDEADBEEF, "NullsoftInst", header length, archive length), then +/// either +/// +/// non-solid: a length-prefixed header block followed by one +/// length-prefixed block per file, each compressed on its own (bit 31 of the length word set) +/// or stored; or +/// solid: one compressed stream holding the same length-prefixed blocks, +/// lengths then without the compression bit. +/// +/// The header holds the install script as an array of 28-byte instructions plus a string +/// table. File names are recovered by replaying SetOutPath (EW_CREATEDIR) and +/// File (EW_EXTRACTFILE) the way 7-Zip's NSIS handler (LGPL-2.1+) does; its compressor +/// signature table and NSIS 2/3 string-code detection were the model for the ones here. /// public sealed class NsisReader : IDisposable { + private readonly Stream _stream; private readonly bool _leaveOpen; + private readonly long _streamStart; // absolute offset just past firstheader + private readonly long _nonSolidDataStart; // absolute offset of data-section offset 0 (non-solid) + private readonly byte[] _header; + private readonly int _stringsOffset; + private readonly int _stringChars; + private readonly List _entries = []; + private bool _sizesResolved; + private bool _disposed; - private readonly int _compressionType; - private readonly bool _isSolid; - private readonly long _overlayOffset; // absolute stream position of the NSIS first-header - private readonly long _headerDataOffset; // absolute stream position of the compressed header - private readonly int _headerDataSize; // compressed size of header block (0 = not known) - private readonly long _dataOffset; // absolute stream position of first data block - private readonly List _entries; + /// Gets the files the install script extracts, in script order. + public IReadOnlyList Entries => this._entries; - private bool _disposed; + /// Gets the compressor the installer was built with. + public NsisCompression Compression { get; } - /// Gets the data blocks found in the installer. - public IReadOnlyList Entries => _entries; + /// Gets whether the header and files share one compressed stream (/SOLID). + public bool IsSolid { get; } - /// - /// Gets the compression method used by this installer. - /// - public NsisCompression Compression => (NsisCompression)_compressionType; + /// Gets whether the installer's strings are UTF-16 (Unicode true, NSIS 3). + public bool IsUnicode { get; } - /// - /// Gets whether the installer uses solid (single-stream) compression. - /// - public bool IsSolid => _isSolid; + /// Gets whether the strings use the NSIS 3 escape codes rather than NSIS 2's. + public bool IsNsis3 { get; } - // ------------------------------------------------------------------------- - // Construction / parsing - // ------------------------------------------------------------------------- + /// Gets whether the header was written for a 64-bit exehead. + public bool Is64Bit { get; } + + /// Gets whether the LZMA stream carries the x86 branch-filter flag byte. + public bool HasFilterFlag { get; } + + /// Gets the file offset of firstheader. + public long FirstHeaderOffset { get; } + + /// Gets firstheader.flags (FH_FLAGS_*: 1 = uninstaller, 2 = silent, 4 = no CRC, 8 = force CRC). + public int FirstHeaderFlags { get; } + + /// Gets the uncompressed size of the script header. + public int HeaderLength { get; } + + /// Gets length_of_all_following_data: firstheader, data and CRC. + public int ArchiveLength { get; } /// - /// Opens an NSIS installer from a stream. The stream must be seekable. + /// Opens an NSIS installer. /// - /// A seekable stream positioned at the start of the PE executable. + /// A seekable stream holding the whole installer executable. /// Whether to leave open on dispose. + /// No NSIS first header, or a header that does not parse. + /// An LZMA stream behind the x86 branch filter. public NsisReader(Stream stream, bool leaveOpen = false) { ArgumentNullException.ThrowIfNull(stream); if (!stream.CanSeek) throw new ArgumentException("Stream must be seekable.", nameof(stream)); + this._stream = stream; + this._leaveOpen = leaveOpen; + + this.FirstHeaderOffset = FindFirstHeader(stream); + Span fh = stackalloc byte[NsisConstants.FirstHeaderSize]; + stream.Position = this.FirstHeaderOffset; + stream.ReadExactly(fh); + this.FirstHeaderFlags = BinaryPrimitives.ReadInt32LittleEndian(fh); + this.HeaderLength = BinaryPrimitives.ReadInt32LittleEndian(fh[20..]); + this.ArchiveLength = BinaryPrimitives.ReadInt32LittleEndian(fh[24..]); + if (this.HeaderLength <= 0 || this.HeaderLength > stream.Length * 64L) + throw new InvalidDataException($"Implausible NSIS header length {this.HeaderLength}."); + + this._streamStart = this.FirstHeaderOffset + NsisConstants.FirstHeaderSize; + Span sig = stackalloc byte[12]; + stream.Position = this._streamStart; + if (stream.ReadAtLeast(sig, sig.Length, throwOnEndOfStream: false) < sig.Length) + throw new InvalidDataException("NSIS data is truncated."); + + // The compressor is not recorded anywhere; it is recognised by the first bytes of the data, + // exactly as the table in 7-Zip's NsisIn.cpp lays out: + // LL LL LL LL (== header length) non-solid, header stored + // 5D 00 00 dd dd dd dd 00 solid LZMA ([00|01] before it: filter flag byte) + // SS SS SS 80 [ff] 5D 00 00 ... non-solid LZMA + // SS SS SS 80 31 tt non-solid bzip2 + // SS SS SS 80 .. non-solid Deflate + // 31 tt solid bzip2 + // anything else solid Deflate + var firstWord = BinaryPrimitives.ReadUInt32LittleEndian(sig); + bool filterFlag; + if (firstWord == (uint)this.HeaderLength) { + this.Compression = NsisCompression.None; + filterFlag = false; + } else if (IsLzma(sig, out filterFlag)) { + this.Compression = NsisCompression.Lzma; + this.IsSolid = true; + } else if (sig[3] == 0x80) { + this.Compression = IsLzma(sig[4..], out filterFlag) ? NsisCompression.Lzma + : IsBzip2(sig[4..]) ? NsisCompression.BZip2 + : NsisCompression.Zlib; + } else { + this.Compression = IsBzip2(sig) ? NsisCompression.BZip2 : NsisCompression.Zlib; + this.IsSolid = true; + } + this.HasFilterFlag = filterFlag; + + if (this.IsSolid) { + this._header = this.ReadSolidHeader(); + } else { + var blockLength = firstWord & ~NsisConstants.CompressedFlag; + this._nonSolidDataStart = this._streamStart + 4 + blockLength; + this._header = this.ReadBlock(this._streamStart); + if (this._header.Length != this.HeaderLength) + throw new InvalidDataException($"NSIS header block holds {this._header.Length} bytes, the first header promises {this.HeaderLength}."); + } - _stream = stream; - _leaveOpen = leaveOpen; - _entries = []; - - _overlayOffset = FindOverlay(); - stream.Position = _overlayOffset; - - // Read the NSIS first-header (28 bytes) - Span hdr = stackalloc byte[NsisConstants.FirstHeaderSize]; - stream.ReadExactly(hdr); - - // Validate signature at offset 4 within the first-header - if (!hdr.Slice(NsisConstants.SignatureOffset, NsisConstants.SignatureLength) - .SequenceEqual(NsisConstants.Signature)) - throw new InvalidDataException("NSIS signature not found at the expected overlay position."); - - var flags = BinaryPrimitives.ReadInt32LittleEndian(hdr); - _compressionType = flags & NsisConstants.CompressionMask; - _isSolid = (flags & NsisConstants.SolidFlag) != 0; - _headerDataSize = BinaryPrimitives.ReadInt32LittleEndian(hdr[20..]); - // archive_size at hdr[24..] — not currently used beyond validation - - _headerDataOffset = _overlayOffset + NsisConstants.FirstHeaderSize; - _dataOffset = _headerDataOffset + _headerDataSize; - - ScanDataBlocks(); + // NSIS 3's Deflate is stock; NSIS 2's stored blocks lack NLEN. Which one applies is known + // only from the strings, so the header itself was read with the stock rule first and the + // NSIS 2 rule as the fallback (DecodeBlock, ReadSolidHeader). + (this.Is64Bit, this._stringsOffset, this._stringChars, this.IsUnicode, this.IsNsis3) = this.ParseLayout(); + this.CollectEntries(); } - // ------------------------------------------------------------------------- - // Extraction - // ------------------------------------------------------------------------- + // ── public operations ─────────────────────────────────────────────── /// - /// Extracts the raw (decompressed) data for the given entry. + /// Returns the decompressed bytes of one entry. /// - /// An entry obtained from . - /// The decompressed bytes. + /// An entry from . public byte[] Extract(NsisEntry entry) { - ObjectDisposedException.ThrowIf(_disposed, this); + ObjectDisposedException.ThrowIf(this._disposed, this); ArgumentNullException.ThrowIfNull(entry); + byte[]? result = null; + this.ExtractMany([entry], (_, data) => result = data); + return result ?? throw new InvalidDataException($"NSIS data block for '{entry.FileName}' was not found."); + } - if (_isSolid) - throw new NotSupportedException( - "Extraction of individual entries from solid NSIS installers is not supported. " + - "Use ExtractSolidStream to obtain the full decompressed stream."); - - var index = _entries.IndexOf(entry); - if (index < 0) - throw new ArgumentException("Entry does not belong to this archive.", nameof(entry)); - - // Seek to the block - var blockPos = GetBlockOffset(index); - _stream.Position = blockPos; - - // Read 4-byte length prefix - Span lenBuf = stackalloc byte[4]; - _stream.ReadExactly(lenBuf); - var word = BinaryPrimitives.ReadUInt32LittleEndian(lenBuf); - - var stored = (word & NsisConstants.UncompressedFlag) != 0; - var compressedSize = (int)(word & ~NsisConstants.UncompressedFlag); + /// + /// Decodes the given entries, calling once per entry. A solid installer + /// is decoded in a single forward pass that stops after the last requested block. + /// + /// Entries from . + /// Receives each entry with its bytes. + public void ExtractMany(IEnumerable entries, Action sink) { + ObjectDisposedException.ThrowIf(this._disposed, this); + ArgumentNullException.ThrowIfNull(entries); + ArgumentNullException.ThrowIfNull(sink); + var byOffset = entries.GroupBy(e => e.DataOffset).ToDictionary(g => (long)g.Key, g => g.ToArray()); + if (byOffset.Count == 0) + return; - var compressedData = new byte[compressedSize]; - _stream.ReadExactly(compressedData); + if (!this.IsSolid) { + foreach (var (offset, group) in byOffset.OrderBy(kv => kv.Key)) { + var data = this.ReadBlock(this._nonSolidDataStart + offset); + foreach (var e in group) + sink(e, data); + } + return; + } - return stored ? compressedData : DecompressBlock(compressedData); + var pending = byOffset.Count; + this.WalkSolid( + wants: (offset, _) => byOffset.ContainsKey(offset), + deliver: (offset, data) => { + foreach (var e in byOffset[offset]) + sink(e, data); + return --pending > 0; + }); + if (pending > 0) + throw new InvalidDataException("The NSIS solid stream ended before every requested file."); } /// - /// Decompresses the entire solid data stream and returns it as a single byte array. - /// Only meaningful when is . + /// Fills in (and, for non-solid installers, ) + /// for every entry. Compressed blocks have to be decoded to learn their size. /// - public byte[] ExtractSolidStream() { - ObjectDisposedException.ThrowIf(_disposed, this); + public void ResolveSizes() { + ObjectDisposedException.ThrowIf(this._disposed, this); + if (this._sizesResolved) + return; - _stream.Position = _dataOffset; - using var slice = new SubStream(_stream, _stream.Length - _dataOffset, leaveOpen: true); - return DecompressFull(slice); + var byOffset = this._entries.GroupBy(e => (long)e.DataOffset).ToDictionary(g => g.Key, g => g.ToArray()); + if (!this.IsSolid) { + foreach (var (offset, group) in byOffset) { + var (stored, compressed) = this.MeasureBlock(this._nonSolidDataStart + offset); + foreach (var e in group) { + e.Size = stored; + e.CompressedSize = compressed; + } + } + } else if (byOffset.Count > 0) { + var pending = byOffset.Count; + this.WalkSolid( + wants: (offset, length) => { + if (!byOffset.TryGetValue(offset, out var group)) + return false; + foreach (var e in group) + e.Size = length; + if (--pending == 0) + throw new StopDecoding(); + return false; + }, + deliver: (_, _) => true); + } + this._sizesResolved = true; } - // ------------------------------------------------------------------------- - // IDisposable - // ------------------------------------------------------------------------- - /// public void Dispose() { - if (_disposed) return; - _disposed = true; - if (!_leaveOpen) - _stream.Dispose(); + if (this._disposed) + return; + this._disposed = true; + if (!this._leaveOpen) + this._stream.Dispose(); } - // ------------------------------------------------------------------------- - // Private helpers - // ------------------------------------------------------------------------- + // ── locating and decoding ─────────────────────────────────────────── - /// - /// Finds the overlay (data after the last PE section) by parsing the DOS/PE headers. - /// Falls back to scanning for the NSIS signature when the stream does not start with a PE. - /// - private long FindOverlay() { - _stream.Position = 0; - - // Try proper PE parsing first - var overlay = TryFindOverlayViaPe(); - if (overlay >= 0) { - // Confirm there is an NSIS signature at that position - if (HasNsisSignatureAt(overlay)) - return overlay; - - // The overlay might start further in if there is a stub before the NSIS header. - // Fall through to scan. + private static long FindFirstHeader(Stream stream) { + Span buf = stackalloc byte[NsisConstants.FirstHeaderSize]; + for (long pos = 0; pos + NsisConstants.FirstHeaderSize <= stream.Length; pos += NsisConstants.FirstHeaderAlignment) { + stream.Position = pos; + if (stream.ReadAtLeast(buf, buf.Length, throwOnEndOfStream: false) < buf.Length) + break; + if ((BinaryPrimitives.ReadInt32LittleEndian(buf) & ~NsisConstants.FirstHeaderFlagsMask) == 0 + && BinaryPrimitives.ReadUInt32LittleEndian(buf[4..]) == NsisConstants.FirstHeaderSignature + && buf.Slice(8, 12).SequenceEqual(NsisConstants.Magic)) + return pos; } - - // Fallback: linear scan from start - return ScanForSignature(); + throw new InvalidDataException("No NSIS first header found."); } - private long TryFindOverlayViaPe() { - try { - _stream.Position = 0; - using var reader = new BinaryReader(_stream, System.Text.Encoding.ASCII, leaveOpen: true); - - // DOS header: "MZ" - if (reader.ReadUInt16() != 0x5A4D) // 'MZ' - return -1; - - // e_lfanew at offset 0x3C - _stream.Position = 0x3C; - var peOffset = reader.ReadInt32(); - if (peOffset <= 0 || peOffset + 24 > _stream.Length) - return -1; - - _stream.Position = peOffset; - var peSig = reader.ReadUInt32(); - if (peSig != 0x00004550) // 'PE\0\0' - return -1; - - reader.ReadUInt16(); // Machine - var numSections = reader.ReadUInt16(); - reader.ReadUInt32(); // TimeDateStamp - reader.ReadUInt32(); // PointerToSymbolTable - reader.ReadUInt32(); // NumberOfSymbols - var sizeOfOptionalHeader = reader.ReadUInt16(); - reader.ReadUInt16(); // Characteristics - - long sectionTableOffset = peOffset + 24 + sizeOfOptionalHeader; - if (sectionTableOffset + numSections * 40L > _stream.Length) - return -1; - - long maxEnd = 0; - _stream.Position = sectionTableOffset; - for (var i = 0; i < numSections; ++i) { - // Section header is 40 bytes: - // 0..7 Name - // 8 VirtualSize - // 12 VirtualAddress - // 16 SizeOfRawData - // 20 PointerToRawData - // 24..39 other fields - var secHdr = reader.ReadBytes(40); - var rawSize = BinaryPrimitives.ReadUInt32LittleEndian(secHdr.AsSpan(16)); - var rawPtr = BinaryPrimitives.ReadUInt32LittleEndian(secHdr.AsSpan(20)); - long end = rawPtr + rawSize; - if (end > maxEnd) - maxEnd = end; - } - - return maxEnd > 0 ? maxEnd : -1; - } catch { - return -1; + private static bool IsLzma(ReadOnlySpan p, out bool filterFlag) { + filterFlag = false; + if (LooksLikeLzmaProperties(p)) + return true; + if (p[0] <= 1 && LooksLikeLzmaProperties(p[1..])) { + filterFlag = true; + return true; } + return false; } - private bool HasNsisSignatureAt(long position) { - if (position + NsisConstants.FirstHeaderSize > _stream.Length) - return false; + // lc=3 lp=0 pb=2 (0x5D), then a dictionary size below 2^31 that is a multiple of 64 KiB. + private static bool LooksLikeLzmaProperties(ReadOnlySpan p) + => p.Length >= 7 && p[0] == 0x5D && p[1] == 0 && p[2] == 0 && p[5] == 0 && (p[6] & 0x80) == 0; + + private static bool IsBzip2(ReadOnlySpan p) => p[0] == 0x31 && p[1] < 14; - _stream.Position = position + NsisConstants.SignatureOffset; - Span buf = stackalloc byte[NsisConstants.SignatureLength]; - if (!TryReadExact(_stream, buf)) - return false; + /// Reads one length-prefixed block of a non-solid installer. + private byte[] ReadBlock(long absoluteOffset) { + var (compressed, length) = this.ReadLengthWord(absoluteOffset); + if (!compressed) { + var raw = new byte[length]; + this._stream.ReadExactly(raw); + return raw; + } - return buf.SequenceEqual(NsisConstants.Signature); + using var output = new MemoryStream(); + this.DecodeBlock(absoluteOffset + 4, length, output); + return output.ToArray(); } - private long ScanForSignature() { - // Scan in 4-byte steps looking for the first 4 bytes of the signature (0xEFBEADDE) - const int bufSize = 65536; - var buf = new byte[bufSize + NsisConstants.SignatureLength]; - long pos = 0; + private (long Size, long Compressed) MeasureBlock(long absoluteOffset) { + var (compressed, length) = this.ReadLengthWord(absoluteOffset); + if (!compressed) + return (length, length); + using var counter = new CountingSink(); + this.DecodeBlock(absoluteOffset + 4, length, counter); + return (counter.Count, length); + } - _stream.Position = 0; - while (pos < _stream.Length) { - var read = _stream.Read(buf, 0, buf.Length); - if (read < NsisConstants.SignatureLength) - break; + private (bool Compressed, int Length) ReadLengthWord(long absoluteOffset) { + if (absoluteOffset < this._streamStart || absoluteOffset + 4 > this._stream.Length) + throw new InvalidDataException("NSIS data offset lies outside the file."); + Span word = stackalloc byte[4]; + this._stream.Position = absoluteOffset; + this._stream.ReadExactly(word); + var value = BinaryPrimitives.ReadUInt32LittleEndian(word); + var compressed = (value & NsisConstants.CompressedFlag) != 0 && this.Compression != NsisCompression.None; + var length = (long)(value & ~NsisConstants.CompressedFlag); + if (absoluteOffset + 4 + length > this._stream.Length) + throw new InvalidDataException("NSIS data block runs past the end of the file."); + return (compressed, (int)length); + } - for (var i = 0; i <= read - NsisConstants.SignatureLength; i++) { - if (buf[i] == 0xEF && buf[i + 1] == 0xBE && - buf[i + 2] == 0xAD && buf[i + 3] == 0xDE) { - var candidate = pos + i - NsisConstants.SignatureOffset; - if (candidate >= 0 && HasNsisSignatureAt(candidate)) - return candidate; - } + private void DecodeBlock(long absoluteOffset, int length, Stream output) { + // NSIS 2's inflate takes a stored block's LEN without NLEN; NSIS 3 is stock zlib. Before the + // strings have told the dialect apart (the header block itself) the stock rule is tried first. + var nsisDeflate = this._header is null ? (bool?)null : !this.IsNsis3; + if (nsisDeflate is null && this.Compression == NsisCompression.Zlib) { + var start = output.Position; + try { + this.DecodeBounded(absoluteOffset, length, output, nsisDeflate: false); + return; + } catch (InvalidDataException) { + output.SetLength(start); + output.Position = start; } - - // Rewind so we don't miss a signature that straddles a buffer boundary - pos += bufSize; - _stream.Position = pos; + nsisDeflate = true; } - - throw new InvalidDataException("NSIS signature not found in stream."); + this.DecodeBounded(absoluteOffset, length, output, nsisDeflate ?? false); } - /// - /// Iterates the non-solid data section to build the entry list. - /// For solid archives a single synthetic entry is added instead. - /// - private void ScanDataBlocks() { - if (_isSolid) { - // Solid: single entry for the whole compressed stream - var solidSize = _stream.Length - _dataOffset; - _entries.Add(new NsisEntry("solid_stream", -1, solidSize, false)); - return; - } - - // Non-solid: walk successive 4+N blocks until we run out of data or hit EOF - _stream.Position = _dataOffset; - var index = 0; - var lenBufArr = new byte[4]; + private void DecodeBounded(long absoluteOffset, int length, Stream output, bool nsisDeflate) { + this._stream.Position = absoluteOffset; + using var bounded = new SubStream(this._stream, length, leaveOpen: true); + this.Decode(bounded, output, nsisDeflate); + } - while (_stream.Position + 4 <= _stream.Length) { - if (!TryReadExact(_stream, lenBufArr)) + /// Decodes one compressed NSIS stream from into . + private void Decode(Stream input, Stream output, bool nsisDeflate) { + switch (this.Compression) { + case NsisCompression.Lzma: { + if (this.HasFilterFlag) { + var flag = input.ReadByte(); + if (flag < 0) + throw new InvalidDataException("NSIS LZMA stream is truncated."); + if (flag > 1) + throw new InvalidDataException($"Invalid NSIS LZMA filter flag {flag}."); + if (flag == 1) + throw new NotSupportedException("NSIS LZMA streams behind the x86 branch filter (seen only in 7-Zip's own installer) are not supported."); + } + var props = new byte[5]; + input.ReadExactly(props); + new LzmaDecoder(input, props).Decode(output); break; - Span lenBuf = lenBufArr; - - var word = BinaryPrimitives.ReadUInt32LittleEndian(lenBuf); - - // A zero word at the end of the section is the sentinel used by some NSIS versions. - if (word == 0) + } + case NsisCompression.Zlib: { + var inflater = new DeflateDecompressor(input) { StoredBlocksOmitComplement = nsisDeflate }; + var buffer = new byte[81920]; + while (true) { + var n = inflater.Decompress(buffer); + if (n <= 0) + break; + output.Write(buffer, 0, n); + } + if (!inflater.IsFinished) + throw new InvalidDataException("NSIS Deflate stream is truncated."); break; - - var stored = (word & NsisConstants.UncompressedFlag) != 0; - var compressedSize = (int)(word & ~NsisConstants.UncompressedFlag); - - if (compressedSize < 0 || _stream.Position + compressedSize > _stream.Length) + } + case NsisCompression.BZip2: { + using var bz = Bzip2Stream.OpenNsisDecoder(input, leaveOpen: true); + bz.CopyTo(output); break; + } + default: + input.CopyTo(output); + break; + } + } - var uncompressedSize = stored ? compressedSize : -1L; - _entries.Add(new NsisEntry( - $"block_{index}", - uncompressedSize, - compressedSize, - false)); - - _stream.Position += compressedSize; - ++index; + private byte[] ReadSolidHeader() { + byte[]? header = null; + try { + this.WalkSolid(wants: (_, _) => false, deliver: (_, _) => true, onHeader: data => header = data, nsisDeflate: false); + } catch (InvalidDataException) when (this.Compression == NsisCompression.Zlib) { + // A stored block without NLEN: an NSIS 2 stream. + header = null; + this.WalkSolid(wants: (_, _) => false, deliver: (_, _) => true, onHeader: data => header = data, nsisDeflate: true); } + return header ?? throw new InvalidDataException("NSIS solid stream ended inside the header."); } /// - /// Returns the absolute stream position of the length-prefix word for the block at - /// by replaying the block list from . + /// Runs the solid stream through a , which splits it into the header + /// block and the data blocks. /// - private long GetBlockOffset(int targetIndex) { - _stream.Position = _dataOffset; - var lenBufArr = new byte[4]; - - for (var i = 0; i < targetIndex; ++i) { - _stream.ReadExactly(lenBufArr); - var word = BinaryPrimitives.ReadUInt32LittleEndian(lenBufArr); - var size = (int)(word & ~NsisConstants.UncompressedFlag); - _stream.Position += size; + /// Called with each data block's offset and length; return true to receive its bytes. + /// Receives a wanted block; return false once nothing more is needed. + /// Receives the header block; when null the header is skipped. + /// Decode stored Deflate blocks the NSIS 2 way; defaults to what the strings showed. + private void WalkSolid(Func wants, Func deliver, Action? onHeader = null, bool? nsisDeflate = null) { + var sink = new SolidSink(this.HeaderLength, wants, deliver, onHeader); + var nsis2 = nsisDeflate ?? !this.IsNsis3; + try { + this._stream.Position = this._streamStart; + var remaining = this._stream.Length - this._streamStart; + using var bounded = new SubStream(this._stream, remaining, leaveOpen: true); + this.Decode(bounded, sink, nsis2); + } catch (StopDecoding) { + // The sink has everything it was asked for. } - - return _stream.Position; } - // ------------------------------------------------------------------------- - // Decompression helpers - // ------------------------------------------------------------------------- - - private byte[] DecompressBlock(byte[] data) => - _compressionType switch { - NsisConstants.CompNone => data, - NsisConstants.CompZlib => DecompressZlib(data), - NsisConstants.CompBzip2 => DecompressBzip2(data), - NsisConstants.CompLzma => DecompressLzma(data), - _ => throw new NotSupportedException($"Unsupported NSIS compression type: {_compressionType}") - }; - - private byte[] DecompressFull(Stream s) => - _compressionType switch { - NsisConstants.CompNone => ReadAllBytes(s), - NsisConstants.CompZlib => DecompressZlibStream(s), - NsisConstants.CompBzip2 => DecompressBzip2Stream(s), - NsisConstants.CompLzma => DecompressLzmaStream(s), - _ => throw new NotSupportedException($"Unsupported NSIS compression type: {_compressionType}") - }; - - // Zlib = 2-byte zlib header + raw Deflate - private static byte[] DecompressZlib(byte[] data) { - if (data.Length < NsisConstants.ZlibHeaderSize) - throw new InvalidDataException("Zlib block too short."); - var deflateData = data.AsSpan(NsisConstants.ZlibHeaderSize); - return DeflateDecompressor.Decompress(deflateData); + // ── header parsing ────────────────────────────────────────────────── + + private (bool Is64Bit, int StringsOffset, int StringChars, bool Unicode, bool Nsis3) ParseLayout() { + var h = this._header; + // header = flags, blocks[8] of {offset, num}; the offset is pointer-sized for a 64-bit + // exehead (12-byte packed records). A 64-bit layout shows as eight zero high words. + var is64 = h.Length >= 4 + 12 * 8; + for (var k = 0; is64 && k < 8; ++k) + if (ReadInt32(h, 4 + 12 * k + 4) != 0) + is64 = false; + var recordSize = is64 ? 12 : 8; + if (h.Length < 4 + recordSize * 8) + throw new InvalidDataException("NSIS header is too short for its block table."); + + int Offset(int block) => ReadInt32(h, 4 + recordSize * block); + int Count(int block) => ReadInt32(h, 4 + recordSize * block + recordSize - 4); + + var entriesOffset = Offset(NsisConstants.BlockEntries); + var entriesCount = Count(NsisConstants.BlockEntries); + var stringsOffset = Offset(NsisConstants.BlockStrings); + var langOffset = Offset(NsisConstants.BlockLangTables); + if (entriesOffset < 0 || stringsOffset < 0 || langOffset < stringsOffset || langOffset > h.Length || entriesOffset > h.Length) + throw new InvalidDataException("NSIS header block table is inconsistent."); + if (entriesCount < 0 || (long)entriesCount * NsisConstants.EntrySize > h.Length - entriesOffset) + throw new InvalidDataException("NSIS instruction table runs past the header."); + + var tableBytes = langOffset - stringsOffset; + if (tableBytes < 2 || h[langOffset - 1] != 0) + throw new InvalidDataException("NSIS string table is not terminated."); + // The table starts with the empty string; a UTF-16 table therefore starts with two zero bytes. + var unicode = h[stringsOffset] == 0 && h[stringsOffset + 1] == 0; + if (unicode && ((tableBytes & 1) != 0 || h[langOffset - 2] != 0)) + throw new InvalidDataException("NSIS UTF-16 string table is malformed."); + var chars = unicode ? tableBytes / 2 : tableBytes; + return (is64, stringsOffset, chars, unicode, DetectNsis3(h.AsSpan(stringsOffset, tableBytes), unicode)); } - private static byte[] DecompressZlibStream(Stream s) { - // Skip 2-byte zlib header - Span skip = stackalloc byte[NsisConstants.ZlibHeaderSize]; - s.ReadExactly(skip); - var decompressor = new DeflateDecompressor(s); - return decompressor.DecompressAll(); + /// + /// NSIS 3 escapes a variable as 0x03 plus a coded short with both high bits set; NSIS 2 used + /// 0xFD. Unicode installers are always NSIS 3. For ANSI ones, count both patterns. + /// + private static bool DetectNsis3(ReadOnlySpan table, bool unicode) { + if (unicode) + return true; + int nsis3 = 0, nsis2 = 0; + for (var i = 0; i + 2 < table.Length; ++i) { + if ((table[i + 1] & 0x80) == 0 || (table[i + 2] & 0x80) == 0) + continue; + if (table[i] == NsisConstants.Nsis3VarCode) + ++nsis3; + else if (table[i] == NsisConstants.Nsis2VarCode) + ++nsis2; + } + return nsis3 >= nsis2; } - private static byte[] DecompressBzip2(byte[] data) { - using var ms = new MemoryStream(data); - return DecompressBzip2Stream(ms); + private void CollectEntries() { + var h = this._header; + var recordSize = this.Is64Bit ? 12 : 8; + var entriesOffset = ReadInt32(h, 4 + recordSize * NsisConstants.BlockEntries); + var entriesCount = ReadInt32(h, 4 + recordSize * NsisConstants.BlockEntries + recordSize - 4); + + var outDir = "$INSTDIR"; + var seen = new HashSet<(string, uint)>(); + Span p = stackalloc int[6]; + for (var i = 0; i < entriesCount; ++i) { + var at = entriesOffset + i * NsisConstants.EntrySize; + var opcode = ReadInt32(h, at); + for (var k = 0; k < 6; ++k) + p[k] = ReadInt32(h, at + 4 + 4 * k); + + switch (opcode) { + case NsisConstants.OpCreateDir when p[1] != 0: { + // SetOutPath. "$OUTDIR\x" is relative to the current output directory. + var path = this.RenderString(p[0], out var leadingVar); + if (leadingVar == NsisConstants.VarOutDir) + path = outDir + path["$OUTDIR".Length..]; + outDir = path; + break; + } + case NsisConstants.OpExtractFile: { + var name = this.RenderString(p[1], out var leadingVar); + var absolute = leadingVar is NsisConstants.VarInstDir or NsisConstants.VarOutDir or NsisConstants.VarExeDir + or NsisConstants.VarTemp or NsisConstants.VarPluginsDir or ShellFolderMarker + || IsAbsolutePath(name); + var full = absolute ? name : outDir.TrimEnd('\\') + "\\" + (name.Length == 0 ? "file" : name); + if (full.StartsWith("$INSTDIR\\", StringComparison.OrdinalIgnoreCase)) + full = full["$INSTDIR\\".Length..].TrimStart('\\'); + full = full.Replace('\\', '/'); + var offset = (uint)p[2]; + if (!seen.Add((full, offset))) + break; + this._entries.Add(new NsisEntry(full, offset, ToFileTime(p[3], p[4]))); + break; + } + } + } } - private static byte[] DecompressBzip2Stream(Stream s) { - using var bzip2 = new FileFormat.Bzip2.Bzip2Stream( - s, global::Compression.Core.Streams.CompressionStreamMode.Decompress, leaveOpen: true); - using var result = new MemoryStream(); - bzip2.CopyTo(result); - return result.ToArray(); - } + private static bool IsAbsolutePath(string s) + => (s.Length >= 2 && s[0] == '\\' && s[1] == '\\') || (s.Length >= 2 && char.IsAsciiLetter(s[0]) && s[1] == ':'); - private static byte[] DecompressLzma(byte[] data) { - using var ms = new MemoryStream(data); - return DecompressLzmaStream(ms); + private static DateTime? ToFileTime(int low, int high) { + var ticks = ((long)(uint)high << 32) | (uint)low; + if (ticks <= 0 || ticks == -1 || ticks > DateTime.MaxValue.ToFileTimeUtc()) + return null; + return DateTime.FromFileTimeUtc(ticks); } - private static byte[] DecompressLzmaStream(Stream s) { - // NSIS LZMA sub-header: 5-byte properties + 8-byte uncompressed size (LE int64) - Span lzmaHdr = stackalloc byte[NsisConstants.LzmaHeaderSize]; - s.ReadExactly(lzmaHdr); + // ── strings ───────────────────────────────────────────────────────── + + private const int ShellFolderMarker = -2; + + /// + /// Renders a script string with its variables spelled out ($INSTDIR, $R0, + /// $SYSDIR, ...). reports what the string starts with: + /// a variable index, for a shell folder, or -1. + /// + private string RenderString(int index, out int leadingVar) { + leadingVar = -1; + if (index < 0) + return $"$(LSTR_{-(index + 1)})"; + if (index >= this._stringChars) + return string.Empty; + + var sb = new StringBuilder(); + int langCode, shellCode, varCode, skipCode; + if (this.IsNsis3) + (langCode, shellCode, varCode, skipCode) = (NsisConstants.Nsis3LangCode, NsisConstants.Nsis3ShellCode, NsisConstants.Nsis3VarCode, NsisConstants.Nsis3SkipCode); + else + (langCode, shellCode, varCode, skipCode) = (NsisConstants.Nsis2LangCode, NsisConstants.Nsis2ShellCode, NsisConstants.Nsis2VarCode, NsisConstants.Nsis2SkipCode); + + var pos = index; + var first = true; + while (pos < this._stringChars) { + var c = this.CharAt(pos++); + if (c == 0) + break; + if (c == langCode || c == shellCode || c == varCode) { + // Two bytes follow: one UTF-16 unit, or two ANSI bytes. Low byte first either way. + int b0, b1; + if (this.IsUnicode) { + var w = pos < this._stringChars ? this.CharAt(pos++) : 0; + (b0, b1) = (w & 0xFF, w >> 8); + } else { + b0 = pos < this._stringChars ? this.CharAt(pos++) : 0; + b1 = pos < this._stringChars ? this.CharAt(pos++) : 0; + } + var data = ((b1 & 0x7F) << 7) | (b0 & 0x7F); + if (c == varCode) { + if (first) + leadingVar = data; + sb.Append('$').Append(VariableName(data)); + } else if (c == shellCode) { + if (first) + leadingVar = ShellFolderMarker; + sb.Append(this.ShellFolderName(b0, b1)); + } else { + sb.Append("$(LSTR_").Append(data).Append(')'); + } + } else if (c == skipCode) { + if (pos < this._stringChars) + sb.Append(ToChar(this.CharAt(pos++))); + } else { + sb.Append(ToChar(c)); + } + first = false; + } + return sb.ToString(); + } - var props = lzmaHdr[..NsisConstants.LzmaPropSize].ToArray(); - var uncompressedSize = BinaryPrimitives.ReadInt64LittleEndian(lzmaHdr[NsisConstants.LzmaPropSize..]); + private int CharAt(int index) => this.IsUnicode + ? BinaryPrimitives.ReadUInt16LittleEndian(this._header.AsSpan(this._stringsOffset + index * 2)) + : this._header[this._stringsOffset + index]; + + // ANSI strings are in the compiling machine's code page, which the file does not record; + // Latin-1 keeps every byte distinct and round-trips the ASCII that file names nearly always are. + private static char ToChar(int c) => (char)c; + + private static string VariableName(int index) => index switch { + < 10 => index.ToString(System.Globalization.CultureInfo.InvariantCulture), + < 20 => "R" + (index - 10).ToString(System.Globalization.CultureInfo.InvariantCulture), + _ when index - 20 < NsisConstants.InternalVariables.Length => NsisConstants.InternalVariables[index - 20], + _ => "_" + (index - 20 - NsisConstants.InternalVariables.Length).ToString(System.Globalization.CultureInfo.InvariantCulture) + "_", + }; + + private string ShellFolderName(int index1, int index2) { + // Bit 7 set: the path comes from HKLM\Software\Microsoft\Windows\CurrentVersion, value name + // in the string table at (index1 & 0x3F); bit 6 asks for the 64-bit registry view. + if ((index1 & 0x80) != 0) { + var valueName = this.RenderString(index1 & 0x3F, out _); + var name = valueName switch { + "ProgramFilesDir" => "$PROGRAMFILES", + "CommonFilesDir" => "$COMMONFILES", + _ => "$_REGISTRY_(" + valueName + ")", + }; + return (index1 & 0x40) != 0 ? name + "64" : name; + } + var folders = NsisConstants.ShellFolders; + if (index1 < folders.Length && folders[index1] is { } a) + return "$" + a; + if (index2 < folders.Length && folders[index2] is { } b) + return "$" + b; + return "$_SHELL_" + index1.ToString(System.Globalization.CultureInfo.InvariantCulture) + "_"; + } - var decoder = new LzmaDecoder(s, props, uncompressedSize); - return decoder.Decode(); + private static int ReadInt32(byte[] data, int offset) + => offset >= 0 && offset + 4 <= data.Length ? BinaryPrimitives.ReadInt32LittleEndian(data.AsSpan(offset)) : 0; + + // ── solid stream splitting ────────────────────────────────────────── + + private sealed class StopDecoding : Exception; + + /// Counts bytes written to it and discards them. + private sealed class CountingSink : Stream { + public long Count { get; private set; } + public override bool CanRead => false; + public override bool CanSeek => false; + public override bool CanWrite => true; + public override long Length => this.Count; + public override long Position { get => this.Count; set => throw new NotSupportedException(); } + public override void Flush() { } + public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException(); + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + public override void SetLength(long value) => throw new NotSupportedException(); + public override void Write(byte[] buffer, int offset, int count) => this.Count += count; + public override void Write(ReadOnlySpan buffer) => this.Count += buffer.Length; } - // ------------------------------------------------------------------------- - // Low-level I/O helpers - // ------------------------------------------------------------------------- + /// + /// Write-only stream that splits a decoded solid stream into its blocks as the decoder + /// produces them: the header block first, then data blocks whose offsets count from the end of + /// the header block. It throws once its caller needs nothing more. + /// + private sealed class SolidSink(int headerLength, Func wants, Func deliver, Action? onHeader) : Stream { + private readonly byte[] _word = new byte[4]; + private int _wordFill; + private bool _inBlock; + private long _blockOffset = -1; // -1 = the header block + private long _nextOffset = -1; + private long _remaining; + private byte[]? _buffer; + private int _bufferFill; + + public override bool CanRead => false; + public override bool CanSeek => false; + public override bool CanWrite => true; + public override long Length => throw new NotSupportedException(); + public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } + public override void Flush() { } + public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException(); + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + public override void SetLength(long value) => throw new NotSupportedException(); + public override void Write(byte[] buffer, int offset, int count) => this.Write(buffer.AsSpan(offset, count)); + + public override void Write(ReadOnlySpan data) { + while (!data.IsEmpty) { + if (!this._inBlock) { + var take = Math.Min(4 - this._wordFill, data.Length); + data[..take].CopyTo(this._word.AsSpan(this._wordFill)); + this._wordFill += take; + data = data[take..]; + if (this._wordFill < 4) + return; + this._wordFill = 0; + this.BeginBlock((int)(BinaryPrimitives.ReadUInt32LittleEndian(this._word) & 0x7FFFFFFF)); + continue; + } - private static bool TryReadExact(Stream s, Span buf) => - s.ReadAtLeast(buf, buf.Length, throwOnEndOfStream: false) == buf.Length; + var n = (int)Math.Min(this._remaining, data.Length); + if (this._buffer != null) + data[..n].CopyTo(this._buffer.AsSpan(this._bufferFill)); + this._bufferFill += n; + this._remaining -= n; + data = data[n..]; + if (this._remaining == 0) + this.EndBlock(); + } + } + + private void BeginBlock(int length) { + this._blockOffset = this._nextOffset; + this._nextOffset = this._blockOffset < 0 ? 0 : this._blockOffset + 4 + length; + bool want; + if (this._blockOffset < 0) { + if (length != headerLength) + throw new InvalidDataException($"NSIS solid stream starts with a {length}-byte block, the first header promises {headerLength}."); + want = onHeader != null; + } else + want = wants(this._blockOffset, length); + + this._buffer = want ? new byte[length] : null; + this._bufferFill = 0; + this._remaining = length; + this._inBlock = true; + if (length == 0) + this.EndBlock(); + } - private static byte[] ReadAllBytes(Stream s) { - using var ms = new MemoryStream(); - s.CopyTo(ms); - return ms.ToArray(); + private void EndBlock() { + this._inBlock = false; + var data = this._buffer; + this._buffer = null; + if (this._blockOffset < 0) { + if (data != null) { + onHeader!(data); + throw new StopDecoding(); + } + return; + } + if (data != null && !deliver(this._blockOffset, data)) + throw new StopDecoding(); + } } } diff --git a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Nsis/NsisWriter.cs b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Nsis/NsisWriter.cs deleted file mode 100644 index 722d36929..000000000 --- a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Nsis/NsisWriter.cs +++ /dev/null @@ -1,54 +0,0 @@ -#pragma warning disable CS1591 -using System.Buffers.Binary; - -namespace FileFormat.Nsis; - -/// -/// Writes a minimal NSIS-formatted file. No PE stub is emitted — the reader's -/// ScanForSignature fallback finds the NSIS overlay via linear scan when -/// PE parsing fails. Files are stored uncompressed as individual data blocks -/// (non-solid). Roundtrips through . -/// -/// Note: the produced file has no executable code and cannot be "run" to -/// install — the NSIS format is primarily an installer stub + overlay data, -/// and emitting a functional installer would require shipping a signed PE stub -/// which is out of scope for a compression toolkit. -/// -public sealed class NsisWriter { - private readonly List _blocks = []; - - /// - /// Performs the add file operation. - /// - public void AddFile(string name, byte[] data) { - ArgumentNullException.ThrowIfNull(data); - // NSIS data blocks have no embedded file names (the reader names them - // "block_N"); we simply treat each input as one opaque block. - _blocks.Add(data); - } - - /// - /// Writes the to to the supplied output. - /// - public void WriteTo(Stream output) { - // ── NSIS first-header (28 bytes) ── - Span hdr = stackalloc byte[NsisConstants.FirstHeaderSize]; - BinaryPrimitives.WriteInt32LittleEndian(hdr[..4], NsisConstants.CompNone); // flags: stored, non-solid - NsisConstants.Signature.CopyTo(hdr[NsisConstants.SignatureOffset..]); - BinaryPrimitives.WriteInt32LittleEndian(hdr.Slice(20, 4), 0); // header_size = 0 (no script header) - // archive_size (int32 LE) = total size of data blocks area — informational; reader doesn't enforce - var blocksSize = 0; - foreach (var b in _blocks) blocksSize += 4 + b.Length; - BinaryPrimitives.WriteInt32LittleEndian(hdr.Slice(24, 4), blocksSize); - output.Write(hdr); - - // ── Data blocks: each = 4-byte length (with UncompressedFlag) + raw data ── - Span lenBuf = stackalloc byte[4]; - foreach (var data in _blocks) { - var word = (uint)data.Length | NsisConstants.UncompressedFlag; // stored - BinaryPrimitives.WriteUInt32LittleEndian(lenBuf, word); - output.Write(lenBuf); - output.Write(data); - } - } -} diff --git a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Nsis/THIRD-PARTY-NOTICE.NSIS.txt b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Nsis/THIRD-PARTY-NOTICE.NSIS.txt new file mode 100644 index 000000000..2836dc9fb --- /dev/null +++ b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Nsis/THIRD-PARTY-NOTICE.NSIS.txt @@ -0,0 +1,29 @@ +The NSIS reader in this directory was written from these sources: + +1. NSIS (Nullsoft Scriptable Install System), https://github.com/kichik/nsis, master as of + 2026-10-19: Source/exehead/fileform.h, fileform.c, util.c (installer layout, opcodes, + string escape codes) and Source/bzip2/decompress.c (the header-less bzip2 framing: block + marker 0x31, end marker 0x17, no CRCs). The structures and constants were taken over; the + code here is a new C# implementation. + + Copyright (C) 1999-2026 Nullsoft and Contributors. zlib/libpng license: + + This software is provided 'as-is', without any express or implied warranty. In no event + will the authors be held liable for any damages arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, including commercial + applications, and to alter it and redistribute it freely, subject to the following + restrictions: + + 1. The origin of this software must not be misrepresented; you must not claim that you + wrote the original software. If you use this software in a product, an acknowledgment + in the product documentation would be appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be misrepresented + as being the original software. + 3. This notice may not be removed or altered from any source distribution. + +2. 7-Zip, https://github.com/ip7z/7zip, CPP/7zip/Archive/Nsis/NsisIn.cpp and NsisDecode.cpp + (GNU LGPL 2.1 or later), Copyright (C) Igor Pavlov: the compressor-signature table, the + NSIS 2 / NSIS 3 string-dialect check, the NSIS 2 stored-block rule (LEN without NLEN), the + shell-folder name table and the rule for naming files relative to $INSTDIR. Adapted into + this LGPL-3.0-or-later code base. diff --git a/Hawkynt.FileFormats.Archives/README.md b/Hawkynt.FileFormats.Archives/README.md index fe0e8de9c..10be4b4ca 100644 --- a/Hawkynt.FileFormats.Archives/README.md +++ b/Hawkynt.FileFormats.Archives/README.md @@ -190,7 +190,7 @@ Every State, Test, Maintenance, Compress/Decompress and Demux/Mux/Remux cell is | [JAR](https://en.wikipedia.org/wiki/JAR_(file_format)) | `Jar` | `.jar` | R/W | ✅ | defrag · wipe | | [docs.oracle.com](https://docs.oracle.com/en-us/javase/8/docs/technotes/guides/jar/jar.html) | | [MSI](https://en.wikipedia.org/wiki/Windows_Installer) | `Msi` | `.msi` `.msp` `.mst` | R/W | ✅ | wipe | CFB envelope; a functional Installer database is not synthesised | [Microsoft Learn](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cfb/) | | [MSIX](https://en.wikipedia.org/wiki/MSIX) | `Msix` | `.msix` `.msixbundle` | R/W | ✅ | defrag · wipe | Unsigned fresh package output | [Microsoft Learn](https://learn.microsoft.com/en-us/windows/msix/) | -| [NSIS](https://en.wikipedia.org/wiki/Nullsoft_Scriptable_Install_System) | `Nsis` | | WORM | ✅ | defrag | Extraction plus overlay-oriented output, not an installer compiler; some versions expose no per-file extraction | [nsis.sourceforge.io](https://nsis.sourceforge.io/Docs/) | +| [NSIS](https://en.wikipedia.org/wiki/Nullsoft_Scriptable_Install_System) | `Nsis` | | R | ✅ | — | Files of the install script, zlib/bzip2/LZMA, solid or not, NSIS 2 and 3; checked against 7-Zip. LZMA behind the x86 branch filter is refused | [nsis.sourceforge.io](https://nsis.sourceforge.io/Docs/) | | [NuGet .nupkg](https://en.wikipedia.org/wiki/NuGet) | `NuPkg` | `.nupkg` | R/W | ✅ | defrag · wipe | | [Microsoft Learn](https://learn.microsoft.com/nuget/reference/nuspec) | | [OVA](https://en.wikipedia.org/wiki/Open_Virtualization_Format) | `Ova` | `.ova` | WORM | ✅ | — | Stays WORM: the manifest must cover every member | [dmtf.org](https://www.dmtf.org/standards/ovf) | | [Pack200](https://en.wikipedia.org/wiki/Pack200) | `Pack200` | `.pack` | R | ✅ | — | Listing only: class names are decoded from a subset of bands; full `.class` reconstruction and creation are not implemented | [docs.oracle.com](https://docs.oracle.com/javase/8/docs/technotes/guides/pack200/pack-spec.html) | diff --git a/Hawkynt.FileFormats.Archives/REFERENCE.md b/Hawkynt.FileFormats.Archives/REFERENCE.md index 0df10d396..d982c7b79 100644 --- a/Hawkynt.FileFormats.Archives/REFERENCE.md +++ b/Hawkynt.FileFormats.Archives/REFERENCE.md @@ -22992,87 +22992,85 @@ Implements `IArchiveFormatOperations`, `IArchiveInMemoryExtract`, `IFormatDescri ### Namespace `FileFormat.Nsis` -[`NsisCompression`](#nsiscompression) · [`NsisEntry`](#nsisentry) · [`NsisFormatDescriptor`](#nsisformatdescriptor) · [`NsisReader`](#nsisreader) · [`NsisWriter`](#nsiswriter) +[`NsisCompression`](#nsiscompression) · [`NsisEntry`](#nsisentry) · [`NsisFormatDescriptor`](#nsisformatdescriptor) · [`NsisReader`](#nsisreader) #### `NsisCompression` -Identifies the compression algorithm used in an NSIS installer. +Identifies the compressor an NSIS installer was built with (`SetCompressor`). | Value | Numeric | Summary | | --- | --- | --- | -| `None` | `0` | No compression — data is stored verbatim. | -| `Zlib` | `1` | Zlib/Deflate compression (2-byte zlib header + raw Deflate stream). | -| `BZip2` | `2` | BZip2 block-sorted compression. | -| `Lzma` | `3` | LZMA compression (5-byte properties header + raw LZMA stream). | +| `None` | `0` | Nothing is compressed: the header and every block are stored. | +| `Zlib` | `1` | "zlib": raw Deflate with no zlib wrapper (NSIS 2's inflate also omits stored-block NLEN). | +| `BZip2` | `2` | "bzip2": NSIS's header-less bzip2 dialect. | +| `Lzma` | `3` | "lzma": a 5-byte properties header, then range-coded data ending in an end marker. | #### `NsisEntry` -Represents a data block embedded in an NSIS installer. +A file an NSIS installer extracts: one `File` instruction of the install script. Implements `IEquatable`. | Member | Signature | Summary | | --- | --- | --- | -| `NsisEntry` | `NsisEntry(string FileName, long Size, long CompressedSize, bool IsDirectory)` | Represents a data block embedded in an NSIS installer. | -| `CompressedSize` | `long CompressedSize { get; init; }` | The compressed size in bytes, or -1 when unknown (solid stream). | -| `FileName` | `string FileName { get; init; }` | The entry name. Will be a generated name such as "block_0" unless the installer header was successfully parsed to recover actual file names. | -| `IsDirectory` | `bool IsDirectory { get; init; }` | Whether the entry represents a directory. | -| `Size` | `long Size { get; init; }` | The uncompressed size in bytes, or -1 when unknown (solid stream). | +| `NsisEntry` | `NsisEntry(string FileName, uint DataOffset, DateTime? LastModified)` | A file an NSIS installer extracts: one `File` instruction of the install script. | +| `CompressedSize` | `long CompressedSize { get; }` | Gets the stored size of the file's block, or -1 when the block lives inside a solid stream and therefore has no size of its own. | +| `DataOffset` | `uint DataOffset { get; init; }` | Offset of the file's length-prefixed block within the data section. | +| `FileName` | `string FileName { get; init; }` | The target path as the script names it, with forward slashes. A leading `$INSTDIR` is dropped, as 7-Zip does; any other variable (`$PLUGINSDIR`, `$SYSDIR`, ...) stays. | +| `IsDirectory` | `bool IsDirectory { get; }` | Gets whether the entry is a directory; NSIS `File` entries never are. | +| `LastModified` | `DateTime? LastModified { get; init; }` | The file time the compiler recorded, in UTC, or `null` when none. | +| `Size` | `long Size { get; }` | Gets the uncompressed size in bytes, or -1 until the size has been resolved. | #### `NsisFormatDescriptor` -NSIS (Nullsoft Scriptable Install System) installer archive — PE stub plus compressed installer data. References: `https://nsis.sourceforge.io` — official NSIS project site and sources — the installer data layout is defined there`https://en.wikipedia.org/wiki/Nullsoft_Scriptable_Install_System` — Wikipedia`https://www.7-zip.org` — 7-Zip — widely used independent parser of NSIS installer payloads +NSIS (Nullsoft Scriptable Install System) installer: the files its install script extracts. References: `https://github.com/kichik/nsis` — NSIS sources; `Source/exehead/fileform.h` defines the layout (zlib/libpng licence)`https://nsis.sourceforge.io/Docs/` — NSIS documentation`https://www.7-zip.org` — 7-Zip's NSIS handler, the reference reader this one is checked against -Implements `IArchiveCreatable`, `IArchiveDefragmentable`, `IArchiveFormatOperations`, `IFormatDescriptor`. +Implements `IArchiveFormatOperations`, `IFormatDescriptor`. | Member | Signature | Summary | | --- | --- | --- | | `NsisFormatDescriptor` | `NsisFormatDescriptor()` | | -| `Capabilities` | `FormatCapabilities Capabilities { get; }` | Gets the capabilities. | -| `Category` | `FormatCategory Category { get; }` | Gets the category. | -| `CompoundExtensions` | `IReadOnlyList CompoundExtensions { get; }` | Gets the compound extensions. | -| `DefaultExtension` | `string DefaultExtension { get; }` | Gets the default extension. | -| `Description` | `string Description { get; }` | Gets the description. | -| `DisplayName` | `string DisplayName { get; }` | Gets the display name. | -| `Extensions` | `IReadOnlyList Extensions { get; }` | Gets the extensions. | -| `Family` | `AlgorithmFamily Family { get; }` | Gets the family. | -| `Id` | `string Id { get; }` | Gets the id. | -| `MagicSignatures` | `IReadOnlyList MagicSignatures { get; }` | Gets the magic signatures. | -| `Methods` | `IReadOnlyList Methods { get; }` | Gets the methods. | -| `TarCompressionFormatId` | `string TarCompressionFormatId { get; }` | Gets the tar compression format id. | -| `Create` | `void Create(Stream output, IReadOnlyList inputs, FormatCreateOptions options)` | Performs the create operation. | -| `Defragment` | `void Defragment(Stream archive)` | Rebuild-based defrag: extracts then re-creates the NSIS data block in listing order. | -| `Defragment` | `void Defragment(Stream archive, DefragOptions options)` | Rebuild-based defrag: extracts then re-creates the NSIS data block per the requested mode. | -| `ExtractEntryToMemory` | `byte[] ExtractEntryToMemory(Stream archive, string entryName, string password)` | Native in-memory single-entry extraction routed through the bounded `OpenEntry`. | -| `Extract` | `void Extract(Stream stream, string outputDir, string password, string[] files)` | Decodes the supplied input. | -| `List` | `List List(Stream stream, string password)` | Lists the entries in the supplied container. | -| `OpenEntry` | `Stream OpenEntry(Stream archive, string entryName, string password)` | Opens a single entry as a bounded read-only stream. The reader produces the decoded bytes per entry; the matched bytes are wrapped in a `BoundedEntryStream` sized to their logical length. | +| `Capabilities` | `FormatCapabilities Capabilities { get; }` | | +| `Category` | `FormatCategory Category { get; }` | | +| `CompoundExtensions` | `IReadOnlyList CompoundExtensions { get; }` | | +| `DefaultExtension` | `string DefaultExtension { get; }` | | +| `Description` | `string Description { get; }` | | +| `DisplayName` | `string DisplayName { get; }` | | +| `Extensions` | `IReadOnlyList Extensions { get; }` | | +| `Family` | `AlgorithmFamily Family { get; }` | | +| `Id` | `string Id { get; }` | | +| `MagicSignatures` | `IReadOnlyList MagicSignatures { get; }` | | +| `Methods` | `IReadOnlyList Methods { get; }` | | +| `TarCompressionFormatId` | `string TarCompressionFormatId { get; }` | | +| `ExtractEntryToMemory` | `byte[] ExtractEntryToMemory(Stream archive, string entryName, string password)` | Decodes one entry into memory; an unknown name yields no bytes. | +| `Extract` | `void Extract(Stream stream, string outputDir, string password, string[] files)` | | +| `List` | `List List(Stream stream, string password)` | | +| `OpenEntry` | `Stream OpenEntry(Stream archive, string entryName, string password)` | Opens one entry as a bounded read-only stream. | #### `NsisReader` -Reads data blocks from an NSIS (Nullsoft Scriptable Install System) installer executable. +Reads the files an NSIS (Nullsoft Scriptable Install System) installer carries. Implements `IDisposable`. | Member | Signature | Summary | | --- | --- | --- | -| `NsisReader` | `NsisReader(Stream stream, bool leaveOpen = false)` | Opens an NSIS installer from a stream. The stream must be seekable. | -| `Compression` | `NsisCompression Compression { get; }` | Gets the compression method used by this installer. | -| `Entries` | `IReadOnlyList Entries { get; }` | Gets the data blocks found in the installer. | -| `IsSolid` | `bool IsSolid { get; }` | Gets whether the installer uses solid (single-stream) compression. | +| `NsisReader` | `NsisReader(Stream stream, bool leaveOpen = false)` | Opens an NSIS installer. | +| `ArchiveLength` | `int ArchiveLength { get; }` | Gets `length_of_all_following_data`: firstheader, data and CRC. | +| `Compression` | `NsisCompression Compression { get; }` | Gets the compressor the installer was built with. | +| `Entries` | `IReadOnlyList Entries { get; }` | Gets the files the install script extracts, in script order. | +| `FirstHeaderFlags` | `int FirstHeaderFlags { get; }` | Gets `firstheader.flags` (FH_FLAGS_*: 1 = uninstaller, 2 = silent, 4 = no CRC, 8 = force CRC). | +| `FirstHeaderOffset` | `long FirstHeaderOffset { get; }` | Gets the file offset of `firstheader`. | +| `HasFilterFlag` | `bool HasFilterFlag { get; }` | Gets whether the LZMA stream carries the x86 branch-filter flag byte. | +| `HeaderLength` | `int HeaderLength { get; }` | Gets the uncompressed size of the script header. | +| `Is64Bit` | `bool Is64Bit { get; }` | Gets whether the header was written for a 64-bit exehead. | +| `IsNsis3` | `bool IsNsis3 { get; }` | Gets whether the strings use the NSIS 3 escape codes rather than NSIS 2's. | +| `IsSolid` | `bool IsSolid { get; }` | Gets whether the header and files share one compressed stream (`/SOLID`). | +| `IsUnicode` | `bool IsUnicode { get; }` | Gets whether the installer's strings are UTF-16 (`Unicode true`, NSIS 3). | | `Dispose` | `void Dispose()` | | -| `ExtractSolidStream` | `byte[] ExtractSolidStream()` | Decompresses the entire solid data stream and returns it as a single byte array. Only meaningful when `IsSolid` is `true`. | -| `Extract` | `byte[] Extract(NsisEntry entry)` | Extracts the raw (decompressed) data for the given entry. | - -#### `NsisWriter` - -Writes a minimal NSIS-formatted file. No PE stub is emitted — the reader's `ScanForSignature` fallback finds the NSIS overlay via linear scan when PE parsing fails. Files are stored uncompressed as individual data blocks (non-solid). Roundtrips through `NsisReader`. Note: the produced file has no executable code and cannot be "run" to install — the NSIS format is primarily an installer stub + overlay data, and emitting a functional installer would require shipping a signed PE stub which is out of scope for a compression toolkit. - -| Member | Signature | Summary | -| --- | --- | --- | -| `NsisWriter` | `NsisWriter()` | | -| `AddFile` | `void AddFile(string name, byte[] data)` | Performs the add file operation. | -| `WriteTo` | `void WriteTo(Stream output)` | Writes the to to the supplied output. | +| `ExtractMany` | `void ExtractMany(IEnumerable entries, Action sink)` | Decodes the given entries, calling `sink` once per entry. A solid installer is decoded in a single forward pass that stops after the last requested block. | +| `Extract` | `byte[] Extract(NsisEntry entry)` | Returns the decompressed bytes of one entry. | +| `ResolveSizes` | `void ResolveSizes()` | Fills in `Size` (and, for non-solid installers, `CompressedSize`) for every entry. Compressed blocks have to be decoded to learn their size. | ### Namespace `FileFormat.NuFx` diff --git a/docs/catalogue/GAPS.md b/docs/catalogue/GAPS.md index e32d9b445..3085c4ea5 100644 --- a/docs/catalogue/GAPS.md +++ b/docs/catalogue/GAPS.md @@ -24,7 +24,7 @@ exists. It warns when a capability letter has drifted from the live descriptor. | Gap type | Rows | Meaning | | --- | ---: | --- | | `MISSING` | 240 | a source lists it; no descriptor or building block covers it | -| `READ-ONLY-BUT-SOURCES-WRITE` | 23 | we only read it; a source or the format's own tool writes it | +| `READ-ONLY-BUT-SOURCES-WRITE` | 24 | we only read it; a source or the format's own tool writes it | | `PARTIAL` | 41 | covered, but the reader handles only part of the format (see the table below) | | `OK` | 221 | covered with no known gap against what the sources list | | `OURS-ONLY` | 458 | in our registry, listed by none of the seven sources | @@ -133,6 +133,7 @@ Every catalogued row where a source records write support (7-Zip, libarchive, an | Acronis True Image | `AcronisTib`, `AcronisTibx` | Acronis True Image | Wikipedia | | BeeGFS | `BeeGfs` | beegfs-setup-storage | Wikipedia | | BitRock InstallBuilder | `BitRock` | InstallBuilder | UniExtract | +| NSIS installer | `Nsis` | makensis | 7-Zip, XADMaster, UniExtract | | GPFS / IBM Storage Scale | `Gpfs` | mmcrfs | Wikipedia | | GlusterFS | `GlusterFs` | gluster volume create | Wikipedia | | HxC Floppy Emulator (HFE) | `Hfe` | HxC Floppy Emulator tool | PRONOM | @@ -170,7 +171,6 @@ running real-world samples. The file named at the end of each row is where the l | APFS | `Apfs` | 3 | no decmpfs (LZVN/LZFSE) compressed files, no encryption (ApfsReader.cs) | | Compiled HTML Help (CHM) | `Chm` | 3 | content sections above 1 unsupported (ChmReader.cs) | | FreeArc | `FreeArc` | 3 | only storing and lzma blocks decode; tornado/ppmd/lzp/grzip/rep/delta/exe and encryption are refused (FreeArcMethods.cs) | -| NSIS installer | `Nsis` | 3 | solid installers cannot be extracted per entry (NsisReader.cs) | | NTFS | `Ntfs` | 3 | WOF (XPRESS/LZX) compressed files not decoded; LZNT1 is | | QCOW / QCOW2 | `Qcow2` | 3 | no encryption, backing files, zstd clusters or extended L2 (Qcow2Structures.cs) | | SquashFS | `SquashFs` | 3 | SquashFS v4 only; v1-v3 rejected (SquashFsSuperblock.cs) | diff --git a/docs/catalogue/our-registry.csv b/docs/catalogue/our-registry.csv index b9f718474..22ea9daba 100644 --- a/docs/catalogue/our-registry.csv +++ b/docs/catalogue/our-registry.csv @@ -353,7 +353,7 @@ format,Nrbf,.NET BinaryFormatter / NRBF object graph,Archive,,.nrbf,Y,Y,Y, format,Nrg,NRG,Archive,,.nrg,Y,Y,Y,Y format,Nsa,NSA,Archive,,.nsa,Y,Y,Y,Y format,Nsf,NES Sound Format,Audio,,.nsf;.nsfe,Y,Y,, -format,Nsis,NSIS,Archive,,,Y,Y,Y, +format,Nsis,NSIS,Archive,,,Y,Y,, format,NsPack,NsPack (Win32 PE),Archive,,,Y,Y,, format,Nss,NSS (Novell Storage Services),Archive,Y,.nss,Y,Y,Y, format,Ntfs,NTFS,Archive,Y,.ntfs;.img,Y,Y,Y,Y diff --git a/docs/catalogue/reference-formats.csv b/docs/catalogue/reference-formats.csv index 56c5deac3..64935c806 100644 --- a/docs/catalogue/reference-formats.csv +++ b/docs/catalogue/reference-formats.csv @@ -210,7 +210,7 @@ MacBinary,bin;macbin,encoding,,,R,,R,fmt/1761;fmt/1762;fmt/1763,,3,https://en.wi AppleSingle / AppleDouble,as;adouble,encoding,,,R,,,fmt/967;fmt/968,,2,https://www.rfc-editor.org/rfc/rfc1740,AppleSingle;AppleDouble,Y,Y,Y,OK,, Intel HEX,hex;ihex,firmware,R,,,,,,,1,https://en.wikipedia.org/wiki/Intel_HEX,IntelHex,Y,Y,Y,OK,, Motorola S-record,srec;s19;mot,firmware,,,,R,,,,1,https://en.wikipedia.org/wiki/SREC_(file_format),Srec,Y,Y,,OK,, -NSIS installer,exe;nsis,installer,R,,R,,R,,,3,https://nsis.sourceforge.io/,Nsis,Y,Y,,PARTIAL,,solid installers cannot be extracted per entry (NsisReader.cs) +NSIS installer,exe;nsis,installer,R,,R,,R,,,3,https://nsis.sourceforge.io/,Nsis,Y,,,READ-ONLY-BUT-SOURCES-WRITE,makensis,"LZMA behind the x86 branch filter refused; uninstaller payload not listed (NsisReader.cs)" Inno Setup,exe,installer,,,,,R,,,1,https://jrsoftware.org/isinfo.php,InnoSetup,Y,Y,,PARTIAL,,listing only; file extraction throws for every Inno version (InnoSetupReader.cs) InstallShield (cab / exe),exe;cab;ex_;hdr,installer,,,,,R,fmt/1466,,2,,,,,,MISSING,, Wise Installer,exe,installer,,,,,R,,,1,,,,,,MISSING,, From 720047749353f43ec70b441d5ed181cff4f2e68b Mon Sep 17 00:00:00 2001 From: Hawkynt Date: Mon, 26 Oct 2026 13:05:40 +0100 Subject: [PATCH 14/34] * regenerate references --- Hawkynt.FileFormats.Archives/README.md | 2 +- docs/catalogue/GAPS.md | 2 +- docs/catalogue/reference-formats.csv | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Hawkynt.FileFormats.Archives/README.md b/Hawkynt.FileFormats.Archives/README.md index 10be4b4ca..dccf473d1 100644 --- a/Hawkynt.FileFormats.Archives/README.md +++ b/Hawkynt.FileFormats.Archives/README.md @@ -544,7 +544,7 @@ Use it when a .NET process needs to enumerate, extract, test, create, edit or in -Every public and protected member of all 2572 types, generated from the built assembly and its XML documentation, is in [REFERENCE.md](https://github.com/Hawkynt/CompressionWorkbench/blob/main/Hawkynt.FileFormats.Archives/REFERENCE.md). +Every public and protected member of all 2571 types, generated from the built assembly and its XML documentation, is in [REFERENCE.md](https://github.com/Hawkynt/CompressionWorkbench/blob/main/Hawkynt.FileFormats.Archives/REFERENCE.md). diff --git a/docs/catalogue/GAPS.md b/docs/catalogue/GAPS.md index 3085c4ea5..9bb95c1b8 100644 --- a/docs/catalogue/GAPS.md +++ b/docs/catalogue/GAPS.md @@ -25,7 +25,7 @@ exists. It warns when a capability letter has drifted from the live descriptor. | --- | ---: | --- | | `MISSING` | 240 | a source lists it; no descriptor or building block covers it | | `READ-ONLY-BUT-SOURCES-WRITE` | 24 | we only read it; a source or the format's own tool writes it | -| `PARTIAL` | 41 | covered, but the reader handles only part of the format (see the table below) | +| `PARTIAL` | 40 | covered, but the reader handles only part of the format (see the table below) | | `OK` | 221 | covered with no known gap against what the sources list | | `OURS-ONLY` | 458 | in our registry, listed by none of the seven sources | diff --git a/docs/catalogue/reference-formats.csv b/docs/catalogue/reference-formats.csv index 64935c806..0184360a7 100644 --- a/docs/catalogue/reference-formats.csv +++ b/docs/catalogue/reference-formats.csv @@ -210,7 +210,7 @@ MacBinary,bin;macbin,encoding,,,R,,R,fmt/1761;fmt/1762;fmt/1763,,3,https://en.wi AppleSingle / AppleDouble,as;adouble,encoding,,,R,,,fmt/967;fmt/968,,2,https://www.rfc-editor.org/rfc/rfc1740,AppleSingle;AppleDouble,Y,Y,Y,OK,, Intel HEX,hex;ihex,firmware,R,,,,,,,1,https://en.wikipedia.org/wiki/Intel_HEX,IntelHex,Y,Y,Y,OK,, Motorola S-record,srec;s19;mot,firmware,,,,R,,,,1,https://en.wikipedia.org/wiki/SREC_(file_format),Srec,Y,Y,,OK,, -NSIS installer,exe;nsis,installer,R,,R,,R,,,3,https://nsis.sourceforge.io/,Nsis,Y,,,READ-ONLY-BUT-SOURCES-WRITE,makensis,"LZMA behind the x86 branch filter refused; uninstaller payload not listed (NsisReader.cs)" +NSIS installer,exe;nsis,installer,R,,R,,R,,,3,https://nsis.sourceforge.io/,Nsis,Y,,,READ-ONLY-BUT-SOURCES-WRITE,makensis,LZMA behind the x86 branch filter refused; uninstaller payload not listed (NsisReader.cs) Inno Setup,exe,installer,,,,,R,,,1,https://jrsoftware.org/isinfo.php,InnoSetup,Y,Y,,PARTIAL,,listing only; file extraction throws for every Inno version (InnoSetupReader.cs) InstallShield (cab / exe),exe;cab;ex_;hdr,installer,,,,,R,fmt/1466,,2,,,,,,MISSING,, Wise Installer,exe,installer,,,,,R,,,1,,,,,,MISSING,, From ffc73b38f2fb86816213d95ad1d70dc238fa763c Mon Sep 17 00:00:00 2001 From: Hawkynt Date: Mon, 26 Oct 2026 13:05:44 +0100 Subject: [PATCH 15/34] + PartitionedDisk write support, 4Kn GPT, BSD/Amiga/Sun/SGI labels, resolver descent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit + partitioned disks: 4Kn GPT, GPT backup recovery, BSD/Amiga RDB/Sun/SGI labels Symptom: a GPT disk with 4096-byte logical blocks was not recognised (the header was only looked for at byte 512), a damaged primary GPT lost the whole table, and bare BSD disklabels, Amiga RDB, Sun and SGI disks were not recognised at all. PartitionEditor's GPT LastUsableLBA overlapped the first backup entry sector, partitions could be added inside the GPT's own structures, and every rewrite replaced unique partition GUIDs, attribute bits and the disk GUID. Root cause: GptParser, MbrParser and PartitionEditor hard-coded 512-byte LBAs; the detector knew GPT, MBR and APM only; the editor validated partitions against the disk length, not the usable LBA range. Fix: the block size is the one at which LBA 1 holds a CRC-valid header (512 or 4096); a primary header or entry array failing its CRC falls back to the backup at AlternateLBA / the last LBA. MBR/EBR parsing takes the block size; the EBR walk stops at loops, at links leaving the extended partition and at EBRs without 0x55AA; a lone protective 0xEE entry is no partition. New readers for the stand-alone BSD disklabel, Amiga RDB (checksummed RDSK/PART chain), Sun label (magic + XOR checksum) and SGI volume header (magic + sum checksum), probed after GPT/MBR/APM. The editor counts in the disk's block size, refuses ranges outside [FirstUsableLBA, LastUsableLBA] (also before ConvertMbrToGpt writes), puts LastUsableLBA before the backup array, keeps existing entries' unique GUIDs and attributes and the disk GUID, and can write an empty GPT/MBR. Sourcing: rung 3 — UEFI 2.10 §5.2/§5.3 for MBR/GPT; 4.4BSD disklabel.h, AmigaOS NDK hardblocks.h/filehandler.h, Sun dklabel.h/vtoc.h and IRIX dvh.h for the label layouts and checksum rules. util-linux sfdisk/fdisk and GNU parted (GPL) were used only as black-box oracles: fdisk -b 4096 and sfdisk-written tables are read at the offsets they report, and parted mklabel amiga/sun/dvh/bsd labels at parted's offsets. parted's BSD labels do not satisfy the dkcksum rule (measured), so a stand-alone BSD label is accepted on its two magics and sane geometry instead. + PartitionedDisk: edit partition filesystems in place, add/remove partitions, create disks Symptom: a raw partitioned disk was read-only; files inside a partition could not be edited, partitions not added or removed, and no disk could be created from partition images. The partition-aware TryAdd/TryRemove the virtual-disk descriptors use wrote straight into the partition (a refusal half-way left a half-edited volume), accepted the generic re-create default as an "editor", deleted the wrong entry on MBR disks with an extended partition (detector and editor index differently), and could place a new partition over the backup GPT. Root cause: no write path on the descriptor; inner edits had no staging; whole-partition deletes used list indices instead of the entry itself. Fix: StagedWindowStream holds a partition edit's writes in 4 KiB pages, refuses writes or SetLength past the partition, and commits only after every edit in the call succeeded. TryAdd/TryRemove route PartitionN_Type/path through the partition filesystem's own Add/Remove (refused when it only has the interface's re-create default), resolve BSD slices, plan new partitions (1 MiB-aligned, inside the usable range, table capacity checked) before writing anything, and delete entries by their start and size. PartitionedDiskFormatDescriptor implements IArchiveModifiable, IArchiveCreatable (partition images on a fresh GPT or MBR, scheme/sectorSize options, loose files refused) and IArchiveWriteConstraints; purge is not offered. Every refusal leaves the image byte-identical. The shell evaluates drops against the folder being browsed, so a file dropped into a partition folder is accepted. Sourcing: rung 3 — UEFI 2.10 §5.2/§5.3 for the table layouts; the inner edits are the existing filesystem editors. Verified against GPL tools as oracles only: sgdisk -v ("No problems found"), sfdisk --verify, parted and fdisk -b 4096 read the created disks at our offsets; fsck.vfat -n and e2fsck -fn are clean after in-place adds/removes on an sfdisk-built GPT disk, mcopy and debugfs read the edits back, and no byte outside the partitions changes. # NestedStreamResolver descends into raw partitioned disks Symptom: ResolveDeepest on a raw MBR/GPT disk image (not wrapped in a VHD/VMDK/...) found no filesystem, and filesystems whose superblock lies past 4 KiB were not recognised at any level. Root cause: partition tables were only tried below a virtual-disk container, and format detection matched magic bytes in a fixed 4 KiB header. Fix: detection goes through FormatDetector.DetectByContent (the disk-image probe plus signatures as deep as the deepest registered one), after the fixed-VHD footer check; a PartitionedDisk result descends into its partitions, BSD slices included. Sourcing: no format work; reuses the content probe and partition readers. --- ARCHITECTURE.md | 18 +- Compression.Core/DiskImage/AmigaRdbParser.cs | 153 ++++++++ .../DiskImage/BsdDisklabelParser.cs | 37 ++ Compression.Core/DiskImage/GptParser.cs | 275 ++++++++++--- Compression.Core/DiskImage/MbrParser.cs | 66 ++-- Compression.Core/DiskImage/PartitionEditor.cs | 344 ++++++++++++----- .../DiskImage/PartitionTableDetector.cs | 135 +++++-- .../DiskImage/PartitionedDiskLister.cs | 362 +++++++++++++----- Compression.Core/DiskImage/SgiDvhParser.cs | 86 +++++ .../DiskImage/StagedWindowStream.cs | 153 ++++++++ Compression.Core/DiskImage/SunVtocParser.cs | 100 +++++ Compression.Core/REFERENCE.md | 133 ++++++- .../PartitionedDiskFormatDescriptor.cs | 236 ++++++++++-- Compression.Lib/FormatDetector.DiskImage.cs | 7 +- Compression.Lib/FormatDetector.cs | 2 +- Compression.Lib/NestedStreamResolver.cs | 72 ++-- .../ViewModels/MainViewModel.cs | 4 +- .../Views/PartitionsWindow.cs | 4 +- .../PartitionSchemeDetectionTests.cs | 317 +++++++++++++++ .../DiskImage/PartitionedDiskExternalTests.cs | 258 +++++++++++++ .../DiskImage/PartitionedDiskWriteTests.cs | 279 ++++++++++++++ .../NativeUI/ShellContentDetectionTests.cs | 20 +- .../Nested/NestedStreamResolverTests.cs | 24 ++ README.md | 4 + docs/catalogue/our-registry.csv | 2 +- docs/catalogue/reference-formats.csv | 8 +- 26 files changed, 2686 insertions(+), 413 deletions(-) create mode 100644 Compression.Core/DiskImage/AmigaRdbParser.cs create mode 100644 Compression.Core/DiskImage/SgiDvhParser.cs create mode 100644 Compression.Core/DiskImage/StagedWindowStream.cs create mode 100644 Compression.Core/DiskImage/SunVtocParser.cs create mode 100644 Compression.Tests/DiskImage/PartitionSchemeDetectionTests.cs create mode 100644 Compression.Tests/DiskImage/PartitionedDiskExternalTests.cs create mode 100644 Compression.Tests/DiskImage/PartitionedDiskWriteTests.cs diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index ca1218294..53444bf50 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -105,15 +105,27 @@ Candidate formats from earlier stages are validated by parsing their headers and |---|----------|--------| | 1 | A signature of ≥ 0.9 confidence and ≥ 4 bytes inside sector 0 | That format (VHDX, QCOW2, VMDK, XFS, SquashFS, gzip, …) | | 2 | ISO 9660 / UDF volume descriptors at 32 KiB | `Iso` / `Udf` — ahead of any partition table, so a hybrid ISO whose system area carries an MBR or GPT browses as the ISO | -| 3 | GPT header at LBA 1 whose CRC32 matches (UEFI 2.10 §5.3.2) | `PartitionedDisk` | +| 3 | GPT header at LBA 1 whose CRC32 matches (UEFI 2.10 §5.3.2), at 512- or 4096-byte logical blocks | `PartitionedDisk` | | 4 | Sector 0 ends in 0x55AA: validated MBR table vs. volume boot record (FAT BPB, NTFS/exFAT OEM ID) | Table only → `PartitionedDisk`; boot record only → that filesystem; both → `PartitionedDisk` only if a partition holds a recognisable filesystem | | 5 | Apple Partition Map | `PartitionedDisk` | -| 6 | A filesystem superblock signature of ≥ 0.75 confidence, up to the deepest one registered (ZFS at 128 KiB) | That filesystem | +| 6 | A checksummed SGI volume header, Sun label or Amiga Rigid Disk Block, or a stand-alone BSD disklabel (both magics, sane geometry) | `PartitionedDisk` | +| 7 | A filesystem superblock signature of ≥ 0.75 confidence, up to the deepest one registered (ZFS at 128 KiB) | That filesystem | -A valid MBR (UEFI 2.10 §5.2.1) has a boot indicator of 0x00 or 0x80 in all four entries, and each used entry starts at LBA ≥ 1 inside the image, is at least one sector long and does not overlap another used entry. A trailing 0x55AA alone does not make a partition table: every FAT, NTFS and exFAT boot sector has one. Bytes that prove nothing stay `Unknown`, so noise named `.img` is no longer handed to the FAT reader. A `.img` path that does not exist yet, or is empty, still resolves to FAT as the create target. The probe reads 40 KiB, and reads further, out to the deepest filesystem signature, only for stage 6. It never scans the image. +A valid MBR (UEFI 2.10 §5.2.1) has a boot indicator of 0x00 or 0x80 in all four entries, and each used entry starts at LBA ≥ 1 inside the image, is at least one sector long and does not overlap another used entry. A trailing 0x55AA alone does not make a partition table: every FAT, NTFS and exFAT boot sector has one. Bytes that prove nothing stay `Unknown`, so noise named `.img` is no longer handed to the FAT reader. A `.img` path that does not exist yet, or is empty, still resolves to FAT as the create target. The probe reads 40 KiB, and reads further, out to the deepest filesystem signature, only for stage 7. It never scans the image. `PartitionedDisk` lists each partition as a `PartitionN_Type/` directory holding the filesystem detected inside it (`InnerFsDetector`, which also reads past 4 KiB to find ISO 9660, UDF, JFS, btrfs and ZFS). A partition it does not recognise becomes a single `PartitionN_Type.raw` entry. Partitions are read through a `PartitionWindowStream` and never copied out. `OpenEntry` hands back the window itself, or the inner filesystem's entry stream over it. Virtual-disk containers (VHD, VHDX, VMDK, QCOW2, VDI, Bochs) are detected by their own signatures and run the same partition-aware listing over their guest disk. +Tables: GPT at 512- or 4096-byte logical blocks (the block size is the one at which LBA 1 holds a CRC-valid header; a damaged primary falls back to the backup header), MBR with its EBR chain of logical partitions, APM, a BSD disklabel inside a BSD slice or standing alone, Amiga RDB, Sun and SGI labels. Writing: + +| Operation | How | Refused when | +|-----------|-----|--------------| +| Add/remove `PartitionN_Type/path` | The partition's own filesystem editor runs in place on a `StagedWindowStream`: writes are staged and reach the disk only when every edit in the call succeeded | No recognised filesystem; its editor is only the generic re-create default; the filesystem refuses (full, unsupported case); it would grow past the partition | +| Add a root-level filesystem or `*.raw`/`*.img` image | New GPT/MBR entry after the last partition, 1 MiB-aligned where room allows, inside the usable LBA range | No free space; table full; not a partition image | +| Remove `PartitionN_Type` | `PartitionEditor.DeletePartition` (GPT, MBR, logical); the sectors stay | BSD/RDB/Sun/SGI/APM tables | +| `Create` | Partition images on a fresh GPT (`scheme=gpt`, default) or MBR, `sectorSize=512`/`4096`, partitions 1 MiB-aligned | A loose file; more than 128 (GPT) / 4 (MBR) images | + +Every refusal leaves the image byte-identical. `sgdisk -v`, `sfdisk --verify`, `parted`, `fdisk -b 4096`, `fsck.vfat` and `e2fsck` check the results (`PartitionedDiskExternalTests`). Resizing a partition is not an archive operation; the partition editor window does it through `PartitionEditor.ResizePartition` and the registered filesystem resizers. + `FormatDetector.DetectByContent(Stream)` runs the same probe over a seekable stream, such as an entry inside a container, without a file on disk. `FormatDetector.DetectCached(path)` is `Detect` with a cache keyed on path, length and last-write time, for UI state that is re-evaluated on every repaint. --- diff --git a/Compression.Core/DiskImage/AmigaRdbParser.cs b/Compression.Core/DiskImage/AmigaRdbParser.cs new file mode 100644 index 000000000..049a15207 --- /dev/null +++ b/Compression.Core/DiskImage/AmigaRdbParser.cs @@ -0,0 +1,153 @@ +using System.Buffers.Binary; +using System.Text; + +namespace Compression.Core.DiskImage; + +/// +/// Parses the Amiga Rigid Disk Block (RDB) partition scheme. Big-endian throughout. +/// +/// +/// +/// The RDSK block may sit in any of the first 16 blocks of the disk +/// (RDB_LOCATION_LIMIT). It links, by block number, a chain of PART blocks; each +/// carries the partition's drive name (a BCPL string) and a DosEnvec whose geometry — +/// low/high cylinder, surfaces, blocks per track, block size in longwords — places the +/// partition on the disk, plus the DosType naming its filesystem (DOS\1 = FFS, +/// PFS\3, LNX\0, …). Every block carries a checksum: the 32-bit sum of its first +/// SummedLongs longwords is zero. +/// +/// +/// A partition's byte range is LowCyl × Surfaces × BlocksPerTrack × SizeBlock × 4 for the +/// start and (HighCyl − LowCyl + 1) × Surfaces × BlocksPerTrack × SizeBlock × 4 for the +/// length. +/// +/// References: +/// +/// AmigaOS NDK, devices/hardblocks.h — struct RigidDiskBlock, struct PartitionBlock, RDB_LOCATION_LIMIT. +/// AmigaOS NDK, dos/filehandler.h — struct DosEnvec field order. +/// +/// +public static class AmigaRdbParser { + + private const uint RdskId = 0x5244534B; // "RDSK" + private const uint PartId = 0x50415254; // "PART" + private const uint EndOfList = 0xFFFFFFFF; + private const int SearchBlockSize = 512; + private const int LocationLimit = 16; + private const int MaxPartitions = 128; + + // RigidDiskBlock field offsets. + private const int RdbSummedLongs = 4; + private const int RdbBlockBytes = 16; + private const int RdbPartitionList = 28; + + // PartitionBlock field offsets. + private const int PbNext = 16; + private const int PbDriveName = 36; + private const int PbEnvironment = 128; + + // DosEnvec longword indices. + private const int DeTableSize = 0; + private const int DeSizeBlock = 1; + private const int DeSurfaces = 3; + private const int DeBlocksPerTrack = 5; + private const int DeLowCyl = 9; + private const int DeHighCyl = 10; + private const int DeDosType = 16; + + /// Reports whether a checksummed RDSK block sits in the first 16 blocks. + /// A seekable stream over the whole disk. + public static bool IsRdb(Stream disk) => FindRdb(disk) >= 0; + + /// Parses every partition the RDB's PART chain describes. + /// A seekable stream over the whole disk. + /// No valid RDB is present. + public static List Parse(Stream disk) { + var rdbBlock = FindRdb(disk); + if (rdbBlock < 0) throw new InvalidDataException("No Amiga Rigid Disk Block (RDSK) in the first 16 blocks."); + + var rdb = ReadBlock(disk, (long)rdbBlock * SearchBlockSize, SearchBlockSize); + var blockBytes = (int)BinaryPrimitives.ReadUInt32BigEndian(rdb.AsSpan(RdbBlockBytes)); + if (blockBytes is < 256 or > 65536 || (blockBytes & (blockBytes - 1)) != 0) blockBytes = SearchBlockSize; + + var result = new List(); + var seen = new HashSet(); + var next = BinaryPrimitives.ReadUInt32BigEndian(rdb.AsSpan(RdbPartitionList)); + while (next != EndOfList && result.Count < MaxPartitions && seen.Add(next)) { + var offset = (long)next * blockBytes; + if (offset + blockBytes > disk.Length) break; + var block = ReadBlock(disk, offset, blockBytes); + if (BinaryPrimitives.ReadUInt32BigEndian(block) != PartId || !ChecksumOk(block)) break; + + uint Env(int i) => BinaryPrimitives.ReadUInt32BigEndian(block.AsSpan(PbEnvironment + i * 4)); + var tableSize = Env(DeTableSize); + if (tableSize >= DeHighCyl) { + var bytesPerBlock = (long)Env(DeSizeBlock) * 4; + var cylinderBytes = (long)Env(DeSurfaces) * Env(DeBlocksPerTrack) * bytesPerBlock; + var low = Env(DeLowCyl); + var high = Env(DeHighCyl); + if (cylinderBytes > 0 && high >= low) { + var dosType = tableSize >= DeDosType ? Env(DeDosType) : 0; + result.Add(new PartitionEntry { + Index = result.Count, + StartOffset = low * cylinderBytes, + Size = (high - low + 1L) * cylinderBytes, + TypeName = DosTypeName(dosType), + TypeCode = $"0x{dosType:X8}", + Name = DriveName(block.AsSpan(PbDriveName, 32)), + Source = "RDB" + }); + } + } + + next = BinaryPrimitives.ReadUInt32BigEndian(block.AsSpan(PbNext)); + } + return result; + } + + private static int FindRdb(Stream disk) { + if (!disk.CanSeek) return -1; + for (var i = 0; i < LocationLimit; ++i) { + var offset = (long)i * SearchBlockSize; + if (offset + SearchBlockSize > disk.Length) break; + var block = ReadBlock(disk, offset, SearchBlockSize); + if (BinaryPrimitives.ReadUInt32BigEndian(block) == RdskId && ChecksumOk(block)) + return i; + } + return -1; + } + + /// The block's first SummedLongs longwords sum to zero (mod 2^32). + private static bool ChecksumOk(ReadOnlySpan block) { + var summed = BinaryPrimitives.ReadUInt32BigEndian(block[RdbSummedLongs..]); + if (summed < 4 || summed * 4L > block.Length) return false; + uint sum = 0; + for (var i = 0; i < summed; ++i) + sum += BinaryPrimitives.ReadUInt32BigEndian(block[(i * 4)..]); + return sum == 0; + } + + /// A BCPL string: a length byte followed by that many characters. + private static string DriveName(ReadOnlySpan bstr) { + var length = Math.Min(bstr[0], (byte)31); + return Encoding.Latin1.GetString(bstr.Slice(1, length)); + } + + /// Renders a DosType as its four characters, the last as a digit (DOS\1 → DOS1). + private static string DosTypeName(uint dosType) { + if (dosType == 0) return "Amiga"; + Span chars = stackalloc char[4]; + for (var i = 0; i < 4; ++i) { + var b = (byte)(dosType >> (24 - i * 8)); + chars[i] = b is >= 0x20 and < 0x7F ? (char)b : (char)('0' + b % 10); + } + return new string(chars); + } + + private static byte[] ReadBlock(Stream disk, long offset, int size) { + var buffer = new byte[size]; + disk.Position = offset; + _ = disk.ReadAtLeast(buffer, size, throwOnEndOfStream: false); + return buffer; + } +} diff --git a/Compression.Core/DiskImage/BsdDisklabelParser.cs b/Compression.Core/DiskImage/BsdDisklabelParser.cs index 87e94fd23..50e573697 100644 --- a/Compression.Core/DiskImage/BsdDisklabelParser.cs +++ b/Compression.Core/DiskImage/BsdDisklabelParser.cs @@ -61,6 +61,43 @@ public static class BsdDisklabelParser { /// true if a disklabel is present. public static bool IsDisklabel(Stream slice) => FindLabelOffset(slice) >= 0; + /// + /// Reports whether carries a disklabel that stands on its own — no MBR + /// slice around it, as a "dangerously dedicated" FreeBSD disk or a NetBSD/OpenBSD disk on a + /// non-PC port writes it. With nothing else vouching for the bytes the label must prove + /// itself structurally: d_magic and d_magic2 both present 132 bytes apart, a + /// sector size that is a power of two in [512, 8192], a partition count within + /// MAXPARTITIONS, and every used partition starting inside the disk. + /// + /// + /// d_checksum is deliberately not required: the labels GNU parted 3.x writes + /// (mklabel bsd) do not satisfy the 4.4BSD dkcksum XOR rule, and neither the + /// Linux kernel nor libblkid checks it — the two magics are what both rely on. + /// + /// A seekable stream over the whole disk. + public static bool IsStandaloneDisklabel(Stream disk) { + var labelOffset = FindLabelOffset(disk); + if (labelOffset < 0) return false; + var label = new byte[PartitionsOffset + MaxPartitions * PartitionRecordSize]; + disk.Position = labelOffset; + var read = disk.ReadAtLeast(label, label.Length, throwOnEndOfStream: false); + if (read < PartitionsOffset) return false; + var span = label.AsSpan(0, read); + if (ReadU32(span, Magic2Offset) != DiskMagic) return false; + var secSize = ReadU32(span, 40); + if (secSize is < 512 or > 8192 || (secSize & (secSize - 1)) != 0) return false; + var npartitions = BinaryPrimitives.ReadUInt16LittleEndian(span[NPartitionsOffset..]); + if (npartitions is 0 or > MaxPartitions) return false; + if (PartitionsOffset + npartitions * PartitionRecordSize > span.Length) return false; + var diskSectors = (ulong)disk.Length / secSize; + for (var i = 0; i < npartitions; ++i) { + var size = ReadU32(span, PartitionsOffset + i * PartitionRecordSize); + var offset = ReadU32(span, PartitionsOffset + i * PartitionRecordSize + 4); + if (size != 0 && offset >= diskSectors) return false; + } + return true; + } + /// /// Parses the BSD disklabel contained in and returns /// its filesystem partitions. diff --git a/Compression.Core/DiskImage/GptParser.cs b/Compression.Core/DiskImage/GptParser.cs index 9ebfb2180..de13f9df4 100644 --- a/Compression.Core/DiskImage/GptParser.cs +++ b/Compression.Core/DiskImage/GptParser.cs @@ -5,18 +5,34 @@ namespace Compression.Core.DiskImage; /// /// Parses GPT (GUID Partition Table) headers and partition entries. -/// GPT header is at LBA 1 (offset 512 for 512-byte sectors), with partition entries starting at LBA 2+. /// +/// +/// +/// The header lives at LBA 1, so its byte offset depends on the disk's logical block size: +/// 512 on a classic or 512e drive, 4096 on a 4Kn drive. Neither the protective MBR nor the +/// header records the block size, so it is found the way the UEFI specification implies: the +/// block size is the one for which LBA 1 holds a header whose CRC32 matches and that names +/// itself as LBA 1 (UEFI 2.10, §5.3.2). Every LBA in the header and the entry array is then +/// counted in that block size. +/// +/// +/// When the primary header or its entry array fails its CRC, the backup header at the last +/// LBA is used instead, as §5.3.2 directs. +/// +/// public static class GptParser { /// GPT signature: "EFI PART". private static ReadOnlySpan Signature => "EFI PART"u8; - /// Standard sector size. - private const int SectorSize = 512; + /// Logical block sizes probed for the header at LBA 1, in probe order. + public static IReadOnlyList SupportedSectorSizes { get; } = [512, 4096]; - /// GPT header offset (LBA 1). - private const int HeaderOffset = SectorSize; + /// Classic sector size, used when nothing else is known. + private const int DefaultSectorSize = 512; + + /// Bytes of the image needed to see LBA 1 at every supported block size. + public const int ProbeBytes = 4096 * 2; /// Minimum GPT header size. private const int MinHeaderSize = 92; @@ -24,28 +40,159 @@ public static class GptParser { /// Size of each GPT partition entry (minimum). private const int MinEntrySize = 128; + /// Upper bound on the entry array the parser will read (UEFI allows any count; real tables hold 128). + private const long MaxEntryArrayBytes = 16L * 1024 * 1024; + + /// Layout facts read from the header that was used to parse the table. + /// Logical block size every LBA is counted in. + /// The LBA the used header sits at (1 for the primary). + /// The LBA of the other header. + /// First LBA a partition may occupy. + /// Last LBA a partition may occupy. + /// The disk GUID. + /// Starting LBA of the entry array the used header points at. + /// Number of entries in the array. + /// Size of one entry in bytes. + /// true when the primary was unusable and the backup was read. + public sealed record HeaderInfo( + int SectorSize, ulong MyLba, ulong AlternateLba, ulong FirstUsableLba, ulong LastUsableLba, + Guid DiskGuid, ulong EntriesLba, uint EntryCount, uint EntrySize, bool FromBackup); + /// - /// Checks whether the given data contains a valid GPT header at LBA 1. + /// Checks whether LBA 1 carries the "EFI PART" signature at any supported block size. /// - /// At least 1024 bytes of disk image data starting at LBA 0. - /// true if a valid GPT signature is found at LBA 1. - public static bool IsGpt(ReadOnlySpan data) - => data.Length >= HeaderOffset + MinHeaderSize - && data.Slice(HeaderOffset, 8).SequenceEqual(Signature); + /// The start of the image (at least 1024 bytes for 512-byte blocks, 8 KiB for 4 KiB blocks). + public static bool IsGpt(ReadOnlySpan data) { + foreach (var size in SupportedSectorSizes) + if (data.Length >= size + MinHeaderSize && data.Slice(size, 8).SequenceEqual(Signature)) + return true; + return false; + } /// /// Checks that LBA 1 holds a GPT header that is internally consistent, not merely the - /// "EFI PART" signature: the header size lies in [92, 512], the header's own CRC32 matches - /// (computed with the CRC field zeroed, UEFI 2.10 §5.3.2), it names itself as LBA 1 and its - /// partition entries are 128 x 2^n bytes each, as the specification requires. + /// "EFI PART" signature, at any supported block size: see . + /// + /// The start of the image. + public static bool HasValidHeader(ReadOnlySpan data) => DetectSectorSize(data) > 0; + + /// + /// The logical block size for which LBA 1 holds a valid GPT header, or 0 when none does. /// - /// At least the first 1024 bytes of the image. - public static bool HasValidHeader(ReadOnlySpan data) { - if (!IsGpt(data)) return false; + /// The start of the image; 8 KiB covers every supported block size. + public static int DetectSectorSize(ReadOnlySpan data) { + foreach (var size in SupportedSectorSizes) + if (HasValidHeader(data, size)) + return size; + return 0; + } + + /// + /// Checks that LBA 1, counted in -byte blocks, holds a GPT header + /// that is internally consistent: the header size lies in [92, block size], the header's own + /// CRC32 matches (computed with the CRC field zeroed, UEFI 2.10 §5.3.2), it names itself as + /// LBA 1 and its partition entries are 128 x 2^n bytes each, as the specification requires. + /// + public static bool HasValidHeader(ReadOnlySpan data, int sectorSize) { + if (data.Length < sectorSize + MinHeaderSize) return false; + var header = data.Slice(sectorSize, Math.Min(sectorSize, data.Length - sectorSize)); + return IsValidHeaderBlock(header, sectorSize, expectedMyLba: 1); + } + + /// + /// The partition table's layout facts, or null when the image carries no usable GPT. + /// Uses the same header would. + /// + public static HeaderInfo? ReadHeader(Stream diskData) { + try { + return Locate(diskData, lenient: true)?.Info; + } catch (Exception ex) when (ex is IOException or InvalidDataException or ArgumentException) { + return null; + } + } + + /// + /// Parses all partitions from a GPT disk image, at whichever block size its header validates. + /// + /// The full disk image as a readable stream. + /// A list of partition entries. + public static List Parse(Stream diskData) => Parse(diskData, out _); + + /// + /// Parses all partitions and reports the header that described them. + /// + /// No GPT header at LBA 1 at any supported block size. + public static List Parse(Stream diskData, out HeaderInfo info) { + var located = Locate(diskData, lenient: true) + ?? throw new InvalidDataException("Invalid GPT: missing 'EFI PART' signature at LBA 1."); + info = located.Info; + return ParseEntries(located.Entries, info); + } + + private sealed record Located(HeaderInfo Info, byte[] Entries); - var header = data.Slice(HeaderOffset, Math.Min(SectorSize, data.Length - HeaderOffset)); + /// + /// Finds the header to trust: a primary whose header and entry-array CRCs both match; else + /// the backup it (or the end of the disk) points at, with the same checks; else, when + /// , a primary that carries only the signature — the reading older + /// images written without CRCs rely on. + /// + private static Located? Locate(Stream disk, bool lenient) { + var head = ReadAt(disk, 0, (int)Math.Min(ProbeBytes, disk.Length)); + foreach (var size in SupportedSectorSizes) { + if (!HasValidHeader(head, size)) continue; + var header = head.AsSpan(size, Math.Min(size, head.Length - size)); + if (TryReadEntries(disk, header, size, out var entries)) + return new Located(ToInfo(header, size, fromBackup: false), entries); + + // Entry array damaged: the backup describes the same table. + if (TryLocateBackup(disk, size, BinaryPrimitives.ReadUInt64LittleEndian(header[32..])) is { } backup) + return backup; + } + + // No primary validates: look for a backup at the end of the disk. + foreach (var size in SupportedSectorSizes) { + if (!IsGpt(head) && !HasProtectiveEntry(head)) break; + if (disk.Length < 2L * size) continue; + if (TryLocateBackup(disk, size, (ulong)(disk.Length / size - 1)) is { } backup) + return backup; + } + + if (!lenient) return null; + foreach (var size in SupportedSectorSizes) { + if (head.Length < size + MinHeaderSize || !head.AsSpan(size, 8).SequenceEqual(Signature)) continue; + var header = head.AsSpan(size, Math.Min(size, head.Length - size)); + var entrySize = BinaryPrimitives.ReadUInt32LittleEndian(header[84..]); + if (entrySize < MinEntrySize) + throw new InvalidDataException($"GPT entry size {entrySize} is smaller than minimum {MinEntrySize}."); + if (!TryReadEntryBytes(disk, header, size, out var entries)) + throw new InvalidDataException("GPT entry array lies outside the image."); + return new Located(ToInfo(header, size, fromBackup: false), entries); + } + return null; + } + + private static Located? TryLocateBackup(Stream disk, int sectorSize, ulong backupLba) { + var offset = (long)backupLba * sectorSize; + if (backupLba < 2 || offset + sectorSize > disk.Length) return null; + var block = ReadAt(disk, offset, sectorSize); + if (block.Length < sectorSize || !IsValidHeaderBlock(block, sectorSize, backupLba)) return null; + return TryReadEntries(disk, block, sectorSize, out var entries) + ? new Located(ToInfo(block, sectorSize, fromBackup: true), entries) + : null; + } + + private static bool HasProtectiveEntry(ReadOnlySpan head) { + if (head.Length < 512 || head[510] != 0x55 || head[511] != 0xAA) return false; + for (var i = 0; i < 4; ++i) + if (head[0x1BE + i * 16 + 4] == 0xEE) return true; + return false; + } + + private static bool IsValidHeaderBlock(ReadOnlySpan header, int sectorSize, ulong expectedMyLba) { + if (header.Length < MinHeaderSize || !header[..8].SequenceEqual(Signature)) return false; var headerSize = BinaryPrimitives.ReadUInt32LittleEndian(header[12..]); - if (headerSize < MinHeaderSize || headerSize > SectorSize || headerSize > header.Length) return false; + if (headerSize < MinHeaderSize || headerSize > sectorSize || headerSize > header.Length) return false; Span copy = stackalloc byte[(int)headerSize]; header[..(int)headerSize].CopyTo(copy); @@ -56,69 +203,71 @@ public static bool HasValidHeader(ReadOnlySpan data) { var myLba = BinaryPrimitives.ReadUInt64LittleEndian(header[24..]); var entrySize = BinaryPrimitives.ReadUInt32LittleEndian(header[84..]); var entryCount = BinaryPrimitives.ReadUInt32LittleEndian(header[80..]); - return myLba == 1 + return myLba == expectedMyLba && entrySize >= MinEntrySize && (entrySize & (entrySize - 1)) == 0 && entryCount is > 0 and <= 65536; } - /// - /// Parses all partitions from a GPT disk image. - /// - /// The full disk image as a readable stream. - /// A list of partition entries. - public static List Parse(Stream diskData) { - // Read GPT header at LBA 1. - var header = new byte[SectorSize]; - diskData.Position = HeaderOffset; - diskData.ReadExactly(header); - - if (!header.AsSpan(0, 8).SequenceEqual(Signature)) - throw new InvalidDataException("Invalid GPT: missing 'EFI PART' signature at LBA 1."); - - var revision = BinaryPrimitives.ReadUInt32LittleEndian(header.AsSpan(8)); - var headerSize = BinaryPrimitives.ReadUInt32LittleEndian(header.AsSpan(12)); - var partitionEntryLba = BinaryPrimitives.ReadUInt64LittleEndian(header.AsSpan(72)); - var numberOfEntries = BinaryPrimitives.ReadUInt32LittleEndian(header.AsSpan(80)); - var entrySize = BinaryPrimitives.ReadUInt32LittleEndian(header.AsSpan(84)); - - if (entrySize < MinEntrySize) - throw new InvalidDataException($"GPT entry size {entrySize} is smaller than minimum {MinEntrySize}."); - - // Read all partition entries. - var totalSize = (long)numberOfEntries * entrySize; - var entryData = new byte[totalSize]; - diskData.Position = (long)partitionEntryLba * SectorSize; - diskData.ReadExactly(entryData); + private static bool TryReadEntries(Stream disk, ReadOnlySpan header, int sectorSize, out byte[] entries) { + if (!TryReadEntryBytes(disk, header, sectorSize, out entries)) return false; + var expected = BinaryPrimitives.ReadUInt32LittleEndian(header[88..]); + return Compression.Core.Checksums.Crc32.Compute(entries) == expected; + } + private static bool TryReadEntryBytes(Stream disk, ReadOnlySpan header, int sectorSize, out byte[] entries) { + entries = []; + var entriesLba = BinaryPrimitives.ReadUInt64LittleEndian(header[72..]); + var count = BinaryPrimitives.ReadUInt32LittleEndian(header[80..]); + var size = BinaryPrimitives.ReadUInt32LittleEndian(header[84..]); + var total = (long)count * size; + if (total <= 0 || total > MaxEntryArrayBytes) return false; + if (entriesLba > (ulong)(long.MaxValue / sectorSize)) return false; + var offset = (long)entriesLba * sectorSize; + if (offset < 0 || offset + total > disk.Length) return false; + entries = ReadAt(disk, offset, (int)total); + return entries.Length == total; + } + + private static HeaderInfo ToInfo(ReadOnlySpan header, int sectorSize, bool fromBackup) => new( + sectorSize, + BinaryPrimitives.ReadUInt64LittleEndian(header[24..]), + BinaryPrimitives.ReadUInt64LittleEndian(header[32..]), + BinaryPrimitives.ReadUInt64LittleEndian(header[40..]), + BinaryPrimitives.ReadUInt64LittleEndian(header[48..]), + ReadMixedEndianGuid(header[56..]), + BinaryPrimitives.ReadUInt64LittleEndian(header[72..]), + BinaryPrimitives.ReadUInt32LittleEndian(header[80..]), + BinaryPrimitives.ReadUInt32LittleEndian(header[84..]), + fromBackup); + + private static List ParseEntries(byte[] entryData, HeaderInfo info) { var result = new List(); var index = 0; + var entrySize = (int)info.EntrySize; + var sectorSize = info.SectorSize; - for (var i = 0; i < (int)numberOfEntries; i++) { - var offset = (int)(i * entrySize); - var entry = entryData.AsSpan(offset, (int)entrySize); + for (var i = 0; i < (int)info.EntryCount; i++) { + var entry = entryData.AsSpan(i * entrySize, entrySize); // Read type GUID (mixed endian: first 3 components LE, last 2 BE). var typeGuid = ReadMixedEndianGuid(entry); if (typeGuid == Guid.Empty) continue; // Unused entry. - // Read unique GUID. - var uniqueGuid = ReadMixedEndianGuid(entry[16..]); - var firstLba = BinaryPrimitives.ReadUInt64LittleEndian(entry[32..]); var lastLba = BinaryPrimitives.ReadUInt64LittleEndian(entry[40..]); var attributes = BinaryPrimitives.ReadUInt64LittleEndian(entry[48..]); + if (lastLba < firstLba || firstLba > (ulong)(long.MaxValue / sectorSize) || lastLba > (ulong)(long.MaxValue / sectorSize)) + continue; // An inverted or impossible range describes no data. // Read UTF-16LE name (up to 36 characters = 72 bytes, starting at offset 56). - var nameLen = Math.Min(72, (int)entrySize - 56); + var nameLen = Math.Min(72, entrySize - 56); var name = Encoding.Unicode.GetString(entry.Slice(56, nameLen)).TrimEnd('\0'); - var sizeBytes = (long)(lastLba - firstLba + 1) * SectorSize; - result.Add(new() { Index = index++, - StartOffset = (long)firstLba * SectorSize, - Size = sizeBytes, + StartOffset = (long)firstLba * sectorSize, + Size = (long)(lastLba - firstLba + 1) * sectorSize, TypeName = PartitionTypeDatabase.GetGptTypeName(typeGuid), IsActive = (attributes & 0x04) != 0, // Legacy BIOS bootable attribute. TypeCode = typeGuid.ToString("D").ToUpperInvariant(), @@ -130,6 +279,14 @@ public static List Parse(Stream diskData) { return result; } + private static byte[] ReadAt(Stream disk, long offset, int count) { + if (count <= 0) return []; + var buffer = new byte[count]; + disk.Position = offset; + var read = disk.ReadAtLeast(buffer, count, throwOnEndOfStream: false); + return read == count ? buffer : buffer[..read]; + } + /// /// Reads a GUID in GPT mixed-endian format: first 3 components are little-endian, /// last 2 are big-endian (stored as-is). diff --git a/Compression.Core/DiskImage/MbrParser.cs b/Compression.Core/DiskImage/MbrParser.cs index 4e6688499..351b5ebe2 100644 --- a/Compression.Core/DiskImage/MbrParser.cs +++ b/Compression.Core/DiskImage/MbrParser.cs @@ -5,6 +5,7 @@ namespace Compression.Core.DiskImage; /// /// Parses MBR (Master Boot Record) partition tables, including extended/logical partition chains. /// The MBR sits at LBA 0 (offset 0) of a disk image, with 4 primary partition entries at offset 0x1BE. +/// The entries count in logical blocks: 512 bytes on a classic or 512e disk, 4096 on a 4Kn disk. /// public static class MbrParser { @@ -20,6 +21,9 @@ public static class MbrParser { /// Standard sector size. private const int SectorSize = 512; + /// Cap on logical partitions walked in one chain, so a damaged or hostile chain cannot spin. + private const int MaxLogicalPartitions = 256; + /// /// Checks whether the given data starts with a valid MBR. /// @@ -44,7 +48,8 @@ public static bool IsMbr(ReadOnlySpan data) /// /// At least the first 512 bytes of the image. /// Length of the whole image in bytes. - public static bool HasValidPartitionTable(ReadOnlySpan sector0, long diskLength) { + /// Logical block size the entries' LBAs are counted in. + public static bool HasValidPartitionTable(ReadOnlySpan sector0, long diskLength, int sectorSize = SectorSize) { if (!IsMbr(sector0)) return false; Span<(ulong Start, ulong End)> used = stackalloc (ulong, ulong)[4]; @@ -57,10 +62,10 @@ public static bool HasValidPartitionTable(ReadOnlySpan sector0, long diskL if (entry.Type == 0x00) continue; if (entry.LbaStart == 0 || entry.SectorCount == 0) return false; - var start = (ulong)entry.LbaStart * SectorSize; + var start = (ulong)entry.LbaStart * (ulong)sectorSize; if (start >= (ulong)diskLength) return false; - var end = start + (ulong)entry.SectorCount * SectorSize; + var end = start + (ulong)entry.SectorCount * (ulong)sectorSize; for (var j = 0; j < count; ++j) if (start < used[j].End && used[j].Start < end) return false; @@ -73,8 +78,9 @@ public static bool HasValidPartitionTable(ReadOnlySpan sector0, long diskL /// Parses all partitions from an MBR, including extended/logical partitions. /// /// The full disk image as a readable stream. + /// Logical block size the entries' LBAs are counted in (512, or 4096 on a 4Kn disk). /// A list of partition entries. - public static List Parse(Stream diskData) { + public static List Parse(Stream diskData, int sectorSize = SectorSize) { var mbr = new byte[SectorSize]; diskData.Position = 0; diskData.ReadExactly(mbr); @@ -90,15 +96,13 @@ public static List Parse(Stream diskData) { if (entry.Type == 0x00) continue; - if (entry.Type is 0x05 or 0x0F or 0x85) { - // Extended partition: walk the chain. - var extStart = entry.LbaStart; - ParseExtendedChain(diskData, extStart, extStart, result, ref index); + if (IsExtended(entry.Type)) { + ParseExtendedChain(diskData, entry.LbaStart, entry.LbaStart, entry.SectorCount, sectorSize, result, ref index); } else { result.Add(new() { Index = index++, - StartOffset = (long)entry.LbaStart * SectorSize, - Size = (long)entry.SectorCount * SectorSize, + StartOffset = (long)entry.LbaStart * sectorSize, + Size = (long)entry.SectorCount * sectorSize, TypeName = PartitionTypeDatabase.GetMbrTypeName(entry.Type), IsActive = entry.IsActive, TypeCode = $"0x{entry.Type:X2}", @@ -115,8 +119,9 @@ public static List Parse(Stream diskData) { /// Operates on a 512-byte buffer (no stream needed). /// /// The first 512 bytes of the disk image. + /// Logical block size the entries' LBAs are counted in. /// A list of primary partition entries (excluding empty entries). - public static List ParsePrimary(ReadOnlySpan mbr) { + public static List ParsePrimary(ReadOnlySpan mbr, int sectorSize = SectorSize) { if (mbr.Length < SectorSize) throw new ArgumentException("MBR data must be at least 512 bytes.", nameof(mbr)); @@ -130,35 +135,50 @@ public static List ParsePrimary(ReadOnlySpan mbr) { result.Add(new() { Index = index++, - StartOffset = (long)entry.LbaStart * SectorSize, - Size = (long)entry.SectorCount * SectorSize, + StartOffset = (long)entry.LbaStart * sectorSize, + Size = (long)entry.SectorCount * sectorSize, TypeName = PartitionTypeDatabase.GetMbrTypeName(entry.Type), IsActive = entry.IsActive, TypeCode = $"0x{entry.Type:X2}", - Source = entry.Type is 0x05 or 0x0F or 0x85 ? "MBR (Extended Container)" : "MBR" + Source = IsExtended(entry.Type) ? "MBR (Extended Container)" : "MBR" }); } return result; } - private static void ParseExtendedChain(Stream diskData, uint ebrLba, uint extStart, List result, ref int index) { + private static bool IsExtended(byte type) => type is 0x05 or 0x0F or 0x85; + + /// + /// Walks the chain of extended boot records. Each EBR's first entry is a logical partition + /// whose start is relative to that EBR; its second entry links to the next EBR, relative to + /// the start of the extended partition. The walk stops at a loop, at an EBR without the boot + /// signature, at a link that leaves the extended partition, and after + /// records. + /// + private static void ParseExtendedChain(Stream diskData, uint ebrLba, uint extStart, uint extSectors, int sectorSize, + List result, ref int index) { var seen = new HashSet(); + var extEnd = (ulong)extStart + extSectors; - while (ebrLba != 0 && seen.Add(ebrLba)) { + while (ebrLba != 0 && seen.Count < MaxLogicalPartitions && seen.Add(ebrLba)) { + if (ebrLba < extStart || ebrLba >= extEnd) + break; var ebr = new byte[SectorSize]; - diskData.Position = (long)ebrLba * SectorSize; - if (diskData.Read(ebr) < SectorSize) + diskData.Position = (long)ebrLba * sectorSize; + if (diskData.ReadAtLeast(ebr, SectorSize, throwOnEndOfStream: false) < SectorSize) + break; + if (BinaryPrimitives.ReadUInt16LittleEndian(ebr.AsSpan(510)) != BootSignature) break; // Entry 0: the logical partition (relative to this EBR's LBA). var logical = ParseEntry(ebr.AsSpan(PartitionTableOffset)); if (logical.Type != 0x00 && logical.SectorCount > 0) { - var logicalLba = ebrLba + logical.LbaStart; + var logicalLba = (long)ebrLba + logical.LbaStart; result.Add(new() { Index = index++, - StartOffset = (long)logicalLba * SectorSize, - Size = (long)logical.SectorCount * SectorSize, + StartOffset = logicalLba * sectorSize, + Size = (long)logical.SectorCount * sectorSize, TypeName = PartitionTypeDatabase.GetMbrTypeName(logical.Type), IsActive = logical.IsActive, TypeCode = $"0x{logical.Type:X2}", @@ -171,7 +191,9 @@ private static void ParseExtendedChain(Stream diskData, uint ebrLba, uint extSta if (next.Type == 0x00 || next.SectorCount == 0) break; - ebrLba = extStart + next.LbaStart; + var nextLba = (ulong)extStart + next.LbaStart; + if (nextLba > uint.MaxValue) break; + ebrLba = (uint)nextLba; } } diff --git a/Compression.Core/DiskImage/PartitionEditor.cs b/Compression.Core/DiskImage/PartitionEditor.cs index 150dc5510..33684d6d8 100644 --- a/Compression.Core/DiskImage/PartitionEditor.cs +++ b/Compression.Core/DiskImage/PartitionEditor.cs @@ -36,9 +36,16 @@ namespace Compression.Core.DiskImage; /// public sealed class PartitionEditor { - /// Standard sector size used by the editor. + /// + /// The classic logical block size, and the one a disk without a GPT is assumed to use unless + /// the caller says otherwise. A 4Kn GPT disk is edited in 4096-byte blocks: see + /// . + /// public const int SectorSize = 512; + /// Logical block sizes the editor supports. + public static IReadOnlyList SupportedSectorSizes => GptParser.SupportedSectorSizes; + private const int MbrSignatureOffset = 510; private const ushort MbrBootSignature = 0xAA55; private const int MbrTableOffset = 0x1BE; @@ -49,10 +56,11 @@ public sealed class PartitionEditor { private const int GptHeaderSize = 92; private const int DefaultGptEntrySize = 128; private const int GptEntryCount = 128; - private const int GptReservedSectors = 33; // 32 sectors of entries + 1 sector of header private static ReadOnlySpan GptSignature => "EFI PART"u8; private readonly Stream _stream; + private readonly int _requestedSectorSize; + private int _sectorSize; private PartitionScheme _scheme; private readonly List _entries = []; @@ -60,25 +68,103 @@ public sealed class PartitionEditor { // defaults applied when initialising a fresh GPT). private uint _gptEntrySize = DefaultGptEntrySize; private uint _gptEntryCount = GptEntryCount; + private Guid? _gptDiskGuid; /// Partition scheme currently in use on the disk image. public PartitionScheme Scheme => this._scheme; + /// + /// The logical block size every LBA on this disk is counted in: the size at which the GPT + /// header validated, or — for an MBR or blank disk — the size the editor was opened with. + /// + public int LogicalSectorSize => this._sectorSize; + /// /// Opens an editor over the given disk-image stream. The stream must be /// readable, writable, and seekable. The scheme (MBR/GPT/None) is /// auto-detected from the on-disk signature. /// - public PartitionEditor(Stream stream) { + public PartitionEditor(Stream stream) : this(stream, SectorSize) { } + + /// + /// Opens an editor over the given disk-image stream, counting LBAs in + /// -byte blocks unless a GPT on the disk validates at another + /// supported block size — the table on the disk decides, since rewriting it at a different + /// size would move every partition. + /// + /// Readable, writable, seekable disk image. + /// 512 or 4096. + public PartitionEditor(Stream stream, int sectorSize) { ArgumentNullException.ThrowIfNull(stream); if (!stream.CanRead) throw new ArgumentException("Stream must be readable.", nameof(stream)); if (!stream.CanWrite) throw new ArgumentException("Stream must be writable.", nameof(stream)); if (!stream.CanSeek) throw new ArgumentException("Stream must be seekable.", nameof(stream)); + if (!SupportedSectorSizes.Contains(sectorSize)) + throw new ArgumentOutOfRangeException(nameof(sectorSize), sectorSize, "Logical sector size must be 512 or 4096."); this._stream = stream; + this._requestedSectorSize = sectorSize; + this._sectorSize = sectorSize; this.Reload(); } + /// + /// Writes an empty partition table of over the start (and, for GPT, + /// the end) of and returns an editor over it. A GPT gets the + /// standard 128 entries of 128 bytes, its backup at the last LBA and a protective MBR. + /// + /// Readable, writable, seekable target, already at the disk's full length. + /// or . + /// 512 or 4096. + public static PartitionEditor CreateEmpty(Stream stream, PartitionScheme scheme, int sectorSize = SectorSize) { + ArgumentNullException.ThrowIfNull(stream); + if (!SupportedSectorSizes.Contains(sectorSize)) + throw new ArgumentOutOfRangeException(nameof(sectorSize), sectorSize, "Logical sector size must be 512 or 4096."); + if (stream.Length % sectorSize != 0) + throw new ArgumentException($"Disk length {stream.Length} is not a whole number of {sectorSize}-byte sectors.", nameof(stream)); + + // Clear whatever table the bytes held, so the editor starts from a blank disk. + var editor = new PartitionEditor(stream, sectorSize); + editor._entries.Clear(); + editor._gptRawSlots.Clear(); + editor._sectorSize = sectorSize; + switch (scheme) { + case PartitionScheme.Gpt: + editor._scheme = PartitionScheme.Gpt; + editor._gptEntrySize = DefaultGptEntrySize; + editor._gptEntryCount = GptEntryCount; + editor._gptDiskGuid = null; + editor.RewriteGpt(); + break; + case PartitionScheme.Mbr: + if (stream.Length / sectorSize > uint.MaxValue) + throw new InvalidOperationException("An MBR cannot address a disk of more than 2^32 sectors; use GPT."); + editor._scheme = PartitionScheme.Mbr; + editor.InitializeEmptyMbr(); + break; + default: + throw new ArgumentOutOfRangeException(nameof(scheme), scheme, "Only GPT and MBR tables can be created."); + } + return editor; + } + + /// Sectors the GPT entry array occupies at the current block size. + private long GptEntrySectors => ((long)this._gptEntrySize * this._gptEntryCount + this._sectorSize - 1) / this._sectorSize; + + /// First LBA a GPT partition may use: after the protective MBR, the header and the entry array. + private long GptFirstUsableLba => 2 + this.GptEntrySectors; + + /// Last LBA a GPT partition may use: before the backup entry array and the backup header. + private long GptLastUsableLba => this._stream.Length / this._sectorSize - this.GptEntrySectors - 2; + + /// + /// The byte range partitions may occupy under the current scheme: everything after LBA 0 for + /// MBR; between the primary and backup GPT structures for GPT. + /// + public (long Start, long End) UsableRange => this._scheme == PartitionScheme.Gpt + ? (this.GptFirstUsableLba * this._sectorSize, (this.GptLastUsableLba + 1) * this._sectorSize) + : (this._sectorSize, this._stream.Length / this._sectorSize * this._sectorSize); + /// /// Convenience factory for an editor backed by an /// container (VHD/VHDX/VMDK/QCOW2/VDI). The caller is responsible for @@ -115,9 +201,10 @@ public IReadOnlyList ListPartitions() /// Range overlaps an existing /// partition or exceeds the disk length. public void AddPartition(long startByteOffset, long lengthBytes, PartitionType type, string? label) { - ValidateRange(startByteOffset, lengthBytes); + this.ValidateRange(startByteOffset, lengthBytes); this.RequireFits(startByteOffset, lengthBytes); this.RequireNoOverlap(startByteOffset, lengthBytes); + this.RequireUsable(startByteOffset, lengthBytes); if (this._scheme == PartitionScheme.None) { // No table yet — promote to MBR. @@ -185,7 +272,7 @@ public void AddPartition(long startByteOffset, long lengthBytes, PartitionType t /// No extended container exists, /// range outside container, or overlap detected. public void AddLogicalPartition(long startByteOffset, long lengthBytes, PartitionType type, string? label) { - ValidateRange(startByteOffset, lengthBytes); + this.ValidateRange(startByteOffset, lengthBytes); if (this._scheme != PartitionScheme.Mbr) throw new InvalidOperationException("Logical partitions only exist in MBR layouts."); @@ -204,13 +291,13 @@ public void AddLogicalPartition(long startByteOffset, long lengthBytes, Partitio var existingLogicals = this._entries.Count(e => e.Source == "EBR"); if (existingLogicals == 0) { // First logical: container_start + 1 sector is the earliest legal data offset. - if (startByteOffset < container.StartOffset + SectorSize) + if (startByteOffset < container.StartOffset + this._sectorSize) throw new InvalidOperationException( - $"First logical must start at or after container_start + 1 sector ({container.StartOffset + SectorSize}); got {startByteOffset}."); + $"First logical must start at or after container_start + 1 sector ({container.StartOffset + this._sectorSize}); got {startByteOffset}."); } else { // Subsequent logical needs an EBR sector before it. - var ebrLba = (startByteOffset / SectorSize) - 1; - var ebrStart = ebrLba * SectorSize; + var ebrLba = (startByteOffset / this._sectorSize) - 1; + var ebrStart = ebrLba * this._sectorSize; if (ebrStart <= container.StartOffset) throw new InvalidOperationException( $"Logical start ({startByteOffset}) leaves no room for an EBR sector inside the extended container [{container.StartOffset}..{container.StartOffset + container.Size})."); @@ -221,8 +308,8 @@ public void AddLogicalPartition(long startByteOffset, long lengthBytes, Partitio $"Logical end ({startByteOffset + lengthBytes}) exceeds extended container end ({container.StartOffset + container.Size})."); // Overlap check: EBR + data for non-first; just data for first (EBR is at container start). - var checkStart = existingLogicals == 0 ? startByteOffset : startByteOffset - SectorSize; - var checkLength = existingLogicals == 0 ? lengthBytes : SectorSize + lengthBytes; + var checkStart = existingLogicals == 0 ? startByteOffset : startByteOffset - this._sectorSize; + var checkLength = existingLogicals == 0 ? lengthBytes : this._sectorSize + lengthBytes; this.RequireNoOverlap(checkStart, checkLength); var entry = new PartitionEntry { @@ -278,9 +365,9 @@ public void PurgePartition(int index) { // For logicals also zero the preceding EBR sector. if (e.Source == "EBR") { - var ebrStart = e.StartOffset - SectorSize; + var ebrStart = e.StartOffset - this._sectorSize; if (ebrStart >= 0) - this.ZeroRange(ebrStart, SectorSize); + this.ZeroRange(ebrStart, this._sectorSize); } this.DeletePartition(index); @@ -319,11 +406,22 @@ public void ConvertMbrToGpt() { }); } + // A partition inside the GPT's own structures would be overwritten by them: refuse before + // writing anything. + this._gptEntrySize = DefaultGptEntrySize; + this._gptEntryCount = GptEntryCount; + var firstUsable = this.GptFirstUsableLba * this._sectorSize; + var lastUsableEnd = (this.GptLastUsableLba + 1) * this._sectorSize; + foreach (var e in translated) + if (e.StartOffset < firstUsable || e.StartOffset + e.Size > lastUsableEnd) + throw new InvalidOperationException( + $"Partition [{e.StartOffset}..{e.StartOffset + e.Size}) overlaps where the GPT's own structures go " + + $"(usable range [{firstUsable}..{lastUsableEnd})); converting would overwrite its data."); + this._entries.Clear(); this._entries.AddRange(translated); this._scheme = PartitionScheme.Gpt; - this._gptEntrySize = DefaultGptEntrySize; - this._gptEntryCount = GptEntryCount; + this._gptDiskGuid = null; this.RewriteGpt(); // Also rewrites the protective MBR. } @@ -359,12 +457,16 @@ public void ConvertGptToMbr() { }); } - // Zero out primary GPT (header + entry array, LBAs 1..33) and backup - // GPT (last 33 sectors) before writing the MBR. - this.ZeroRange(GptHeaderLba * SectorSize, (long)GptReservedSectors * SectorSize); - var backupStart = this._stream.Length - (long)GptReservedSectors * SectorSize; - if (backupStart > 0) - this.ZeroRange(backupStart, (long)GptReservedSectors * SectorSize); + if (translated.Any(e => (e.StartOffset + e.Size) / this._sectorSize > uint.MaxValue)) + throw new InvalidOperationException("A partition ends past 2^32 sectors, which an MBR entry cannot address."); + + // Zero out primary GPT (header + entry array) and backup GPT (entry array + header at the + // end) before writing the MBR. Both lie outside the usable range, so no partition is touched. + var reserved = (this.GptEntrySectors + 1) * this._sectorSize; + this.ZeroRange(GptHeaderLba * (long)this._sectorSize, reserved); + var backupStart = this._stream.Length / this._sectorSize * this._sectorSize - reserved; + if (backupStart > reserved) + this.ZeroRange(backupStart, reserved); this._entries.Clear(); this._entries.AddRange(translated); @@ -508,8 +610,8 @@ private static bool MatchesAtOffset(byte[] probe, MagicSignature sig) { public void ResizePartition(int index, long newSizeBytes) { this.RequireValidIndex(index); if (newSizeBytes <= 0) throw new ArgumentOutOfRangeException(nameof(newSizeBytes)); - if (newSizeBytes % SectorSize != 0) - throw new ArgumentException($"New size must be sector-aligned ({SectorSize} bytes).", nameof(newSizeBytes)); + if (newSizeBytes % this._sectorSize != 0) + throw new ArgumentException($"New size must be sector-aligned ({this._sectorSize} bytes).", nameof(newSizeBytes)); var resizer = InPlaceFilesystemResizer ?? throw new InvalidOperationException( @@ -527,6 +629,9 @@ public void ResizePartition(int index, long newSizeBytes) { if (newEnd > this._stream.Length) throw new InvalidOperationException( $"New partition end ({newEnd}) exceeds disk length ({this._stream.Length})."); + if (newEnd > this.UsableRange.End) + throw new InvalidOperationException( + $"New partition end ({newEnd}) runs into the backup partition table (usable end {this.UsableRange.End})."); if (!isShrink) { foreach (var other in this._entries) { if (other.Index == e.Index) continue; @@ -658,14 +763,17 @@ public PartitionTableVerification Verify() { /// public void Reload() { this._entries.Clear(); + this._gptRawSlots.Clear(); + this._gptDiskGuid = null; + this._sectorSize = this._requestedSectorSize; if (this._stream.Length < SectorSize * 2L) { this._scheme = PartitionScheme.None; return; } - var head = new byte[Math.Min(4096, (int)this._stream.Length)]; + var head = new byte[(int)Math.Min(GptParser.ProbeBytes, this._stream.Length)]; this._stream.Position = 0; - var read = this._stream.Read(head, 0, head.Length); + var read = this._stream.ReadAtLeast(head, head.Length, throwOnEndOfStream: false); if (read < SectorSize) { this._scheme = PartitionScheme.None; return; @@ -673,22 +781,24 @@ public void Reload() { if (GptParser.IsGpt(head)) { try { - var parsed = GptParser.Parse(this._stream); - // Read entry size from the on-disk header for round-trip preservation. - this._stream.Position = (long)GptHeaderLba * SectorSize; - Span hdr = stackalloc byte[GptHeaderSize]; - this._stream.ReadExactly(hdr); - this._gptEntryCount = BinaryPrimitives.ReadUInt32LittleEndian(hdr[80..]); - this._gptEntrySize = BinaryPrimitives.ReadUInt32LittleEndian(hdr[84..]); - if (this._gptEntrySize < DefaultGptEntrySize) this._gptEntrySize = DefaultGptEntrySize; - if (this._gptEntryCount == 0) this._gptEntryCount = GptEntryCount; + var parsed = GptParser.Parse(this._stream, out var info); + // Keep the on-disk layout (block size, entry size/count, disk GUID) for round-trip preservation. + this._sectorSize = info.SectorSize; + this._gptEntryCount = info.EntryCount == 0 ? GptEntryCount : info.EntryCount; + this._gptEntrySize = info.EntrySize < DefaultGptEntrySize ? DefaultGptEntrySize : info.EntrySize; + this._gptDiskGuid = info.DiskGuid == Guid.Empty ? null : info.DiskGuid; + var entryArray = new byte[(long)info.EntryCount * info.EntrySize]; + this._stream.Position = (long)info.EntriesLba * info.SectorSize; + this._stream.ReadExactly(entryArray); + this.CaptureGptSlots(entryArray); this._scheme = PartitionScheme.Gpt; this._entries.AddRange(parsed); this.ReindexAll(); return; - } catch { + } catch (Exception ex) when (ex is IOException or InvalidDataException or ArgumentException or OverflowException) { // Fall through to MBR. + this._sectorSize = this._requestedSectorSize; } } @@ -697,9 +807,9 @@ public void Reload() { // Use the full parser so logical entries are included as well. List parsed; try { - parsed = MbrParser.Parse(this._stream); - } catch { - parsed = [.. MbrParser.ParsePrimary(head)]; + parsed = MbrParser.Parse(this._stream, this._sectorSize); + } catch (Exception ex) when (ex is IOException or InvalidDataException or ArgumentException) { + parsed = [.. MbrParser.ParsePrimary(head, this._sectorSize)]; } // MbrParser.Parse returns primaries (non-extended) plus EBR-discovered @@ -708,7 +818,7 @@ public void Reload() { this._stream.Position = 0; var sector0 = new byte[SectorSize]; this._stream.ReadExactly(sector0); - var primariesFromTable = MbrParser.ParsePrimary(sector0); + var primariesFromTable = MbrParser.ParsePrimary(sector0, this._sectorSize); var entries = new List(); foreach (var p in primariesFromTable) entries.Add(p); @@ -726,13 +836,13 @@ public void Reload() { // ── Overlap / validation helpers ─────────────────────────────────── - private static void ValidateRange(long startByteOffset, long lengthBytes) { + private void ValidateRange(long startByteOffset, long lengthBytes) { if (startByteOffset < 0) throw new ArgumentOutOfRangeException(nameof(startByteOffset)); if (lengthBytes <= 0) throw new ArgumentOutOfRangeException(nameof(lengthBytes)); - if (startByteOffset % SectorSize != 0) - throw new ArgumentException($"Start offset must be sector-aligned ({SectorSize} bytes).", nameof(startByteOffset)); - if (lengthBytes % SectorSize != 0) - throw new ArgumentException($"Length must be a multiple of sector size ({SectorSize} bytes).", nameof(lengthBytes)); + if (startByteOffset % this._sectorSize != 0) + throw new ArgumentException($"Start offset must be sector-aligned ({this._sectorSize} bytes).", nameof(startByteOffset)); + if (lengthBytes % this._sectorSize != 0) + throw new ArgumentException($"Length must be a multiple of sector size ({this._sectorSize} bytes).", nameof(lengthBytes)); } private void RequireFits(long startByteOffset, long lengthBytes) { @@ -741,6 +851,22 @@ private void RequireFits(long startByteOffset, long lengthBytes) { $"Partition end ({startByteOffset + lengthBytes}) exceeds disk length ({this._stream.Length})."); } + /// + /// A partition must stay clear of the table itself: LBA 0 for MBR, and for GPT the primary + /// header and entry array at the front and their backups at the end (UEFI 2.10, §5.3.1 — + /// FirstUsableLBA / LastUsableLBA). An MBR entry also cannot address past 2^32 sectors. + /// + private void RequireUsable(long startByteOffset, long lengthBytes) { + var (usableStart, usableEnd) = this.UsableRange; + if (startByteOffset < usableStart || startByteOffset + lengthBytes > usableEnd) + throw new InvalidOperationException( + $"Partition [{startByteOffset}..{startByteOffset + lengthBytes}) leaves the usable range [{usableStart}..{usableEnd}) " + + "and would overwrite the partition table."); + if (this._scheme != PartitionScheme.Gpt + && ((startByteOffset + lengthBytes) / this._sectorSize > uint.MaxValue)) + throw new InvalidOperationException("An MBR entry cannot address past 2^32 sectors; use GPT."); + } + private void RequireNoOverlap(long startByteOffset, long lengthBytes) { foreach (var existing in this._entries) { // Extended container is allowed to encompass its logicals — skip it @@ -825,8 +951,8 @@ private void RewriteMbr() { entry[1] = 0xFE; entry[2] = 0xFF; entry[3] = 0xFF; entry[4] = mbrByte; entry[5] = 0xFE; entry[6] = 0xFF; entry[7] = 0xFF; - BinaryPrimitives.WriteUInt32LittleEndian(entry[8..], (uint)(e.StartOffset / SectorSize)); - BinaryPrimitives.WriteUInt32LittleEndian(entry[12..], (uint)(e.Size / SectorSize)); + BinaryPrimitives.WriteUInt32LittleEndian(entry[8..], (uint)(e.StartOffset / this._sectorSize)); + BinaryPrimitives.WriteUInt32LittleEndian(entry[12..], (uint)(e.Size / this._sectorSize)); primarySlot++; } @@ -853,12 +979,12 @@ private void RewriteEbrChain() { .OrderBy(e => e.StartOffset) .ToList(); - var containerStartLba = (uint)(container.StartOffset / SectorSize); + var containerStartLba = (uint)(container.StartOffset / this._sectorSize); // Always zero the first sector of the container first — when the chain // shrinks (logicals removed), we don't want a stale EBR lingering. The // active first EBR will be rewritten below if logicals.Count > 0. - this.ZeroRange(container.StartOffset, SectorSize); + this.ZeroRange(container.StartOffset, this._sectorSize); if (logicals.Count == 0) { this._stream.Flush(); @@ -867,14 +993,14 @@ private void RewriteEbrChain() { for (var i = 0; i < logicals.Count; ++i) { var logical = logicals[i]; - var dataLba = (uint)(logical.StartOffset / SectorSize); + var dataLba = (uint)(logical.StartOffset / this._sectorSize); // EBR location: first logical -> container start; subsequent -> data-1. var ebrLba = i == 0 ? containerStartLba : dataLba - 1; - var ebrOffset = (long)ebrLba * SectorSize; + var ebrOffset = (long)ebrLba * this._sectorSize; var sector = new byte[SectorSize]; - if (ebrOffset + SectorSize <= this._stream.Length) { + if (ebrOffset + this._sectorSize <= this._stream.Length) { this._stream.Position = ebrOffset; this._stream.ReadExactly(sector); Array.Clear(sector, MbrTableOffset, 4 * MbrEntrySize); @@ -889,14 +1015,14 @@ private void RewriteEbrChain() { entry[4] = mbrByte; entry[5] = 0xFE; entry[6] = 0xFF; entry[7] = 0xFF; BinaryPrimitives.WriteUInt32LittleEndian(entry[8..], dataLba - ebrLba); - BinaryPrimitives.WriteUInt32LittleEndian(entry[12..], (uint)(logical.Size / SectorSize)); + BinaryPrimitives.WriteUInt32LittleEndian(entry[12..], (uint)(logical.Size / this._sectorSize)); } // Entry 1: pointer to next EBR (relative to extended container start), // or all-zero on the last EBR. if (i + 1 < logicals.Count) { var nextLogical = logicals[i + 1]; - var nextDataLba = (uint)(nextLogical.StartOffset / SectorSize); + var nextDataLba = (uint)(nextLogical.StartOffset / this._sectorSize); var nextEbrLba = nextDataLba - 1; var entry = sector.AsSpan(MbrTableOffset + MbrEntrySize, MbrEntrySize); @@ -905,7 +1031,7 @@ private void RewriteEbrChain() { entry[4] = 0x05; entry[5] = 0xFE; entry[6] = 0xFF; entry[7] = 0xFF; BinaryPrimitives.WriteUInt32LittleEndian(entry[8..], nextEbrLba - containerStartLba); - var nextLastLba = nextDataLba + (uint)(nextLogical.Size / SectorSize) - 1; + var nextLastLba = nextDataLba + (uint)(nextLogical.Size / this._sectorSize) - 1; BinaryPrimitives.WriteUInt32LittleEndian(entry[12..], nextLastLba - nextEbrLba + 1); } @@ -918,15 +1044,24 @@ private void RewriteEbrChain() { // ── GPT writers ──────────────────────────────────────────────────── + /// + /// Writes the protective MBR, the primary header and entry array at LBA 1/2, and the backup + /// entry array and header at the end of the disk (UEFI 2.10, §5.3). LastUsableLBA is the LBA + /// before the backup entry array, so the two never overlap. Entries that already existed keep + /// their unique GUID and attribute bits; the disk keeps its GUID. + /// private void RewriteGpt() { - var totalSectors = this._stream.Length / SectorSize; - if (totalSectors < 34 + GptReservedSectors) - throw new InvalidOperationException( - $"Stream too small for GPT layout ({this._stream.Length} bytes); need at least {(34 + GptReservedSectors) * SectorSize}."); - + var totalSectors = this._stream.Length / this._sectorSize; var entrySize = (int)this._gptEntrySize; var entryCount = (int)this._gptEntryCount; var entryArrayBytes = entrySize * entryCount; + var entrySectors = this.GptEntrySectors; + var reservedSectors = entrySectors + 1; + var firstUsable = 2 + entrySectors; + var lastUsable = totalSectors - reservedSectors - 1; + if (lastUsable < firstUsable) + throw new InvalidOperationException( + $"Stream too small for GPT layout ({this._stream.Length} bytes); need more than {(firstUsable + reservedSectors) * this._sectorSize}."); // Build entry array. var entriesBuf = new byte[entryArrayBytes]; @@ -934,35 +1069,34 @@ private void RewriteGpt() { var e = this._entries[i]; var slot = entriesBuf.AsSpan(i * entrySize, entrySize); var typeGuid = Guid.TryParse(e.TypeCode, out var g) ? g : Guid.Empty; + var known = this._gptRawSlots.TryGetValue(e.StartOffset, out var raw) && raw.Length == entrySize; + if (known) raw.CopyTo(slot); WriteMixedEndianGuid(slot, typeGuid); - WriteMixedEndianGuid(slot[16..], DeriveUniqueGuid(i, e.StartOffset)); - var firstLba = (ulong)(e.StartOffset / SectorSize); - var lastLba = (ulong)((e.StartOffset + e.Size) / SectorSize - 1); + if (!known) WriteMixedEndianGuid(slot[16..], DeriveUniqueGuid(i, e.StartOffset)); + var firstLba = (ulong)(e.StartOffset / this._sectorSize); + var lastLba = (ulong)((e.StartOffset + e.Size) / this._sectorSize - 1); BinaryPrimitives.WriteUInt64LittleEndian(slot[32..], firstLba); BinaryPrimitives.WriteUInt64LittleEndian(slot[40..], lastLba); - BinaryPrimitives.WriteUInt64LittleEndian(slot[48..], e.IsActive ? 0x04UL : 0UL); - var name = e.Name ?? string.Empty; - var nameBytes = Encoding.Unicode.GetBytes(name); + var attributes = known ? BinaryPrimitives.ReadUInt64LittleEndian(slot[48..]) & ~0x04UL : 0UL; + BinaryPrimitives.WriteUInt64LittleEndian(slot[48..], attributes | (e.IsActive ? 0x04UL : 0UL)); var nameMax = Math.Min(72, entrySize - 56); if (nameMax > 0) { - var copyLen = Math.Min(nameMax, nameBytes.Length); - nameBytes.AsSpan(0, copyLen).CopyTo(slot[56..]); + slot.Slice(56, nameMax).Clear(); + var nameBytes = Encoding.Unicode.GetBytes(e.Name ?? string.Empty); + nameBytes.AsSpan(0, Math.Min(nameMax, nameBytes.Length) & ~1).CopyTo(slot[56..]); } } var entriesCrc = Crc32.Compute(entriesBuf); - - // Number of sectors needed for the entry array. - var entrySectors = (entryArrayBytes + SectorSize - 1) / SectorSize; - var reservedSectors = Math.Max(GptReservedSectors, entrySectors + 1); + var diskGuid = this._gptDiskGuid ??= this.DeriveDiskGuid(); // Primary header at LBA 1. var primaryHeader = BuildGptHeader( myLba: 1, altLba: (ulong)(totalSectors - 1), - firstUsableLba: (ulong)(2 + entrySectors), - lastUsableLba: (ulong)(totalSectors - reservedSectors), - diskGuid: this.DeriveDiskGuid(), + firstUsableLba: (ulong)firstUsable, + lastUsableLba: (ulong)lastUsable, + diskGuid: diskGuid, entriesLba: 2, entriesCrc: entriesCrc, entryCount: entryCount, @@ -972,9 +1106,9 @@ private void RewriteGpt() { var backupHeader = BuildGptHeader( myLba: (ulong)(totalSectors - 1), altLba: 1, - firstUsableLba: (ulong)(2 + entrySectors), - lastUsableLba: (ulong)(totalSectors - reservedSectors), - diskGuid: this.DeriveDiskGuid(), + firstUsableLba: (ulong)firstUsable, + lastUsableLba: (ulong)lastUsable, + diskGuid: diskGuid, entriesLba: backupEntriesLba, entriesCrc: entriesCrc, entryCount: entryCount, @@ -983,23 +1117,39 @@ private void RewriteGpt() { // Protective MBR at LBA 0. this.WriteProtectiveMbr(totalSectors); - var headerSector = new byte[SectorSize]; + var headerSector = new byte[this._sectorSize]; primaryHeader.CopyTo(headerSector, 0); - this._stream.Position = SectorSize; + this._stream.Position = this._sectorSize; this._stream.Write(headerSector); - this._stream.Position = 2L * SectorSize; + this._stream.Position = 2L * this._sectorSize; this._stream.Write(entriesBuf); - this._stream.Position = (long)backupEntriesLba * SectorSize; + this._stream.Position = (long)backupEntriesLba * this._sectorSize; this._stream.Write(entriesBuf); Array.Clear(headerSector); backupHeader.CopyTo(headerSector, 0); - this._stream.Position = (totalSectors - 1) * SectorSize; + this._stream.Position = (totalSectors - 1) * this._sectorSize; this._stream.Write(headerSector); this._stream.Flush(); + this.CaptureGptSlots(entriesBuf); + } + + /// Raw entry-array slots by partition start, so a rewrite keeps unique GUIDs and attributes. + private readonly Dictionary _gptRawSlots = []; + + private void CaptureGptSlots(ReadOnlySpan entryArray) { + this._gptRawSlots.Clear(); + var size = (int)this._gptEntrySize; + for (var i = 0; (i + 1) * size <= entryArray.Length; ++i) { + var slot = entryArray.Slice(i * size, size); + if (slot[..16].IndexOfAnyExcept((byte)0) < 0) continue; + var firstLba = BinaryPrimitives.ReadUInt64LittleEndian(slot[32..]); + if (firstLba > (ulong)(long.MaxValue / this._sectorSize)) continue; + this._gptRawSlots[(long)firstLba * this._sectorSize] = slot.ToArray(); + } } private void WriteProtectiveMbr(long totalSectors) { @@ -1092,7 +1242,7 @@ private void VerifyMbr(List issues) { var container = this.FindExtendedContainer(); if (container is not null) { foreach (var logical in this._entries.Where(p => p.Source == "EBR")) { - if (logical.StartOffset < container.StartOffset + SectorSize) + if (logical.StartOffset < container.StartOffset + this._sectorSize) issues.Add($"Logical #{logical.Index} starts before container_start + 1 sector."); if (logical.StartOffset + logical.Size > container.StartOffset + container.Size) issues.Add($"Logical #{logical.Index} data extends past extended container end."); @@ -1101,14 +1251,24 @@ private void VerifyMbr(List issues) { } private void VerifyGpt(List issues) { - if (this._stream.Length < (34 + GptReservedSectors) * SectorSize) { + if (this.GptLastUsableLba < this.GptFirstUsableLba) { issues.Add("Stream too small to hold primary + backup GPT."); return; } + var (usableStart, usableEnd) = this.UsableRange; + var ordered = this._entries.OrderBy(e => e.StartOffset).ToList(); + for (var i = 0; i < ordered.Count; ++i) { + var e = ordered[i]; + if (e.StartOffset < usableStart || e.StartOffset + e.Size > usableEnd) + issues.Add($"Partition #{e.Index} [{e.StartOffset}..{e.StartOffset + e.Size}) lies outside the usable range [{usableStart}..{usableEnd})."); + if (i > 0 && ordered[i - 1].StartOffset + ordered[i - 1].Size > e.StartOffset) + issues.Add($"Partition #{ordered[i - 1].Index} overlaps partition #{e.Index}."); + } + // Primary header. - this._stream.Position = (long)GptHeaderLba * SectorSize; - var headerSector = new byte[SectorSize]; + this._stream.Position = (long)GptHeaderLba * this._sectorSize; + var headerSector = new byte[this._sectorSize]; this._stream.ReadExactly(headerSector); var primaryOk = VerifyGptHeader(headerSector, isPrimary: true, expectedMyLba: 1, issues, out var entriesLba, out var entryCount, out var entrySize, out var entriesCrc); @@ -1118,7 +1278,7 @@ private void VerifyGpt(List issues) { issues.Add($"GPT entry array size {entryBytes} out of range."); } else { var buf = new byte[entryBytes]; - this._stream.Position = (long)entriesLba * SectorSize; + this._stream.Position = (long)entriesLba * this._sectorSize; this._stream.ReadExactly(buf); var crc = Crc32.Compute(buf); if (crc != entriesCrc) @@ -1127,9 +1287,9 @@ private void VerifyGpt(List issues) { } // Backup header. - var totalSectors = this._stream.Length / SectorSize; - this._stream.Position = (totalSectors - 1) * SectorSize; - var backupSector = new byte[SectorSize]; + var totalSectors = this._stream.Length / this._sectorSize; + this._stream.Position = (totalSectors - 1) * this._sectorSize; + var backupSector = new byte[this._sectorSize]; this._stream.ReadExactly(backupSector); var backupOk = VerifyGptHeader(backupSector, isPrimary: false, expectedMyLba: (ulong)(totalSectors - 1), issues, out var backupEntriesLba, out var backupEntryCount, out var backupEntrySize, out var backupEntriesCrc); @@ -1138,7 +1298,7 @@ private void VerifyGpt(List issues) { var entryBytes = (long)backupEntryCount * backupEntrySize; if (entryBytes > 0 && entryBytes <= int.MaxValue) { var buf = new byte[entryBytes]; - this._stream.Position = (long)backupEntriesLba * SectorSize; + this._stream.Position = (long)backupEntriesLba * this._sectorSize; this._stream.ReadExactly(buf); var crc = Crc32.Compute(buf); if (crc != backupEntriesCrc) @@ -1166,7 +1326,7 @@ private static bool VerifyGptHeader(byte[] sector, bool isPrimary, ulong expecte return false; } var headerSize = BinaryPrimitives.ReadUInt32LittleEndian(sector.AsSpan(12)); - if (headerSize < GptHeaderSize || headerSize > SectorSize) { + if (headerSize < GptHeaderSize || headerSize > sector.Length) { issues.Add($"{label} GPT header size {headerSize} out of range."); return false; } diff --git a/Compression.Core/DiskImage/PartitionTableDetector.cs b/Compression.Core/DiskImage/PartitionTableDetector.cs index c87e1b495..93d234c13 100644 --- a/Compression.Core/DiskImage/PartitionTableDetector.cs +++ b/Compression.Core/DiskImage/PartitionTableDetector.cs @@ -4,89 +4,158 @@ namespace Compression.Core.DiskImage; /// /// Detects and parses partition tables from raw disk data. -/// Tries GPT first (since GPT disks also have a protective MBR), then falls back to MBR. /// +/// +/// Probe order, first hit wins: +/// +/// GPT, at 512- or 4096-byte logical blocks — ahead of the MBR, because a GPT +/// disk carries a protective (or hybrid) MBR too; +/// SGI volume header and Sun label — sector-0 labels with a magic and a +/// checksum, so they are certain when they match, and their last bytes could otherwise pass +/// for an MBR boot signature; +/// MBR, with its extended/logical (EBR) chain; +/// Apple Partition Map; +/// a stand-alone BSD disklabel (no MBR around it), both magics and its geometry verified; +/// an Amiga Rigid Disk Block in the first 16 blocks, checksum verified. +/// +/// public static class PartitionTableDetector { /// /// Result of partition table detection, including the scheme used and the discovered partitions. /// public sealed class DetectionResult { - /// The partition scheme detected: "GPT", "MBR", "APM", or "None". + /// The partition scheme detected: "GPT", "MBR", "APM", "BSD", "RDB", "Sun", "SGI", or "None". public required string Scheme { get; init; } /// Discovered partitions. Empty if no partition table was found. public required List Partitions { get; init; } + + /// Logical block size the table counts in (512 unless a 4Kn GPT was found). + public int SectorSize { get; init; } = 512; } /// Minimum data size needed to attempt detection (GPT header is at offset 512). private const int MinimumSize = 1024; + private static DetectionResult None => new() { Scheme = "None", Partitions = [] }; + /// /// Attempts to detect a partition table in the given stream. - /// Tries GPT first (higher precedence), then MBR. /// Returns an empty partition list if no valid partition table is found. /// /// A seekable stream containing raw disk image data. /// Detection result with scheme name and partitions. public static DetectionResult Detect(Stream diskData) { if (!diskData.CanSeek || diskData.Length < MinimumSize) - return new DetectionResult { Scheme = "None", Partitions = [] }; + return None; - // Read the first 1024 bytes for quick signature checks. - // Clamp against Length as long first to avoid overflow on multi-GiB disks. - var header = new byte[(int)Math.Min(4096L, diskData.Length)]; + // Enough to see LBA 1 at every supported block size, and an APM map at 2048-byte blocks. + var header = new byte[(int)Math.Min(GptParser.ProbeBytes, diskData.Length)]; diskData.Position = 0; - var bytesRead = diskData.Read(header, 0, header.Length); + var bytesRead = diskData.ReadAtLeast(header, header.Length, throwOnEndOfStream: false); diskData.Position = 0; if (bytesRead < MinimumSize) - return new DetectionResult { Scheme = "None", Partitions = [] }; + return None; + var head = header.AsSpan(0, bytesRead); - // Try GPT first (GPT disks have a protective MBR, so GPT takes priority). - if (GptParser.IsGpt(header.AsSpan(0, bytesRead))) { + // GPT first (GPT disks have a protective MBR, so GPT takes priority). + if (GptParser.IsGpt(head) || HasProtectiveEntry(head)) { try { - var gptPartitions = GptParser.Parse(diskData); + var gptPartitions = GptParser.Parse(diskData, out var info); if (gptPartitions.Count > 0) - return new DetectionResult { Scheme = "GPT", Partitions = gptPartitions }; - } catch { + return new DetectionResult { Scheme = "GPT", Partitions = gptPartitions, SectorSize = info.SectorSize }; + } catch (Exception ex) when (ex is IOException or InvalidDataException or ArgumentException or OverflowException) { // GPT parsing failed — fall through to MBR. } } + if (SgiDvhParser.IsDvh(head)) + return Bounded("SGI", SgiDvhParser.Parse(head), diskData.Length); + if (SunVtocParser.IsSunLabel(head)) + return Bounded("Sun", SunVtocParser.Parse(head), diskData.Length); + // Try MBR. - if (MbrParser.IsMbr(header.AsSpan(0, bytesRead))) { + if (MbrParser.IsMbr(head)) { try { - var mbrPartitions = MbrParser.Parse(diskData); - // Filter out partitions that reference data beyond the stream. - var validPartitions = mbrPartitions - .Where(p => p.StartOffset >= 0 && p.StartOffset < diskData.Length && p.Size > 0) - .ToList(); - if (validPartitions.Count > 0) - return new DetectionResult { Scheme = "MBR", Partitions = validPartitions }; - } catch { + // A protective entry describes the GPT, not data; with no readable GPT it holds nothing. + var mbrPartitions = MbrParser.Parse(diskData).Where(p => p.TypeCode != "0xEE").ToList(); + var result = Bounded("MBR", mbrPartitions, diskData.Length); + if (result.Partitions.Count > 0) + return result; + } catch (Exception ex) when (ex is IOException or InvalidDataException or ArgumentException) { // MBR parsing failed — fall through to APM. } } // Try APM (Apple Partition Map). APM disks carry neither an MBR boot // signature nor a GPT header, so this is reached only when both above fail. - if (ApmParser.IsApm(header.AsSpan(0, bytesRead))) { + if (ApmParser.IsApm(head)) { try { - var apmPartitions = ApmParser.Parse(diskData); - var validApm = apmPartitions - .Where(p => p.StartOffset >= 0 && p.StartOffset < diskData.Length && p.Size > 0) - .ToList(); - if (validApm.Count > 0) - return new DetectionResult { Scheme = "APM", Partitions = validApm }; - } catch { - // APM parsing failed — no partition table. + var apm = Bounded("APM", ApmParser.Parse(diskData), diskData.Length); + if (apm.Partitions.Count > 0) + return apm; + } catch (Exception ex) when (ex is IOException or InvalidDataException or ArgumentException) { + // APM parsing failed — try the remaining schemes. + } + } + + try { + if (BsdDisklabelParser.IsStandaloneDisklabel(diskData)) { + var bsd = Bounded("BSD", BsdDisklabelParser.Parse(diskData, 0), diskData.Length); + if (bsd.Partitions.Count > 0) + return bsd; } + if (AmigaRdbParser.IsRdb(diskData)) { + var rdb = Bounded("RDB", AmigaRdbParser.Parse(diskData), diskData.Length); + if (rdb.Partitions.Count > 0) + return rdb; + } + } catch (Exception ex) when (ex is IOException or InvalidDataException or ArgumentException) { + // An unreadable label holds nothing. + } finally { + diskData.Position = 0; } - return new DetectionResult { Scheme = "None", Partitions = [] }; + return None; } + /// + /// Whether the first sector holds a partition table of a scheme that has no MBR-style + /// boot signature to go on — SGI, Sun, a stand-alone BSD disklabel or an Amiga RDB — each + /// verified by its own checksum (BSD: by its two magics and geometry). Used by content detection, which has already ruled out + /// GPT, MBR and APM by then. + /// + public static bool HasSelfValidatingLabel(Stream disk) { + if (!disk.CanSeek || disk.Length < MinimumSize) return false; + var saved = disk.Position; + try { + var sector0 = new byte[512]; + disk.Position = 0; + if (disk.ReadAtLeast(sector0, 512, throwOnEndOfStream: false) < 512) return false; + return SgiDvhParser.IsDvh(sector0) || SunVtocParser.IsSunLabel(sector0) + || BsdDisklabelParser.IsStandaloneDisklabel(disk) || AmigaRdbParser.IsRdb(disk); + } catch (Exception ex) when (ex is IOException or InvalidDataException or ArgumentException) { + return false; + } finally { + disk.Position = saved; + } + } + + private static bool HasProtectiveEntry(ReadOnlySpan head) { + if (!MbrParser.IsMbr(head)) return false; + for (var i = 0; i < 4; ++i) + if (head[0x1BE + i * 16 + 4] == 0xEE) return true; + return false; + } + + /// Keeps only partitions that start inside the image and have a size. + private static DetectionResult Bounded(string scheme, List partitions, long length) => new() { + Scheme = scheme, + Partitions = partitions.Where(p => p.StartOffset >= 0 && p.StartOffset < length && p.Size > 0).ToList(), + }; + /// /// Attempts to detect a partition table in the given byte array. /// Convenience overload that wraps the data in a . diff --git a/Compression.Core/DiskImage/PartitionedDiskLister.cs b/Compression.Core/DiskImage/PartitionedDiskLister.cs index dfe67ccad..54316a58c 100644 --- a/Compression.Core/DiskImage/PartitionedDiskLister.cs +++ b/Compression.Core/DiskImage/PartitionedDiskLister.cs @@ -87,7 +87,8 @@ private static bool TryListBsdSlices(Stream disk, PartitionEntry part, string pr /// private static bool TryReadBsdSlices(Stream disk, PartitionEntry part, out List slices) { slices = []; - if (part.Size <= 0 || part.StartOffset < 0 || part.StartOffset >= disk.Length) + // A slice of a disklabel can start at the label itself; it does not nest a second time. + if (part.Source == "BSD" || part.Size <= 0 || part.StartOffset < 0 || part.StartOffset >= disk.Length) return false; try { using var window = new PartitionWindowStream(disk, part.StartOffset, part.Size); @@ -178,160 +179,309 @@ public static string MakePartitionPrefix(PartitionEntry part) { return $"Partition{part.Index + 1}_{safeName}"; } + // ── Partition addressing ────────────────────────────────────────────── + + /// + /// One listed partition: a top-level partition, or a slice of a BSD disklabel nested inside + /// one, with the prefix its entries are listed under. + /// + /// The table entry (for a BSD slice, the slice's own entry). + /// The listing prefix, e.g. Partition1_FAT32 or Partition1_FreeBSD/Partition1_4_2BSD. + /// Byte offset of the partition on the disk. + /// Bytes of the partition that lie inside the image. + /// Whether this is a slice inside a BSD disklabel rather than a table entry. + public sealed record PartitionTarget(PartitionEntry Partition, string Prefix, long Offset, long Size, bool IsBsdSlice); + + /// + /// Every partition the listing shows a prefix for, in listing order: top-level partitions, + /// with a BSD-disklabel container replaced by its slices. + /// + public static List EnumerateTargets(Stream disk, PartitionTableDetector.DetectionResult detection) { + var result = new List(); + foreach (var part in detection.Partitions) { + var prefix = MakePartitionPrefix(part); + if (TryReadBsdSlices(disk, part, out var slices)) { + foreach (var slice in slices) + result.Add(new PartitionTarget(slice, $"{prefix}/{MakePartitionPrefix(slice)}", slice.StartOffset, + Clamp(disk, slice), IsBsdSlice: true)); + continue; + } + result.Add(new PartitionTarget(part, prefix, part.StartOffset, Clamp(disk, part), IsBsdSlice: false)); + } + return result; + } + + private static long Clamp(Stream disk, PartitionEntry part) + => Math.Max(0, Math.Min(part.Size, disk.Length - part.StartOffset)); + + /// + /// Finds the partition an entry name addresses. is the rest of the + /// name below the partition's prefix — empty when the name is the partition itself + /// (PartitionN_Type or PartitionN_Type.raw). + /// + public static PartitionTarget? Resolve(IEnumerable targets, string entryName, out string innerPath) { + var name = entryName.Replace('\\', '/').Trim('/'); + innerPath = string.Empty; + PartitionTarget? best = null; + foreach (var t in targets) { + if (name.Equals(t.Prefix, StringComparison.Ordinal) || name.Equals(t.Prefix + ".raw", StringComparison.Ordinal)) { + innerPath = string.Empty; + return t; + } + if (name.StartsWith(t.Prefix + "/", StringComparison.Ordinal) && (best is null || t.Prefix.Length > best.Prefix.Length)) + best = t; + } + if (best is not null) innerPath = name[(best.Prefix.Length + 1)..]; + return best; + } + + /// + /// Whether edits through its own code for + /// (Add or Remove) rather than the interface's generic extract → re-create + /// default, which re-creates the volume and loses what its writer does not reproduce. + /// + public static bool HasNativeEditor(IArchiveModifiable modifier, string method) { + var map = modifier.GetType().GetInterfaceMap(typeof(IArchiveModifiable)); + for (var i = 0; i < map.InterfaceMethods.Length; ++i) { + var m = map.InterfaceMethods[i]; + if (m.Name != method || m.GetParameters().Length != 2) continue; + return map.TargetMethods[i].DeclaringType != typeof(IArchiveModifiable); + } + return false; + } + // ── Add / Remove (partition-aware) ──────────────────────────────────── /// - /// Partition-aware . Returns true - /// when a partition table was present and at least one input was dispatched - /// through the partition-aware path; false when no partition table - /// exists so the caller can fall through to the existing single-FS path. + /// Partition-aware . + /// Returns false when no partition table exists (or the stream is read-only) so the + /// caller can fall through to its single-filesystem path. /// /// /// Two input shapes are recognised: /// - /// Partition<N>_<Type>/<inner> — - /// add the file to partition N's inner filesystem (delegated to its - /// ). - /// No prefix — treat the input bytes as a raw filesystem - /// image, find free space past the last existing partition, write a new - /// partition entry (FS type detected via ), - /// and copy the bytes into the new partition window. + /// PartitionN_Type/inner/path — adds the file to that partition's + /// filesystem through the filesystem's own in-place editor, working on a staged window over + /// the partition: it can neither write outside the partition nor grow past it. + /// A root-level filesystem image (recognised by content, or named + /// *.raw/*.img/PartitionN_…) — becomes a new partition in the free + /// space after the last one, 1 MiB-aligned where room allows, inside the table's usable + /// range. GPT and MBR tables only. /// + /// + /// Every edit is planned and staged before anything is written; a refusal — a filesystem with + /// no lossless in-place editor, a full volume, no free space, a full table — throws + /// and leaves the disk byte-identical. + /// /// public static bool TryAdd(Stream disk, IReadOnlyList inputs) { if (!disk.CanWrite) return false; var detection = PartitionTableDetector.Detect(disk); if (detection.Partitions.Count == 0) return false; + var targets = EnumerateTargets(disk, detection); - PartitionEditor? editor = null; + var innerAdds = new Dictionary>(); + var newPartitions = new List(); foreach (var input in inputs) { - if (input.IsDirectory) continue; - var data = input.InMemoryContent ?? File.ReadAllBytes(input.FullPath); - var name = input.ArchiveName.Replace('\\', '/'); - - if (TryParsePartitionPrefix(name, out var partIdx, out var innerPath) - && partIdx < detection.Partitions.Count) { - var part = detection.Partitions[partIdx]; - using var window = new PartitionWindowStream(disk, part.StartOffset, part.Size); - var innerDesc = InnerFsDetector.Detect(window); - if (innerDesc is not IArchiveModifiable mod) - throw new InvalidOperationException( - $"Partition {partIdx + 1} ({part.TypeName}) has no writable filesystem reader; cannot Add '{innerPath}'."); - window.Position = 0; - mod.Add(window, new[] { ArchiveInputInfo.InMemory(innerPath, data) }); + if (input.IsDirectory || string.IsNullOrEmpty(input.ArchiveName)) continue; + var target = Resolve(targets, input.ArchiveName, out var innerPath); + if (target is not null && innerPath.Length > 0) { + if (!innerAdds.TryGetValue(target, out var list)) innerAdds[target] = list = []; + list.Add(input with { ArchiveName = innerPath }); continue; } - - // Root-level FS image drop → new partition past the last existing one. - editor ??= new PartitionEditor(disk); - var ptype = DetectInputPartitionType(data); - var lengthAligned = AlignUp(data.LongLength, SectorSize); - var startOffset = FindAppendOffset(editor.ListPartitions(), disk.Length, lengthAligned); - if (startOffset < 0) + if (target is not null) + throw new InvalidOperationException( + $"'{input.ArchiveName}' names an existing partition; remove it first to replace it."); + if (input.ArchiveName.Replace('\\', '/').Trim('/').Contains('/')) throw new InvalidOperationException( - $"Not enough free space at the end of the disk to land a {data.LongLength}-byte filesystem image."); - editor.AddPartition(startOffset, lengthAligned, ptype, label: null); - disk.Position = startOffset; - disk.Write(data, 0, data.Length); - // Pad the partition window with zeros to its sector-aligned length. - var pad = lengthAligned - data.Length; - if (pad > 0) { - var zeros = new byte[512]; - var remaining = pad; - while (remaining > 0) { - var chunk = (int)Math.Min(remaining, zeros.Length); - disk.Write(zeros, 0, chunk); - remaining -= chunk; - } + $"'{input.ArchiveName}' is not inside any partition of this disk."); + newPartitions.Add(input); + } + + // Stage every filesystem edit first: nothing reaches the disk until all of them succeed. + var staged = innerAdds.Select(kv => StageInnerEdit(disk, kv.Key, "Add", + (mod, window) => mod.Add(window, kv.Value))).ToList(); + var plan = newPartitions.Count > 0 ? PlanNewPartitions(disk, detection, newPartitions) : []; + + foreach (var window in staged) window.Commit(); + if (plan.Count > 0) { + var editor = new PartitionEditor(disk, detection.SectorSize); + foreach (var (input, start, length, type, label) in plan) { + WritePartitionData(disk, input, start, length); + editor.AddPartition(start, length, type, label); } } + disk.Flush(); return true; } /// - /// Partition-aware . Returns true - /// when a partition table was present and at least one entry was removed; - /// false if no partition table was detected. Entry name shapes: - /// Partition<N>_<Type>/<inner> deletes an inner-FS - /// file; Partition<N>_<Type> or - /// Partition<N>_<Type>.raw deletes the whole partition. + /// Partition-aware . Returns + /// false when no partition table exists (or the stream is read-only). Entry name shapes: + /// PartitionN_Type/inner removes a file — or a folder with everything beneath it — + /// through the partition filesystem's own in-place editor; PartitionN_Type or + /// PartitionN_Type.raw deletes the partition's table entry (GPT and MBR, including + /// logical partitions), leaving its sectors as they were. /// + /// + /// A name that addresses nothing, a filesystem without a lossless in-place editor, or a + /// whole-partition delete on a scheme the editor cannot rewrite is refused with the disk left + /// byte-identical. + /// public static bool TryRemove(Stream disk, string[] entryNames) { if (!disk.CanWrite) return false; var detection = PartitionTableDetector.Detect(disk); if (detection.Partitions.Count == 0) return false; - - PartitionEditor? editor = null; - foreach (var rawName in entryNames) { - var name = rawName.Replace('\\', '/'); - - if (TryParsePartitionPrefix(name, out var partIdx, out var innerPath) - && partIdx < detection.Partitions.Count) { - var part = detection.Partitions[partIdx]; - using var window = new PartitionWindowStream(disk, part.StartOffset, part.Size); - var innerDesc = InnerFsDetector.Detect(window); - if (innerDesc is IArchiveModifiable mod) { - window.Position = 0; - mod.Remove(window, new[] { innerPath }); - } + var targets = EnumerateTargets(disk, detection); + + var innerRemoves = new Dictionary>(); + var deletions = new List(); + foreach (var rawName in entryNames ?? []) { + var target = Resolve(targets, rawName, out var innerPath) + ?? throw new FileNotFoundException($"'{rawName}' is not on this disk.", rawName); + if (innerPath.Length > 0) { + if (!innerRemoves.TryGetValue(target, out var list)) innerRemoves[target] = list = []; + list.Add(innerPath); continue; } + if (detection.Scheme is not ("GPT" or "MBR") || target.IsBsdSlice) + throw new InvalidOperationException( + $"Deleting '{target.Prefix}' would need a {(target.IsBsdSlice ? "BSD disklabel" : detection.Scheme)} table writer; only GPT and MBR entries can be deleted."); + if (!deletions.Contains(target)) deletions.Add(target); + } - // Whole-partition delete: "Partition3_FAT12" or "Partition3_FAT12.raw" - var stripped = name.EndsWith(".raw", StringComparison.OrdinalIgnoreCase) ? name[..^4] : name; - if (TryParsePartitionIndex(stripped, out var idx) && idx < detection.Partitions.Count) { - editor ??= new PartitionEditor(disk); - editor.DeletePartition(idx); + var staged = innerRemoves.Select(kv => StageInnerEdit(disk, kv.Key, "Remove", + (mod, window) => mod.Remove(window, [.. kv.Value]))).ToList(); + + PartitionEditor? editor = null; + var editorIndices = new List(); + if (deletions.Count > 0) { + editor = new PartitionEditor(disk, detection.SectorSize); + foreach (var target in deletions) { + var entry = editor.ListPartitions().FirstOrDefault(e => + e.StartOffset == target.Partition.StartOffset && e.Size == target.Partition.Size + && e.Source != "MBR (Extended Container)") + ?? throw new InvalidOperationException($"'{target.Prefix}' has no entry the partition editor can delete."); + editorIndices.Add(entry.Index); } } + + foreach (var window in staged) window.Commit(); + if (editor is not null) + foreach (var index in editorIndices.OrderDescending()) + editor.DeletePartition(index); + disk.Flush(); return true; } - private static bool TryParsePartitionPrefix(string entryName, out int partitionIndex, out string innerPath) { - partitionIndex = -1; - innerPath = string.Empty; - var slash = entryName.IndexOf('/'); - if (slash <= 0) return false; - var head = entryName[..slash]; - if (!TryParsePartitionIndex(head, out partitionIndex)) return false; - innerPath = entryName[(slash + 1)..]; - return innerPath.Length > 0; + /// + /// Runs a filesystem edit against a staged window over . The edit + /// must go through the filesystem's own in-place editor; one that would only re-create the + /// volume is refused, as is one that throws. Nothing is written until the caller commits. + /// + private static StagedWindowStream StageInnerEdit(Stream disk, PartitionTarget target, string method, + Action edit) { + var window = new StagedWindowStream(disk, target.Offset, target.Size); + var descriptor = InnerFsDetector.Detect(window); + if (descriptor is not IArchiveModifiable modifier) + throw new InvalidOperationException( + $"{target.Prefix} holds {(descriptor is null ? "no filesystem CompressionWorkbench recognises" : $"a {descriptor.DisplayName} volume CompressionWorkbench cannot edit")}; nothing was changed."); + if (!HasNativeEditor(modifier, method)) + throw new InvalidOperationException( + $"{target.Prefix}: {descriptor.DisplayName} can only be edited by re-creating the volume, which would not preserve it; nothing was changed."); + try { + window.Position = 0; + edit(modifier, window); + } catch (Exception ex) when (ex is IOException or NotSupportedException or InvalidDataException + or ArgumentException or InvalidOperationException) { + throw new InvalidOperationException($"{target.Prefix} ({descriptor.DisplayName}) refused the edit: {ex.Message}", ex); + } + return window; } - private static bool TryParsePartitionIndex(string head, out int zeroBasedIndex) { - zeroBasedIndex = -1; - if (!head.StartsWith("Partition", StringComparison.Ordinal)) return false; - var us = head.IndexOf('_'); - if (us <= 9) return false; - if (!int.TryParse(head[9..us], out var oneBased) || oneBased <= 0) return false; - zeroBasedIndex = oneBased - 1; - return true; + private static List<(ArchiveInputInfo Input, long Start, long Length, PartitionType Type, string? Label)> PlanNewPartitions( + Stream disk, PartitionTableDetector.DetectionResult detection, List inputs) { + if (detection.Scheme is not ("GPT" or "MBR")) + throw new InvalidOperationException($"New partitions can only be added to a GPT or MBR table, not {detection.Scheme}."); + + var probe = new PartitionEditor(new ReadOnlyProbe(disk), detection.SectorSize); + var sectorSize = probe.LogicalSectorSize; + var (usableStart, usableEnd) = probe.UsableRange; + var existing = probe.ListPartitions(); + var primaries = existing.Count(e => e.Source != "EBR"); + var capacity = probe.Scheme == PartitionScheme.Gpt ? 128 : 4; + if ((probe.Scheme == PartitionScheme.Gpt ? existing.Count : primaries) + inputs.Count > capacity) + throw new InvalidOperationException( + $"The {detection.Scheme} table has room for {capacity} partitions; adding {inputs.Count} would exceed it."); + + var cursor = Math.Max(usableStart, existing.Count == 0 ? 0 : existing.Max(e => e.StartOffset + e.Size)); + var plan = new List<(ArchiveInputInfo, long, long, PartitionType, string?)>(); + foreach (var input in inputs) { + using var content = OpenInput(input); + var type = DetectInputPartitionType(content); + var nameLooksLikeImage = Path.GetFileName(input.ArchiveName) is var leaf + && (leaf.EndsWith(".raw", StringComparison.OrdinalIgnoreCase) || leaf.EndsWith(".img", StringComparison.OrdinalIgnoreCase) + || leaf.StartsWith("Partition", StringComparison.Ordinal)); + if (type == PartitionType.Unknown && !nameLooksLikeImage) + throw new InvalidOperationException( + $"'{input.ArchiveName}' is neither a filesystem image nor a raw partition image (*.raw/*.img); a disk holds partitions, not loose files."); + if (content.Length <= 0) + throw new InvalidOperationException($"'{input.ArchiveName}' is empty; a partition needs at least one sector."); + + var length = AlignUp(content.Length, sectorSize); + var aligned = AlignUp(cursor, MiB); + var start = aligned + length <= usableEnd ? aligned : AlignUp(cursor, sectorSize); + if (start + length > usableEnd) + throw new InvalidOperationException( + $"Not enough free space after the last partition for '{input.ArchiveName}' ({content.Length:N0} bytes); nothing was changed."); + plan.Add((input, start, length, type == PartitionType.Unknown ? PartitionType.Linux : type, + Path.GetFileNameWithoutExtension(input.ArchiveName))); + cursor = start + length; + } + return plan; } - private const int SectorSize = 512; + private const long MiB = 1024 * 1024; - private static long AlignUp(long value, int alignment) - => (value + alignment - 1) / alignment * alignment; + private static Stream OpenInput(ArchiveInputInfo input) + => input.InMemoryContent is { } bytes + ? new MemoryStream(bytes, writable: false) + : new FileStream(input.FullPath, FileMode.Open, FileAccess.Read, FileShare.Read); - private static long FindAppendOffset(IReadOnlyList existing, long diskLength, long lengthBytes) { - long highest = SectorSize; // skip MBR - foreach (var e in existing) { - var end = e.StartOffset + e.Size; - if (end > highest) highest = end; - } - highest = AlignUp(highest, SectorSize); - return highest + lengthBytes <= diskLength ? highest : -1; + private static void WritePartitionData(Stream disk, ArchiveInputInfo input, long start, long length) { + using var content = OpenInput(input); + disk.Position = start; + content.CopyTo(disk); + var pad = length - content.Length; + if (pad > 0) disk.Write(new byte[pad]); } + /// A view that satisfies 's writable-stream check but refuses writes — for planning only. + private sealed class ReadOnlyProbe(Stream inner) : Stream { + public override bool CanRead => true; + public override bool CanSeek => true; + public override bool CanWrite => true; + public override long Length => inner.Length; + public override long Position { get => inner.Position; set => inner.Position = value; } + public override void Flush() { } + public override int Read(byte[] buffer, int offset, int count) => inner.Read(buffer, offset, count); + public override long Seek(long offset, SeekOrigin origin) => inner.Seek(offset, origin); + public override void SetLength(long value) => throw new NotSupportedException(); + public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException("Planning view."); + } + + private static long AlignUp(long value, long alignment) + => (value + alignment - 1) / alignment * alignment; + /// /// Maps the input bytes to a logical by running - /// over a wrapping memory stream and translating - /// the resulting descriptor ID. + /// over the input and translating the resulting descriptor ID; + /// when no filesystem is recognised. /// - private static PartitionType DetectInputPartitionType(byte[] data) { + private static PartitionType DetectInputPartitionType(Stream data) { if (data.Length < 512) return PartitionType.Unknown; - using var ms = new MemoryStream(data, writable: false); - var desc = InnerFsDetector.Detect(ms); + var desc = InnerFsDetector.Detect(data); if (desc is null) return PartitionType.Unknown; return desc.Id switch { "Fat" => PartitionType.Fat32Lba, diff --git a/Compression.Core/DiskImage/SgiDvhParser.cs b/Compression.Core/DiskImage/SgiDvhParser.cs new file mode 100644 index 000000000..581104172 --- /dev/null +++ b/Compression.Core/DiskImage/SgiDvhParser.cs @@ -0,0 +1,86 @@ +using System.Buffers.Binary; + +namespace Compression.Core.DiskImage; + +/// +/// Parses the SGI disk volume header (DVH) in sector 0 — the IRIX partition scheme. Big-endian. +/// +/// +/// +/// The header opens with the magic 0x0BE5A941 and carries a checksum chosen so that the +/// 32-bit sum of all 128 big-endian longwords of the 512-byte sector is zero. Sixteen partition +/// records at byte 312 each hold a length and a first block (512-byte blocks) and a type. The +/// volume partition (type 6, conventionally slot 10) covers the whole disk and is not listed; +/// the volume-header partition (type 0) is, since it holds the boot files. +/// +/// References: +/// +/// Silicon Graphics, IRIX sys/dvh.h — struct volume_header, VHMAGIC, partition types, checksum rule. +/// +/// +public static class SgiDvhParser { + + private const int LabelSize = 512; + private const uint Magic = 0x0BE5A941; + private const int PartitionsOffset = 312; + private const int PartitionCount = 16; + private const int PartitionRecordSize = 12; + private const uint VolumeType = 6; + + /// Reports whether is an SGI volume header with a valid checksum. + public static bool IsDvh(ReadOnlySpan sector0) { + if (sector0.Length < LabelSize || BinaryPrimitives.ReadUInt32BigEndian(sector0) != Magic) return false; + uint sum = 0; + for (var i = 0; i < LabelSize; i += 4) + sum += BinaryPrimitives.ReadUInt32BigEndian(sector0[i..]); + return sum == 0; + } + + /// Parses the header's partitions, excluding the whole-volume partition and empty slots. + /// Sector 0 is not an SGI volume header. + public static List Parse(ReadOnlySpan sector0) { + if (!IsDvh(sector0)) throw new InvalidDataException("Sector 0 is not a checksummed SGI volume header."); + + var result = new List(); + for (var i = 0; i < PartitionCount; ++i) { + var record = sector0[(PartitionsOffset + i * PartitionRecordSize)..]; + var blocks = BinaryPrimitives.ReadUInt32BigEndian(record); + var first = BinaryPrimitives.ReadUInt32BigEndian(record[4..]); + var type = BinaryPrimitives.ReadUInt32BigEndian(record[8..]); + if (blocks == 0 || type == VolumeType) continue; + + result.Add(new PartitionEntry { + Index = result.Count, + StartOffset = (long)first * LabelSize, + Size = (long)blocks * LabelSize, + TypeName = TypeName(type), + TypeCode = $"0x{type:X2}", + Name = i.ToString(System.Globalization.CultureInfo.InvariantCulture), + Source = "SGI" + }); + } + return result; + } + + private static string TypeName(uint type) => type switch { + 0x00 => "SGI volhdr", + 0x01 => "SGI trkrepl", + 0x02 => "SGI secrepl", + 0x03 => "SGI raw", + 0x04 => "SGI bsd", + 0x05 => "SGI sysv", + 0x06 => "SGI volume", + 0x07 => "SGI efs", + 0x08 => "SGI lvol", + 0x09 => "SGI rlvol", + 0x0A => "SGI xfs", + 0x0B => "SGI xfslog", + 0x0C => "SGI xlv", + 0x0D => "SGI xvm", + 0x82 => "Linux swap", + 0x83 => "Linux native", + 0x8E => "Linux LVM", + 0xFD => "Linux RAID", + _ => $"SGI type 0x{type:X2}" + }; +} diff --git a/Compression.Core/DiskImage/StagedWindowStream.cs b/Compression.Core/DiskImage/StagedWindowStream.cs new file mode 100644 index 000000000..62b53b8c2 --- /dev/null +++ b/Compression.Core/DiskImage/StagedWindowStream.cs @@ -0,0 +1,153 @@ +namespace Compression.Core.DiskImage; + +/// +/// A writable, fixed-length window over a range of a disk whose writes are held back until +/// . Reads see the staged writes on top of the disk's bytes, so a +/// filesystem editor working through the window behaves exactly as it would on a standalone +/// image; but nothing reaches the disk unless the whole edit finishes and the caller commits. +/// An editor that throws half-way, or that tries to grow its volume past the partition, leaves +/// the disk byte-identical. +/// +/// +/// Writes are staged in 4 KiB pages, so the memory held is proportional to what the edit +/// touches, not to the partition. +/// +public sealed class StagedWindowStream : Stream { + private const int PageSize = 4096; + + private readonly Stream _disk; + private readonly long _offset; + private readonly long _length; + private readonly Dictionary _pages = []; + private long _position; + private bool _committed; + + /// Opens a staged window of bytes at on . + public StagedWindowStream(Stream disk, long offset, long length) { + ArgumentNullException.ThrowIfNull(disk); + if (!disk.CanSeek || !disk.CanRead) throw new ArgumentException("The disk must be readable and seekable.", nameof(disk)); + ArgumentOutOfRangeException.ThrowIfNegative(offset); + ArgumentOutOfRangeException.ThrowIfNegative(length); + this._disk = disk; + this._offset = offset; + this._length = length; + } + + /// Whether any write has been staged. + public bool HasChanges => this._pages.Count > 0; + + /// + public override bool CanRead => true; + /// + public override bool CanSeek => true; + /// + public override bool CanWrite => !this._committed; + /// + public override long Length => this._length; + + /// + public override long Position { + get => this._position; + set { + ArgumentOutOfRangeException.ThrowIfNegative(value); + this._position = value; + } + } + + /// + public override int Read(byte[] buffer, int offset, int count) => this.Read(buffer.AsSpan(offset, count)); + + /// + public override int Read(Span buffer) { + if (this._position >= this._length) return 0; + var total = (int)Math.Min(buffer.Length, this._length - this._position); + var done = 0; + while (done < total) { + var pos = this._position + done; + var pageIndex = pos / PageSize; + var inPage = (int)(pos % PageSize); + var chunk = Math.Min(total - done, PageSize - inPage); + if (this._pages.TryGetValue(pageIndex, out var page)) { + page.AsSpan(inPage, chunk).CopyTo(buffer.Slice(done, chunk)); + } else { + this._disk.Position = this._offset + pos; + var read = this._disk.ReadAtLeast(buffer.Slice(done, chunk), chunk, throwOnEndOfStream: false); + if (read < chunk) buffer.Slice(done + read, chunk - read).Clear(); + } + done += chunk; + } + this._position += total; + return total; + } + + /// + public override void Write(byte[] buffer, int offset, int count) => this.Write(buffer.AsSpan(offset, count)); + + /// + public override void Write(ReadOnlySpan buffer) { + if (this._committed) throw new InvalidOperationException("The staged window has already been committed."); + if (this._position + buffer.Length > this._length) + throw new IOException( + $"Write at {this._position} for {buffer.Length} bytes runs past the end of the {this._length}-byte partition."); + var done = 0; + while (done < buffer.Length) { + var pos = this._position + done; + var pageIndex = pos / PageSize; + var inPage = (int)(pos % PageSize); + var chunk = Math.Min(buffer.Length - done, PageSize - inPage); + buffer.Slice(done, chunk).CopyTo(this.PageFor(pageIndex).AsSpan(inPage, chunk)); + done += chunk; + } + this._position += buffer.Length; + } + + private byte[] PageFor(long pageIndex) { + if (this._pages.TryGetValue(pageIndex, out var page)) return page; + page = new byte[PageSize]; + var pageStart = pageIndex * PageSize; + var valid = (int)Math.Min(PageSize, this._length - pageStart); + this._disk.Position = this._offset + pageStart; + _ = this._disk.ReadAtLeast(page.AsSpan(0, valid), valid, throwOnEndOfStream: false); + this._pages[pageIndex] = page; + return page; + } + + /// + public override long Seek(long offset, SeekOrigin origin) { + this.Position = origin switch { + SeekOrigin.Begin => offset, + SeekOrigin.Current => this._position + offset, + SeekOrigin.End => this._length + offset, + _ => throw new ArgumentOutOfRangeException(nameof(origin)), + }; + return this._position; + } + + /// + /// A partition cannot grow from inside: asking for more than its length fails, and asking for + /// less leaves the trailing bytes where they are, as unused space inside the partition. + /// + public override void SetLength(long value) { + if (value > this._length) + throw new IOException($"The filesystem asked for {value} bytes, but its partition holds {this._length}."); + } + + /// Staged writes stay staged; only writes them. + public override void Flush() { } + + /// Writes every staged page to the disk, in disk order, then flushes it. + public void Commit() { + if (this._committed) return; + if (this._pages.Count > 0 && !this._disk.CanWrite) + throw new NotSupportedException("The disk is read-only."); + foreach (var (pageIndex, page) in this._pages.OrderBy(p => p.Key)) { + var pageStart = pageIndex * PageSize; + var valid = (int)Math.Min(PageSize, this._length - pageStart); + this._disk.Position = this._offset + pageStart; + this._disk.Write(page, 0, valid); + } + this._disk.Flush(); + this._committed = true; + this._pages.Clear(); + } +} diff --git a/Compression.Core/DiskImage/SunVtocParser.cs b/Compression.Core/DiskImage/SunVtocParser.cs new file mode 100644 index 000000000..4896b639e --- /dev/null +++ b/Compression.Core/DiskImage/SunVtocParser.cs @@ -0,0 +1,100 @@ +using System.Buffers.Binary; + +namespace Compression.Core.DiskImage; + +/// +/// Parses the Sun disk label (SunOS / Solaris SPARC "VTOC" label) in sector 0. Big-endian. +/// +/// +/// +/// The 512-byte label ends in the magic 0xDABE at byte 508 and a checksum at 510 chosen +/// so that the XOR of all 256 big-endian 16-bit words is zero. Eight partition records at byte +/// 444 hold a start cylinder and a length in sectors; a cylinder is ntrks × nsect +/// sectors, from the geometry fields at bytes 436 and 438. The VTOC at byte 128 tags each +/// slice (root, swap, usr, whole disk, Linux native, …) and is recognised by its sanity word +/// 0x600DDEEE; a label without one still describes its partitions. +/// +/// +/// The whole-disk slice (tag 5, conventionally slice 2) overlaps every other slice and is not +/// listed. +/// +/// References: +/// +/// Sun Microsystems, sys/dklabel.h — struct dk_label layout, DKL_MAGIC, checksum rule. +/// Sun Microsystems, sys/vtoc.h — slice tag values (V_BOOT, V_ROOT, V_BACKUP, …) and VTOC_SANE. +/// +/// +public static class SunVtocParser { + + private const int LabelSize = 512; + private const ushort Magic = 0xDABE; + private const int MagicOffset = 508; + private const uint VtocSane = 0x600DDEEE; + private const int VtocSanityOffset = 188; + private const int VtocInfosOffset = 142; + private const int NtrksOffset = 436; + private const int NsectOffset = 438; + private const int PartitionsOffset = 444; + private const int PartitionCount = 8; + private const ushort WholeDiskTag = 5; + + /// Reports whether is a Sun label with a valid checksum. + public static bool IsSunLabel(ReadOnlySpan sector0) { + if (sector0.Length < LabelSize) return false; + if (BinaryPrimitives.ReadUInt16BigEndian(sector0[MagicOffset..]) != Magic) return false; + ushort xor = 0; + for (var i = 0; i < LabelSize; i += 2) + xor ^= BinaryPrimitives.ReadUInt16BigEndian(sector0[i..]); + return xor == 0; + } + + /// Parses the label's slices, excluding the whole-disk slice and empty slots. + /// Sector 0 is not a Sun label. + public static List Parse(ReadOnlySpan sector0) { + if (!IsSunLabel(sector0)) throw new InvalidDataException("Sector 0 is not a checksummed Sun disk label."); + + var ntrks = BinaryPrimitives.ReadUInt16BigEndian(sector0[NtrksOffset..]); + var nsect = BinaryPrimitives.ReadUInt16BigEndian(sector0[NsectOffset..]); + var cylinderSectors = (long)ntrks * nsect; + var hasVtoc = BinaryPrimitives.ReadUInt32BigEndian(sector0[VtocSanityOffset..]) == VtocSane; + + var result = new List(); + for (var i = 0; i < PartitionCount; ++i) { + var record = sector0[(PartitionsOffset + i * 8)..]; + var startCylinder = BinaryPrimitives.ReadUInt32BigEndian(record); + var sectors = BinaryPrimitives.ReadUInt32BigEndian(record[4..]); + if (sectors == 0) continue; + + var tag = hasVtoc ? BinaryPrimitives.ReadUInt16BigEndian(sector0[(VtocInfosOffset + i * 4)..]) : (ushort)0; + if (tag == WholeDiskTag || (!hasVtoc && i == 2)) continue; + + result.Add(new PartitionEntry { + Index = result.Count, + StartOffset = startCylinder * cylinderSectors * LabelSize, + Size = (long)sectors * LabelSize, + TypeName = TagName(tag), + TypeCode = $"0x{tag:X2}", + Name = ((char)('a' + i)).ToString(), + Source = "Sun" + }); + } + return result; + } + + private static string TagName(ushort tag) => tag switch { + 0x00 => "Unassigned", + 0x01 => "Boot", + 0x02 => "SunOS root", + 0x03 => "SunOS swap", + 0x04 => "SunOS usr", + 0x05 => "Whole disk", + 0x06 => "SunOS stand", + 0x07 => "SunOS var", + 0x08 => "SunOS home", + 0x82 => "Linux swap", + 0x83 => "Linux native", + 0x8E => "Linux LVM", + 0xFD => "Linux raid autodetect", + _ => $"Sun tag 0x{tag:X2}" + }; +} diff --git a/Compression.Core/REFERENCE.md b/Compression.Core/REFERENCE.md index ac9593fae..86510a075 100644 --- a/Compression.Core/REFERENCE.md +++ b/Compression.Core/REFERENCE.md @@ -3609,7 +3609,16 @@ Implements `IBuildingBlock`. ### Namespace `Compression.Core.DiskImage` -[`ApmParser`](#apmparser) · [`BsdDisklabelParser`](#bsddisklabelparser) · [`ClusterAllocator`](#clusterallocator) · [`DeferredPayloads`](#deferredpayloads) · [`ExtBlockGroupGeometry`](#extblockgroupgeometry) · [`ExtBlockGroupGeometry.ExtGeometry`](#extblockgroupgeometryextgeometry) · [`ExtentCopy`](#extentcopy) · [`FilePayload`](#filepayload) · [`GptParser`](#gptparser) · [`ImageAccessor`](#imageaccessor) · [`MbrParser`](#mbrparser) · [`MbrWrapper`](#mbrwrapper) · [`MbrWrapper.PartitionType`](#mbrwrapperpartitiontype) · [`PartitionEditor`](#partitioneditor) · [`PartitionEntry`](#partitionentry) · [`PartitionScheme`](#partitionscheme) · [`PartitionTableDetector`](#partitiontabledetector) · [`PartitionTableDetector.DetectionResult`](#partitiontabledetectordetectionresult) · [`PartitionTableVerification`](#partitiontableverification) · [`PartitionType`](#partitiontype) · [`PartitionTypeDatabase`](#partitiontypedatabase) · [`PartitionTypeMapping`](#partitiontypemapping) · [`PartitionWindowStream`](#partitionwindowstream) · [`PartitionedDiskLister`](#partitioneddisklister) · [`SparseBlockImage`](#sparseblockimage) +[`AmigaRdbParser`](#amigardbparser) · [`ApmParser`](#apmparser) · [`BsdDisklabelParser`](#bsddisklabelparser) · [`ClusterAllocator`](#clusterallocator) · [`DeferredPayloads`](#deferredpayloads) · [`ExtBlockGroupGeometry`](#extblockgroupgeometry) · [`ExtBlockGroupGeometry.ExtGeometry`](#extblockgroupgeometryextgeometry) · [`ExtentCopy`](#extentcopy) · [`FilePayload`](#filepayload) · [`GptParser`](#gptparser) · [`GptParser.HeaderInfo`](#gptparserheaderinfo) · [`ImageAccessor`](#imageaccessor) · [`MbrParser`](#mbrparser) · [`MbrWrapper`](#mbrwrapper) · [`MbrWrapper.PartitionType`](#mbrwrapperpartitiontype) · [`PartitionEditor`](#partitioneditor) · [`PartitionEntry`](#partitionentry) · [`PartitionScheme`](#partitionscheme) · [`PartitionTableDetector`](#partitiontabledetector) · [`PartitionTableDetector.DetectionResult`](#partitiontabledetectordetectionresult) · [`PartitionTableVerification`](#partitiontableverification) · [`PartitionType`](#partitiontype) · [`PartitionTypeDatabase`](#partitiontypedatabase) · [`PartitionTypeMapping`](#partitiontypemapping) · [`PartitionWindowStream`](#partitionwindowstream) · [`PartitionedDiskLister`](#partitioneddisklister) · [`PartitionedDiskLister.PartitionTarget`](#partitioneddisklisterpartitiontarget) · [`SgiDvhParser`](#sgidvhparser) · [`SparseBlockImage`](#sparseblockimage) · [`StagedWindowStream`](#stagedwindowstream) · [`SunVtocParser`](#sunvtocparser) + +#### `AmigaRdbParser` + +Parses the Amiga Rigid Disk Block (RDB) partition scheme. Big-endian throughout. + +| Member | Signature | Summary | +| --- | --- | --- | +| `IsRdb` | `static bool IsRdb(Stream disk)` | Reports whether a checksummed `RDSK` block sits in the first 16 blocks. | +| `Parse` | `static List Parse(Stream disk)` | Parses every partition the RDB's `PART` chain describes. | #### `ApmParser` @@ -3627,6 +3636,7 @@ Parses the 4.4BSD `disklabel` that FreeBSD/NetBSD/OpenBSD write into a BSD slice | Member | Signature | Summary | | --- | --- | --- | | `IsDisklabel` | `static bool IsDisklabel(Stream slice)` | Reports whether a BSD disklabel magic is present at either candidate offset within the given slice stream. | +| `IsStandaloneDisklabel` | `static bool IsStandaloneDisklabel(Stream disk)` | Reports whether `disk` carries a disklabel that stands on its own — no MBR slice around it, as a "dangerously dedicated" FreeBSD disk or a NetBSD/OpenBSD disk on a non-PC port writes it. With nothing else vouching for the bytes the label must prove itself structurally: `d_magic` and `d_magic2` both present 132 bytes apart, a sector size that is a power of two in [512, 8192], a partition count within `MAXPARTITIONS`, and every used partition starting inside the disk. | | `Parse` | `static List Parse(Stream slice, long sliceStartByteOffset = 0)` | Parses the BSD disklabel contained in `slice` and returns its filesystem partitions. | #### `ClusterAllocator` @@ -3716,13 +3726,39 @@ Implements `IEquatable`. #### `GptParser` -Parses GPT (GUID Partition Table) headers and partition entries. GPT header is at LBA 1 (offset 512 for 512-byte sectors), with partition entries starting at LBA 2+. +Parses GPT (GUID Partition Table) headers and partition entries. + +| Member | Signature | Summary | +| --- | --- | --- | +| `ProbeBytes` | `const int ProbeBytes` | Bytes of the image needed to see LBA 1 at every supported block size. | +| `SupportedSectorSizes` | `static IReadOnlyList SupportedSectorSizes { get; }` | Logical block sizes probed for the header at LBA 1, in probe order. | +| `DetectSectorSize` | `static int DetectSectorSize(ReadOnlySpan data)` | The logical block size for which LBA 1 holds a valid GPT header, or 0 when none does. | +| `HasValidHeader` | `static bool HasValidHeader(ReadOnlySpan data)` | Checks that LBA 1 holds a GPT header that is internally consistent, not merely the "EFI PART" signature, at any supported block size: see `HasValidHeader`. | +| `HasValidHeader` | `static bool HasValidHeader(ReadOnlySpan data, int sectorSize)` | Checks that LBA 1, counted in `sectorSize`-byte blocks, holds a GPT header that is internally consistent: the header size lies in [92, block size], the header's own CRC32 matches (computed with the CRC field zeroed, UEFI 2.10 §5.3.2), it names itself as LBA 1 and its partition entries are 128 x 2^n bytes each, as the specification requires. | +| `IsGpt` | `static bool IsGpt(ReadOnlySpan data)` | Checks whether LBA 1 carries the "EFI PART" signature at any supported block size. | +| `Parse` | `static List Parse(Stream diskData)` | Parses all partitions from a GPT disk image, at whichever block size its header validates. | +| `Parse` | `static List Parse(Stream diskData, out HeaderInfo info)` | Parses all partitions and reports the header that described them. | +| `ReadHeader` | `static HeaderInfo ReadHeader(Stream diskData)` | The partition table's layout facts, or `null` when the image carries no usable GPT. Uses the same header `Parse` would. | + +#### `GptParser.HeaderInfo` + +Layout facts read from the header that was used to parse the table. + +Implements `IEquatable`. | Member | Signature | Summary | | --- | --- | --- | -| `HasValidHeader` | `static bool HasValidHeader(ReadOnlySpan data)` | Checks that LBA 1 holds a GPT header that is internally consistent, not merely the "EFI PART" signature: the header size lies in [92, 512], the header's own CRC32 matches (computed with the CRC field zeroed, UEFI 2.10 §5.3.2), it names itself as LBA 1 and its partition entries are 128 x 2^n bytes each, as the specification requires. | -| `IsGpt` | `static bool IsGpt(ReadOnlySpan data)` | Checks whether the given data contains a valid GPT header at LBA 1. | -| `Parse` | `static List Parse(Stream diskData)` | Parses all partitions from a GPT disk image. | +| `HeaderInfo` | `HeaderInfo(int SectorSize, ulong MyLba, ulong AlternateLba, ulong FirstUsableLba, ulong LastUsableLba, Guid DiskGuid, ulong EntriesLba, uint EntryCount, uint EntrySize, bool FromBackup)` | Layout facts read from the header that was used to parse the table. | +| `AlternateLba` | `ulong AlternateLba { get; init; }` | The LBA of the other header. | +| `DiskGuid` | `Guid DiskGuid { get; init; }` | The disk GUID. | +| `EntriesLba` | `ulong EntriesLba { get; init; }` | Starting LBA of the entry array the used header points at. | +| `EntryCount` | `uint EntryCount { get; init; }` | Number of entries in the array. | +| `EntrySize` | `uint EntrySize { get; init; }` | Size of one entry in bytes. | +| `FirstUsableLba` | `ulong FirstUsableLba { get; init; }` | First LBA a partition may occupy. | +| `FromBackup` | `bool FromBackup { get; init; }` | `true` when the primary was unusable and the backup was read. | +| `LastUsableLba` | `ulong LastUsableLba { get; init; }` | Last LBA a partition may occupy. | +| `MyLba` | `ulong MyLba { get; init; }` | The LBA the used header sits at (1 for the primary). | +| `SectorSize` | `int SectorSize { get; init; }` | Logical block size every LBA is counted in. | #### `ImageAccessor` @@ -3749,14 +3785,14 @@ Implements `IDisposable`. #### `MbrParser` -Parses MBR (Master Boot Record) partition tables, including extended/logical partition chains. The MBR sits at LBA 0 (offset 0) of a disk image, with 4 primary partition entries at offset 0x1BE. +Parses MBR (Master Boot Record) partition tables, including extended/logical partition chains. The MBR sits at LBA 0 (offset 0) of a disk image, with 4 primary partition entries at offset 0x1BE. The entries count in logical blocks: 512 bytes on a classic or 512e disk, 4096 on a 4Kn disk. | Member | Signature | Summary | | --- | --- | --- | -| `HasValidPartitionTable` | `static bool HasValidPartitionTable(ReadOnlySpan sector0, long diskLength)` | Checks whether sector 0 holds a partition table that is actually usable, rather than merely ending in the 0x55AA boot signature — which every FAT, NTFS and exFAT volume boot record does as well. | +| `HasValidPartitionTable` | `static bool HasValidPartitionTable(ReadOnlySpan sector0, long diskLength, int sectorSize = 512)` | Checks whether sector 0 holds a partition table that is actually usable, rather than merely ending in the 0x55AA boot signature — which every FAT, NTFS and exFAT volume boot record does as well. | | `IsMbr` | `static bool IsMbr(ReadOnlySpan data)` | Checks whether the given data starts with a valid MBR. | -| `ParsePrimary` | `static List ParsePrimary(ReadOnlySpan mbr)` | Parses the 4 primary partition entries from an MBR without following extended chains. Operates on a 512-byte buffer (no stream needed). | -| `Parse` | `static List Parse(Stream diskData)` | Parses all partitions from an MBR, including extended/logical partitions. | +| `ParsePrimary` | `static List ParsePrimary(ReadOnlySpan mbr, int sectorSize = 512)` | Parses the 4 primary partition entries from an MBR without following extended chains. Operates on a 512-byte buffer (no stream needed). | +| `Parse` | `static List Parse(Stream diskData, int sectorSize = 512)` | Parses all partitions from an MBR, including extended/logical partitions. | #### `MbrWrapper` @@ -3788,15 +3824,20 @@ Read/write partition-table editor for raw disk images. Handles MBR (4 primary en | Member | Signature | Summary | | --- | --- | --- | | `PartitionEditor` | `PartitionEditor(Stream stream)` | Opens an editor over the given disk-image stream. The stream must be readable, writable, and seekable. The scheme (MBR/GPT/None) is auto-detected from the on-disk signature. | -| `SectorSize` | `const int SectorSize` | Standard sector size used by the editor. | +| `PartitionEditor` | `PartitionEditor(Stream stream, int sectorSize)` | Opens an editor over the given disk-image stream, counting LBAs in `sectorSize`-byte blocks unless a GPT on the disk validates at another supported block size — the table on the disk decides, since rewriting it at a different size would move every partition. | +| `SectorSize` | `const int SectorSize` | The classic logical block size, and the one a disk without a GPT is assumed to use unless the caller says otherwise. A 4Kn GPT disk is edited in 4096-byte blocks: see `LogicalSectorSize`. | | `InPlaceFilesystemConverter` | `static Func InPlaceFilesystemConverter { get; set; }` | Pluggable in-place filesystem-variant converter. Set by the higher-level `Compression.Lib` layer at startup (it can reference the per-family FileSystem.* projects, which Core cannot). When non-null, `ConvertFilesystem` attempts a metadata-only conversion via this delegate before falling back to `false`. Contract: takes (partitionStream, sourceFsId, targetFsId), returns `true` if the conversion succeeded entirely via metadata edits, `false` if no in-place path exists for the pair (the caller should then do a full migration: extract files → FormatPartition → re-import). | | `InPlaceFilesystemResizer` | `static Func InPlaceFilesystemResizer { get; set; }` | Pluggable in-place filesystem-resizer. Set by the higher-level `Compression.Lib` layer at startup (Core cannot reference FileSystem.* projects, so the actual resizer lives there). The contract is: (partitionStream, fsId, newSizeBytes, isShrink) → bool Returns `true` when the resize succeeded entirely via in-place edits, `false` when no in-place path exists for the fsId (the caller should fall back to a full extract → reformat → re-import). | +| `LogicalSectorSize` | `int LogicalSectorSize { get; }` | The logical block size every LBA on this disk is counted in: the size at which the GPT header validated, or — for an MBR or blank disk — the size the editor was opened with. | | `Scheme` | `PartitionScheme Scheme { get; }` | Partition scheme currently in use on the disk image. | +| `SupportedSectorSizes` | `static IReadOnlyList SupportedSectorSizes { get; }` | Logical block sizes the editor supports. | +| `UsableRange` | `ValueTuple UsableRange { get; }` | The byte range partitions may occupy under the current scheme: everything after LBA 0 for MBR; between the primary and backup GPT structures for GPT. | | `AddLogicalPartition` | `void AddLogicalPartition(long startByteOffset, long lengthBytes, PartitionType type, string label)` | Adds a new logical partition inside the existing MBR extended container. The byte range must lie entirely within the extended container and not overlap any existing logical (each logical occupies its EBR sector plus the data range). | | `AddPartition` | `void AddPartition(long startByteOffset, long lengthBytes, PartitionType type, string label)` | Adds a new primary partition entry covering [`startByteOffset`, `startByteOffset` + `lengthBytes`). | | `ConvertFilesystem` | `bool ConvertFilesystem(int partitionIndex, string newFsId)` | Attempts an in-place filesystem-variant conversion of the partition at `partitionIndex`. Reads the partition's current contents as a substream, dispatches to `InPlaceFilesystemConverter` (set by the Compression.Lib bootstrap), and returns the converter's answer. Returns `false` when:No converter has been registered (Core-only build).The source/target pair has no metadata-only conversion path (e.g. FAT → ext, NTFS → exFAT — any cross-family conversion).The image geometry rules out the requested target (e.g. a tiny FAT12 floppy can't fit FAT32's 32-reserved-sector overhead). In any of these cases the caller is expected to fall back to a full extract → reformat → re-import migration. | | `ConvertGptToMbr` | `void ConvertGptToMbr()` | Reads the current GPT partition table, takes the first 4 entries (MBR hard limit), zero-fills both GPT header areas, and writes a fresh MBR with the translated entries. | | `ConvertMbrToGpt` | `void ConvertMbrToGpt()` | Reads the current MBR partition table, drops it, then rewrites the equivalent GPT layout: protective MBR at LBA 0, primary GPT header at LBA 1, entries starting at LBA 2, backup GPT at the end of the disk. Translates MBR type bytes to GPT GUIDs via `PartitionTypeMapping`. The extended container (if present) is dropped — its logical children are promoted to top-level GPT entries. | +| `CreateEmpty` | `static PartitionEditor CreateEmpty(Stream stream, PartitionScheme scheme, int sectorSize = 512)` | Writes an empty partition table of `scheme` over the start (and, for GPT, the end) of `stream` and returns an editor over it. A GPT gets the standard 128 entries of 128 bytes, its backup at the last LBA and a protective MBR. | | `DeletePartition` | `void DeletePartition(int index)` | Deletes the partition at the given index. The partition byte range is not modified — see `PurgePartition` to zero it. | | `FormatPartition` | `void FormatPartition(int index, string formatId, FormatCreateOptions options)` | Writes a fresh filesystem image of the given `formatId` into the partition's byte range. The format must be registered with `FormatRegistry` and support `IArchiveCreatable`. The resulting bytes must fit within the partition (an exception is thrown otherwise). | | `ListPartitions` | `IReadOnlyList ListPartitions()` | Returns a snapshot of the current partition table. | @@ -3834,14 +3875,15 @@ Partition scheme detected on a disk image. #### `PartitionTableDetector` -Detects and parses partition tables from raw disk data. Tries GPT first (since GPT disks also have a protective MBR), then falls back to MBR. +Detects and parses partition tables from raw disk data. | Member | Signature | Summary | | --- | --- | --- | -| `Detect` | `static DetectionResult Detect(Stream diskData)` | Attempts to detect a partition table in the given stream. Tries GPT first (higher precedence), then MBR. Returns an empty partition list if no valid partition table is found. | +| `Detect` | `static DetectionResult Detect(Stream diskData)` | Attempts to detect a partition table in the given stream. Returns an empty partition list if no valid partition table is found. | | `Detect` | `static DetectionResult Detect(byte[] diskData)` | Attempts to detect a partition table in the given byte array. Convenience overload that wraps the data in a `MemoryStream`. | | `ExtractPartitionData` | `static byte[] ExtractPartitionData(Stream diskData, PartitionEntry partition)` | Extracts partition data from a disk image stream. Returns the raw bytes of the partition at the given offset and size. | | `ExtractPartitionData` | `static byte[] ExtractPartitionData(byte[] diskData, PartitionEntry partition)` | Extracts partition data from a byte array. | +| `HasSelfValidatingLabel` | `static bool HasSelfValidatingLabel(Stream disk)` | Whether the first sector holds a partition table of a scheme that has no MBR-style boot signature to go on — SGI, Sun, a stand-alone BSD disklabel or an Amiga RDB — each verified by its own checksum (BSD: by its two magics and geometry). Used by content detection, which has already ruled out GPT, MBR and APM by then. | #### `PartitionTableDetector.DetectionResult` @@ -3851,7 +3893,8 @@ Result of partition table detection, including the scheme used and the discovere | --- | --- | --- | | `DetectionResult` | `DetectionResult()` | | | `Partitions` | `List Partitions { get; init; }` | Discovered partitions. Empty if no partition table was found. | -| `Scheme` | `string Scheme { get; init; }` | The partition scheme detected: "GPT", "MBR", "APM", or "None". | +| `Scheme` | `string Scheme { get; init; }` | The partition scheme detected: "GPT", "MBR", "APM", "BSD", "RDB", "Sun", "SGI", or "None". | +| `SectorSize` | `int SectorSize { get; init; }` | Logical block size the table counts in (512 unless a 4Kn GPT was found). | #### `PartitionTableVerification` @@ -3941,11 +3984,38 @@ Partition-aware archive surface for raw disk image streams (VHD/VHDX/VMDK/Qcow2/ | Member | Signature | Summary | | --- | --- | --- | +| `EnumerateTargets` | `static List EnumerateTargets(Stream disk, DetectionResult detection)` | Every partition the listing shows a prefix for, in listing order: top-level partitions, with a BSD-disklabel container replaced by its slices. | | `Extract` | `static bool Extract(Stream disk, string outputDir, string password, string[] files)` | Extracts entries across every partition into per-partition subdirectories of `outputDir`. Returns `true` if a partition table was detected and handled; `false` if no partition table was found (caller falls through to the unpartitioned path). | +| `HasNativeEditor` | `static bool HasNativeEditor(IArchiveModifiable modifier, string method)` | Whether `modifier` edits through its own code for `method` (`Add` or `Remove`) rather than the interface's generic extract → re-create default, which re-creates the volume and loses what its writer does not reproduce. | | `List` | `static List List(Stream disk, string password)` | Lists entries across every partition. Returns `null` if no partition table is present so the caller can fall through to the unpartitioned path. | | `MakePartitionPrefix` | `static string MakePartitionPrefix(PartitionEntry part)` | The directory name a partition is listed under: `Partition{N}_{TypeName}`, with N one-based and the type name reduced to letters, digits, '-' and '_'. | -| `TryAdd` | `static bool TryAdd(Stream disk, IReadOnlyList inputs)` | Partition-aware `Add`. Returns `true` when a partition table was present and at least one input was dispatched through the partition-aware path; `false` when no partition table exists so the caller can fall through to the existing single-FS path. | -| `TryRemove` | `static bool TryRemove(Stream disk, string[] entryNames)` | Partition-aware `Remove`. Returns `true` when a partition table was present and at least one entry was removed; `false` if no partition table was detected. Entry name shapes: `Partition_/` deletes an inner-FS file; `Partition_` or `Partition_.raw` deletes the whole partition. | +| `Resolve` | `static PartitionTarget Resolve(IEnumerable targets, string entryName, out string innerPath)` | Finds the partition an entry name addresses. `innerPath` is the rest of the name below the partition's prefix — empty when the name is the partition itself (`PartitionN_Type` or `PartitionN_Type.raw`). | +| `TryAdd` | `static bool TryAdd(Stream disk, IReadOnlyList inputs)` | Partition-aware `Add`. Returns `false` when no partition table exists (or the stream is read-only) so the caller can fall through to its single-filesystem path. | +| `TryRemove` | `static bool TryRemove(Stream disk, string[] entryNames)` | Partition-aware `Remove`. Returns `false` when no partition table exists (or the stream is read-only). Entry name shapes: `PartitionN_Type/inner` removes a file — or a folder with everything beneath it — through the partition filesystem's own in-place editor; `PartitionN_Type` or `PartitionN_Type.raw` deletes the partition's table entry (GPT and MBR, including logical partitions), leaving its sectors as they were. | + +#### `PartitionedDiskLister.PartitionTarget` + +One listed partition: a top-level partition, or a slice of a BSD disklabel nested inside one, with the prefix its entries are listed under. + +Implements `IEquatable`. + +| Member | Signature | Summary | +| --- | --- | --- | +| `PartitionTarget` | `PartitionTarget(PartitionEntry Partition, string Prefix, long Offset, long Size, bool IsBsdSlice)` | One listed partition: a top-level partition, or a slice of a BSD disklabel nested inside one, with the prefix its entries are listed under. | +| `IsBsdSlice` | `bool IsBsdSlice { get; init; }` | Whether this is a slice inside a BSD disklabel rather than a table entry. | +| `Offset` | `long Offset { get; init; }` | Byte offset of the partition on the disk. | +| `Partition` | `PartitionEntry Partition { get; init; }` | The table entry (for a BSD slice, the slice's own entry). | +| `Prefix` | `string Prefix { get; init; }` | The listing prefix, e.g. `Partition1_FAT32` or `Partition1_FreeBSD/Partition1_4_2BSD`. | +| `Size` | `long Size { get; init; }` | Bytes of the partition that lie inside the image. | + +#### `SgiDvhParser` + +Parses the SGI disk volume header (DVH) in sector 0 — the IRIX partition scheme. Big-endian. + +| Member | Signature | Summary | +| --- | --- | --- | +| `IsDvh` | `static bool IsDvh(ReadOnlySpan sector0)` | Reports whether `sector0` is an SGI volume header with a valid checksum. | +| `Parse` | `static List Parse(ReadOnlySpan sector0)` | Parses the header's partitions, excluding the whole-volume partition and empty slots. | #### `SparseBlockImage` @@ -3965,6 +4035,39 @@ Sparse, block-addressed image buffer. Only the blocks a writer actually touches | `WriteTo` | `void WriteTo(Stream output)` | Writes the volume from the current position, extending the stream to its full size. | | `Write` | `void Write(long offset, ReadOnlySpan data)` | Copies `data` to `offset`, spanning blocks as needed. | +#### `StagedWindowStream` + +A writable, fixed-length window over a range of a disk whose writes are held back until `Commit`. Reads see the staged writes on top of the disk's bytes, so a filesystem editor working through the window behaves exactly as it would on a standalone image; but nothing reaches the disk unless the whole edit finishes and the caller commits. An editor that throws half-way, or that tries to grow its volume past the partition, leaves the disk byte-identical. + +Inherits `Stream`. Implements `IAsyncDisposable`, `IDisposable`. + +| Member | Signature | Summary | +| --- | --- | --- | +| `StagedWindowStream` | `StagedWindowStream(Stream disk, long offset, long length)` | Opens a staged window of `length` bytes at `offset` on `disk`. | +| `CanRead` | `override bool CanRead { get; }` | | +| `CanSeek` | `override bool CanSeek { get; }` | | +| `CanWrite` | `override bool CanWrite { get; }` | | +| `HasChanges` | `bool HasChanges { get; }` | Whether any write has been staged. | +| `Length` | `override long Length { get; }` | | +| `Position` | `override long Position { get; set; }` | | +| `Commit` | `void Commit()` | Writes every staged page to the disk, in disk order, then flushes it. | +| `Flush` | `override void Flush()` | Staged writes stay staged; only `Commit` writes them. | +| `Read` | `override int Read(Span buffer)` | | +| `Read` | `override int Read(byte[] buffer, int offset, int count)` | | +| `Seek` | `override long Seek(long offset, SeekOrigin origin)` | | +| `SetLength` | `override void SetLength(long value)` | A partition cannot grow from inside: asking for more than its length fails, and asking for less leaves the trailing bytes where they are, as unused space inside the partition. | +| `Write` | `override void Write(ReadOnlySpan buffer)` | | +| `Write` | `override void Write(byte[] buffer, int offset, int count)` | | + +#### `SunVtocParser` + +Parses the Sun disk label (SunOS / Solaris SPARC "VTOC" label) in sector 0. Big-endian. + +| Member | Signature | Summary | +| --- | --- | --- | +| `IsSunLabel` | `static bool IsSunLabel(ReadOnlySpan sector0)` | Reports whether `sector0` is a Sun label with a valid checksum. | +| `Parse` | `static List Parse(ReadOnlySpan sector0)` | Parses the label's slices, excluding the whole-disk slice and empty slots. | + ### Namespace `Compression.Core.DiskImage.Raid` [`ImsmContainer`](#imsmcontainer) · [`ImsmDisk`](#imsmdisk) · [`ImsmMetadataParser`](#imsmmetadataparser) · [`ImsmVolume`](#imsmvolume) · [`Md09SuperblockParser`](#md09superblockparser) · [`Md1SuperblockParser`](#md1superblockparser) · [`RaidArray`](#raidarray) · [`RaidAssembledStream`](#raidassembledstream) · [`RaidAssembler`](#raidassembler) · [`RaidLevel`](#raidlevel) · [`RaidMember`](#raidmember) · [`RaidMemberMetadata`](#raidmembermetadata) · [`RaidMetadataFormat`](#raidmetadataformat) diff --git a/Compression.Lib/FileFormats/FileFormat.PartitionedDisk/PartitionedDiskFormatDescriptor.cs b/Compression.Lib/FileFormats/FileFormat.PartitionedDisk/PartitionedDiskFormatDescriptor.cs index d0d66502b..03d905c3a 100644 --- a/Compression.Lib/FileFormats/FileFormat.PartitionedDisk/PartitionedDiskFormatDescriptor.cs +++ b/Compression.Lib/FileFormats/FileFormat.PartitionedDisk/PartitionedDiskFormatDescriptor.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +using System.Text.RegularExpressions; using Compression.Core.DiskImage; using Compression.Registry; @@ -15,15 +16,25 @@ namespace FileFormat.PartitionedDisk; /// /// There is no signature to register. The format is claimed by /// FormatDetector's disk-image probe only after the table has been validated: a GPT header -/// whose CRC32 matches, a legacy MBR whose four entries all carry a legal boot indicator and -/// describe non-overlapping ranges that start inside the image, or an Apple Partition Map. No -/// extension is claimed either: .img, .raw, .dd and .bin are just -/// as often a bare filesystem or something else entirely, and it is the content that decides. +/// whose CRC32 matches (at 512- or 4096-byte logical blocks), a legacy MBR whose four entries all +/// carry a legal boot indicator and describe non-overlapping ranges that start inside the image, +/// an Apple Partition Map, or a checksummed SGI volume header, Sun label, stand-alone BSD +/// disklabel or Amiga Rigid Disk Block. No extension is claimed either: .img, +/// .raw, .dd and .bin are just as often a bare filesystem or something else +/// entirely, and it is the content that decides. /// /// /// Partitions are read through a window over the image, never copied out, so browsing a /// multi-gigabyte disk touches only the sectors each filesystem reader asks for. /// +/// +/// Editing goes through two paths. A file inside a partition is added or removed by that +/// partition's own filesystem editor, working in place on a staged window over the partition — +/// refused when the filesystem has no lossless in-place editor, and committed only when the whole +/// edit succeeds. Whole partitions are added (a root-level filesystem or *.raw image) and +/// deleted (PartitionN_Type) on GPT and MBR tables. lays partition +/// images out on a fresh GPT or MBR disk. +/// /// References: /// /// UEFI Specification 2.10, §5.2 (legacy MBR) and §5.3 (GUID Partition Table) @@ -31,7 +42,8 @@ namespace FileFormat.PartitionedDisk; /// https://en.wikipedia.org/wiki/Disk_image — overview /// /// -public sealed class PartitionedDiskFormatDescriptor : IFormatDescriptor, IArchiveFormatOperations { +public sealed class PartitionedDiskFormatDescriptor : IFormatDescriptor, IArchiveFormatOperations, IArchiveModifiable, IArchiveCreatable, + IArchiveWriteConstraints { /// Gets the id. public string Id => "PartitionedDisk"; /// Gets the display name. @@ -41,7 +53,8 @@ public sealed class PartitionedDiskFormatDescriptor : IFormatDescriptor, IArchiv /// Gets the capabilities. public FormatCapabilities Capabilities => FormatCapabilities.CanList | FormatCapabilities.CanExtract | FormatCapabilities.CanTest | - FormatCapabilities.SupportsMultipleEntries; + FormatCapabilities.CanCreate | FormatCapabilities.CanModify | + FormatCapabilities.SupportsMultipleEntries | FormatCapabilities.SupportsDirectories; /// Gets the default extension. public string DefaultExtension => ".img"; /// Gets the extensions — none: generic disk-image extensions are settled by content. @@ -58,7 +71,7 @@ public sealed class PartitionedDiskFormatDescriptor : IFormatDescriptor, IArchiv public AlgorithmFamily Family => AlgorithmFamily.Archive; /// Gets the description. public string Description => - "Raw whole-disk image (MBR, GPT or APM); each partition browses as the filesystem inside it."; + "Raw whole-disk image (GPT, MBR, APM, BSD, Amiga RDB, Sun, SGI); each partition browses as the filesystem inside it."; private const string WholeDiskEntry = "disk.raw"; @@ -94,36 +107,208 @@ public void Extract(Stream stream, string outputDir, string? password, string[]? /// /// Opens an entry without extracting the partition around it: a PartitionN_Type.raw /// entry is a read-only window over the partition's sectors, and a file inside a recognised - /// filesystem is opened by that filesystem's reader over the same window. + /// filesystem is opened by that filesystem's reader over the same window — for a BSD slice, + /// the window over the slice. /// public Stream OpenEntry(Stream archive, string entryName, string? password) { ArgumentNullException.ThrowIfNull(archive); ArgumentException.ThrowIfNullOrWhiteSpace(entryName); - var name = entryName.Replace('\\', '/'); - var table = PartitionTableDetector.Detect(archive); - foreach (var part in table.Partitions) { - var prefix = PartitionedDiskLister.MakePartitionPrefix(part); - - if (name.Equals(prefix + ".raw", StringComparison.Ordinal)) - return new ReadOnlyWindow(new PartitionWindowStream(archive, part.StartOffset, ClampedSize(archive, part))); - - if (!name.StartsWith(prefix + "/", StringComparison.Ordinal)) - continue; - - var window = new PartitionWindowStream(archive, part.StartOffset, ClampedSize(archive, part)); - if (BsdDisklabelParser.IsDisklabel(window)) - break; // slices nest one level deeper; the lister's own layout handles them + var targets = PartitionedDiskLister.EnumerateTargets(archive, PartitionTableDetector.Detect(archive)); + if (PartitionedDiskLister.Resolve(targets, entryName, out var innerPath) is { } target) { + var window = new PartitionWindowStream(archive, target.Offset, target.Size); + if (innerPath.Length == 0) + return new ReadOnlyWindow(window); if (InnerFsDetector.Detect(window) is IArchiveFormatOperations inner) { window.Position = 0; - return inner.OpenEntry(window, name[(prefix.Length + 1)..], password); + return inner.OpenEntry(window, innerPath, password); } } - // BSD slices, and anything else the lister nests deeper, take the extracting default. return ((IArchiveFormatOperations)new ExtractingView(this)).OpenEntry(archive, entryName, password); } + // ── IArchiveModifiable ───────────────────────────────────────────── + + /// + /// Adds files inside partitions (PartitionN_Type/path) through each partition + /// filesystem's own in-place editor, and root-level filesystem or *.raw images as new + /// partitions. Refusals leave the disk byte-identical: see + /// . + /// + /// The edit was refused; nothing was written. + public void Add(Stream archive, IReadOnlyList inputs) { + ArgumentNullException.ThrowIfNull(archive); + ArgumentNullException.ThrowIfNull(inputs); + if (!PartitionedDiskLister.TryAdd(archive, inputs)) + throw new InvalidOperationException("The image carries no partition table that can be edited."); + } + + /// + /// Removes files inside partitions through each partition filesystem's own in-place editor, + /// and deletes whole partitions (PartitionN_Type) from GPT and MBR tables. Refusals + /// leave the disk byte-identical: see . + /// + /// The edit was refused; nothing was written. + /// A name addresses nothing on the disk; nothing was written. + public void Remove(Stream archive, string[] entryNames) { + ArgumentNullException.ThrowIfNull(archive); + if (!PartitionedDiskLister.TryRemove(archive, entryNames ?? [])) + throw new InvalidOperationException("The image carries no partition table that can be edited."); + } + + /// + /// Not offered: emptying a whole disk would mean deleting every partition, which is a + /// partition-table decision, not a maintenance verb — each volume is maintained on its own. + /// + public bool CanPurgeToEmpty => false; + + // ── IArchiveWriteConstraints ─────────────────────────────────────── + + /// + /// Inside a partition (PartitionN_Type/…) any file goes to that partition's filesystem, + /// which has the last word. At the disk root only partition images belong: a filesystem image, + /// or a raw image named *.raw/*.img/PartitionN_…. + /// + public bool CanAccept(ArchiveInputInfo input, out string? reason) { + ArgumentNullException.ThrowIfNull(input); + reason = null; + var name = input.ArchiveName.Replace('\\', '/').Trim('/'); + if (input.IsDirectory || name.Contains('/') || IsNamedPartitionImage(name)) return true; + try { + using var content = OpenInput(input); + if (DetectPartitionType(content) is not null) return true; + } catch (Exception ex) when (ex is IOException or UnauthorizedAccessException) { + // unreadable: refuse below + } + reason = $"'{name}' is not a partition image; open a partition's folder to add files to its filesystem."; + return false; + } + + /// No ceiling of its own: each partition's filesystem bounds what it holds. + public long? MaxTotalArchiveSize => null; + + /// Summary for drop tooltips. + public string AcceptedInputsDescription => + "inside a partition folder: files for its filesystem; at the disk root: filesystem or *.raw/*.img partition images"; + + private static bool IsNamedPartitionImage(string name) { + var leaf = Path.GetFileName(name); + return leaf.EndsWith(".raw", StringComparison.OrdinalIgnoreCase) + || leaf.EndsWith(".img", StringComparison.OrdinalIgnoreCase) + || PartitionName.IsMatch(leaf); + } + + // ── IArchiveCreatable ────────────────────────────────────────────── + + private static readonly Regex PartitionName = new(@"^Partition(\d+)_", RegexOptions.CultureInvariant); + + /// + /// Writes a fresh disk holding one partition per input image. An input must be a filesystem + /// image (recognised by content) or be named as a raw partition image (*.raw, + /// *.img, or PartitionN_… as this format lists them); a loose file is refused. + /// + /// + /// + /// Layout: the first partition starts at 1 MiB and each further one at the next 1 MiB + /// boundary — the alignment sgdisk, parted and Windows use — sized to its image + /// rounded up to whole sectors. Inputs named PartitionN_… are placed in N order, + /// others in the order given. The partition type follows the image's filesystem. + /// + /// + /// Options (): scheme = gpt + /// (default, up to 128 partitions, named after the input) or mbr (up to 4 primaries, + /// below 2^32 sectors); sectorSize = 512 (default) or 4096 for a 4Kn disk. + /// + /// + /// No inputs, a loose file, or an unknown option value. + public void Create(Stream output, IReadOnlyList inputs, FormatCreateOptions options) { + ArgumentNullException.ThrowIfNull(output); + ArgumentNullException.ThrowIfNull(inputs); + options ??= new FormatCreateOptions(); + + var scheme = (options.GetString("scheme") ?? "gpt").ToLowerInvariant() switch { + "gpt" => PartitionScheme.Gpt, + "mbr" or "dos" or "msdos" => PartitionScheme.Mbr, + var other => throw new ArgumentException($"Unknown partition scheme '{other}'; use gpt or mbr.", nameof(options)), + }; + var sectorSize = int.TryParse(options.GetString("sectorSize") ?? "512", out var ss) ? ss : 0; + if (!PartitionEditor.SupportedSectorSizes.Contains(sectorSize)) + throw new ArgumentException("sectorSize must be 512 or 4096.", nameof(options)); + + var images = inputs.Where(i => !i.IsDirectory && !string.IsNullOrEmpty(i.ArchiveName)).ToList(); + if (images.Count == 0) + throw new ArgumentException("A partitioned disk needs at least one partition image.", nameof(inputs)); + if (images.All(i => PartitionName.IsMatch(Path.GetFileName(i.ArchiveName)))) + images = [.. images.OrderBy(i => int.Parse(PartitionName.Match(Path.GetFileName(i.ArchiveName)).Groups[1].Value, + System.Globalization.CultureInfo.InvariantCulture))]; + + var capacity = scheme == PartitionScheme.Gpt ? 128 : 4; + if (images.Count > capacity) + throw new ArgumentException($"A {scheme} table holds at most {capacity} partitions; {images.Count} images were given.", nameof(inputs)); + + // Plan the layout before writing anything. + const long alignment = 1024 * 1024; + var layout = new List<(ArchiveInputInfo Input, long Start, long Length, PartitionType Type)>(); + var cursor = alignment; + foreach (var image in images) { + using var content = OpenInput(image); + var type = DetectPartitionType(content); + if (type is null && !IsNamedPartitionImage(image.ArchiveName)) + throw new ArgumentException( + $"'{image.ArchiveName}' is neither a filesystem image nor a raw partition image (*.raw/*.img); a disk holds partitions, not loose files.", + nameof(inputs)); + if (content.Length == 0) + throw new ArgumentException($"'{image.ArchiveName}' is empty; a partition needs at least one sector.", nameof(inputs)); + var length = (content.Length + sectorSize - 1) / sectorSize * sectorSize; + layout.Add((image, cursor, length, type ?? PartitionType.Linux)); + cursor = (cursor + length + alignment - 1) / alignment * alignment; + } + + var lastEnd = layout[^1].Start + layout[^1].Length; + var trailer = scheme == PartitionScheme.Gpt ? (16384L + sectorSize - 1) / sectorSize * sectorSize + sectorSize : 0; + var diskLength = (lastEnd + trailer + sectorSize - 1) / sectorSize * sectorSize; + if (scheme == PartitionScheme.Mbr && diskLength / sectorSize > uint.MaxValue) + throw new ArgumentException("The images need more than 2^32 sectors, which an MBR cannot address; use scheme=gpt.", nameof(options)); + + output.SetLength(0); + output.SetLength(diskLength); + var editor = PartitionEditor.CreateEmpty(output, scheme, sectorSize); + foreach (var (input, start, length, type) in layout) { + using (var content = OpenInput(input)) { + output.Position = start; + content.CopyTo(output); + } + editor.AddPartition(start, length, type, scheme == PartitionScheme.Gpt ? GptLabel(input.ArchiveName) : null); + } + output.Flush(); + } + + /// A GPT name from the input's file name: without extension and PartitionN_ prefix, at most 36 UTF-16 units. + private static string GptLabel(string archiveName) { + var stem = Path.GetFileNameWithoutExtension(archiveName.Replace('\\', '/')); + stem = PartitionName.Replace(stem, ""); + return stem.Length > 36 ? stem[..36] : stem; + } + + private static Stream OpenInput(ArchiveInputInfo input) + => input.InMemoryContent is { } bytes + ? new MemoryStream(bytes, writable: false) + : new FileStream(input.FullPath, FileMode.Open, FileAccess.Read, FileShare.Read); + + /// The partition type an image's filesystem calls for, or null when no filesystem is recognised. + private static PartitionType? DetectPartitionType(Stream image) { + if (image.Length < 512) return null; + return InnerFsDetector.Detect(image)?.Id switch { + null => null, + "Fat" => PartitionType.Fat32Lba, + "ExFat" or "Ntfs" or "Hpfs" => PartitionType.NtfsExfat, + "HfsPlus" or "Hfs" => PartitionType.AppleHfsPlus, + "Apfs" => PartitionType.AppleApfs, + _ => PartitionType.Linux, + }; + } + /// Exposes List/Extract without the OpenEntry override, to reach the interface default. private sealed class ExtractingView(PartitionedDiskFormatDescriptor owner) : IArchiveFormatOperations { public List List(Stream stream, string? password) => owner.List(stream, password); @@ -131,9 +316,6 @@ public void Extract(Stream stream, string outputDir, string? password, string[]? => owner.Extract(stream, outputDir, password, files); } - private static long ClampedSize(Stream disk, PartitionEntry part) - => Math.Max(0, Math.Min(part.Size, disk.Length - part.StartOffset)); - /// Read-only view of a partition window: browsing must never write to the disk. private sealed class ReadOnlyWindow(Stream inner) : Stream { public override bool CanRead => inner.CanRead; diff --git a/Compression.Lib/FormatDetector.DiskImage.cs b/Compression.Lib/FormatDetector.DiskImage.cs index ab71f72b6..c5774e52f 100644 --- a/Compression.Lib/FormatDetector.DiskImage.cs +++ b/Compression.Lib/FormatDetector.DiskImage.cs @@ -64,12 +64,15 @@ private static IFormatDescriptor[] FilesystemDescriptors { /// virtual-disk containers, compressed streams, filesystems that start at byte 0; /// ISO 9660 / UDF volume descriptors at 32 KiB — ahead of any partition /// table, so a hybrid ISO whose system area carries an MBR or GPT browses as the ISO; - /// a GPT header whose CRC32 matches → ; + /// a GPT header whose CRC32 matches, at 512- or 4096-byte logical blocks → + /// ; /// sector 0 ending in 0x55AA: a validated MBR table versus a volume boot record /// (FAT BPB, NTFS/exFAT/… OEM signature). A valid table with no boot record is the disk; a boot /// record with no valid table is the filesystem; when both parse, the disk wins only if one of /// its partitions holds a recognisable filesystem; /// an Apple Partition Map → ; + /// a checksummed SGI volume header or Sun label, a stand-alone BSD disklabel (both magics) or a checksummed + /// Amiga Rigid Disk Block → ; /// when is set, a filesystem superblock /// signature (≥ 0.75 confidence) anywhere up to the deepest registered one (ZFS at 128 KiB). /// @@ -105,6 +108,8 @@ internal static Format DetectDiskImageByContent(Stream stream, bool includeSuper if (ApmParser.IsApm(head)) return Format.PartitionedDisk; + if (PartitionTableDetector.HasSelfValidatingLabel(stream)) return Format.PartitionedDisk; + if (!includeSuperblocks) return Format.Unknown; var deep = (int)Math.Min(FormatDetector._maxFilesystemMagicExtent, length); diff --git a/Compression.Lib/FormatDetector.cs b/Compression.Lib/FormatDetector.cs index 25df6b418..849934333 100644 --- a/Compression.Lib/FormatDetector.cs +++ b/Compression.Lib/FormatDetector.cs @@ -152,7 +152,7 @@ private static Format DetectByMagicFromFile(string path) { } /// Get the descriptor for a format, or null if no descriptor exists (Sfx, Iso, Udf). - private static IFormatDescriptor? GetDesc(Format f) { + internal static IFormatDescriptor? GetDesc(Format f) { EnsureRegistryMapped(); return FormatDetector._formatToDesc!.GetValueOrDefault(f); } diff --git a/Compression.Lib/NestedStreamResolver.cs b/Compression.Lib/NestedStreamResolver.cs index bafaf9f4f..bf6183c91 100644 --- a/Compression.Lib/NestedStreamResolver.cs +++ b/Compression.Lib/NestedStreamResolver.cs @@ -115,6 +115,10 @@ public void DisposeAll() { var desc = DetectFormat(current); if (desc == null) return null; + // A raw whole-disk image: the filesystems are inside its partitions, not at byte 0. + if (desc.Id == PartitionedDiskId) + return TryDescendPartitions(current, ownership, pathSegments, depth + 1); + // If it's a disk image container, try to open the virtual disk stream if (DiskImageFormatIds.Contains(desc.Id)) { var virtualStream = TryOpenVirtualDisk(current, desc.Id); @@ -155,13 +159,14 @@ public void DisposeAll() { if (detection.Scheme == "None" || detection.Partitions.Count == 0) return null; - // Try each partition, preferring the first that yields a writable FS - foreach (var part in detection.Partitions) { - if (part.Size <= 0 || part.StartOffset < 0 || part.StartOffset >= diskStream.Length) + // Try each partition — a BSD slice container contributes its slices — preferring the first + // that yields a writable FS. + foreach (var target in PartitionedDiskLister.EnumerateTargets(diskStream, detection)) { + var part = target.Partition; + if (target.Size <= 0 || target.Offset < 0 || target.Offset >= diskStream.Length) continue; - var actualSize = Math.Min(part.Size, diskStream.Length - part.StartOffset); - var partStream = new WritableSubStream(diskStream, part.StartOffset, actualSize); + var partStream = new WritableSubStream(diskStream, target.Offset, target.Size); // Don't add to ownership — it doesn't own the underlying stream var partSegments = new List(pathSegments) { @@ -175,48 +180,41 @@ public void DisposeAll() { return null; } + private const string PartitionedDiskId = "PartitionedDisk"; + + /// + /// Identifies the stream: a fixed VHD by its footer first (its first bytes are the guest + /// disk's own), then by content — the disk-image probe that recognises a raw partitioned + /// disk, and signatures as deep as the deepest registered one (superblocks at 32/64/128 KiB + /// included) rather than a fixed 4 KiB — then the FAT boot-sector heuristic. + /// private static IFormatDescriptor? DetectFormat(Stream stream) { if (stream.Length < 4) return null; var savedPos = stream.Position; try { - stream.Position = 0; - var headerSize = (int)Math.Min(4096, stream.Length); - var header = new byte[headerSize]; - var bytesRead = 0; - while (bytesRead < headerSize) { - var n = stream.Read(header, bytesRead, headerSize - bytesRead); - if (n == 0) break; - bytesRead += n; - } - - var headerSpan = header.AsSpan(0, bytesRead); - IFormatDescriptor? best = null; - var bestConfidence = 0.0; - - foreach (var desc in FormatRegistry.All) { - foreach (var sig in desc.MagicSignatures) { - if (MatchesMagic(headerSpan, sig) && sig.Confidence > bestConfidence) { - bestConfidence = sig.Confidence; - best = desc; - } - } - } - - if (best != null) return best; - // Heuristic: VHD fixed-disk detection — the "conectix" footer is at the // END of the file (offset = length - 512), not at offset 0. The registry // magic signature only checks offset 0 (which works for dynamic VHD). if (stream.Length >= 512 && stream.CanSeek) { stream.Position = stream.Length - 512; Span footer = stackalloc byte[8]; - if (stream.Read(footer) == 8 && "conectix"u8.SequenceEqual(footer)) { + if (stream.ReadAtLeast(footer, 8, throwOnEndOfStream: false) == 8 && "conectix"u8.SequenceEqual(footer)) { var vhdDesc = FormatRegistry.GetById("Vhd"); if (vhdDesc != null) return vhdDesc; } } + var format = FormatDetector.DetectByContent(stream); + if (format != FormatDetector.Format.Unknown && FormatDetector.GetDesc(format) is { } byContent) + return byContent; + + stream.Position = 0; + var headerSize = (int)Math.Min(4096, stream.Length); + var header = new byte[headerSize]; + var bytesRead = stream.ReadAtLeast(header, headerSize, throwOnEndOfStream: false); + var headerSpan = header.AsSpan(0, bytesRead); + // Heuristic: FAT detection (no magic, BPB-based) if (bytesRead >= 64 && headerSpan[0] is 0xEB or 0xE9) { var bytesPerSector = (int)(headerSpan[11] | (headerSpan[12] << 8)); @@ -234,18 +232,6 @@ public void DisposeAll() { } } - private static bool MatchesMagic(ReadOnlySpan header, MagicSignature sig) { - if (header.Length < sig.Offset + sig.Bytes.Length) return false; - var slice = header.Slice(sig.Offset, sig.Bytes.Length); - if (sig.Mask != null) { - for (var i = 0; i < sig.Bytes.Length; i++) - if ((slice[i] & sig.Mask[i]) != (sig.Bytes[i] & sig.Mask[i])) - return false; - return true; - } - return slice.SequenceEqual(sig.Bytes); - } - /// + /// Logical sector size the table counts in: 512, or 4096 on a 4Kn GPT disk. public sealed record PartitionListResult( PartitionScheme Scheme, - IReadOnlyList Partitions); + IReadOnlyList Partitions, + int SectorSize = PartitionEditor.SectorSize); /// /// Lists all partitions in the given image. @@ -60,7 +62,22 @@ public static PartitionListResult List(string imagePath) { .Select(p => new PartitionInfo(p.Index, p.Source, p.StartOffset, p.Size, p.TypeCode, p.TypeName, p.Name, p.IsActive)) .ToList() .AsReadOnly(); - return new PartitionListResult(editor.Scheme, snapshot); + return new PartitionListResult(editor.Scheme, snapshot, editor.LogicalSectorSize); + } + + /// + /// with the range given in the disk's own logical sectors — 512 bytes, or + /// 4096 on a 4Kn GPT disk — as fdisk/sgdisk count them. + /// + /// The logical sector size the LBAs were counted in. + public static int AddBySectors( + string imagePath, long startLba, long lengthSectors, + PartitionType type, string? label = null) { + ArgumentOutOfRangeException.ThrowIfNegative(startLba); + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(lengthSectors); + var sectorSize = List(imagePath).SectorSize; + Add(imagePath, checked(startLba * sectorSize), checked(lengthSectors * sectorSize), type, label); + return sectorSize; } /// diff --git a/Compression.NativeUI/Views/PartitionDialogs.cs b/Compression.NativeUI/Views/PartitionDialogs.cs index 5622e1bc0..254f9a8ba 100644 --- a/Compression.NativeUI/Views/PartitionDialogs.cs +++ b/Compression.NativeUI/Views/PartitionDialogs.cs @@ -14,6 +14,7 @@ internal sealed class AddPartitionDialog : Form { private const int LabelWidth = 120; private readonly long _diskBytes; + private readonly int _sectorSize; private readonly Label _heading = new(); private readonly TextBox _startLba = new(); private readonly TextBox _lengthSectors = new(); @@ -24,8 +25,9 @@ internal sealed class AddPartitionDialog : Form { private readonly Button _cancel = new() { Text = "Cancel", DialogResult = DialogResult.Cancel }; private readonly ToolTip _toolTips = new(); - public AddPartitionDialog(long diskBytes, bool isLogical) { + public AddPartitionDialog(long diskBytes, bool isLogical, int sectorSize = PartitionEditor.SectorSize) { this._diskBytes = diskBytes; + this._sectorSize = sectorSize; this.Text = "Add Partition"; this.ClientSize = new(460, 340); @@ -46,18 +48,18 @@ public AddPartitionDialog(long diskBytes, bool isLogical) { } this._type.SelectedItem = PartitionType.Linux; - // Start at sector 2048 (one mebibyte in), leaving room for the MBR, and suggest either the + // Start one mebibyte in (sector 2048, or 256 on a 4Kn disk), leaving room for the table, and suggest either the // first 64 MiB or whatever is left if the disk is smaller. - const int DefaultStartLba = 2048; - var totalLba = Math.Max(0, diskBytes / PartitionEditor.SectorSize); - var suggestedLength = Math.Max(1, Math.Min(64L * 1024 * 1024 / PartitionEditor.SectorSize, Math.Max(1, totalLba - DefaultStartLba))); + var defaultStartLba = 1024 * 1024 / sectorSize; + var totalLba = Math.Max(0, diskBytes / sectorSize); + var suggestedLength = Math.Max(1, Math.Min(64L * 1024 * 1024 / sectorSize, Math.Max(1, totalLba - defaultStartLba))); - this._startLba.Text = DefaultStartLba.ToString(); + this._startLba.Text = defaultStartLba.ToString(); this._lengthSectors.Text = suggestedLength.ToString(); - this._hint.Text = $"Disk size: {diskBytes:N0} bytes ({totalLba:N0} sectors of 512 B)."; + this._hint.Text = $"Disk size: {diskBytes:N0} bytes ({totalLba:N0} sectors of {sectorSize} B)."; - this._toolTips.SetToolTip(this._startLba, "Sector index from the start of the disk. Each sector is 512 bytes."); - this._toolTips.SetToolTip(this._lengthSectors, "Number of 512-byte sectors. Multiply by 512 to get bytes."); + this._toolTips.SetToolTip(this._startLba, $"Sector index from the start of the disk. Each sector is {sectorSize} bytes."); + this._toolTips.SetToolTip(this._lengthSectors, $"Number of {sectorSize}-byte sectors. Multiply by {sectorSize} to get bytes."); this._ok.Click += (_, _) => this.OnOk(); this._cancel.Click += (_, _) => this.Close(); @@ -110,8 +112,8 @@ private void OnOk() { return; } - var startBytes = startLba * PartitionEditor.SectorSize; - var lengthBytes = lengthLba * PartitionEditor.SectorSize; + var startBytes = startLba * this._sectorSize; + var lengthBytes = lengthLba * this._sectorSize; if (startBytes + lengthBytes > this._diskBytes) { MessageBox.Show(this, $"Partition end ({startBytes + lengthBytes:N0}) exceeds disk size ({this._diskBytes:N0}).", diff --git a/Compression.NativeUI/Views/PartitionsWindow.cs b/Compression.NativeUI/Views/PartitionsWindow.cs index 5005e789f..6f484e4c4 100644 --- a/Compression.NativeUI/Views/PartitionsWindow.cs +++ b/Compression.NativeUI/Views/PartitionsWindow.cs @@ -310,7 +310,7 @@ private void UpdateSelectionActions() { private void OnAdd(bool isLogical) { if (this._editor is not { } editor || this._guestStream is not { } guest) return; - var dialog = new AddPartitionDialog(guest.Length, isLogical); + var dialog = new AddPartitionDialog(guest.Length, isLogical, editor.LogicalSectorSize); if (dialog.ShowDialog(this) != DialogResult.OK) return; try { diff --git a/Compression.Tests/Cli/PartitionCliTests.cs b/Compression.Tests/Cli/PartitionCliTests.cs index d22ce552d..b06478637 100644 --- a/Compression.Tests/Cli/PartitionCliTests.cs +++ b/Compression.Tests/Cli/PartitionCliTests.cs @@ -267,4 +267,61 @@ public void Verify_OnBlankImage_ReportsNoScheme() { if (File.Exists(path)) File.Delete(path); } } + + // ── Logical sector size ───────────────────────────────────────────── + + private static string CreateEmptyGpt(int sectorSize, int sectors) { + var path = Path.Combine(Path.GetTempPath(), $"cwb-partcli-{Guid.NewGuid():N}.partcli-test"); + using (var fs = new FileStream(path, FileMode.CreateNew, FileAccess.ReadWrite)) { + fs.SetLength((long)sectorSize * sectors); + PartitionEditor.CreateEmpty(fs, PartitionScheme.Gpt, sectorSize); + } + return path; + } + + [TestCase(512, 2048L)] + [TestCase(4096, 256L)] + [Category("HappyPath")] + public void Given_GptDisk_When_AddedBySectors_Then_LbasCountInTheDisksOwnSectorSize(int sectorSize, long startLba) { + var path = CreateEmptyGpt(sectorSize, 4096); + try { + var used = PartitionOperations.AddBySectors(path, startLba, 1024, PartitionType.Linux, "data"); + + var result = PartitionOperations.List(path); + Assert.That(used, Is.EqualTo(sectorSize)); + Assert.That(result.SectorSize, Is.EqualTo(sectorSize)); + Assert.That(result.Partitions.Single().StartOffset, Is.EqualTo(startLba * sectorSize)); + Assert.That(result.Partitions.Single().Size, Is.EqualTo(1024L * sectorSize)); + } finally { + if (File.Exists(path)) File.Delete(path); + } + } + + [Test, Category("Boundary")] + public void Given_4KnGpt_When_AddedPastTheLastUsableSector_Then_RefusedAndTableUnchanged() { + var path = CreateEmptyGpt(4096, 1024); + try { + var before = File.ReadAllBytes(path); + // 1024 blocks: LastUsableLBA is 1024 - 4 entry blocks - 1 header - 1 = 1018. + Assert.That(() => PartitionOperations.AddBySectors(path, 256, 1018 - 256 + 2, PartitionType.Linux), + Throws.InvalidOperationException); + Assert.That(File.ReadAllBytes(path), Is.EqualTo(before)); + Assert.That(() => PartitionOperations.AddBySectors(path, 256, 1018 - 256 + 1, PartitionType.Linux), Throws.Nothing); + } finally { + if (File.Exists(path)) File.Delete(path); + } + } + + [TestCase(-1L, 8L)] + [TestCase(256L, 0L)] + [Category("ErrorPath")] + public void Given_NegativeStartOrEmptyLength_When_AddedBySectors_Then_Refused(long startLba, long length) { + var path = CreateEmptyGpt(4096, 1024); + try { + Assert.That(() => PartitionOperations.AddBySectors(path, startLba, length, PartitionType.Linux), + Throws.InstanceOf()); + } finally { + if (File.Exists(path)) File.Delete(path); + } + } } From dfd763c9d37fdde7b1598aab108fcfc931e19f88 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 26 Oct 2026 12:07:04 +0000 Subject: [PATCH 20/34] * refresh the application screenshot --- screenshots/main-window.png | Bin 65657 -> 66093 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/screenshots/main-window.png b/screenshots/main-window.png index e3795c0d04a8041389e0aaa3edc8cc5c89e36820..bf88801e9a99bf1f40e4ecad6833433b3753c894 100644 GIT binary patch literal 66093 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 literal 65657 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

J-t(C?D*vNbSd$Mpldw%6G^3`$t6)P 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 O*JNyHeySLKDeAvN*8HCU From c6d48b5733a749ea6a401af0d4b881d27b6c0a69 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 26 Oct 2026 12:07:21 +0000 Subject: [PATCH 21/34] * refresh the archive browser screenshot --- docs/screenshots/archive-browser.png | Bin 66518 -> 65657 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/screenshots/archive-browser.png b/docs/screenshots/archive-browser.png index de88d7dd43f38a733fe5557dd0e66b254aa140ee..e3795c0d04a8041389e0aaa3edc8cc5c89e36820 100644 GIT binary patch literal 65657 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

J-t(C?D*vNbSd$Mpldw%6G^3`$t6)P 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 O*JNyHeySLKDeAvN*8HCU literal 66518 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 Kj%4xcAO8X%gq{lk From 1ab5fe62eb4155d15b83ceb5150c9161790835bc Mon Sep 17 00:00:00 2001 From: Hawkynt Date: Mon, 26 Oct 2026 13:07:39 +0100 Subject: [PATCH 22/34] * CVF codecs: clean-room DS/JM/SQ reimplementation, SD-4 encoder with back-references * CvfLzCodec (DS-0-x, JM-0-x, SQ-0-0 and the Stacker dispatch) is replaced by an independent implementation (MsdblLzCodec + ClusterMatchFinder) written from docs/CVF-CODECS-ON-DISK.md and the dmsdos reference vectors. Public API unchanged; CvfLzMethod moves to its own file. Clean-room reimplementation from docs/CVF-CODECS-ON-DISK.md; the implementer did not read the previous code or dmsdos sources (rung 3/4 spec + black-box oracle). * The DS/JM encoder now writes a sync after every 512 decoded bytes and never lets a match cross a 512-byte boundary - measured in all 16 dmsdos DS/JM reference streams and recorded in the spec. A sync followed only by zero padding ends a short cluster (tail reads zero). * Auto in the codec keeps the smaller of DS and JM; GenuineDvr3Writer's Auto also tries SQ. + Sd4Codec rewritten from the spec: the encoder emits short/long matches with every length extension and run-coded code-length tables, codes built by package-merge (optimal, complete, 15-bit limit). Within 10% of the dmsdos SD-4 encoder on the five reference samples; dmsdos reads our SD-4 volumes (and 15-bit-deep main codes) byte-exact. + BB_StackerSd4 building block. # Stacker: a compressed run that needs every sector of its cluster reads as stored (AMAP has no flag for it), so ForceCompress wrote clusters dmsdos and our reader returned verbatim. Such a cluster is now stored raw; verified by dmsdos and our reader. + DmsdosOracle: the dmsdos gates run through WSL on Windows as well as natively on Linux; the existing Stacker/DriveSpace 3/DoubleSpace driver-proof tests use it. + Boundary tests for every CVF codec: empty/1-byte clusters, 512/8K/32K clusters, incompressible data, maximum match lengths and distances, malformed streams. ! Fragmented DriveSpace 3 clusters and fragmented/suballocated Stacker clusters are still refused; they follow in a separate change. --- .../StackerSd4/StackerSd4BuildingBlock.cs | 39 ++ .../FileSystems/Cvf/ClusterMatchFinder.cs | 81 +++ .../FileSystems/Cvf/CvfLzCodec.cs | 387 +++-------- .../FileSystems/Cvf/CvfLzMethod.cs | 23 + .../FileSystems/Cvf/MsdblLzCodec.cs | 268 ++++++++ .../FileSystems/Cvf/PackageMerge.cs | 65 ++ Compression.Core/FileSystems/Cvf/Sd4Codec.cs | 599 ++++++++++-------- Compression.Core/REFERENCE.md | 54 +- .../Cvf/CvfCodecBoundaryTests.cs | 188 ++++++ .../DoubleSpace/DoubleSpaceHugeVolumeTests.cs | 8 +- .../DriveSpace3GenuineDmsdosTests.cs | 9 +- .../DriveSpace3GenuineMaintenanceTests.cs | 8 +- .../DriveSpace3/DriveSpace3HugeVolumeTests.cs | 8 +- Compression.Tests/ExternalFsInteropTests.cs | 9 +- .../Stacker/GenuineStackerCodecOracleTests.cs | 187 ++++++ .../Stacker/GenuineStackerDmsdosTests.cs | 9 +- .../Stacker/StackerHugeVolumeTests.cs | 8 +- Compression.Tests/Support/DmsdosOracle.cs | 53 ++ .../GenuineDvr3Writer.cs | 7 +- .../FileSystem.Stacker/FORMAT-NOTES.md | 2 +- .../GenuineStackerWriter.cs | 9 +- .../StackerFormatDescriptor.cs | 3 +- Hawkynt.FileFormats.FileSystems/REFERENCE.md | 2 +- docs/CVF-CODECS-ON-DISK.md | 26 +- 24 files changed, 1452 insertions(+), 600 deletions(-) create mode 100644 Compression.Core/Dictionary/StackerSd4/StackerSd4BuildingBlock.cs create mode 100644 Compression.Core/FileSystems/Cvf/ClusterMatchFinder.cs create mode 100644 Compression.Core/FileSystems/Cvf/CvfLzMethod.cs create mode 100644 Compression.Core/FileSystems/Cvf/MsdblLzCodec.cs create mode 100644 Compression.Core/FileSystems/Cvf/PackageMerge.cs create mode 100644 Compression.Tests/Cvf/CvfCodecBoundaryTests.cs create mode 100644 Compression.Tests/Stacker/GenuineStackerCodecOracleTests.cs create mode 100644 Compression.Tests/Support/DmsdosOracle.cs diff --git a/Compression.Core/Dictionary/StackerSd4/StackerSd4BuildingBlock.cs b/Compression.Core/Dictionary/StackerSd4/StackerSd4BuildingBlock.cs new file mode 100644 index 000000000..7fb8ff544 --- /dev/null +++ b/Compression.Core/Dictionary/StackerSd4/StackerSd4BuildingBlock.cs @@ -0,0 +1,39 @@ +using System.Buffers.Binary; +using Compression.Core.FileSystems.Cvf; +using Compression.Registry; + +namespace Compression.Core.Dictionary.StackerSd4; + +///

+/// Stacker 4's SD-4 codec as a raw primitive: LZ77 over a 2720-byte window, Huffman-coded with a +/// run-length coded code table (docs/CVF-CODECS-ON-DISK.md). The building-block wrapper stores +/// the uncompressed size (32-bit little-endian) before one SD-4 stream covering the whole input. +/// +public sealed class StackerSd4BuildingBlock : IBuildingBlock { + /// + public string Id => "BB_StackerSd4"; + /// + public string DisplayName => "Stacker SD-4"; + /// + public string Description => "Stac Stacker 4 cluster codec: LZ77 (2720-byte window) with canonical Huffman codes and a run-length coded table"; + /// + public AlgorithmFamily Family => AlgorithmFamily.Dictionary; + + /// + public byte[] Compress(ReadOnlySpan data) { + var stream = Sd4Codec.Encode(data, level: 3); + var result = new byte[4 + stream.Length]; + BinaryPrimitives.WriteInt32LittleEndian(result, data.Length); + stream.CopyTo(result, 4); + return result; + } + + /// + public byte[] Decompress(ReadOnlySpan data) { + if (data.Length < 4) throw new InvalidDataException("Stacker SD-4: missing size header."); + var size = BinaryPrimitives.ReadInt32LittleEndian(data); + if (size < 0) throw new InvalidDataException("Stacker SD-4: negative size."); + var stream = data[4..].ToArray(); + return Sd4Codec.Decode(stream, stream.Length, size); + } +} diff --git a/Compression.Core/FileSystems/Cvf/ClusterMatchFinder.cs b/Compression.Core/FileSystems/Cvf/ClusterMatchFinder.cs new file mode 100644 index 000000000..54708d990 --- /dev/null +++ b/Compression.Core/FileSystems/Cvf/ClusterMatchFinder.cs @@ -0,0 +1,81 @@ +namespace Compression.Core.FileSystems.Cvf; + +/// +/// Hash-chain LZ77 match finder over one cluster, shared by the DS/JM and SD-4 encoders. Every match +/// it reports lies wholly inside the cluster: no CVF codec carries a dictionary from one cluster to +/// the next. +/// +internal sealed class ClusterMatchFinder { + private const int HashBits = 13; + private const int MinLength = 3; + + private readonly byte[] _data; + private readonly int[] _head = new int[1 << HashBits]; + private readonly int[] _previous; + private readonly int _maxDistance; + private readonly int _maxLength; + private readonly int _maxChain; + private readonly int _niceLength; + private int _inserted; + + /// The cluster. + /// The farthest distance the format can express. + /// The longest match the format can express. + /// Search effort, 1 (fastest) and up: how many earlier positions with the same + /// hash are tried, and how long a match must be to end the search early. + public ClusterMatchFinder(byte[] data, int maxDistance, int maxLength, int level) { + this._data = data; + this._previous = new int[data.Length]; + this._maxDistance = maxDistance; + this._maxLength = maxLength; + (this._maxChain, this._niceLength) = level switch { + <= 1 => (16, 64), + 2 => (64, 258), + 3 => (256, 1024), + _ => (1024, int.MaxValue), + }; + Array.Fill(this._head, -1); + } + + /// + /// The longest match for the bytes at , preferring the nearest on ties; + /// (0, 0) when there is none of at least three bytes. Positions before + /// are indexed first, so calls must come in ascending order. + /// + /// Where the match starts. + /// Exclusive bound the match may not run past; the cluster end when omitted. + public (int Length, int Distance) Find(int position, int end = int.MaxValue) { + this.IndexUpTo(position); + var data = this._data; + var limit = Math.Min(this._maxLength, Math.Min(data.Length, end) - position); + if (limit < MinLength) return (0, 0); + + int bestLength = 0, bestDistance = 0; + var candidate = this._head[Hash(data, position)]; + for (var chain = this._maxChain; candidate >= 0 && chain > 0; --chain, candidate = this._previous[candidate]) { + var distance = position - candidate; + if (distance > this._maxDistance) break; + if (data[candidate + bestLength] != data[position + bestLength]) continue; + + var length = 0; + while (length < limit && data[candidate + length] == data[position + length]) ++length; + if (length <= bestLength) continue; + + (bestLength, bestDistance) = (length, distance); + if (length == limit || length >= this._niceLength) break; + } + return bestLength >= MinLength ? (bestLength, bestDistance) : (0, 0); + } + + private void IndexUpTo(int position) { + var last = Math.Min(position, this._data.Length - MinLength + 1); + for (; this._inserted < last; ++this._inserted) { + var h = Hash(this._data, this._inserted); + this._previous[this._inserted] = this._head[h]; + this._head[h] = this._inserted; + } + } + + private static int Hash(byte[] data, int position) + => (int)(((uint)data[position] << 16 | (uint)data[position + 1] << 8 | data[position + 2]) * 2654435761u >> (32 - HashBits)); +} diff --git a/Compression.Core/FileSystems/Cvf/CvfLzCodec.cs b/Compression.Core/FileSystems/Cvf/CvfLzCodec.cs index 414453bd2..118ed3a0d 100644 --- a/Compression.Core/FileSystems/Cvf/CvfLzCodec.cs +++ b/Compression.Core/FileSystems/Cvf/CvfLzCodec.cs @@ -1,324 +1,135 @@ -#pragma warning disable CS1591 -using System; -using System.Collections.Generic; -using System.IO; +using System.Buffers.Binary; using Compression.Core.Deflate; -using Compression.Core.Streams; namespace Compression.Core.FileSystems.Cvf; /// -/// Per-cluster compression methods of the DoubleSpace/DriveSpace/Stacker CVF -/// family (see docs/CVF-CODECS-ON-DISK.md). and -/// are the Stacker 3 and Stacker 4 codecs. -/// -public enum CvfLzMethod { Stored, Ds, Jm, Auto, Sq, Sd4, Sd3 } - -/// -/// Genuine DoubleSpace/DriveSpace per-cluster compression codec (DS-0-x and -/// JM-0-x), verified byte-exact against the independent dmsdos decoder. The -/// bitstream packs bits LSB-first into little-endian 16-bit words; a cluster -/// payload is a 16-bit magic ("DS"=0x5344 / "MJ"=0x4D4A) + 16-bit -/// version + an LZ77 token stream terminated by the 0x113f sync. +/// Per-cluster codecs of the DoubleSpace / DriveSpace / DriveSpace 3 / Stacker compressed volumes, +/// implemented from docs/CVF-CODECS-ON-DISK.md. An MSDBL cluster payload starts with a 16-bit +/// magic and a 16-bit version — "DS" (DS-0-x), "JM" (JM-0-x) or "SQ" (SQ-0-0, +/// raw DEFLATE) — followed by the token stream; a Stacker payload carries no method field and is +/// recognised by its first word. /// public static class CvfLzCodec { - public const uint DS_0_0 = 0x00005344; - public const uint JM_0_0 = 0x00004D4A; - public const uint SQ_0_0 = 0x00005153; - - private const int DsMagic = 0x5344; - private const int JmMagic = 0x4D4A; - private const int SqMagic = 0x5153; - private const int Sync = 0x113f; - private const int MaxLen = 512; // safe for both (DS replen<=512, JM<=513) - - // ── bit writer: bit i -> word[i/16] bit i%16, words stored little-endian ─── - private sealed class BitWriter { - private readonly List _words = []; - private int _bit; - public void Put(int value, int n) { - for (var i = 0; i < n; i++, this._bit++) { - var w = this._bit >> 4; - while (this._words.Count <= w) this._words.Add(0); - if (((value >> i) & 1) != 0) this._words[w] |= (ushort)(1 << (this._bit & 15)); - } - } - public byte[] ToBytes() { - var b = new byte[this._words.Count * 2]; - for (var i = 0; i < this._words.Count; i++) { - b[i * 2] = (byte)this._words[i]; - b[i * 2 + 1] = (byte)(this._words[i] >> 8); - } - return b; - } - } - - // ── bit reader: faithful port of dmsdos dblb_* ───────────────────────────── - private sealed class BitReader { - private static readonly uint[] Mask = - [0x0, 0x1, 0x3, 0x7, 0xF, 0x1F, 0x3F, 0x7F, 0xFF, - 0x1FF, 0x3FF, 0x7FF, 0xFFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF]; - private readonly byte[] _d; - private readonly int _len; - private uint _buf; - private int _pb = 32; - private int _pd; - private readonly int _pe; - public BitReader(byte[] d, int len) { this._d = d; this._len = len; this._pe = (len + 1) >> 1; } - private ushort Word(int i) { - var o = i * 2; - var lo = o < this._len ? this._d[o] : 0; - var hi = o + 1 < this._len ? this._d[o + 1] : 0; - return (ushort)(lo | (hi << 8)); - } - public uint Peek() { - if (this._pb >= 16) { - this._buf >>= 16; this._pb -= 16; - if (this._pd < this._pe) this._buf |= (uint)Word(this._pd++) << 16; - } - return this._buf >> this._pb; - } - public void Skip(int n) => this._pb += n; - public uint ReadN(int n) { var u = this.Peek(); this._pb += n; return u & Mask[n]; } + /// Method header of a DS-0-0 payload, read as a little-endian 32-bit word. + public const uint DS_0_0 = 'D' | 'S' << 8; - public int RdOffs() { - var u = this.Peek(); - switch (u & 3) { - case 0: case 2: this._pb += 1 + 6; return (int)(63 & (u >> 1)); - case 1: this._pb += 2 + 8; return (int)((255 & (u >> 2)) + 64); - default: this._pb += 2 + 12; return (int)((4095 & (u >> 2)) + 320); - } - } - public int RdLen() { - var u = this.Peek(); - switch (u & 15) { - case 1: case 3: case 5: case 7: case 9: case 11: case 13: case 15: this._pb += 1; return 3; - case 2: case 6: case 10: case 14: this._pb += 3; return (int)((1 & (u >> 2)) + 4); - case 4: case 12: this._pb += 5; return (int)((3 & (u >> 3)) + 6); - case 8: this._pb += 7; return (int)((7 & (u >> 4)) + 10); - } - switch ((u >> 4) & 15) { - case 1: case 3: case 5: case 7: case 9: case 11: case 13: case 15: this._pb += 9; return (int)((15 & (u >> 5)) + 18); - case 2: case 6: case 10: case 14: this._pb += 11; return (int)((31 & (u >> 6)) + 34); - case 4: case 12: this._pb += 13; return (int)((63 & (u >> 7)) + 66); - case 8: this._pb += 15; return (int)((127 & (u >> 8)) + 130); - } - this._pb += 9; - if ((u & 256) != 0) return (int)this.ReadN(8) + 258; - return -1; - } - } + /// Method header of a JM-0-0 payload, read as a little-endian 32-bit word. + public const uint JM_0_0 = 'J' | 'M' << 8; - // ── length / offset encoders (inverse of RdLen / RdOffs) ─────────────────── - private static void PutLen(BitWriter w, int v) { // v = rdlen value, 3..513 - var x = v - 2; - var c = 0; - while ((1 << (c + 1)) <= x) c++; - w.Put(1 << c, c + 1); // c zero bits then a 1 - w.Put(x - (1 << c), c); // c data bits - } - - private static void PutOffs(BitWriter w, int off) { - if (off <= 63) { w.Put(0, 1); w.Put(off, 6); } - else if (off <= 319) { w.Put(1, 2); w.Put(off - 64, 8); } - else { w.Put(3, 2); w.Put(off - 320, 12); } - } + /// Method header of an SQ-0-0 payload, read as a little-endian 32-bit word. + public const uint SQ_0_0 = 'S' | 'Q' << 8; - // =========================================================================== - // Compression - // =========================================================================== + private const ushort Sd4Signature = 0x0081; + private const int HeaderBytes = 4; /// - /// Compresses one cluster. Returns the payload (4-byte method header + token - /// stream, padded to a 2-byte word), or null if it would not be smaller - /// than the raw cluster (caller stores raw instead). + /// Compresses one cluster. Returns the payload, or when it would not be + /// smaller than the raw cluster (the caller then stores the cluster raw). /// public static byte[]? Compress(ReadOnlySpan data, CvfLzMethod method, int level) { - var bytes = Encode(data, method, level); - if (bytes is null) return null; - return bytes.Length < data.Length ? bytes : null; - } - - /// Encodes a cluster with the given method, always returning the - /// payload (4-byte header + token stream), or null for - /// / unsupported methods. The caller decides - /// whether the result fits the cluster's sector budget. - public static byte[]? Encode(ReadOnlySpan data, CvfLzMethod method, int level) { - switch (method) { - case CvfLzMethod.Ds: return CompressDs(data, level); - case CvfLzMethod.Jm: return CompressJm(data, level); - case CvfLzMethod.Sq: return CompressSq(data); - case CvfLzMethod.Sd4: return Sd4Codec.Encode(data); - case CvfLzMethod.Sd3: return Sd3Codec.Encode(data); - case CvfLzMethod.Auto: - // Per-cluster best-of: each cluster carries its own method header, so we - // pick whichever codec yields the smallest payload for this cluster. - byte[]?[] cands = [CompressDs(data, level), CompressJm(data, level), CompressSq(data)]; - byte[]? best = null; - foreach (var c in cands) if (c is not null && (best is null || c.Length < best.Length)) best = c; - return best; - default: return null; - } + var payload = Encode(data, method, level); + return payload is not null && payload.Length < data.Length ? payload : null; } - private static byte[] CompressDs(ReadOnlySpan data, int level) { - var w = new BitWriter(); - w.Put(DsMagic, 16); - w.Put(0, 16); // version 0 (DS-0-0) - var src = data.ToArray(); - var pos = 0; - while (pos < src.Length) { - var (mlen, moff) = FindMatch(src, pos, 4415, 2, level); - if (mlen >= 2) { - if (moff <= 63) w.Put(0 | (moff << 2), 8); - else if (moff <= 319) w.Put(3 | ((moff - 64) << 3), 11); - else w.Put(7 | ((moff - 320) << 3), 15); - PutLen(w, mlen + 1); // DS: copy length = rdlen - 1 - pos += mlen; - } else { - var b = src[pos++]; - if (b < 128) w.Put(2 | (b << 2), 9); - else w.Put(1 | ((b & 127) << 2), 9); - } - } - w.Put(7, 3); w.Put(Sync - 320, 12); // final sync - return w.ToBytes(); - } - - private static byte[] CompressJm(ReadOnlySpan data, int level) { - var w = new BitWriter(); - w.Put(JmMagic, 16); - w.Put(0, 16); // version 0 (JM-0-0) - var src = data.ToArray(); - var pos = 0; - while (pos < src.Length) { - var (mlen, moff) = FindMatch(src, pos, 4415, 3, level); - if (mlen >= 3) { - w.Put(1, 2); // match token - PutOffs(w, moff); - PutLen(w, mlen); // JM: copy length = rdlen - pos += mlen; - } else { - var b = src[pos++]; - if (b < 128) w.Put(b << 1, 8); - else w.Put(3 | ((b & 127) << 2), 9); - } - } - w.Put(1, 2); w.Put(3, 2); w.Put(Sync - 320, 12); // final sync (rdn(2)=1 + rdoffs 0x113f) - return w.ToBytes(); - } - - // SQ-0-0 (DriveSpace 3 "Ultra") is, byte-for-byte, "SQ" + 16-bit version + a - // raw RFC-1951 DEFLATE stream: dmsdos sq_dec's fixed-Huffman table, length and - // distance base/extra tables, and bit-reversed LSB-first packing all match - // DEFLATE exactly, so our Deflate encoder produces a genuine SQ body. - private static byte[] CompressSq(ReadOnlySpan data) { - using var ms = new MemoryStream(); - ms.WriteByte(0x53); ms.WriteByte(0x51); ms.WriteByte(0); ms.WriteByte(0); // "SQ", version 0 - using (var ds = new RawDeflateStream(ms, CompressionStreamMode.Compress, DeflateCompressionLevel.Default, leaveOpen: true)) - ds.Write(data); - return ms.ToArray(); - } - - private static byte[] DecompressSq(byte[] payload, int inLen, int outLen) { - using var ins = new MemoryStream(payload, 4, inLen - 4); - using var ds = new RawDeflateStream(ins, CompressionStreamMode.Decompress); - var outp = new byte[outLen]; - var n = 0; int r; - while (n < outLen && (r = ds.Read(outp, n, outLen - n)) > 0) n += r; - return outp; - } - - // Greedy longest-match finder. level scales the search depth (chain length). - private static (int Len, int Off) FindMatch(byte[] src, int pos, int maxOff, int minLen, int level) { - var bestLen = 0; var bestOff = 0; - var start = Math.Max(0, pos - maxOff); - var limit = Math.Min(MaxLen, src.Length - pos); - if (limit < minLen) return (0, 0); - var probes = level <= 0 ? 256 : level * 1024; - for (var cand = pos - 1; cand >= start && probes-- > 0; cand--) { - if (src[cand] != src[pos]) continue; - var l = 1; - while (l < limit && src[cand + l] == src[pos + l]) l++; - if (l > bestLen) { bestLen = l; bestOff = pos - cand; if (l >= limit) break; } - } - return bestLen >= minLen ? (bestLen, bestOff) : (0, 0); - } - - // =========================================================================== - // Decompression - // =========================================================================== + /// + /// Encodes a cluster with the given method and always returns the payload, or + /// for . The caller decides whether the + /// result fits the cluster's sector budget. keeps the smaller of DS + /// and JM. + /// + public static byte[]? Encode(ReadOnlySpan data, CvfLzMethod method, int level) => method switch { + CvfLzMethod.Ds => EncodeMsdbl(data, MsdblDialect.Ds, level), + CvfLzMethod.Jm => EncodeMsdbl(data, MsdblDialect.Jm, level), + CvfLzMethod.Auto => Smaller(EncodeMsdbl(data, MsdblDialect.Ds, level), EncodeMsdbl(data, MsdblDialect.Jm, level)), + CvfLzMethod.Sq => EncodeSq(data, level), + CvfLzMethod.Sd3 => Sd3Codec.Encode(data), + CvfLzMethod.Sd4 => Sd4Codec.Encode(data, level), + _ => null, + }; - /// Decompresses a cluster payload to exactly bytes. + /// + /// Decompresses an MSDBL cluster payload (DS, JM or SQ, told apart by its header) to exactly + /// bytes. + /// + /// Unknown method header or a malformed stream. public static byte[] Decompress(byte[] payload, int inLen, int outLen) { - var method = (uint)(payload[0] | (payload[1] << 8) | (payload[2] << 16) | (payload[3] << 24)); - var magic16 = (int)(method & 0xFFFF); - if (magic16 == SqMagic) return DecompressSq(payload, inLen, outLen); - if (magic16 == 0x0081) return Sd4Codec.Decode(payload, inLen, outLen); - var br = new BitReader(payload, inLen); - var read = br.ReadN(16); - if (read != (uint)magic16) throw new InvalidDataException($"CVF codec: bad magic 0x{read:X4}"); - br.ReadN(16); // version - var outp = new byte[outLen]; - var p = 0; - if (magic16 == DsMagic) DecodeDs(br, outp, ref p); - else if (magic16 == JmMagic) DecodeJm(br, outp, ref p); - else throw new InvalidDataException($"CVF codec: unsupported method 0x{method:X8}"); - return outp; + var input = payload.AsSpan(0, Math.Min(inLen, payload.Length)); + if (input.Length < HeaderBytes) throw new InvalidDataException("CVF: cluster payload shorter than its method header."); + + var output = new byte[outLen]; + var stream = input[HeaderBytes..]; + switch (BinaryPrimitives.ReadUInt16LittleEndian(input)) { + case (ushort)DS_0_0: MsdblLzCodec.Decode(stream, output, MsdblDialect.Ds, stopAtEndOfInput: false); break; + case (ushort)JM_0_0: MsdblLzCodec.Decode(stream, output, MsdblDialect.Jm, stopAtEndOfInput: false); break; + case (ushort)SQ_0_0: DecodeSq(stream, output); break; + case var magic: throw new InvalidDataException($"CVF: unknown cluster method header 0x{magic:X4}."); + } + return output; } /// /// Decompresses a Stacker cluster payload to bytes. Stacker records no /// method per cluster; the payload's first little-endian word selects it — 0x0081 SD-4, - /// 0x5344 ("DS") the DoubleSpace codec, anything else SD-3, whose first token is always a + /// 0x5344 ("DS") the DoubleSpace grammar, anything else SD-3, whose first token is always a /// literal and so can never begin with either word. /// + /// A malformed stream. public static byte[] DecompressStacker(byte[] payload, int inLen, int outLen) { - ArgumentNullException.ThrowIfNull(payload); - if (inLen < 2) throw new InvalidDataException("Stacker cluster payload is too short."); - return (payload[0] | (payload[1] << 8)) switch { - 0x0081 => Sd4Codec.Decode(payload, inLen, outLen), - DsMagic => Decompress(payload, inLen, outLen), - _ => Sd3Codec.Decode(payload.AsSpan(0, inLen), outLen), - }; + var input = payload.AsSpan(0, Math.Min(inLen, payload.Length)); + var word = input.Length >= 2 ? BinaryPrimitives.ReadUInt16LittleEndian(input) : -1; + switch (word) { + case Sd4Signature: + return Sd4Codec.Decode(payload, input.Length, outLen); + case (int)DS_0_0 when input.Length >= HeaderBytes: + var output = new byte[outLen]; + MsdblLzCodec.Decode(input[HeaderBytes..], output, MsdblDialect.Ds, stopAtEndOfInput: true); + return output; + default: + return Sd3Codec.Decode(input, outLen); + } } - private static void DecodeDs(BitReader br, byte[] outp, ref int p) { - while (p < outp.Length) { - var u = br.Peek(); - switch (u & 3) { - case 0: br.Skip(8); Copy(outp, ref p, (int)((u >> 2) & 63), br, -1); break; - case 1: br.Skip(9); outp[p++] = (byte)((u >> 2) | 128); break; - case 2: br.Skip(9); outp[p++] = (byte)((u >> 2) & 127); break; - default: - int off; - if ((u & 4) != 0) { br.Skip(15); off = (int)((u >> 3) & 4095) + 320; } - else { br.Skip(11); off = (int)((u >> 3) & 255) + 64; } - Copy(outp, ref p, off, br, -1); - break; - } - } + private static byte[] EncodeMsdbl(ReadOnlySpan data, MsdblDialect dialect, int level) { + var writer = new LsbBitWriter(); + WriteHeader(writer, dialect == MsdblDialect.Ds ? DS_0_0 : JM_0_0); + MsdblLzCodec.Encode(data, writer, dialect, level); + return writer.ToArray(alignment: 2); } - private static void DecodeJm(BitReader br, byte[] outp, ref int p) { - while (p < outp.Length) { - var u = br.Peek(); - switch (u & 3) { - case 0: case 2: br.Skip(8); outp[p++] = (byte)((u >> 1) & 127); break; - case 1: br.Skip(2); Copy(outp, ref p, br.RdOffs(), br, 0); break; - default: br.Skip(9); outp[p++] = (byte)(((u >> 2) & 127) | 128); break; + private static byte[] EncodeSq(ReadOnlySpan data, int level) { + var deflateLevel = level switch { + <= 1 => DeflateCompressionLevel.Fast, + 2 => DeflateCompressionLevel.Default, + 3 => DeflateCompressionLevel.Best, + _ => DeflateCompressionLevel.Maximum, + }; + var stream = DeflateCompressor.Compress(data, deflateLevel); + var payload = new byte[HeaderBytes + stream.Length + (stream.Length & 1)]; + BinaryPrimitives.WriteUInt32LittleEndian(payload, SQ_0_0); + stream.CopyTo(payload, HeaderBytes); + return payload; + } + + // The DEFLATE stream ends at its final block or when the cluster is full, whichever comes first. + private static void DecodeSq(ReadOnlySpan stream, Span output) { + try { + using var input = new MemoryStream(stream.ToArray(), writable: false); + var inflater = new DeflateDecompressor(input); + var filled = 0; + while (filled < output.Length) { + var n = inflater.Decompress(output[filled..]); + if (n == 0) break; + filled += n; } + } catch (Exception e) when (e is IOException or ArgumentException or IndexOutOfRangeException) { + throw new InvalidDataException("SQ: malformed DEFLATE stream.", e); } } - private static void Copy(byte[] outp, ref int p, int off, BitReader br, int k) { - if (off == Sync) return; // sync token mid-stream (shouldn't fill) - var len = br.RdLen() + k; - // Past the end of the input every token reads as a zero-distance, invalid-length match, so - // these checks also guarantee that a truncated stream terminates. - if (len <= 0) throw new InvalidDataException("CVF codec: invalid match length."); - if (off <= 0 || off > p) throw new InvalidDataException("CVF codec: match reaches before the start of the cluster."); - var src = p - off; - for (var i = 0; i < len && p < outp.Length; i++) outp[p++] = outp[src + i]; + private static void WriteHeader(LsbBitWriter writer, uint header) { + for (var i = 0; i < HeaderBytes; ++i) writer.WriteByte((byte)(header >> (8 * i))); } + + private static byte[] Smaller(byte[] a, byte[] b) => b.Length < a.Length ? b : a; } diff --git a/Compression.Core/FileSystems/Cvf/CvfLzMethod.cs b/Compression.Core/FileSystems/Cvf/CvfLzMethod.cs new file mode 100644 index 000000000..acfcc979d --- /dev/null +++ b/Compression.Core/FileSystems/Cvf/CvfLzMethod.cs @@ -0,0 +1,23 @@ +namespace Compression.Core.FileSystems.Cvf; + +/// +/// Per-cluster compression methods of the DoubleSpace/DriveSpace/Stacker CVF family (see +/// docs/CVF-CODECS-ON-DISK.md). and are the Stacker 3 and +/// Stacker 4 codecs. +/// +public enum CvfLzMethod { + /// No compression; the cluster is stored as is. + Stored = 0, + /// DoubleSpace DS-0-x LZ77. + Ds = 1, + /// DriveSpace JM-0-x LZ77 (DriveSpace 3 Normal/High). + Jm = 2, + /// Per cluster the smaller of and , both of which every MSDBL volume reads. + Auto = 3, + /// DriveSpace 3 SQ-0-0 (Ultra): a raw DEFLATE stream behind the method header. + Sq = 4, + /// Stacker 4 SD-4: Huffman-coded LZ77 behind the header word 0x0081. + Sd4 = 5, + /// Stacker 3 SD-3: a bare LZS stream closed by a check byte. + Sd3 = 6, +} diff --git a/Compression.Core/FileSystems/Cvf/MsdblLzCodec.cs b/Compression.Core/FileSystems/Cvf/MsdblLzCodec.cs new file mode 100644 index 000000000..ba8d954ca --- /dev/null +++ b/Compression.Core/FileSystems/Cvf/MsdblLzCodec.cs @@ -0,0 +1,268 @@ +namespace Compression.Core.FileSystems.Cvf; + +/// The two Microsoft LZ77 token grammars of docs/CVF-CODECS-ON-DISK.md. +internal enum MsdblDialect { + /// DoubleSpace DS-0-x: two-bit literal tags, matches copy one byte less than their length code. + Ds, + /// DriveSpace JM-0-x: one-bit tag for low literals, matches copy their full length code. + Jm, +} + +/// +/// The DS-0-x / JM-0-x token stream (without the 4-byte method header): bits LSB-first from +/// consecutive bytes, literals and back-references of up to 4414 bytes distance, and the sync marker +/// (distance 4415) that closes every cluster. +/// +internal static class MsdblLzCodec { + /// The all-ones 12-bit long distance, which is the sync marker rather than a match. + public const int SyncDistance = 320 + 0xFFF; + + /// The farthest real match. + public const int MaxDistance = SyncDistance - 1; + + /// The longest length code: nine-step prefix, 2 + 2^8 + 255. + private const int MaxLengthCode = 513; + + private const int MaxLengthPrefix = 8; + + /// Decoded bytes between two sync markers in an encoded cluster: one sector. + private const int SyncInterval = 512; + + // ── decoding ──────────────────────────────────────────────────────────── + + /// + /// Decodes the token stream in until is full. + /// + /// The token stream, starting right after the method header. + /// The cluster; bytes not written stay as they are (zero). + /// DS or JM. + /// + /// Stacker's use of the DS grammar: a stream that runs out (or reaches its zero padding) before the + /// cluster is full ends there instead of being an error. + /// + /// The number of bytes decoded. + /// The stream is malformed or ends early. + public static int Decode(ReadOnlySpan stream, Span output, MsdblDialect dialect, bool stopAtEndOfInput) { + var reader = new LsbBitReader(stream); + var position = 0; + while (position < output.Length) { + var token = dialect == MsdblDialect.Ds ? ReadDsToken(ref reader) : ReadJmToken(ref reader); + if (reader.Overran) { + if (stopAtEndOfInput) break; + throw new InvalidDataException($"{dialect}: token stream ends after {position} of {output.Length} bytes."); + } + + if (token.Distance == 0) { // literal (a real match never has distance 0) + if (token.Length != 0) { + if (stopAtEndOfInput) break; // zero padding read as a distance-0 match + throw new InvalidDataException($"{dialect}: match with distance 0."); + } + output[position++] = token.Literal; + continue; + } + + if (token.Distance == SyncDistance) { + // A cluster whose data ends before its nominal size is closed by a sync with nothing but + // padding behind it; the remainder of the cluster reads as zero. + if (reader.RestIsZero) break; + continue; + } + + var length = ReadLengthCode(ref reader); + if (reader.Overran) { + if (stopAtEndOfInput) break; + throw new InvalidDataException($"{dialect}: token stream ends inside a match."); + } + if (length < 0) throw new InvalidDataException($"{dialect}: length code has more than {MaxLengthPrefix} leading zeros."); + if (dialect == MsdblDialect.Ds) --length; + if (token.Distance > position) throw new InvalidDataException($"{dialect}: match reaches before the start of the cluster."); + + length = Math.Min(length, output.Length - position); + for (var end = position + length; position < end; ++position) + output[position] = output[position - token.Distance]; + } + return position; + } + + /// A literal ( 0, 0) or the distance of a match/sync. + private readonly record struct Token(byte Literal, int Distance, int Length = 0); + + private static Token ReadDsToken(ref LsbBitReader reader) { + var first = reader.Read(1); + var second = reader.Read(1); + return (first, second) switch { + (0, 0) => Match(reader.Read(6)), + (1, 0) => new((byte)(0x80 | reader.Read(7)), 0), + (0, 1) => new((byte)reader.Read(7), 0), + _ => reader.Read(1) == 0 ? Match(64 + reader.Read(8)) : Match(320 + reader.Read(12)), + }; + } + + private static Token ReadJmToken(ref LsbBitReader reader) { + if (reader.Read(1) == 0) return new((byte)reader.Read(7), 0); + if (reader.Read(1) == 1) return new((byte)(0x80 | reader.Read(7)), 0); + if (reader.Read(1) == 0) return Match(reader.Read(6)); + return reader.Read(1) == 0 ? Match(64 + reader.Read(8)) : Match(320 + reader.Read(12)); + } + + // Distance 0 cannot be a literal here: flag it with a non-zero length so the caller rejects it. + private static Token Match(int distance) => new(0, distance, distance == 0 ? 1 : 0); + + /// Reads z zeros, a one, then z bits: 2 + 2^z + v; -1 for nine zeros. + private static int ReadLengthCode(ref LsbBitReader reader) { + var prefix = 0; + while (reader.Read(1) == 0) { + if (++prefix > MaxLengthPrefix) return -1; + if (reader.Overran) return 0; + } + return 2 + (1 << prefix) + reader.Read(prefix); + } + + // ── encoding ──────────────────────────────────────────────────────────── + + /// + /// Encodes as a token stream with a sync marker after every 512 decoded + /// bytes and after the last one, appended to . As in the streams the dmsdos + /// compressors write, no match runs across a 512-byte boundary (distances still reach back across + /// them), so every sector of the cluster ends on a sync. + /// + public static void Encode(ReadOnlySpan data, LsbBitWriter writer, MsdblDialect dialect, int level) { + // A DS match copies one byte less than its length code, so its shortest copy is two bytes and its + // longest 512; the finder reports matches of three bytes and more. + var maxCopy = dialect == MsdblDialect.Ds ? MaxLengthCode - 1 : MaxLengthCode; + var copyBias = dialect == MsdblDialect.Ds ? 1 : 0; + var bytes = data.ToArray(); + var finder = new ClusterMatchFinder(bytes, MaxDistance, maxCopy, level); + + var sectorStart = 0; + do { + var sectorEnd = Math.Min(sectorStart + SyncInterval, bytes.Length); + var position = sectorStart; + var pending = finder.Find(position, sectorEnd); + while (position < sectorEnd) { + var (length, distance) = pending; + if (length == 0) { + WriteLiteral(writer, dialect, bytes[position]); + pending = ++position < sectorEnd ? finder.Find(position, sectorEnd) : default; + continue; + } + + // One step of lazy evaluation: a longer match starting at the next byte wins over this one. + var next = position + 1 < sectorEnd ? finder.Find(position + 1, sectorEnd) : default; + if (next.Length > length) { + WriteLiteral(writer, dialect, bytes[position]); + ++position; + pending = next; + continue; + } + + WriteMatch(writer, dialect, distance, length + copyBias); + position += length; + pending = position < sectorEnd ? finder.Find(position, sectorEnd) : default; + } + WriteSync(writer, dialect); + sectorStart = sectorEnd; + } while (sectorStart < bytes.Length); + } + + private static void WriteLiteral(LsbBitWriter writer, MsdblDialect dialect, byte value) { + var high = value >= 0x80; + if (dialect == MsdblDialect.Ds) + writer.Write(high ? 0b01 : 0b10, 2); // read order: "1 0" high, "0 1" low + else if (high) + writer.Write(0b11, 2); + else + writer.Write(0, 1); + writer.Write(value & 0x7F, 7); + } + + private static void WriteMatch(LsbBitWriter writer, MsdblDialect dialect, int distance, int lengthCode) { + WriteDistance(writer, dialect, distance); + var prefix = 31 - int.LeadingZeroCount(lengthCode - 2); + writer.Write(1 << prefix, prefix + 1); // `prefix` zeros, then a one + writer.Write(lengthCode - 2 - (1 << prefix), prefix); + } + + private static void WriteSync(LsbBitWriter writer, MsdblDialect dialect) => WriteDistance(writer, dialect, SyncDistance); + + private static void WriteDistance(LsbBitWriter writer, MsdblDialect dialect, int distance) { + if (dialect == MsdblDialect.Ds) { + switch (distance) { + case < 64: writer.Write(0b00, 2); writer.Write(distance, 6); break; + case < 320: writer.Write(0b011, 3); writer.Write(distance - 64, 8); break; + default: writer.Write(0b111, 3); writer.Write(distance - 320, 12); break; + } + return; + } + + writer.Write(0b01, 2); // read order: "1 0" + switch (distance) { + case < 64: writer.Write(0, 1); writer.Write(distance, 6); break; + case < 320: writer.Write(0b01, 2); writer.Write(distance - 64, 8); break; + default: writer.Write(0b11, 2); writer.Write(distance - 320, 12); break; + } + } +} + +/// Reads bits LSB-first from consecutive bytes; past the end it yields zeros and records the overrun. +internal ref struct LsbBitReader { + private readonly ReadOnlySpan _data; + private int _next; + private ulong _buffer; + private int _count; + + public LsbBitReader(ReadOnlySpan data) => this._data = data; + + /// True once a read has needed bits beyond the end of the data. + public readonly bool Overran => (long)this._next * 8 - this._count > (long)this._data.Length * 8; + + /// True when every bit not yet read is zero. + public readonly bool RestIsZero => this._buffer == 0 && (this._next >= this._data.Length || !this._data[this._next..].ContainsAnyExcept((byte)0)); + + /// Reads (at most 24) bits, the first read becoming the least significant. + public int Read(int count) { + while (this._count < count) { + if (this._next < this._data.Length) this._buffer |= (ulong)this._data[this._next] << this._count; + ++this._next; + this._count += 8; + } + var value = (int)(this._buffer & ((1UL << count) - 1)); + this._buffer >>= count; + this._count -= count; + return value; + } +} + +/// Writes bits LSB-first into consecutive bytes. +internal sealed class LsbBitWriter { + private byte[] _bytes = new byte[256]; + private int _length; + private ulong _accumulator; + private int _count; + + /// Appends a whole byte; only valid at a byte boundary. + public void WriteByte(byte value) => this.Write(value, 8); + + /// Appends the low (at most 32) bits of , least significant first. + public void Write(int value, int count) { + this._accumulator |= ((ulong)(uint)value & ((1UL << count) - 1)) << this._count; + this._count += count; + while (this._count >= 8) { + this.Push((byte)this._accumulator); + this._accumulator >>= 8; + this._count -= 8; + } + } + + /// Flushes the partial byte and pads with zero bytes to a multiple of . + public byte[] ToArray(int alignment) { + if (this._count > 0) this.Write(0, 8 - this._count); + while (this._length % alignment != 0) this.Push(0); + return this._bytes.AsSpan(0, this._length).ToArray(); + } + + private void Push(byte value) { + if (this._length == this._bytes.Length) Array.Resize(ref this._bytes, this._bytes.Length * 2); + this._bytes[this._length++] = value; + } +} diff --git a/Compression.Core/FileSystems/Cvf/PackageMerge.cs b/Compression.Core/FileSystems/Cvf/PackageMerge.cs new file mode 100644 index 000000000..1074b827c --- /dev/null +++ b/Compression.Core/FileSystems/Cvf/PackageMerge.cs @@ -0,0 +1,65 @@ +namespace Compression.Core.FileSystems.Cvf; + +/// +/// Optimal length-limited prefix code lengths by the package-merge algorithm (Larmore and Hirschberg, +/// "A fast algorithm for optimal length-limited Huffman codes", JACM 37(3), 1990). The result is +/// always a complete code — Kraft sum exactly one — which SD-4 requires of both its codes. +/// +internal static class PackageMerge { + /// + /// Code lengths for (zero frequency = unused, length 0), none longer + /// than . + /// + /// Fewer than two used symbols, or more than 2^maxLength. + public static int[] Lengths(ReadOnlySpan frequencies, int maxLength) { + var lengths = new int[frequencies.Length]; + var leaves = new List(); + for (var s = 0; s < frequencies.Length; ++s) + if (frequencies[s] > 0) leaves.Add(s); + if (leaves.Count < 2 || leaves.Count > 1L << maxLength) + throw new ArgumentException($"package-merge needs 2..2^{maxLength} used symbols, got {leaves.Count}.", nameof(frequencies)); + + var freq = frequencies.ToArray(); + leaves.Sort((a, b) => freq[a] != freq[b] ? freq[a].CompareTo(freq[b]) : a.CompareTo(b)); + + // Nodes: a leaf (Symbol >= 0) or a package of two nodes from the previous level. + var nodes = new List<(long Weight, int Symbol, int Left, int Right)>(); + var leafNodes = new List(leaves.Count); + foreach (var s in leaves) { + leafNodes.Add(nodes.Count); + nodes.Add((freq[s], s, -1, -1)); + } + + var level = leafNodes; + for (var depth = 1; depth < maxLength; ++depth) { + var packages = new List(level.Count / 2); + for (var i = 0; i + 1 < level.Count; i += 2) { + packages.Add(nodes.Count); + nodes.Add((nodes[level[i]].Weight + nodes[level[i + 1]].Weight, -1, level[i], level[i + 1])); + } + + // Merge the packages into the leaves by weight; leaves first on ties. + var merged = new List(leafNodes.Count + packages.Count); + int a = 0, b = 0; + while (a < leafNodes.Count || b < packages.Count) + merged.Add(b >= packages.Count || (a < leafNodes.Count && nodes[leafNodes[a]].Weight <= nodes[packages[b]].Weight) + ? leafNodes[a++] + : packages[b++]); + level = merged; + } + + // Each appearance of a leaf among the first 2n - 2 items adds one bit to its code. + var stack = new Stack(); + for (var i = 0; i < 2 * leaves.Count - 2; ++i) stack.Push(level[i]); + while (stack.Count > 0) { + var node = nodes[stack.Pop()]; + if (node.Symbol >= 0) { + ++lengths[node.Symbol]; + continue; + } + stack.Push(node.Left); + stack.Push(node.Right); + } + return lengths; + } +} diff --git a/Compression.Core/FileSystems/Cvf/Sd4Codec.cs b/Compression.Core/FileSystems/Cvf/Sd4Codec.cs index 394d2c0d9..58c188081 100644 --- a/Compression.Core/FileSystems/Cvf/Sd4Codec.cs +++ b/Compression.Core/FileSystems/Cvf/Sd4Codec.cs @@ -1,315 +1,418 @@ -#pragma warning disable CS1591 -using System; -using System.IO; - namespace Compression.Core.FileSystems.Cvf; /// -/// Genuine Stacker 4 (SD-4, cluster header 0x0081) per-cluster codec, as -/// specified in docs/CVF-CODECS-ON-DISK.md. SD-4 is Huffman-coded LZ77: a -/// helper code (22 symbols: lengths 0..15 and six run tokens) transmits the 0x150 -/// code lengths of the main code, whose symbols are the 256 literals, 63 short-match -/// tokens, 16 long-match tokens and the end token. -/// -/// reads the complete format — run tokens, short and long -/// matches with length extensions — and is pinned to streams the dmsdos SD-4 -/// encoder wrote. emits an all-literals stream (main code -/// over the cluster's byte frequencies, no match tokens), which the dmsdos driver -/// reads back byte-exact. The bitstream is MSB-first within little-endian 16-bit -/// words; codes are canonical and MSB-first. -/// +/// Stacker 4 (SD-4, cluster header 0x0081) per-cluster codec, implemented from +/// docs/CVF-CODECS-ON-DISK.md. SD-4 is Huffman-coded LZ77: a helper code (code lengths 0..15 +/// and six run tokens) transmits the 336 code lengths of the main code, whose symbols are the 256 +/// literals, 63 short-match tokens, 16 long-match tokens and the end token. Bits are read MSB-first +/// from little-endian 16-bit words; both codes are canonical and complete. /// public static class Sd4Codec { - private const int MaxLen = 10; // keep table2 codes within the decoder's fast table (bn<=10) - - // ── MSB-first bit writer into 16-bit little-endian words ─────────────────── - private sealed class BitWriter { - private readonly System.Collections.Generic.List _words = []; - private uint _cur; - private int _bits; - public void Put(int value, int n) { - for (var i = n - 1; i >= 0; i--) { - this._cur = (this._cur << 1) | (uint)((value >> i) & 1); - if (++this._bits == 16) { this._words.Add((ushort)this._cur); this._cur = 0; this._bits = 0; } - } - } - public byte[] ToBytes() { - if (this._bits > 0) { this._cur <<= 16 - this._bits; this._words.Add((ushort)this._cur); } - var b = new byte[this._words.Count * 2]; - for (var i = 0; i < this._words.Count; i++) { b[i * 2] = (byte)this._words[i]; b[i * 2 + 1] = (byte)(this._words[i] >> 8); } - return b; - } - } - - // ── MSB-first bit reader (mirror of sd4b_rd*) ────────────────────────────── - private sealed class BitReader { - private readonly byte[] _d; private readonly int _len; private long _buf; private int _pb; private int _pd; private readonly int _pe; - public BitReader(byte[] d, int len) { this._d = d; this._len = len; this._pe = (len + 1) >> 1; } - private ushort Word(int i) { var o = i * 2; var lo = o < this._len ? this._d[o] : 0; var hi = o + 1 < this._len ? this._d[o + 1] : 0; return (ushort)(lo | (hi << 8)); } - public int ReadN(int n) { - if (this._pb < n) { this._buf = (this._buf << 16) | (this._pd < this._pe ? Word(this._pd++) : 0u); this._pb += 16; if (this._pb < n) { this._buf = (this._buf << 16) | (this._pd < this._pe ? Word(this._pd++) : 0u); this._pb += 16; } } - this._pb -= n; - return (int)((this._buf >> this._pb) & ((1u << n) - 1)); - } - } - - // ── canonical Huffman: lengths -> (code,len) per symbol, matching sd4b_rdhufi - private static (int[] Code, int[] Len) Canonical(byte[] lengths) { - var cdLn = new int[17]; - foreach (var l in lengths) if (l is > 0 and < 16) cdLn[l]++; - var firstCode = new int[17]; - int us = 0; - for (var i = 1; i <= 16; i++) { firstCode[i] = us; us += cdLn[i]; us <<= 1; } - var next = (int[])firstCode.Clone(); - var code = new int[lengths.Length]; - var len = new int[lengths.Length]; - for (var s = 0; s < lengths.Length; s++) { var l = lengths[s]; if (l == 0) continue; code[s] = next[l]++; len[s] = l; } - return (code, len); - } - - // ── length-limited Huffman code lengths from frequencies ─────────────────── - private static byte[] HuffmanLengths(int[] freq, int maxLen) { - var n = freq.Length; - var lengths = new byte[n]; - var nodes = new System.Collections.Generic.List<(long F, int Sym, int Left, int Right)>(); - for (var s = 0; s < n; s++) if (freq[s] > 0) nodes.Add((freq[s], s, -1, -1)); - if (nodes.Count == 0) return lengths; - if (nodes.Count == 1) { lengths[nodes[0].Sym] = 1; FixKraft(lengths, maxLen); return lengths; } - - var all = new System.Collections.Generic.List<(long F, int Sym, int Left, int Right)>(nodes); - var live = new System.Collections.Generic.List(); - for (var i = 0; i < all.Count; i++) live.Add(i); - while (live.Count > 1) { - live.Sort((a, b) => all[a].F.CompareTo(all[b].F)); - var a = live[0]; var b = live[1]; - live.RemoveRange(0, 2); - all.Add((all[a].F + all[b].F, -1, a, b)); - live.Add(all.Count - 1); - } - AssignDepth(all, live[0], 0, lengths); - FixKraft(lengths, maxLen); - return lengths; - } - - private static void AssignDepth(System.Collections.Generic.List<(long F, int Sym, int Left, int Right)> all, int node, int depth, byte[] lengths) { - var nd = all[node]; - if (nd.Sym >= 0) { lengths[nd.Sym] = (byte)Math.Max(1, depth); return; } - AssignDepth(all, nd.Left, depth + 1, lengths); - AssignDepth(all, nd.Right, depth + 1, lengths); - } - - // Clamp to maxLen and repair the Kraft sum so the code is complete. - private static void FixKraft(byte[] lengths, int maxLen) { - for (var i = 0; i < lengths.Length; i++) if (lengths[i] > maxLen) lengths[i] = (byte)maxLen; - // Kraft sum in units of 2^-maxLen. - long total = 0; - foreach (var l in lengths) if (l > 0) total += 1L << (maxLen - l); - var full = 1L << maxLen; - // Over-full: lengthen some short codes. - while (total > full) { - for (var i = 0; i < lengths.Length && total > full; i++) { - if (lengths[i] > 0 && lengths[i] < maxLen) { total -= 1L << (maxLen - lengths[i]); lengths[i]++; total += 1L << (maxLen - lengths[i]); } - } - } - // Under-full: shorten codes to fill the space (keeps it complete). - var guard = 0; - while (total < full && guard++ < 100000) { - for (var i = 0; i < lengths.Length && total < full; i++) { - if (lengths[i] > 1) { - var delta = 1L << (maxLen - lengths[i]); - if (total + delta <= full) { total += delta; lengths[i]--; } // shortening adds delta again - } - } - if (guard > 99999) break; - } - } + private const int Signature = 0x0081; + private const int HelperSymbols = 22; // code lengths 0..15, run tokens 16..21 + private const int MainSymbols = 0x150; // literals 0..255, tokens 1..80 + private const int MaxCodeLength = 15; + private const int FirstToken = 0x100 - 1; // main symbol of token t is FirstToken + t + private const int FirstLongToken = 64; + private const int EndToken = 80; + private const int ShortMaxLength = 5; + private const int LongMinLength = 6; + private const int LongTokenMaxLength = 21; + private const int MaxMatchLength = LongTokenMaxLength + 0xF + 0xFF + 0xFFF + 0xFFFF; // every extension saturated - public static byte[] Encode(ReadOnlySpan data) { - // table2: Huffman over 0x150 symbols — 256 literals + the end token 0x14F - // (sd4_decomp reads an explicit end token when the output fills). A complete - // code needs >= 2 symbols, so empty/uniform clusters get a nominal literal. - var freq = new int[0x150]; - foreach (var b in data) freq[b]++; - freq[0x14F] = Math.Max(1, freq[0x14F]); // end symbol always present - var nz = 0; foreach (var f in freq) if (f > 0) nz++; - for (var i = 0; i < 256 && nz < 2; i++) if (freq[i] == 0) { freq[i] = 1; nz++; } - var clen = HuffmanLengths(freq, MaxLen); // 0x150 code-lengths - var bmax2 = 0; foreach (var l in clen) if (l > bmax2) bmax2 = l; - if (bmax2 == 0) bmax2 = 1; - - // table1: Huffman over the frequency of each length-VALUE (0..bmax2) in clen. - var lvFreq = new int[0x16]; - foreach (var v in clen) lvFreq[v]++; - var l1 = HuffmanLengths(lvFreq, 7); // table1 bn capped at 7 by the decoder - var bmax1 = 0; foreach (var l in l1) if (l > bmax1) bmax1 = l; - if (bmax1 == 0) bmax1 = 1; - - var (t1code, t1len) = Canonical(l1); - var (t2code, t2len) = Canonical(clen); - - var w = new BitWriter(); - w.Put(0x81, 16); // SD-4 magic - w.Put(0, 3); // start = rdn(3)+1 = 1 - w.Put(bmax2, 5); // ie = bmax2 - w.Put(l1[0], 4); // ca[0] = bmax1 field (length-value 0) - for (var i = 1; i <= bmax2; i++) w.Put(l1[i], 4); // ca[1..bmax2] - for (var i = 0x10; i <= 0x15; i++) w.Put(0, 4); // RLE tokens unused - // emit the 0x150 code-lengths as table1 length-value codes. - foreach (var v in clen) w.Put(t1code[v], t1len[v]); - // emit data as table2 literal codes, then the end token (read by sd4_decomp - // via len_150 bits once the output is full). - foreach (var b in data) w.Put(t2code[b], t2len[b]); - w.Put(t2code[0x14F], t2len[0x14F]); - return w.ToBytes(); - } - - // ── decoding (docs/CVF-CODECS-ON-DISK.md, "SD-4") ────────────────────────── - - private const int HelperSymbols = 0x16; // code lengths 0..15 + run tokens 1..6 - private const int MainSymbols = 0x150; // literals 0..255 + tokens 1..0x50 - private const int EndToken = 0x50; - private const int FirstLongToken = 0x40; - - // Short-match classes: token t (1..0x3F) is class (t-1)/3 with length 3 + (t-1)%3; the offset is - // ShortBase[class] plus ShortBits[class] extra bits. - private static readonly int[] ShortBits = [0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9]; + // Short-match distance classes (token t = 1..63: class (t-1)/3, length 3 + (t-1)%3). private static readonly int[] ShortBase = [1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128, 192, 256, 384, 512, 768, 1024, 1536]; + private static readonly int[] ShortBits = [0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9]; - // Long-match offset classes, chosen by a 2-bit selector. + // Long-match distance classes, picked by a 2-bit selector. + private static readonly int[] LongBase = [1, 33, 161, 673]; private static readonly int[] LongBits = [5, 7, 9, 11]; - private static readonly int[] LongBase = [0x1, 0x21, 0xA1, 0x2A1]; - // Escalating length extensions of a long match whose token says 21. + // A long match whose token says 21 carries these extensions, each taken only while the previous one saturated. private static readonly int[] LengthExtensionBits = [4, 8, 12, 16]; + private static readonly int ShortMaxDistance = ShortBase[^1] + (1 << ShortBits[^1]) - 1; // 2047 + private static readonly int LongMaxDistance = LongBase[^1] + (1 << LongBits[^1]) - 1; // 2720 + + // ── decoding ──────────────────────────────────────────────────────────── + /// /// Decodes one SD-4 cluster payload into bytes. A stream that ends (end /// token) before the cluster is full leaves the tail zero. /// - /// Bad magic, an inconsistent code table, or a match that reaches before the cluster start. + /// Bad signature, an inconsistent code table, or a match that reaches before the cluster start. public static byte[] Decode(byte[] payload, int inLen, int outLen) { - var r = new BitReader(payload, inLen); - if (r.ReadN(16) != 0x81) throw new InvalidDataException("SD-4: bad magic."); - - // Helper-table code lengths: symbol 0, symbols first..last, then the six run tokens. - var first = r.ReadN(3) + 1; - var last = r.ReadN(5); - if (first > last || last > 15) throw new InvalidDataException("SD-4: helper table range is invalid."); - var helperLengths = new byte[HelperSymbols]; - helperLengths[0] = (byte)r.ReadN(4); - for (var i = first; i <= last; ++i) helperLengths[i] = (byte)r.ReadN(4); - for (var i = 0x10; i < HelperSymbols; ++i) helperLengths[i] = (byte)r.ReadN(4); - var helper = new CanonicalDecoder(helperLengths); - - var mainLengths = new byte[MainSymbols]; - for (var n = 0; n < MainSymbols;) { - var symbol = helper.Decode(r); - if (symbol < 0x10) { mainLengths[n++] = (byte)symbol; continue; } - if (n == 0 && symbol >= 0x13) throw new InvalidDataException("SD-4: length repeat with no previous length."); - var (value, count) = (symbol - 0x0F) switch { - 1 => ((byte)0, 2), - 2 => ((byte)0, 3), - 3 => ((byte)0, ZeroRunLength(r)), - 4 => (mainLengths[n - 1], 2), - 5 => (mainLengths[n - 1], 3), - _ => (mainLengths[n - 1], RepeatRunLength(r)), - }; - count = Math.Min(count, MainSymbols - n); - mainLengths.AsSpan(n, count).Fill(value); - n += count; - } - var main = new CanonicalDecoder(mainLengths); + var reader = new MsbWordReader(payload.AsSpan(0, Math.Min(inLen, payload.Length))); + if (reader.Read(16) != Signature) throw new InvalidDataException("SD-4: bad signature."); + var main = new CanonicalDecoder(ReadMainLengths(ref reader)); var output = new byte[outLen]; - var p = 0; - while (p < outLen) { - var symbol = main.Decode(r); - if (symbol < 0x100) { output[p++] = (byte)symbol; continue; } + var position = 0; + while (position < outLen) { + var symbol = main.Decode(ref reader); + if (symbol <= 0xFF) { + output[position++] = (byte)symbol; + continue; + } - var token = symbol - 0xFF; + var token = symbol - FirstToken; if (token == EndToken) break; - int length, offset; + int length, distance; if (token < FirstLongToken) { var cls = (token - 1) / 3; length = 3 + (token - 1) % 3; - offset = ShortBase[cls] + r.ReadN(ShortBits[cls]); + distance = ShortBase[cls] + reader.Read(ShortBits[cls]); } else { - var cls = r.ReadN(2); - offset = LongBase[cls] + r.ReadN(LongBits[cls]); - length = token - FirstLongToken + 6; - var saturated = 21; + var selector = reader.Read(2); + distance = LongBase[selector] + reader.Read(LongBits[selector]); + length = token - 58; + var saturated = LongTokenMaxLength; foreach (var bits in LengthExtensionBits) { if (length != saturated) break; - var extra = r.ReadN(bits); - length += extra; + length += reader.Read(bits); saturated += (1 << bits) - 1; } } - if (offset > p) throw new InvalidDataException("SD-4: match reaches before the start of the cluster."); - length = Math.Min(length, outLen - p); // the reference driver clips an overlong final match - for (var k = 0; k < length; ++k, ++p) output[p] = output[p - offset]; + if (distance > position) throw new InvalidDataException("SD-4: match reaches before the start of the cluster."); + length = Math.Min(length, outLen - position); + for (var end = position + length; position < end; ++position) + output[position] = output[position - distance]; } return output; } - // Run token 3: 4 + 3 bits; a saturated 7 continues with 7-bit groups while they read 0x7F. - private static int ZeroRunLength(BitReader r) { - var v = r.ReadN(3); + private static byte[] ReadMainLengths(ref MsbWordReader reader) { + var first = reader.Read(3) + 1; + var last = reader.Read(5); + if (first > last || last > MaxCodeLength) throw new InvalidDataException("SD-4: helper length range is invalid."); + + var helperLengths = new byte[HelperSymbols]; + helperLengths[0] = (byte)reader.Read(4); + for (var i = first; i <= last; ++i) helperLengths[i] = (byte)reader.Read(4); + for (var i = 16; i < HelperSymbols; ++i) helperLengths[i] = (byte)reader.Read(4); + var helper = new CanonicalDecoder(helperLengths); + + var lengths = new byte[MainSymbols]; + for (var n = 0; n < MainSymbols;) { + var symbol = helper.Decode(ref reader); + if (symbol < 16) { + lengths[n++] = (byte)symbol; + continue; + } + if (symbol >= 19 && n == 0) throw new InvalidDataException("SD-4: length repeat with no previous length."); + var (value, count) = symbol switch { + 16 => ((byte)0, 2), + 17 => ((byte)0, 3), + 18 => ((byte)0, ReadZeroRun(ref reader)), + 19 => (lengths[n - 1], 2), + 20 => (lengths[n - 1], 3), + _ => (lengths[n - 1], ReadRepeatRun(ref reader)), + }; + count = Math.Min(count, MainSymbols - n); + lengths.AsSpan(n, count).Fill(value); + n += count; + } + return lengths; + } + + // Helper symbol 18: 4 + 3 bits; a 7 continues with 7-bit groups while they read 0x7F. + private static int ReadZeroRun(ref MsbWordReader reader) { + var v = reader.Read(3); var n = 4 + v; if (v == 7) - do { v = r.ReadN(7); n += v; } while (v == 0x7F); + do n += v = reader.Read(7); while (v == 0x7F); return n; } - // Run token 6: 4 + 3-bit groups, continuing while a group reads 7. - private static int RepeatRunLength(BitReader r) { + // Helper symbol 21: 4 + 3-bit groups, continuing while a group reads 7. + private static int ReadRepeatRun(ref MsbWordReader reader) { var n = 4; int v; - do { v = r.ReadN(3); n += v; } while (v == 7); + do n += v = reader.Read(3); while (v == 7); return n; } + // ── encoding ──────────────────────────────────────────────────────────── + + /// Encodes one cluster with back-references, Huffman-coded behind a run-length coded code table. + /// The cluster. + /// 1 (fastest) and up; deeper match search at higher levels. + public static byte[] Encode(ReadOnlySpan data, int level = 2) + => Encode(data, level, matches: true); + + /// Encodes with back-references switched off when is false — a + /// literals-only stream whose code depth follows the byte statistics alone (used to probe decoders). + internal static byte[] Encode(ReadOnlySpan data, int level, bool matches) { + var symbols = matches ? Parse(data, level) : LiteralsOnly(data); + + var mainFrequencies = new long[MainSymbols]; + foreach (var s in symbols) ++mainFrequencies[s.Symbol]; + EnsureTwoSymbols(mainFrequencies, 0); + var mainLengths = PackageMerge.Lengths(mainFrequencies, MaxCodeLength); + + var runs = RunLengthCode(mainLengths); + var helperFrequencies = new long[HelperSymbols]; + foreach (var r in runs) ++helperFrequencies[r.Symbol]; + EnsureTwoSymbols(helperFrequencies, helperFrequencies[0] == 0 ? 0 : 16); + var helperLengths = PackageMerge.Lengths(helperFrequencies, MaxCodeLength); + + var writer = new MsbWordWriter(); + writer.Write(Signature, 16); + WriteHelperHeader(writer, mainLengths, helperLengths); + + WriteSymbols(writer, runs, helperLengths); + WriteSymbols(writer, symbols, mainLengths); + return writer.ToArray(); + } + + /// A symbol of the main or helper code followed by its extra-bit fields, in stream order. + private readonly record struct CodedSymbol(int Symbol, params (int Value, int Bits)[] Extras); + + private static void WriteSymbols(MsbWordWriter writer, List symbols, int[] lengths) { + var codes = CanonicalCodes(lengths); + foreach (var s in symbols) { + writer.Write(codes[s.Symbol], lengths[s.Symbol]); + foreach (var (value, bits) in s.Extras) writer.Write(value, bits); + } + } + + private static List LiteralsOnly(ReadOnlySpan data) { + var symbols = new List(data.Length + 1); + foreach (var b in data) symbols.Add(new(b)); + symbols.Add(new(FirstToken + EndToken)); + return symbols; + } + + private static List Parse(ReadOnlySpan data, int level) { + var bytes = data.ToArray(); + var finder = new ClusterMatchFinder(bytes, LongMaxDistance, MaxMatchLength, level); + var symbols = new List(bytes.Length / 2 + 2); + + var position = 0; + var pending = Usable(finder.Find(0)); + while (position < bytes.Length) { + if (pending.Length == 0) { + symbols.Add(new(bytes[position], [])); + pending = Usable(finder.Find(++position)); + continue; + } + + var next = position + 1 < bytes.Length ? Usable(finder.Find(position + 1)) : (Length: 0, Distance: 0); + if (next.Length > pending.Length) { + symbols.Add(new(bytes[position], [])); + ++position; + pending = next; + continue; + } + + symbols.Add(MatchSymbol(pending.Length, pending.Distance)); + position += pending.Length; + pending = position < bytes.Length ? Usable(finder.Find(position)) : (Length: 0, Distance: 0); + } + + symbols.Add(new(FirstToken + EndToken, [])); + return symbols; + } + + // Lengths 3..5 only reach as far as the short classes; everything longer may use the long ones. + private static (int Length, int Distance) Usable((int Length, int Distance) match) + => match.Length is > 0 and <= ShortMaxLength && match.Distance > ShortMaxDistance ? (0, 0) : match; + + private static CodedSymbol MatchSymbol(int length, int distance) { + if (length <= ShortMaxLength) { + var cls = ClassOf(ShortBase, ShortBits, distance); + var token = 1 + cls * 3 + (length - 3); + return new(FirstToken + token, [(distance - ShortBase[cls], ShortBits[cls])]); + } + + var selector = ClassOf(LongBase, LongBits, distance); + var extras = new List<(int, int)> { (selector, 2), (distance - LongBase[selector], LongBits[selector]) }; + var longToken = FirstLongToken + Math.Min(length, LongTokenMaxLength) - LongMinLength; + var remaining = length - LongTokenMaxLength; + foreach (var bits in LengthExtensionBits) { + if (remaining < 0) break; + var max = (1 << bits) - 1; + extras.Add((Math.Min(remaining, max), bits)); + remaining -= max; // continues only when this extension saturated + } + return new(FirstToken + longToken, [.. extras]); + } + + private static int ClassOf(int[] bases, int[] bits, int distance) { + for (var c = bases.Length - 1; c >= 0; --c) + if (distance >= bases[c]) + return distance < bases[c] + (1 << bits[c]) ? c : throw new ArgumentOutOfRangeException(nameof(distance)); + throw new ArgumentOutOfRangeException(nameof(distance)); + } + + // A complete prefix code needs at least two symbols; give a spare one a nominal count. + private static void EnsureTwoSymbols(long[] frequencies, int spare) { + var used = frequencies.Count(f => f > 0); + for (var s = spare; used < 2 && s < frequencies.Length; ++s) + if (frequencies[s] == 0) { frequencies[s] = 1; ++used; } + } + /// - /// Canonical MSB-first prefix code: codes ascend with length and, within a length, with symbol - /// number. The code must be complete (Kraft sum exactly one) with lengths of at most 15 bits. + /// Transmits the main code lengths as helper symbols: a length as itself, runs of zeros as tokens + /// 16/17/18, repeats of the previous length as tokens 19/20/21. + /// + private static List RunLengthCode(int[] lengths) { + var runs = new List(); + for (var i = 0; i < lengths.Length;) { + var value = lengths[i]; + var run = 1; + while (i + run < lengths.Length && lengths[i + run] == value) ++run; + i += run; + + if (value == 0) { + runs.Add(run switch { + 1 => new(0), + 2 => new(16), + 3 => new(17), + < 11 => new(18, (run - 4, 3)), + _ => new(18, [(7, 3), .. Groups(run - 11, 7)]), + }); + continue; + } + + runs.Add(new(value)); + var repeats = run - 1; + switch (repeats) { + case 0: break; + case 1: runs.Add(new(value)); break; + case 2: runs.Add(new(19)); break; + case 3: runs.Add(new(20)); break; + default: runs.Add(new(21, [.. Groups(repeats - 4, 3)])); break; + } + } + return runs; + } + + /// + /// Splits into -bit groups where every group but the + /// last is saturated (all ones) and the last is not, which is how the decoder knows to stop. + /// + private static IEnumerable<(int Value, int Bits)> Groups(int total, int bits) { + var saturated = (1 << bits) - 1; + for (; total >= saturated; total -= saturated) yield return (saturated, bits); + yield return (total, bits); + } + + + private static void WriteHelperHeader(MsbWordWriter writer, int[] mainLengths, int[] helperLengths) { + int shortest = MaxCodeLength, longest = 1; + foreach (var l in mainLengths) { + if (l == 0) continue; + shortest = Math.Min(shortest, l); + longest = Math.Max(longest, l); + } + var first = Math.Min(shortest, 8); + writer.Write(first - 1, 3); + writer.Write(longest, 5); + writer.Write(helperLengths[0], 4); + for (var i = first; i <= longest; ++i) writer.Write(helperLengths[i], 4); + for (var i = 16; i < HelperSymbols; ++i) writer.Write(helperLengths[i], 4); + } + + /// Canonical codes: ascending by length, then by symbol, each the previous plus one. + private static int[] CanonicalCodes(int[] lengths) { + var perLength = new int[MaxCodeLength + 1]; + foreach (var l in lengths) ++perLength[l]; + perLength[0] = 0; + var next = new int[MaxCodeLength + 2]; + for (var len = 1; len <= MaxCodeLength; ++len) next[len + 1] = (next[len] + perLength[len]) << 1; + var codes = new int[lengths.Length]; + for (var s = 0; s < lengths.Length; ++s) + if (lengths[s] != 0) codes[s] = next[lengths[s]]++; + return codes; + } + + /// + /// Canonical MSB-first prefix code decoder. The code must be complete (Kraft sum exactly one) with + /// lengths of at most 15 bits. /// private sealed class CanonicalDecoder { - private readonly int[] _count = new int[16]; + private readonly int[] _perLength = new int[MaxCodeLength + 1]; private readonly int[] _symbols; public CanonicalDecoder(ReadOnlySpan lengths) { foreach (var l in lengths) { - if (l > 15) throw new InvalidDataException("SD-4: code length above 15."); - ++this._count[l]; + if (l > MaxCodeLength) throw new InvalidDataException("SD-4: code length above 15."); + ++this._perLength[l]; } - this._count[0] = 0; - var left = 1; - for (var len = 1; len <= 15; ++len) { - left = (left << 1) - this._count[len]; - if (left < 0) throw new InvalidDataException("SD-4: over-subscribed code table."); + this._perLength[0] = 0; + var free = 1; + for (var len = 1; len <= MaxCodeLength; ++len) { + free = (free << 1) - this._perLength[len]; + if (free < 0) throw new InvalidDataException("SD-4: over-subscribed code table."); } - if (left != 0) throw new InvalidDataException("SD-4: incomplete code table."); + if (free != 0) throw new InvalidDataException("SD-4: incomplete code table."); - var offsets = new int[16]; - for (var len = 1; len < 15; ++len) offsets[len + 1] = offsets[len] + this._count[len]; - this._symbols = new int[offsets[15] + this._count[15]]; + var offsets = new int[MaxCodeLength + 2]; + for (var len = 1; len <= MaxCodeLength; ++len) offsets[len + 1] = offsets[len] + this._perLength[len]; + this._symbols = new int[offsets[MaxCodeLength + 1]]; for (var s = 0; s < lengths.Length; ++s) if (lengths[s] != 0) this._symbols[offsets[lengths[s]]++] = s; } - public int Decode(BitReader r) { - int code = 0, firstCode = 0, index = 0; - for (var len = 1; len <= 15; ++len) { - code |= r.ReadN(1); - var count = this._count[len]; - if (code - firstCode < count) return this._symbols[index + code - firstCode]; + public int Decode(ref MsbWordReader reader) { + int code = 0, first = 0, index = 0; + for (var len = 1; len <= MaxCodeLength; ++len) { + code |= reader.Read(1); + var count = this._perLength[len]; + if (code - first < count) return this._symbols[index + code - first]; index += count; - firstCode = (firstCode + count) << 1; + first = (first + count) << 1; code <<= 1; } throw new InvalidDataException("SD-4: undecodable bit sequence."); } } + + /// Reads bits MSB-first from little-endian 16-bit words; zeros past the end. + private ref struct MsbWordReader { + private readonly ReadOnlySpan _data; + private int _next; + private uint _buffer; + private int _count; + + public MsbWordReader(ReadOnlySpan data) => this._data = data; + + public int Read(int count) { + if (count == 0) return 0; + while (this._count < count) { + var lo = this._next < this._data.Length ? this._data[this._next] : 0; + var hi = this._next + 1 < this._data.Length ? this._data[this._next + 1] : 0; + this._next += 2; + this._buffer = this._buffer << 16 | (uint)(lo | hi << 8); + this._count += 16; + } + this._count -= count; + return (int)(this._buffer >> this._count) & ((1 << count) - 1); + } + } + + /// Writes bits MSB-first into little-endian 16-bit words. + private sealed class MsbWordWriter { + private readonly List _bytes = []; + private int _word; + private int _count; + + public void Write(int value, int count) { + for (var i = count - 1; i >= 0; --i) { + this._word = this._word << 1 | (value >> i & 1); + if (++this._count < 16) continue; + this._bytes.Add((byte)this._word); + this._bytes.Add((byte)(this._word >> 8)); + (this._word, this._count) = (0, 0); + } + } + + public byte[] ToArray() { + if (this._count > 0) this.Write(0, 16 - this._count); + return [.. this._bytes]; + } + } } diff --git a/Compression.Core/REFERENCE.md b/Compression.Core/REFERENCE.md index 86510a075..5ce0d6fd2 100644 --- a/Compression.Core/REFERENCE.md +++ b/Compression.Core/REFERENCE.md @@ -3335,6 +3335,26 @@ SQX multimedia compressor/decompressor using delta coders (E0-E4) and arithmetic | `Decode` | `static byte[] Decode(byte[] compressed, int originalSize)` | Decompresses multimedia data. | | `Encode` | `static byte[] Encode(ReadOnlySpan data)` | Compresses multimedia data using automatic delta order selection + arithmetic coding. | +### Namespace `Compression.Core.Dictionary.StackerSd4` + +[`StackerSd4BuildingBlock`](#stackersd4buildingblock) + +#### `StackerSd4BuildingBlock` + +Stacker 4's SD-4 codec as a raw primitive: LZ77 over a 2720-byte window, Huffman-coded with a run-length coded code table (`docs/CVF-CODECS-ON-DISK.md`). The building-block wrapper stores the uncompressed size (32-bit little-endian) before one SD-4 stream covering the whole input. + +Implements `IBuildingBlock`. + +| Member | Signature | Summary | +| --- | --- | --- | +| `StackerSd4BuildingBlock` | `StackerSd4BuildingBlock()` | | +| `Description` | `string Description { get; }` | | +| `DisplayName` | `string DisplayName { get; }` | | +| `Family` | `AlgorithmFamily Family { get; }` | | +| `Id` | `string Id { get; }` | | +| `Compress` | `byte[] Compress(ReadOnlySpan data)` | | +| `Decompress` | `byte[] Decompress(ReadOnlySpan data)` | | + ### Namespace `Compression.Core.Dictionary.SuffixTree` [`SuffixTreeBuildingBlock`](#suffixtreebuildingblock) @@ -5747,17 +5767,17 @@ Implements `IEquatable`. #### `CvfLzCodec` -Genuine DoubleSpace/DriveSpace per-cluster compression codec (DS-0-x and JM-0-x), verified byte-exact against the independent dmsdos decoder. The bitstream packs bits LSB-first into little-endian 16-bit words; a cluster payload is a 16-bit magic (`"DS"`=0x5344 / `"MJ"`=0x4D4A) + 16-bit version + an LZ77 token stream terminated by the 0x113f sync. +Per-cluster codecs of the DoubleSpace / DriveSpace / DriveSpace 3 / Stacker compressed volumes, implemented from `docs/CVF-CODECS-ON-DISK.md`. An MSDBL cluster payload starts with a 16-bit magic and a 16-bit version — `"DS"` (DS-0-x), `"JM"` (JM-0-x) or `"SQ"` (SQ-0-0, raw DEFLATE) — followed by the token stream; a Stacker payload carries no method field and is recognised by its first word. | Member | Signature | Summary | | --- | --- | --- | -| `DS_0_0` | `const uint DS_0_0` | | -| `JM_0_0` | `const uint JM_0_0` | | -| `SQ_0_0` | `const uint SQ_0_0` | | -| `Compress` | `static byte[] Compress(ReadOnlySpan data, CvfLzMethod method, int level)` | Compresses one cluster. Returns the payload (4-byte method header + token stream, padded to a 2-byte word), or `null` if it would not be smaller than the raw cluster (caller stores raw instead). | -| `DecompressStacker` | `static byte[] DecompressStacker(byte[] payload, int inLen, int outLen)` | Decompresses a Stacker cluster payload to `outLen` bytes. Stacker records no method per cluster; the payload's first little-endian word selects it — `0x0081` SD-4, `0x5344` ("DS") the DoubleSpace codec, anything else SD-3, whose first token is always a literal and so can never begin with either word. | -| `Decompress` | `static byte[] Decompress(byte[] payload, int inLen, int outLen)` | Decompresses a cluster payload to exactly `outLen` bytes. | -| `Encode` | `static byte[] Encode(ReadOnlySpan data, CvfLzMethod method, int level)` | Encodes a cluster with the given method, always returning the payload (4-byte header + token stream), or `null` for `Stored` / unsupported methods. The caller decides whether the result fits the cluster's sector budget. | +| `DS_0_0` | `const uint DS_0_0` | Method header of a DS-0-0 payload, read as a little-endian 32-bit word. | +| `JM_0_0` | `const uint JM_0_0` | Method header of a JM-0-0 payload, read as a little-endian 32-bit word. | +| `SQ_0_0` | `const uint SQ_0_0` | Method header of an SQ-0-0 payload, read as a little-endian 32-bit word. | +| `Compress` | `static byte[] Compress(ReadOnlySpan data, CvfLzMethod method, int level)` | Compresses one cluster. Returns the payload, or `null` when it would not be smaller than the raw cluster (the caller then stores the cluster raw). | +| `DecompressStacker` | `static byte[] DecompressStacker(byte[] payload, int inLen, int outLen)` | Decompresses a Stacker cluster payload to `outLen` bytes. Stacker records no method per cluster; the payload's first little-endian word selects it — `0x0081` SD-4, `0x5344` ("DS") the DoubleSpace grammar, anything else SD-3, whose first token is always a literal and so can never begin with either word. | +| `Decompress` | `static byte[] Decompress(byte[] payload, int inLen, int outLen)` | Decompresses an MSDBL cluster payload (DS, JM or SQ, told apart by its header) to exactly `outLen` bytes. | +| `Encode` | `static byte[] Encode(ReadOnlySpan data, CvfLzMethod method, int level)` | Encodes a cluster with the given method and always returns the payload, or `null` for `Stored`. The caller decides whether the result fits the cluster's sector budget. `Auto` keeps the smaller of DS and JM. | #### `CvfLzMethod` @@ -5765,13 +5785,13 @@ Per-cluster compression methods of the DoubleSpace/DriveSpace/Stacker CVF family | Value | Numeric | Summary | | --- | --- | --- | -| `Stored` | `0` | | -| `Ds` | `1` | | -| `Jm` | `2` | | -| `Auto` | `3` | | -| `Sq` | `4` | | -| `Sd4` | `5` | | -| `Sd3` | `6` | | +| `Stored` | `0` | No compression; the cluster is stored as is. | +| `Ds` | `1` | DoubleSpace DS-0-x LZ77. | +| `Jm` | `2` | DriveSpace JM-0-x LZ77 (DriveSpace 3 Normal/High). | +| `Auto` | `3` | Per cluster the smaller of `Ds` and `Jm`, both of which every MSDBL volume reads. | +| `Sq` | `4` | DriveSpace 3 SQ-0-0 (Ultra): a raw DEFLATE stream behind the method header. | +| `Sd4` | `5` | Stacker 4 SD-4: Huffman-coded LZ77 behind the header word `0x0081`. | +| `Sd3` | `6` | Stacker 3 SD-3: a bare LZS stream closed by a check byte. | #### `Sd3Codec` @@ -5784,12 +5804,12 @@ Stacker 3 ("SD-3") per-cluster codec, as specified in `docs/CVF-CODECS-ON-DISK.m #### `Sd4Codec` -Genuine Stacker 4 (SD-4, cluster header `0x0081`) per-cluster codec, as specified in `docs/CVF-CODECS-ON-DISK.md`. SD-4 is Huffman-coded LZ77: a helper code (22 symbols: lengths 0..15 and six run tokens) transmits the 0x150 code lengths of the main code, whose symbols are the 256 literals, 63 short-match tokens, 16 long-match tokens and the end token. `Decode` reads the complete format — run tokens, short and long matches with length extensions — and is pinned to streams the dmsdos SD-4 encoder wrote. `Encode` emits an all-literals stream (main code over the cluster's byte frequencies, no match tokens), which the dmsdos driver reads back byte-exact. The bitstream is MSB-first within little-endian 16-bit words; codes are canonical and MSB-first. +Stacker 4 (SD-4, cluster header `0x0081`) per-cluster codec, implemented from `docs/CVF-CODECS-ON-DISK.md`. SD-4 is Huffman-coded LZ77: a helper code (code lengths 0..15 and six run tokens) transmits the 336 code lengths of the main code, whose symbols are the 256 literals, 63 short-match tokens, 16 long-match tokens and the end token. Bits are read MSB-first from little-endian 16-bit words; both codes are canonical and complete. | Member | Signature | Summary | | --- | --- | --- | | `Decode` | `static byte[] Decode(byte[] payload, int inLen, int outLen)` | Decodes one SD-4 cluster payload into `outLen` bytes. A stream that ends (end token) before the cluster is full leaves the tail zero. | -| `Encode` | `static byte[] Encode(ReadOnlySpan data)` | | +| `Encode` | `static byte[] Encode(ReadOnlySpan data, int level = 2)` | Encodes one cluster with back-references, Huffman-coded behind a run-length coded code table. | ### Namespace `Compression.Core.Image` diff --git a/Compression.Tests/Cvf/CvfCodecBoundaryTests.cs b/Compression.Tests/Cvf/CvfCodecBoundaryTests.cs new file mode 100644 index 000000000..ab1057dbd --- /dev/null +++ b/Compression.Tests/Cvf/CvfCodecBoundaryTests.cs @@ -0,0 +1,188 @@ +using Compression.Core.FileSystems.Cvf; + +namespace Compression.Tests.Cvf; + +/// +/// Boundary behaviour of every CVF cluster codec (docs/CVF-CODECS-ON-DISK.md): empty and one-byte +/// clusters, the three cluster sizes the volumes use, incompressible data, and the longest match length +/// and farthest distance each grammar can express — one past each must still round-trip, by falling back +/// to a shorter form. +/// +[TestFixture] +public sealed class CvfCodecBoundaryTests { + + private static readonly CvfLzMethod[] AllMethods = [CvfLzMethod.Ds, CvfLzMethod.Jm, CvfLzMethod.Sq, CvfLzMethod.Sd3, CvfLzMethod.Sd4]; + + private static bool IsStacker(CvfLzMethod method) => method is CvfLzMethod.Sd3 or CvfLzMethod.Sd4; + + private static byte[] RoundTrip(byte[] data, CvfLzMethod method, int level = 2) { + var payload = CvfLzCodec.Encode(data, method, level)!; + return IsStacker(method) + ? CvfLzCodec.DecompressStacker(payload, payload.Length, data.Length) + : CvfLzCodec.Decompress(payload, payload.Length, data.Length); + } + + private static byte[] Random(int length, int seed) { + var data = new byte[length]; + new Random(seed).NextBytes(data); + return data; + } + + [Test, Category("Codec")] + public void GivenAnEmptyCluster_WhenEncodedAndDecoded_ThenNothingComesBack([ValueSource(nameof(AllMethods))] CvfLzMethod method) + => Assert.That(RoundTrip([], method), Is.Empty); + + [Test, Category("Codec")] + public void GivenAOneByteCluster_WhenEncodedAndDecoded_ThenTheByteComesBack( + [ValueSource(nameof(AllMethods))] CvfLzMethod method, [Values(0x00, 0x7F, 0x80, 0xFF)] int value) + => Assert.That(RoundTrip([(byte)value], method), Is.EqualTo(new[] { (byte)value })); + + [Test, Category("Codec")] + public void GivenAClusterOfEachVolumeSize_WhenEncodedAndDecoded_ThenItIsByteExact( + [ValueSource(nameof(AllMethods))] CvfLzMethod method, [Values(512, 8192, 32768)] int size, + [Values("text", "zeros", "random")] string shape) { + var data = shape switch { + "zeros" => new byte[size], + "random" => Random(size, size), + _ => CvfSamples.Get("text-32k")[..size], + }; + + Assert.That(RoundTrip(data, method), Is.EqualTo(data)); + } + + [Test, Category("Codec")] + public void GivenIncompressibleData_WhenCompressed_ThenTheCallerIsToldToStoreIt([Values(CvfLzMethod.Ds, CvfLzMethod.Jm, CvfLzMethod.Sq, CvfLzMethod.Sd4)] CvfLzMethod method) + => Assert.That(CvfLzCodec.Compress(Random(8192, 99), method, 2), Is.Null); + + [Test, Category("Codec")] + public void GivenCompressibleData_WhenCompressed_ThenThePayloadIsSmaller([ValueSource(nameof(AllMethods))] CvfLzMethod method) { + var data = CvfSamples.Get("text-8k"); + + Assert.That(CvfLzCodec.Compress(data, method, 2)!.Length, Is.LessThan(data.Length / 2)); + } + + // DS copies at most 512 bytes per match, JM 513; SD-4's long matches extend at 21, 36, 291 and 4386. + [Test, Category("Codec")] + public void GivenARunAroundEachMaximumMatchLength_WhenEncodedAndDecoded_ThenItIsByteExact( + [ValueSource(nameof(AllMethods))] CvfLzMethod method, + [Values(2, 3, 5, 6, 20, 21, 22, 35, 36, 37, 290, 291, 292, 511, 512, 513, 514, 1025, 4385, 4386, 4387, 8191)] int runLength) { + var data = Random(8192 + 64, runLength); + data.AsSpan(17, runLength).Fill(data[16]); // a literal then a run: one long distance-1 match + + Assert.That(RoundTrip(data, method), Is.EqualTo(data)); + } + + // DS/JM reach 4414 (4415 is the sync marker); SD-4 short matches 2047, long ones 2720; SD-3 2047. + [Test, Category("Codec")] + public void GivenARepeatAtEachDistanceBoundary_WhenEncodedAndDecoded_ThenItIsByteExact( + [ValueSource(nameof(AllMethods))] CvfLzMethod method, + [Values(1, 63, 64, 319, 320, 2047, 2048, 2720, 2721, 4414, 4415, 4416)] int distance, + [Values(3, 5, 6, 40)] int length) { + var data = Random(distance + length + 32, distance * 7 + length); + data.AsSpan(8, length).CopyTo(data.AsSpan(8 + distance)); + + Assert.That(RoundTrip(data, method), Is.EqualTo(data)); + } + + [Test, Category("Codec")] + public void GivenADsOrJmPayload_WhenEncoded_ThenItCarriesTheMethodHeaderAndAWholeNumberOfWords( + [Values(CvfLzMethod.Ds, CvfLzMethod.Jm, CvfLzMethod.Sq)] CvfLzMethod method) { + var payload = CvfLzCodec.Encode(CvfSamples.Get("x86-8k"), method, 1)!; + var expected = method switch { CvfLzMethod.Ds => "DS\0\0"u8.ToArray(), CvfLzMethod.Jm => "JM\0\0"u8.ToArray(), _ => "SQ\0\0"u8.ToArray() }; + + Assert.Multiple(() => { + Assert.That(payload[..4], Is.EqualTo(expected)); + Assert.That(payload.Length % 2, Is.Zero); + }); + } + + [Test, Category("Codec")] + public void GivenAutoMethod_WhenEncoded_ThenTheSmallerOfDsAndJmIsKept() { + var data = CvfSamples.Get("table-8k"); + var ds = CvfLzCodec.Encode(data, CvfLzMethod.Ds, 2)!; + var jm = CvfLzCodec.Encode(data, CvfLzMethod.Jm, 2)!; + + Assert.That(CvfLzCodec.Encode(data, CvfLzMethod.Auto, 2)!.Length, Is.EqualTo(Math.Min(ds.Length, jm.Length))); + } + + [Test, Category("Codec")] + public void GivenStoredMethod_WhenEncoded_ThenThereIsNoPayload() + => Assert.That(CvfLzCodec.Encode(CvfSamples.Get("text-8k"), CvfLzMethod.Stored, 2), Is.Null); + + [Test, Category("Codec")] + public void GivenAPayloadPaddedToWholeSectors_WhenDecoded_ThenThePaddingIsIgnored( + [Values(CvfLzMethod.Ds, CvfLzMethod.Jm, CvfLzMethod.Sq)] CvfLzMethod method) { + var data = CvfSamples.Get("dir-8k"); + var payload = CvfLzCodec.Encode(data, method, 2)!; + var padded = new byte[(payload.Length + 511) / 512 * 512]; + payload.CopyTo(padded, 0); + + Assert.That(CvfLzCodec.Decompress(padded, padded.Length, data.Length), Is.EqualTo(data)); + } + + // A file's last cluster may hold fewer sectors than the cluster: its stream closes with the sync and + // the rest of the cluster reads as zero. + [Test, Category("Codec")] + public void GivenAStreamShorterThanTheCluster_WhenDecoded_ThenTheTailIsZero([Values(CvfLzMethod.Ds, CvfLzMethod.Jm)] CvfLzMethod method) { + var data = CvfSamples.Get("text-8k")[..1536]; + var payload = CvfLzCodec.Encode(data, method, 2)!; + + var cluster = CvfLzCodec.Decompress(payload, payload.Length, 8192); + + Assert.Multiple(() => { + Assert.That(cluster[..1536], Is.EqualTo(data)); + Assert.That(cluster[1536..], Is.All.EqualTo((byte)0)); + }); + } + + [Test, Category("Codec")] + public void GivenATruncatedStream_WhenDecoded_ThenItIsRejected([Values(CvfLzMethod.Ds, CvfLzMethod.Jm)] CvfLzMethod method) { + var payload = CvfLzCodec.Encode(CvfSamples.Get("text-8k"), method, 2)!; + + Assert.That(() => CvfLzCodec.Decompress(payload[..(payload.Length / 2)], payload.Length / 2, 8192), Throws.InstanceOf()); + } + + [Test, Category("Codec")] + public void GivenAnUnknownMethodHeader_WhenDecoded_ThenItIsRejected() + => Assert.That(() => CvfLzCodec.Decompress("XY\0\0\0\0\0\0"u8.ToArray(), 8, 512), Throws.InstanceOf()); + + [Test, Category("Codec")] + public void GivenAPayloadShorterThanItsHeader_WhenDecoded_ThenItIsRejected() + => Assert.That(() => CvfLzCodec.Decompress([0x44, 0x53], 2, 512), Throws.InstanceOf()); + + [Test, Category("Codec")] + public void GivenAMatchBeforeTheClusterStart_WhenDecoded_ThenItIsRejected() { + // DS: "0 0" + distance 5 (LSB-first) then a length code — with nothing decoded yet. + var writer = new LsbBitWriter(); + foreach (var b in "DS\0\0"u8) writer.WriteByte(b); + writer.Write(0b00, 2); writer.Write(5, 6); writer.Write(1, 1); + var payload = writer.ToArray(2); + + Assert.That(() => CvfLzCodec.Decompress(payload, payload.Length, 512), Throws.InstanceOf()); + } + + [Test, Category("Codec")] + public void GivenALengthCodeWithNineLeadingZeros_WhenDecoded_ThenItIsRejected() { + var writer = new LsbBitWriter(); + foreach (var b in "JM\0\0"u8) writer.WriteByte(b); + writer.Write(0x41, 8); // literal 'A' ("0" + 7 bits) + writer.Write(0b01, 2); writer.Write(0, 1); writer.Write(1, 6); // match, distance 1 + writer.Write(0, 9); writer.Write(1, 1); // nine zeros: invalid + var payload = writer.ToArray(2); + + Assert.That(() => CvfLzCodec.Decompress(payload, payload.Length, 512), Throws.InstanceOf()); + } + + [Test, Category("Codec")] + public void GivenDifferentLevels_WhenEncoded_ThenEveryLevelRoundTripsAndHigherIsNoLarger( + [Values(CvfLzMethod.Ds, CvfLzMethod.Jm, CvfLzMethod.Sd4)] CvfLzMethod method) { + var data = CvfSamples.Get("x86-8k"); + var sizes = new List(); + foreach (var level in new[] { 1, 2, 3, 4 }) { + Assert.That(RoundTrip(data, method, level), Is.EqualTo(data), $"level {level}"); + sizes.Add(CvfLzCodec.Encode(data, method, level)!.Length); + } + + Assert.That(sizes[^1], Is.LessThanOrEqualTo(sizes[0])); + } +} diff --git a/Compression.Tests/DoubleSpace/DoubleSpaceHugeVolumeTests.cs b/Compression.Tests/DoubleSpace/DoubleSpaceHugeVolumeTests.cs index 9d53e7c56..15e22d666 100644 --- a/Compression.Tests/DoubleSpace/DoubleSpaceHugeVolumeTests.cs +++ b/Compression.Tests/DoubleSpace/DoubleSpaceHugeVolumeTests.cs @@ -41,8 +41,7 @@ public void Large_Volume_RoundTrips_ViaReader() { [Test, Category("DriverProof")] public void Large_Volume_IsReadByRealDmsdosDriver() { - var build = DmsdosCache.EnsureTools(); - if (build is null) Assert.Ignore("dmsdos unavailable."); + DmsdosOracle.Require(); var files = BigSet(150, 16 * 1024, 2); // ~300 clusters var w = new GenuineCvfWriter { CompressionMethod = CvfLzMethod.Auto, CompressionLevel = 1 }; @@ -52,13 +51,12 @@ public void Large_Volume_IsReadByRealDmsdosDriver() { var cvf = Path.Combine(Path.GetTempPath(), $"cwb-dblhuge-{Guid.NewGuid():N}.cvf"); try { File.WriteAllBytes(cvf, image); - var (_, det) = DmsdosCache.RunTool(DmsdosCache.CvfTest(build!), $"\"{cvf}\" -v"); + var det = Encoding.ASCII.GetBytes(DmsdosOracle.Detect(cvf)); Assert.That(Encoding.ASCII.GetString(det), Does.Contain("drivespace CVF (version 2)")); foreach (var idx in new[] { 0, 50, 100, 149 }) { var (name, data) = files[idx]; - var (exit, raw) = DmsdosCache.RunTool(DmsdosCache.DcRead(build!), $"\"{cvf}\" /{name} raw"); + var (exit, payload) = DmsdosOracle.ReadFile(cvf, name); Assert.That(exit, Is.EqualTo(0), $"dcread {name}"); - var payload = DmsdosCache.PayloadAfterDiagnostics(raw); Assert.That(payload.AsSpan(0, data.Length).ToArray(), Is.EqualTo(data), $"{name} byte-exact"); } } finally { try { File.Delete(cvf); } catch { } } diff --git a/Compression.Tests/DriveSpace3/DriveSpace3GenuineDmsdosTests.cs b/Compression.Tests/DriveSpace3/DriveSpace3GenuineDmsdosTests.cs index e26bee17e..7b322a90d 100644 --- a/Compression.Tests/DriveSpace3/DriveSpace3GenuineDmsdosTests.cs +++ b/Compression.Tests/DriveSpace3/DriveSpace3GenuineDmsdosTests.cs @@ -30,9 +30,7 @@ private static (string Name, byte[] Data)[] SampleFiles() { [TestCase(CvfLzMethod.Sq)] [TestCase(CvfLzMethod.Auto)] public void GenuineDvr3_IsMountedAndReadByteExact_ByRealDmsdosDriver(CvfLzMethod method) { - var build = DmsdosCache.EnsureTools(); - if (build is null) - Assert.Ignore("dmsdos tools unavailable (need Linux + git + cmake + C compiler, or set CWB_DMSDOS_BUILD). Skipping driver-proof gate."); + DmsdosOracle.Require(); var files = SampleFiles(); var writer = new GenuineDvr3Writer { CompressionMethod = method, CompressionLevel = 2 }; @@ -44,7 +42,7 @@ public void GenuineDvr3_IsMountedAndReadByteExact_ByRealDmsdosDriver(CvfLzMethod File.WriteAllBytes(cvfPath, image); // 1. Detection: the real driver must recognise it as DriveSpace 3. - var (_, detOut) = DmsdosCache.RunTool(DmsdosCache.CvfTest(build!), $"\"{cvfPath}\" -v"); + var detOut = Encoding.ASCII.GetBytes(DmsdosOracle.Detect(cvfPath)); var detected = Encoding.ASCII.GetString(detOut); Assert.That(detected, Does.Contain("drivespace 3 CVF"), $"dmsdos did not detect a genuine DriveSpace 3 CVF; got: {detected.Trim()}"); @@ -52,9 +50,8 @@ public void GenuineDvr3_IsMountedAndReadByteExact_ByRealDmsdosDriver(CvfLzMethod // 2. Read-back (write proof): every file must decompress byte-exact // through the real driver — i.e. our writer's output is genuine. foreach (var (name, expected) in files) { - var (exit, raw) = DmsdosCache.RunTool(DmsdosCache.DcRead(build!), $"\"{cvfPath}\" /{name} raw"); + var (exit, payload) = DmsdosOracle.ReadFile(cvfPath, name); Assert.That(exit, Is.EqualTo(0), $"dcread failed for {name}"); - var payload = DmsdosCache.PayloadAfterDiagnostics(raw); Assert.That(payload.Length, Is.GreaterThanOrEqualTo(expected.Length), $"dcread returned too few bytes for {name}"); Assert.That(payload.AsSpan(0, expected.Length).ToArray(), Is.EqualTo(expected), diff --git a/Compression.Tests/DriveSpace3/DriveSpace3GenuineMaintenanceTests.cs b/Compression.Tests/DriveSpace3/DriveSpace3GenuineMaintenanceTests.cs index ddc22e12c..25f7566c3 100644 --- a/Compression.Tests/DriveSpace3/DriveSpace3GenuineMaintenanceTests.cs +++ b/Compression.Tests/DriveSpace3/DriveSpace3GenuineMaintenanceTests.cs @@ -70,8 +70,7 @@ public void Add_Then_Remove_RoundTrips() { [Test, Category("DriverProof")] public void RebuiltImage_StillReadByRealDmsdosDriver() { - var build = DmsdosCache.EnsureTools(); - if (build is null) Assert.Ignore("dmsdos unavailable."); + DmsdosOracle.Require(); var d = new DriveSpace3FormatDescriptor(); using var ms = new MemoryStream(); ms.Write(MakeGenuine()); ms.Position = 0; @@ -81,12 +80,11 @@ public void RebuiltImage_StillReadByRealDmsdosDriver() { var cvf = Path.Combine(Path.GetTempPath(), $"cwb-dvr3reb-{Guid.NewGuid():N}.cvf"); try { File.WriteAllBytes(cvf, img); - var (_, det) = DmsdosCache.RunTool(DmsdosCache.CvfTest(build!), $"\"{cvf}\" -v"); + var det = Encoding.ASCII.GetBytes(DmsdosOracle.Detect(cvf)); Assert.That(Encoding.ASCII.GetString(det), Does.Contain("drivespace 3 CVF")); foreach (var (n, data) in Seed) { - var (exit, raw) = DmsdosCache.RunTool(DmsdosCache.DcRead(build!), $"\"{cvf}\" /{n} raw"); + var (exit, payload) = DmsdosOracle.ReadFile(cvf, n); Assert.That(exit, Is.EqualTo(0), $"dcread {n}"); - var payload = DmsdosCache.PayloadAfterDiagnostics(raw); Assert.That(payload.AsSpan(0, data.Length).ToArray(), Is.EqualTo(data), $"{n} byte-exact after rebuild"); } } finally { try { File.Delete(cvf); } catch { } } diff --git a/Compression.Tests/DriveSpace3/DriveSpace3HugeVolumeTests.cs b/Compression.Tests/DriveSpace3/DriveSpace3HugeVolumeTests.cs index bda8d7eda..01bf2fa30 100644 --- a/Compression.Tests/DriveSpace3/DriveSpace3HugeVolumeTests.cs +++ b/Compression.Tests/DriveSpace3/DriveSpace3HugeVolumeTests.cs @@ -68,8 +68,7 @@ public void Large_Volume_FullReadWrite_AddRemove_ViaDescriptor() { [Test, Category("DriverProof")] public void Large_Volume_IsReadByRealDmsdosDriver() { - var build = DmsdosCache.EnsureTools(); - if (build is null) Assert.Ignore("dmsdos unavailable."); + DmsdosOracle.Require(); var files = BigSet(120, 64 * 1024, 2); // ~240 clusters, mixed var w = new GenuineDvr3Writer { CompressionMethod = CvfLzMethod.Auto, CompressionLevel = 1 }; @@ -79,14 +78,13 @@ public void Large_Volume_IsReadByRealDmsdosDriver() { var cvf = Path.Combine(Path.GetTempPath(), $"cwb-dvr3huge-{Guid.NewGuid():N}.cvf"); try { File.WriteAllBytes(cvf, image); - var (_, det) = DmsdosCache.RunTool(DmsdosCache.CvfTest(build!), $"\"{cvf}\" -v"); + var det = Encoding.ASCII.GetBytes(DmsdosOracle.Detect(cvf)); Assert.That(Encoding.ASCII.GetString(det), Does.Contain("drivespace 3 CVF")); // sample a handful across the volume foreach (var idx in new[] { 0, 37, 80, 119 }) { var (name, data) = files[idx]; - var (exit, raw) = DmsdosCache.RunTool(DmsdosCache.DcRead(build!), $"\"{cvf}\" /{name} raw"); + var (exit, payload) = DmsdosOracle.ReadFile(cvf, name); Assert.That(exit, Is.EqualTo(0), $"dcread {name}"); - var payload = DmsdosCache.PayloadAfterDiagnostics(raw); Assert.That(payload.AsSpan(0, data.Length).ToArray(), Is.EqualTo(data), $"{name} byte-exact"); } } finally { try { File.Delete(cvf); } catch { } } diff --git a/Compression.Tests/ExternalFsInteropTests.cs b/Compression.Tests/ExternalFsInteropTests.cs index 616ce7d8f..772c3b401 100644 --- a/Compression.Tests/ExternalFsInteropTests.cs +++ b/Compression.Tests/ExternalFsInteropTests.cs @@ -2303,9 +2303,7 @@ public void DoubleSpace_OurImage_DmsdosAccepts(Compression.Core.FileSystems.Cvf. // mount it, and read every file back byte-exact. The feature-rich // DoubleSpaceWriter emits a self-consistent (non-genuine) layout; the // driver-genuine emitter is GenuineCvfWriter, proven here. - var build = DmsdosCache.EnsureTools(); - if (build is null) - Assert.Ignore("dmsdos tools unavailable (need Linux + git + cmake + C compiler, or set CWB_DMSDOS_BUILD)."); + DmsdosOracle.Require(); var files = new (string Name, byte[] Data)[] { ("HELLO.TXT", SmallText), @@ -2318,14 +2316,13 @@ public void DoubleSpace_OurImage_DmsdosAccepts(Compression.Core.FileSystems.Cvf. var cvfPath = Path.Combine(this._tmpDir, "dblspace_genuine.cvf"); File.WriteAllBytes(cvfPath, image); - var (_, det) = DmsdosCache.RunTool(DmsdosCache.CvfTest(build!), $"\"{cvfPath}\" -v"); + var det = Encoding.ASCII.GetBytes(DmsdosOracle.Detect(cvfPath)); Assert.That(Encoding.ASCII.GetString(det), Does.Contain("drivespace CVF (version 2)"), "dmsdos did not detect our CVF as a genuine DoubleSpace/DriveSpace v2 volume"); foreach (var (name, expected) in files) { - var (exit, raw) = DmsdosCache.RunTool(DmsdosCache.DcRead(build!), $"\"{cvfPath}\" /{name} raw"); + var (exit, payload) = DmsdosOracle.ReadFile(cvfPath, name); Assert.That(exit, Is.EqualTo(0), $"dcread failed for {name}"); - var payload = DmsdosCache.PayloadAfterDiagnostics(raw); Assert.That(payload.Length, Is.GreaterThanOrEqualTo(expected.Length), $"short read for {name}"); Assert.That(payload.AsSpan(0, expected.Length).ToArray(), Is.EqualTo(expected), $"{name} did not read back byte-exact through the dmsdos driver"); diff --git a/Compression.Tests/Stacker/GenuineStackerCodecOracleTests.cs b/Compression.Tests/Stacker/GenuineStackerCodecOracleTests.cs new file mode 100644 index 000000000..4db2bebb1 --- /dev/null +++ b/Compression.Tests/Stacker/GenuineStackerCodecOracleTests.cs @@ -0,0 +1,187 @@ +using System.Text; +using Compression.Core.FileSystems.Cvf; +using Compression.Tests.Cvf; +using Compression.Tests.Support; +using FileSystem.Stacker; + +namespace Compression.Tests.Stacker; + +/// +/// Stacker cluster codecs against the dmsdos driver as a black box (Linux natively, Windows through +/// WSL): volumes our writer builds must read back byte-exact through dcread and through our own +/// reader. Covers the SD-4 encoder with back-references and the whole-cluster ForceCompress case. +/// +[TestFixture] +public sealed class GenuineStackerCodecOracleTests { + private const int ClusterBytes = 8192; + + private string _dir = null!; + + [SetUp] + public void SetUp() => Directory.CreateDirectory(this._dir = Path.Combine(Path.GetTempPath(), $"cwb-stac-oracle-{Guid.NewGuid():N}")); + + [TearDown] + public void TearDown() { + try { Directory.Delete(this._dir, true); } catch (IOException) { /* best effort */ } + } + + private static byte[] Build(CvfLzMethod method, bool force, params (string Name, byte[] Data)[] files) { + var writer = new GenuineStackerWriter { CompressionMethod = method, CompressionLevel = 3, ForceCompress = force }; + foreach (var (n, d) in files) writer.AddFile(n, d); + return writer.Build(); + } + + private static void AssertOurReaderReads(byte[] image, (string Name, byte[] Data)[] files) { + using var reader = new GenuineStackerReader(new MemoryStream(image)); + foreach (var (name, expected) in files) + Assert.That(reader.Extract(reader.Entries.Single(e => e.Name == name)), Is.EqualTo(expected), $"our reader: {name}"); + } + + private void AssertDmsdosReads(byte[] image, (string Name, byte[] Data)[] files) { + var path = Path.Combine(this._dir, "stac.cvf"); + File.WriteAllBytes(path, image); + Assert.That(DmsdosOracle.Detect(path), Does.Contain("stacker"), "dmsdos does not detect the volume"); + foreach (var (name, expected) in files) { + var (exit, data) = DmsdosOracle.ReadFile(path, name); + Assert.That(exit, Is.Zero, $"dcread failed for {name}"); + Assert.That(data.Length, Is.GreaterThanOrEqualTo(expected.Length), $"dcread returned too few bytes for {name}"); + Assert.That(data[..expected.Length], Is.EqualTo(expected), $"dmsdos: {name}"); + } + } + + /// A cluster whose SD-3 payload needs all 16 sectors: 15 sectors < payload ≤ 16 sectors. + internal static byte[] WholeClusterPayloadData() { + var random = new byte[ClusterBytes]; + new Random(4711).NextBytes(random); + int lo = 0, hi = ClusterBytes; // number of random bytes; the rest repeats one byte + while (lo < hi) { + var mid = (lo + hi) / 2; + var size = Sd3Codec.Encode(Mix(random, mid)).Length; + if (size <= 15 * 512) lo = mid + 1; else hi = mid; + } + var data = Mix(random, lo); + var sectors = (Sd3Codec.Encode(data).Length + 511) / 512; + Assume.That(sectors, Is.EqualTo(16), "could not construct a whole-cluster SD-3 payload"); + return data; + + static byte[] Mix(byte[] random, int count) { + var d = new byte[ClusterBytes]; + random.AsSpan(0, count).CopyTo(d); + return d; + } + } + + [Test, Category("Codec")] + public void GivenForceCompress_WhenACompressedClusterNeedsAllSectors_ThenItIsStoredAndReadsBackExact() { + var data = WholeClusterPayloadData(); + (string, byte[])[] files = [("FULL.BIN", data)]; + + var image = Build(CvfLzMethod.Sd3, force: true, files); + + AssertOurReaderReads(image, files); + } + + [Test, Category("DriverProof")] + public void GivenForceCompress_WhenACompressedClusterNeedsAllSectors_ThenDmsdosReadsItBackExact() { + DmsdosOracle.Require(); + (string, byte[])[] files = [("FULL.BIN", WholeClusterPayloadData())]; + + AssertDmsdosReads(Build(CvfLzMethod.Sd3, force: true, files), files); + } + + private static (string, byte[])[] CodecSamples() { + var text = Encoding.ASCII.GetBytes(string.Concat(Enumerable.Repeat("SD-4 encoder verification line with some repetition.\r\n", 600))); + var runs = new byte[3 * ClusterBytes]; + for (var i = 0; i < runs.Length; ++i) runs[i] = (byte)(i / 5000); // long matches: every extension + return [ + ("TEXT.TXT", text), + ("RUNS.BIN", runs), + ("SAMPTEXT.TXT", CvfSamples.Get("text-8k")), + ("SAMPX86.BIN", CvfSamples.Get("x86-8k")), + ("SAMPTAB.BIN", CvfSamples.Get("table-8k")), + ("SAMPALPH.BIN", CvfSamples.Get("alphabet-8k")), + ("SAMPDIR.BIN", CvfSamples.Get("dir-8k")), + ("ONE.BIN", [0x5A]), + ]; + } + + [Test, Category("DriverProof")] + public void GivenSd4ClustersWithBackReferences_WhenReadByDmsdos_ThenEveryFileIsByteExact([Values(CvfLzMethod.Sd4, CvfLzMethod.Auto)] CvfLzMethod method) { + DmsdosOracle.Require(); + var files = CodecSamples(); + var image = Build(method, force: false, files); + + AssertDmsdosReads(image, files); + AssertOurReaderReads(image, files); + } + + /// + /// One cluster whose byte counts grow just faster than Fibonacci (1, 1, 3, 5, 9, 15, … 1973, each + /// the sum of the two before plus one, so no tie lets a code stay shallow), the rest one more value: + /// an optimal code over them is 16 bits deep, so a literals-only SD-4 stream needs the full 15-bit + /// limit. + /// + internal static byte[] SkewedCluster() { + var data = new List(ClusterBytes); + int a = 1, b = 1; + for (var symbol = 0; symbol < 16; ++symbol, (a, b) = (b, a + b + 1)) + data.AddRange(Enumerable.Repeat((byte)(symbol * 13), a)); + data.AddRange(Enumerable.Repeat((byte)250, ClusterBytes - data.Count)); + var array = data.ToArray(); + new Random(1).Shuffle(array); + return array; + } + + // The 5-bit "last" field after the signature word is the longest main-code length. + private static int LongestMainCode(byte[] payload) => (payload[2] | payload[3] << 8) >> 8 & 0x1F; + + [Test, Category("Codec")] + public void GivenSkewedLiterals_WhenSd4Encoded_ThenTheMainCodeUsesTheFull15BitsAndRoundTrips() { + var data = SkewedCluster(); + var payload = Sd4Codec.Encode(data, level: 2, matches: false); + + Assert.Multiple(() => { + Assert.That(LongestMainCode(payload), Is.EqualTo(15)); + Assert.That(Sd4Codec.Decode(payload, payload.Length, data.Length), Is.EqualTo(data)); + }); + } + + // A stored volume whose one cluster is then rewritten as a compressed run holding the given payload. + private static byte[] VolumeWithClusterPayload(byte[] cluster, byte[] payload) { + var image = Build(CvfLzMethod.Stored, force: false, ("SKEW.BIN", cluster)); + const int amapEntry = (2 + 3) * 512 + 2 * 3; // AMAP area 0 for cluster 2 (FAT start 2) + var sector = image[amapEntry] | image[amapEntry + 1] << 8; + var sectors = (payload.Length + 511) / 512; + Assume.That(sectors, Is.LessThan(16), "payload must be shorter than the cluster"); + Array.Clear(image, sector * 512, 16 * 512); + payload.CopyTo(image, sector * 512); + image[amapEntry + 2] = (byte)(sectors - 1); // flags 0: compressed + return image; + } + + [Test, Category("Codec")] + public void GivenAPatchedSd4Cluster_WhenReadByOurReader_ThenItIsByteExact() { + var data = SkewedCluster(); + (string, byte[])[] files = [("SKEW.BIN", data)]; + + AssertOurReaderReads(VolumeWithClusterPayload(data, Sd4Codec.Encode(data, 2, matches: false)), files); + } + + [Test, Category("DriverProof")] + public void GivenSd4CodesOf15Bits_WhenReadByDmsdos_ThenTheClusterIsByteExact() { + DmsdosOracle.Require(); + var data = SkewedCluster(); + (string, byte[])[] files = [("SKEW.BIN", data)]; + + AssertDmsdosReads(VolumeWithClusterPayload(data, Sd4Codec.Encode(data, 2, matches: false)), files); + } + + [Test, Category("Codec")] + public void GivenSd4Clusters_WhenEncoded_ThenTheyAreWithinTenPercentOfTheDmsdosEncoder( + [Values("text-8k", "x86-8k", "table-8k", "alphabet-8k", "dir-8k")] string sample) { + var ours = Sd4Codec.Encode(CvfSamples.Get(sample), level: 3).Length; + var theirs = CvfReferenceVectorTests.Load($"{sample}.sd4").Length; + + Assert.That(ours, Is.LessThanOrEqualTo(theirs * 11 / 10), $"ours {ours} B, dmsdos {theirs} B"); + } +} diff --git a/Compression.Tests/Stacker/GenuineStackerDmsdosTests.cs b/Compression.Tests/Stacker/GenuineStackerDmsdosTests.cs index efa89ffb3..439273de3 100644 --- a/Compression.Tests/Stacker/GenuineStackerDmsdosTests.cs +++ b/Compression.Tests/Stacker/GenuineStackerDmsdosTests.cs @@ -30,9 +30,7 @@ private static (string Name, byte[] Data)[] SampleFiles() { [TestCase(CvfLzMethod.Sd3)] [TestCase(CvfLzMethod.Auto)] public void GenuineStacker_IsMountedAndReadByteExact_ByRealDmsdosDriver(CvfLzMethod method) { - var build = DmsdosCache.EnsureTools(); - if (build is null) - Assert.Ignore("dmsdos tools unavailable (need Linux + git + cmake + C compiler, or set CWB_DMSDOS_BUILD). Skipping driver-proof gate."); + DmsdosOracle.Require(); var files = SampleFiles(); var writer = new GenuineStackerWriter { CompressionMethod = method, CompressionLevel = 2 }; @@ -44,16 +42,15 @@ public void GenuineStacker_IsMountedAndReadByteExact_ByRealDmsdosDriver(CvfLzMet File.WriteAllBytes(cvfPath, image); // 1. Detection. - var (_, detOut) = DmsdosCache.RunTool(DmsdosCache.CvfTest(build!), $"\"{cvfPath}\" -v"); + var detOut = Encoding.ASCII.GetBytes(DmsdosOracle.Detect(cvfPath)); var detected = Encoding.ASCII.GetString(detOut); Assert.That(detected, Does.Contain("stacker version 3 CVF"), $"dmsdos did not detect a genuine Stacker 3 CVF; got: {detected.Trim()}"); // 2. Write proof: the real driver reads every file back byte-exact. foreach (var (name, expected) in files) { - var (exit, raw) = DmsdosCache.RunTool(DmsdosCache.DcRead(build!), $"\"{cvfPath}\" /{name} raw"); + var (exit, payload) = DmsdosOracle.ReadFile(cvfPath, name); Assert.That(exit, Is.EqualTo(0), $"dcread failed for {name}"); - var payload = DmsdosCache.PayloadAfterDiagnostics(raw); Assert.That(payload.Length, Is.GreaterThanOrEqualTo(expected.Length), $"dcread returned too few bytes for {name}"); Assert.That(payload.AsSpan(0, expected.Length).ToArray(), Is.EqualTo(expected), diff --git a/Compression.Tests/Stacker/StackerHugeVolumeTests.cs b/Compression.Tests/Stacker/StackerHugeVolumeTests.cs index eeb6b1fd6..1eb1abd9a 100644 --- a/Compression.Tests/Stacker/StackerHugeVolumeTests.cs +++ b/Compression.Tests/Stacker/StackerHugeVolumeTests.cs @@ -40,8 +40,7 @@ public void Above512Clusters_RoundTrips_ViaReader() { [Test, Category("DriverProof")] public void Above512Clusters_IsReadByRealDmsdosDriver() { - var build = DmsdosCache.EnsureTools(); - if (build is null) Assert.Ignore("dmsdos unavailable."); + DmsdosOracle.Require(); var files = BigSet(300, 16 * 1024, 2); // ~600 clusters var w = new GenuineStackerWriter { CompressionMethod = CvfLzMethod.Auto, CompressionLevel = 1 }; @@ -51,13 +50,12 @@ public void Above512Clusters_IsReadByRealDmsdosDriver() { var cvf = Path.Combine(Path.GetTempPath(), $"cwb-stachuge-{Guid.NewGuid():N}.cvf"); try { File.WriteAllBytes(cvf, image); - var (_, det) = DmsdosCache.RunTool(DmsdosCache.CvfTest(build!), $"\"{cvf}\" -v"); + var det = Encoding.ASCII.GetBytes(DmsdosOracle.Detect(cvf)); Assert.That(Encoding.ASCII.GetString(det), Does.Contain("stacker")); foreach (var idx in new[] { 0, 100, 200, 299 }) { var (name, data) = files[idx]; - var (exit, raw) = DmsdosCache.RunTool(DmsdosCache.DcRead(build!), $"\"{cvf}\" /{name} raw"); + var (exit, payload) = DmsdosOracle.ReadFile(cvf, name); Assert.That(exit, Is.EqualTo(0), $"dcread {name}"); - var payload = DmsdosCache.PayloadAfterDiagnostics(raw); Assert.That(payload.AsSpan(0, data.Length).ToArray(), Is.EqualTo(data), $"{name} byte-exact"); } } finally { try { File.Delete(cvf); } catch { } } diff --git a/Compression.Tests/Support/DmsdosOracle.cs b/Compression.Tests/Support/DmsdosOracle.cs new file mode 100644 index 000000000..94a1465e5 --- /dev/null +++ b/Compression.Tests/Support/DmsdosOracle.cs @@ -0,0 +1,53 @@ +namespace Compression.Tests.Support; + +/// +/// The GPL dmsdos tools as a black-box oracle on any host: natively on Linux (built by +/// ), through WSL on Windows (cloned and built once under +/// ~/.cache/cwb-dmsdos inside the distro). Only the tools' behaviour is used — volumes go in, +/// detection text and file contents come out. +/// +internal static class DmsdosOracle { + private static readonly Lazy Build = new(Locate); + + /// Skips the calling test when the tools cannot be had on this machine. + public static void Require() { + if (Build.Value is null) + Assert.Ignore("dmsdos tools unavailable (need Linux or WSL with git + cmake + a C compiler, or CWB_DMSDOS_BUILD)."); + } + + /// What cvftest -v makes of the volume. + public static string Detect(string cvfPath) + => FsInteropToolbox.RunWsl($"'{Build.Value}/cvftest' {FsInteropToolbox.WinToWsl(cvfPath)} -v 2>&1").StdOut; + + /// + /// Reads from the root of the volume with dcread … raw; the bytes are + /// the whole clusters the driver decoded, so callers compare the first file-size bytes. + /// + public static (int Exit, byte[] Data) ReadFile(string cvfPath, string name) { + var outFile = $"{cvfPath}.{name}.out"; + try { + var r = FsInteropToolbox.RunWsl( + $"'{Build.Value}/dcread' {FsInteropToolbox.WinToWsl(cvfPath)} /{name} raw > {FsInteropToolbox.WinToWsl(outFile)} 2>/dev/null"); + var raw = File.Exists(outFile) ? File.ReadAllBytes(outFile) : []; + return (r.ExitCode, DmsdosCache.PayloadAfterDiagnostics(raw)); + } finally { + try { File.Delete(outFile); } catch (IOException) { /* best effort */ } + } + } + + private static string? Locate() { + if (!OperatingSystem.IsWindows()) return DmsdosCache.EnsureTools(); + if (!FsInteropToolbox.WslAvailable) return null; + + const string script = + "set -e; d=$HOME/.cache/cwb-dmsdos; " + + "if [ ! -x $d/build/dcread ] || [ ! -x $d/build/cvftest ]; then " + + "command -v git >/dev/null && command -v cmake >/dev/null && command -v cc >/dev/null; " + + "mkdir -p $d; [ -d $d/src/.git ] || git clone -q --depth 1 " + DmsdosCache.RepoUrl + " $d/src; " + + "cmake -S $d/src -B $d/build -DCMAKE_BUILD_TYPE=Release -DCMAKE_POLICY_VERSION_MINIMUM=3.5 >/dev/null 2>&1; " + + "cmake --build $d/build -j 2 >/dev/null 2>&1; fi; echo $d/build"; + var result = FsInteropToolbox.RunWsl(script); + var lines = result.StdOut.Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + return result.ExitCode == 0 && lines.Length > 0 ? lines[^1] : null; + } +} diff --git a/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.DriveSpace3/GenuineDvr3Writer.cs b/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.DriveSpace3/GenuineDvr3Writer.cs index 97ae13e73..bff647676 100644 --- a/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.DriveSpace3/GenuineDvr3Writer.cs +++ b/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.DriveSpace3/GenuineDvr3Writer.cs @@ -66,6 +66,8 @@ public sealed class GenuineDvr3Writer { /// smaller of compressed/stored is chosen per cluster (auto-best).
public bool ForceCompress { get; init; } + private static byte[] Smaller(byte[] a, byte[] b) => b.Length < a.Length ? b : a; + /// Adds a file to the root directory of the compressed volume. public void AddFile(string name, byte[] data) { ArgumentNullException.ThrowIfNull(name); @@ -120,7 +122,10 @@ public byte[] Build() { var physCursor = physDataStart; var layout = new List(); foreach (var (cl, full) in clusterFull) { - var comp = CvfLzCodec.Encode(full, this.CompressionMethod, this.CompressionLevel); + // Auto covers every codec a DriveSpace 3 volume reads: DS/JM (the codec's Auto) and SQ. + var comp = this.CompressionMethod == CvfLzMethod.Auto + ? Smaller(CvfLzCodec.Encode(full, CvfLzMethod.Auto, this.CompressionLevel)!, CvfLzCodec.Encode(full, CvfLzMethod.Sq, this.CompressionLevel)!) + : CvfLzCodec.Encode(full, this.CompressionMethod, this.CompressionLevel); var ksize = comp is null ? Spc : (comp.Length + Ss - 1) / Ss; if (comp is not null && ksize <= Spc && (ksize < Spc || this.ForceCompress)) { layout.Add(new ClusterPlan(cl, comp, ksize, true, physCursor)); diff --git a/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.Stacker/FORMAT-NOTES.md b/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.Stacker/FORMAT-NOTES.md index b1d46e809..687195d16 100644 --- a/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.Stacker/FORMAT-NOTES.md +++ b/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.Stacker/FORMAT-NOTES.md @@ -12,7 +12,7 @@ > [`docs/CVF-CODECS-ON-DISK.md`](../../../docs/CVF-CODECS-ON-DISK.md). The reader > decodes the full SD-4 format (run tokens, short and long matches), pinned to > clusters the dmsdos SD-3/SD-4 compressors wrote (`CvfReferenceVectorTests`); the -> writer's SD-4 output is literals-only. The AMAP flag nibble is honoured: +> writer's SD-4 output uses back-references and run-coded tables, read back by dmsdos. The AMAP flag nibble is honoured: > fragmented and suballocated clusters are refused, not guessed at. > - **The legacy `StackerWriter` (invented `STKMAP01` trailer) is NOT genuine:** > `dmsdos` panics on it. It is retained only for self-round-trip. diff --git a/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.Stacker/GenuineStackerWriter.cs b/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.Stacker/GenuineStackerWriter.cs index 718afd2d2..fe11ba9dc 100644 --- a/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.Stacker/GenuineStackerWriter.cs +++ b/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.Stacker/GenuineStackerWriter.cs @@ -74,7 +74,10 @@ public sealed class GenuineStackerWriter { /// Codec effort (search depth). Higher = better ratio, slower. public int CompressionLevel { get; init; } = 1; - /// Keep a compressed cluster even if it does not shrink (auto-best off). + /// Keep a compressed cluster even if it does not shrink (auto-best off). Stacker cannot + /// record a compressed run that fills a whole cluster — such a run reads as stored — so a payload that + /// needs every sector of its cluster is stored raw regardless; any shorter payload is kept compressed + /// either way, which leaves this option without effect on Stacker volumes. public bool ForceCompress { get; init; } private readonly List<(string Name, byte[] Data)> _files = []; @@ -147,7 +150,9 @@ public byte[] Build() { _ => CvfLzCodec.Encode(full, CvfLzMethod.Ds, this.CompressionLevel), }; var ksize = comp is null ? Spc : (comp.Length + Ss - 1) / Ss; - if (comp is not null && ksize < Spc || (comp is not null && this.ForceCompress && ksize <= Spc)) { + // A run as long as the cluster always reads as stored (the AMAP has no flag for a whole-cluster + // compressed run), so a payload needing all Spc sectors is stored raw even under ForceCompress. + if (comp is not null && ksize < Spc) { layout.Add(new ClusterPlan(cl, comp!, ksize, physCursor)); physCursor += ksize; } else { diff --git a/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.Stacker/StackerFormatDescriptor.cs b/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.Stacker/StackerFormatDescriptor.cs index 63e9ac31f..be615e7ae 100644 --- a/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.Stacker/StackerFormatDescriptor.cs +++ b/Hawkynt.FileFormats.FileSystems/FileSystems/FileSystem.Stacker/StackerFormatDescriptor.cs @@ -154,7 +154,8 @@ public sealed class StackerFormatDescriptor : IFormatDescriptor, IArchiveFormatO new FormatOptionDescriptor( Key: "ForceCompress", DisplayName: "Force compression", Kind: FormatOptionKind.Boolean, Default: "false", - Description: "Keep the compressed form even when it does not shrink a cluster.", + Description: "Keep the compressed form even when it does not shrink a cluster. A compressed run " + + "filling a whole cluster cannot be told from a stored one on Stacker, so such a cluster is still stored.", DependsOn: "Compatibility=Genuine"), ]; diff --git a/Hawkynt.FileFormats.FileSystems/REFERENCE.md b/Hawkynt.FileFormats.FileSystems/REFERENCE.md index 446cc52d5..a53eb4bf9 100644 --- a/Hawkynt.FileFormats.FileSystems/REFERENCE.md +++ b/Hawkynt.FileFormats.FileSystems/REFERENCE.md @@ -12929,7 +12929,7 @@ Builds a genuine Stac Electronics STACVOL — the on-disk shape a real Stacker d | `GenuineStackerWriter` | `GenuineStackerWriter()` | | | `CompressionLevel` | `int CompressionLevel { get; init; }` | Codec effort (search depth). Higher = better ratio, slower. | | `CompressionMethod` | `CvfLzMethod CompressionMethod { get; init; }` | Per-cluster compression: Stored (default), Sd3 (Stacker 3), Sd4 (Stacker 4), Ds, or Auto (smallest of Ds/Sd3/Sd4 per cluster). The Stacker driver tells them apart by the payload's first word — 0x0081 SD-4, 0x5344 ("DS") the DoubleSpace codec, anything else SD-3. JM/SQ map to Ds. | -| `ForceCompress` | `bool ForceCompress { get; init; }` | Keep a compressed cluster even if it does not shrink (auto-best off). | +| `ForceCompress` | `bool ForceCompress { get; init; }` | Keep a compressed cluster even if it does not shrink (auto-best off). Stacker cannot record a compressed run that fills a whole cluster — such a run reads as stored — so a payload that needs every sector of its cluster is stored raw regardless; any shorter payload is kept compressed either way, which leaves this option without effect on Stacker volumes. | | `Timestamp` | `DateTime Timestamp { get; init; }` | Creation/modification timestamp stamped on every file entry. Default (before 1980) leaves the FAT date/time fields zero. | | `Version` | `int Version { get; init; }` | Stacker major version recorded in the decoded superblock (< 410 ⇒ v3). | | `VolumeLabel` | `string VolumeLabel { get; init; }` | Optional inner-volume label (≤11 chars). Empty = no label entry. | diff --git a/docs/CVF-CODECS-ON-DISK.md b/docs/CVF-CODECS-ON-DISK.md index b2c7052ed..26b757432 100644 --- a/docs/CVF-CODECS-ON-DISK.md +++ b/docs/CVF-CODECS-ON-DISK.md @@ -3,7 +3,8 @@ The per-cluster compression formats of the DOS compressed-volume family: Microsoft DoubleSpace / DriveSpace (DS-0-x, JM-0-x), DriveSpace 3 (JM-0-1 "High", SQ-0-0 "Ultra") and Stac Stacker (SD-3, SD-4). `Compression.Core.FileSystems.Cvf` (`CvfLzCodec`, `Sd3Codec`, -`Sd4Codec`) implements this document; the container layouts (MDFAT, AMAP, BitFAT) live in the +`Sd4Codec`) implements this document — it was written from this document and the reference vectors +alone, with dmsdos run only as a black box; the container layouts (MDFAT, AMAP, BitFAT) live in the `FORMAT-NOTES.md` beside each file-system reader. ## 📚 Provenance @@ -45,6 +46,10 @@ its kind), together with whether the run fills a whole cluster: | 1 | 0 | **fragmented** (first sector starts `0xED`, piece count, 4-byte piece list) | stored (directory) | | 1 | 1 | **suballocated** (signature `0x1234` at the sector end) | suballocated | +A compressed cluster can therefore never occupy all of its cluster's sectors: a run that long reads +as stored whatever its content (the table above). A writer whose +compressed payload needs every sector stores the cluster raw instead. + Fragmented and suballocated clusters are refused by `GenuineStackerReader` — no sample of either has been verified. @@ -85,6 +90,17 @@ decoder does not require the final sync. Copies may overlap (distance shorter than length) and are performed byte by byte. +**Measured: one sync per sector.** In all 16 DS/JM reference streams (8 KiB and 32 KiB clusters) +the dmsdos compressors emit a sync after every 512 decoded bytes, not only after the last one, and +no match runs across a 512-byte boundary (0 of all matches) — distances do reach back across +boundaries (up to 4414). Our encoder writes the same shape. The decoder accepts syncs anywhere. + +**A cluster shorter than its nominal size** (a file's last cluster, MDFAT decoded size below the +cluster) ends with a sync followed by nothing but zero padding; decoding stops there and the rest of +the cluster reads as zero. A sync with non-zero bits behind it is a mid-stream marker and decoding +continues. Stacker's use of the DS grammar also ends where the stream runs out or reaches its zero +padding (a `0 0` token with distance 0). + ## 🔢 SQ-0-0 Bytes `53 51` ("SQ"), a 16-bit version (0), then a raw **RFC 1951 DEFLATE** stream: the same block @@ -166,6 +182,12 @@ is extended by 4 bits; if the sum is 36, by 8 more; at 291 by 12 more; at 4386 b not needed to recover the data). A match reaching before the cluster start is invalid; a final match longer than the space left is clipped. +**Our encoder** emits short and long matches (all four length extensions), literals and the end +token; it transmits the code lengths with all six run tokens and builds both codes with package-merge +(optimal, length-limited, always complete). Measured: dmsdos reads main codes 15 bits deep +(`GivenSd4CodesOf15Bits_WhenReadByDmsdos_ThenTheClusterIsByteExact`), so the encoder uses the format's +full 15-bit limit for both codes. + ## ✅ Verification * `Compression.Tests/Cvf/ReferenceVectors/` — 32 cluster payloads written by the dmsdos @@ -179,8 +201,6 @@ match longer than the space left is clipped. ## ⚠️ Not covered -* **Our SD-4 encoder emits literals only.** The decoder reads the whole format; an encoder that - emits match and run tokens has not been written. * **DriveSpace 3 fragmented clusters** (an MDFAT flag dmsdos itself describes as guessed) and **Stacker fragmented / suballocated clusters** are not read. * No Microsoft or Stac tool has produced or read any of these streams on this machine. From 148a6229d322df75d7381b2713a2c6065050d9aa Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 26 Oct 2026 12:08:07 +0000 Subject: [PATCH 23/34] * refresh the archive browser screenshot --- docs/screenshots/archive-browser.png | Bin 66518 -> 66630 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/screenshots/archive-browser.png b/docs/screenshots/archive-browser.png index de88d7dd43f38a733fe5557dd0e66b254aa140ee..27445a0db06cc4a4e7e4207f9b8865c437514210 100644 GIT binary patch delta 28390 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�c>h*)qJgxitOneF>34ZtTfY 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 delta 28164 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 U8ok6 Date: Mon, 26 Oct 2026 13:08:34 +0100 Subject: [PATCH 24/34] * regenerate references --- Compression.Core/README.md | 2 +- docs/catalogue/our-registry.csv | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Compression.Core/README.md b/Compression.Core/README.md index e918da817..e5e126063 100644 --- a/Compression.Core/README.md +++ b/Compression.Core/README.md @@ -205,7 +205,7 @@ Use the concrete version you intend to consume; this document does not predict a -Every public and protected member of all 734 types, generated from the built assembly and its XML documentation, is in [REFERENCE.md](https://github.com/Hawkynt/CompressionWorkbench/blob/main/Compression.Core/REFERENCE.md). +Every public and protected member of all 735 types, generated from the built assembly and its XML documentation, is in [REFERENCE.md](https://github.com/Hawkynt/CompressionWorkbench/blob/main/Compression.Core/REFERENCE.md). diff --git a/docs/catalogue/our-registry.csv b/docs/catalogue/our-registry.csv index a9ce7575f..746cbf749 100644 --- a/docs/catalogue/our-registry.csv +++ b/docs/catalogue/our-registry.csv @@ -734,6 +734,7 @@ block,bb:BB_Shoco,Shoco,Dictionary,,,,Y,Y, block,bb:BB_Shrink,Shrink,Dictionary,,,,Y,Y, block,bb:BB_Snappy,Snappy,Dictionary,,,,Y,Y, block,bb:BB_Sqx,SQX,Dictionary,,,,Y,Y, +block,bb:BB_StackerSd4,Stacker SD-4,Dictionary,,,,Y,Y, block,bb:BB_SuffixTree,Suffix Tree Compression,Dictionary,,,,Y,Y, block,bb:BB_tANS,tANS,Entropy,,,,Y,Y, block,bb:BB_Tunstall,Tunstall Coding,Entropy,,,,Y,Y, From b17128312861323295d2171a1a73fb53a189fc3a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 26 Oct 2026 12:08:51 +0000 Subject: [PATCH 25/34] * refresh the archive browser screenshot --- docs/screenshots/archive-browser.png | Bin 66518 -> 66134 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/screenshots/archive-browser.png b/docs/screenshots/archive-browser.png index de88d7dd43f38a733fe5557dd0e66b254aa140ee..07fb9ef52326c8c4ccf4e3e4056102cda5ca6e14 100644 GIT binary patch delta 27956 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 delta 28333 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 Date: Mon, 26 Oct 2026 12:08:57 +0000 Subject: [PATCH 26/34] * refresh the application screenshot --- screenshots/main-window.png | Bin 65657 -> 66758 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/screenshots/main-window.png b/screenshots/main-window.png index e3795c0d04a8041389e0aaa3edc8cc5c89e36820..a6de9fe48d17b5ab50eb5c6153d5031f1bfaeee0 100644 GIT binary patch literal 66758 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

J-t(C?D*vNbSd$Mpldw%6G^3`$t6)P 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 O*JNyHeySLKDeAvN*8HCU From d69e70719ef00a7831647186bba01e83a35e78fe Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 26 Oct 2026 12:09:45 +0000 Subject: [PATCH 27/34] * refresh the archive browser screenshot --- docs/screenshots/archive-browser.png | Bin 66518 -> 66882 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/screenshots/archive-browser.png b/docs/screenshots/archive-browser.png index de88d7dd43f38a733fe5557dd0e66b254aa140ee..08c67e5cd90781b9d092817eec18d52fb8b48082 100644 GIT binary patch delta 28761 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 Ief0Fd0Ke-e6#xJL delta 28290 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 Date: Mon, 26 Oct 2026 12:10:36 +0000 Subject: [PATCH 28/34] * refresh the archive browser screenshot --- docs/screenshots/archive-browser.png | Bin 66518 -> 65657 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/screenshots/archive-browser.png b/docs/screenshots/archive-browser.png index de88d7dd43f38a733fe5557dd0e66b254aa140ee..e3795c0d04a8041389e0aaa3edc8cc5c89e36820 100644 GIT binary patch literal 65657 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

J-t(C?D*vNbSd$Mpldw%6G^3`$t6)P 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 O*JNyHeySLKDeAvN*8HCU literal 66518 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 Kj%4xcAO8X%gq{lk From f7ceb835b7db58e761f0817ff10a17c3b6e1cc66 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 26 Oct 2026 12:10:47 +0000 Subject: [PATCH 29/34] * refresh the archive browser screenshot --- docs/screenshots/archive-browser.png | Bin 66518 -> 66542 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/screenshots/archive-browser.png b/docs/screenshots/archive-browser.png index de88d7dd43f38a733fe5557dd0e66b254aa140ee..5a0c859e84abab8b88df1cbca136e2103c3af24b 100644 GIT binary patch delta 28443 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 bo@MVC(m5TPYCYt_`AAROP^(1qX2^d4eShVN delta 28271 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 Date: Mon, 26 Oct 2026 12:11:26 +0000 Subject: [PATCH 30/34] * refresh the archive browser screenshot --- docs/screenshots/archive-browser.png | Bin 66518 -> 66467 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/screenshots/archive-browser.png b/docs/screenshots/archive-browser.png index de88d7dd43f38a733fe5557dd0e66b254aa140ee..e03df9d4bb6b3f99978ae04296bf29138d51ffd0 100644 GIT binary patch delta 28248 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ⅅ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 J1#0(#{sUDROb!45 delta 28339 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π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 RUoropbz4^*qx$HNe*#b=eM0~M From 987e25db86e59551025d6e66385cfe1ac9c0f8de Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 26 Oct 2026 12:12:17 +0000 Subject: [PATCH 31/34] * refresh the application screenshot --- screenshots/main-window.png | Bin 65657 -> 66555 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/screenshots/main-window.png b/screenshots/main-window.png index e3795c0d04a8041389e0aaa3edc8cc5c89e36820..fc928f6d0dc4e508e434ff9b001a8a9263cd1570 100644 GIT binary patch literal 66555 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

J-t(C?D*vNbSd$Mpldw%6G^3`$t6)P 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 O*JNyHeySLKDeAvN*8HCU From d31ee62473140daf1d07fef171c26bc221a6346e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 27 Oct 2026 11:14:00 +0000 Subject: [PATCH 32/34] * refresh the application screenshot --- screenshots/main-window.png | Bin 66510 -> 65657 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/screenshots/main-window.png b/screenshots/main-window.png index a8d91cb6875fd05a6e6bed08a6f241f4bb446625..e3795c0d04a8041389e0aaa3edc8cc5c89e36820 100644 GIT binary patch literal 65657 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

J-t(C?D*vNbSd$Mpldw%6G^3`$t6)P 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 O*JNyHeySLKDeAvN*8HCU literal 66510 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 z zQ6F!zX{^9NEH0^6$gnqg7@ImJsj+e9bSC@bGbO1(*+673z6xedyi|;3<_5#UiDyUm zp0=RI7d8F(3b8QOJiGm{iP)W%st1N7pjlPmcdcO&r>@H~d@dkSID`Ez6!8-KBk{3j z7<>m9nD3*1OMC;>$98Iac*`GhNnGO%{E!+ExQ1*^?Nk*i7>Ix6|LV)}_3FAUQgznG T5?6D@2WE2C;!LiA^I!i3jR{9W From 19c95c35e5762b68cc9119783e53aa4d5025da17 Mon Sep 17 00:00:00 2001 From: Hawkynt Date: Tue, 27 Oct 2026 12:25:25 +0100 Subject: [PATCH 33/34] * retain main artwork for the CVF clean-room rewrite Preserve the completed one-time screenshot refresh and restore main artwork so the CVF codec change remains independent of pending formats. --- screenshots/main-window.png | Bin 65657 -> 66510 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/screenshots/main-window.png b/screenshots/main-window.png index e3795c0d04a8041389e0aaa3edc8cc5c89e36820..a8d91cb6875fd05a6e6bed08a6f241f4bb446625 100644 GIT binary patch literal 66510 zcmeFZg;!kNmi9{=NC*`O5Ihy!9fCvQQn+gZ!2<+$h!l{Z!QI{6ErH;{T`Rb|`(5Om z)7`iG^u6C7@YOrUn;KQScI{ewt-0p>J zcfl)*(J^V@pO=P`N^leuS4tF=j|dc$bMVr~RTLBlW)zh5Hz+7P(I_Z*AL45j`M?_w z4P+!lQEqPkOK!*t14p7ri3+PYPj1dQX<&B}KH8P~h=N9p_43YrobQw*gTH3oS_)St zt|p#69xZ$9*f-1XhR09;bsX|oL&5JkFT>$1>~WKznjO)1h{Cho3m0eS(9TWc{oePS z){dnzsNfs_@8f@rz>gD=u*(mgdW;75kCgY#+S|?t#aAj}H=`X1H(L^r+v9V8y`zzG z4i7@P^Y_!2vEL!LcN_)fGL_lr`QK;B1I^)zf1jZU!TveY4)GAVSb?F9a|{F_!SweR zyf$47`pCBDXQ82?9NUDohuduC)1m<)w;v-%NZt(*{>d~?Q1~2`(+?PISmaU01gmwuiK^FSZiH?Zo!D(72f{TU)@&(oEtX0tmL8i_S95Z(>M)Nj znY5ODZca@NnsHOz8@Zcsy?CAAN;s89B}c-yf8v+ne{s0Er>mwxS}RgM)5-QCHa3=l zfnNLxse!RMtxJy64pKWhrc5LMyq+N-iN`YgAlb9Q^{xH`aQ%YRg!P0mOM|*6+GBQ9 zpCE%HvK6D2)r2vsbsX+p*N5u6z48-DsL<}ak=o`O0j&5&8D?j8STI<}*OcBRJLYWf zpZCt0G)v4IyWdk}Fj3w03-tHGA!4=e-8>(Xba_bjV~51St3x#B1$z2`&(uE)(8mzS4Ty0!GNf>Cl=H>+*DlT$f)IW8BcPYGG0 zX=~OUdIqQKUCJ_X<~5DjwlB_hc!`I)LUI=HT|_3oFu@(!J69D zE*C#o8E7b}p|LoXP4O6)r^4p-hsErhG%D_zni(D^+Zb+LMa2*Z7B)7Q-FAIP2Q{3b z=Zw*gX!CuyQ@dDSo*tJ~BA@MdEh<6i+tt;QsJ_I(k%64D6hZf@=C z$&nOYUV=cirLsQl`oqp46mwJfi;IgJO8I=qeQSLrmHa(Z_Sm3G$D76ms(`Bm@w8_! zB_-tLypqCW-oVljw;e4#6NOOXwP&Y ziKGABv))-^1MaV>`^2A*x*<3uN3k=o=F*6bF~+pK_>fUkj8ES_ZRooyR7ns7zeL2l zIVD1x6&C|I@l}UDULwi@3LQ_r1_mPQ4CBT!4tyRywCYwz^={^IJ2uKwTUlOC;IUmD z|9K_V*_k(5<4@wcyBo5RugS&7q#YaA7gP0;hjRO{gslB3vGd_#r-$GTk11b*9BF^m z$;if7PF-tjE0?%&OCyNG)N+-YAUk0=L<8+k z)~{b}ThmouNBbE9V}-qmoLk2Ub;t%w_4>kK0s$2{Ijhw_pVSL=nQr`pTI(&|HsVbM1y!j(^|4Yiy2ecM|#43Y?he=;)e!^k(YB z+JDu%-6Yn!wM?~VMpP6A^~&R~^Vd1aNWlYx0&z|@r#P!IiTOVCf4siqd2-F=VzBh` z{nQ_lKTbP){K&u!HMfh?=|;C53&BD~B?CPHC)e(WPS(hq-x$|z7+JQ?V+EIG-Z`E7 z6jtEl-!VWi{eju_u<0OnNAki#$j0)!Cx*#v0vX~(&WkP6CXe#Y>;cL&$8w}qxy72)5D>T?T3OwhB3&X} z7%%CL+eOXgOYFmRC9dxu5tEToj%$h8VZD$NdZVR9aJ0G$?PZXKN6UK8Hn=W7zHes& zS65UU9b1@~7#D4DQ|AfXj-BeMhU+1%lV1L#;{K=o59zz znwXf+pPT!Izp%2hD&I_b8X09ATP75`AYUQEBO+#Y_twU$oDwjO(95}rV3Lk%j)94~ z&k_4bc=QWGOv260n^QC>IcE?l7D43?qoCMWg5d5176;q2)+U>l5F$OWTt1#BVMr$p zNLhVhw?KtIPV?k?J?0t|(iQd1TAWs##B#I@XTm>;K7}MgPxr>>d!IZx;*gLv-~BDj z09KYMjx1rLWj7u?j2RA0Uwn>_kH5Z(0Y$VgJcEUXhH-Fkph4Hy^`8!~E-pTQ{ycl_ zGXmnnmsYHxz!f+)IQV|5ZEw9|19#b2y-0UwrmXJs=g)^M2VA5%&A z>bjk*tUPXRCd2KuyGqfHs{VnF?oWO{zIkLsx6WXq*W;qBv@{@&lUU&WSDP|pWQipR zPUI9+RU79!@{c@rka!6Nq7vE{7ARq>_O&EMex$g07TMp#??OdiQs*k9cqQr4jn(!g z9yg7pq;PZBBurHmm8IzAkcFZ_bIo%(Tpbl5s8FY!3t~YJN>0YYY;?G&$K9adU@Cpg+~K4)Op<=< zwJwg9_(u)yN3OH+njGdn2?ae}92=_P5ybDcEzC_#&CC+&>=nhtCbGZ%lt;@?E>?*B zSY>BxzOqRE9Nik(z-t-V9Y_~NoAtAT>^XXam2(pRUKN;cDxGCfOqj&nl+vhXs{h8%e4%`wXlty5BI92x2(z?kb)X>MkxVd5tQZ>@~E!EyFlGT*QB}mIS8T zVL*W3SpJblS=VSKhqCb8C51fF?F1G2p25+x@g*Ide;3-q8JeJLUeVLnYv05-&XuFU_Ha=tXTitW z_Nxu;{^ze99b(<;&v8ZuSniNSUBo$b_jql|%E-t_%gxprsuMo)-IvH)7g-?Q+*DCj z#l5NN>T1@pKgXttduwSH6P2{HwP0Z~6$=5s&hC8kjFewr1@1FeX=aSLX{< z@)fKg2OV>(y4}qPCL9GKooY5(j2CFYreM$0)UZ`5m@WC ztgLV6=2jT>QoD|hs?~6DwH-Hq|Nb4Ya&SVzcbQh@$^Z|+wGAVCtd2u;5_Jz}W zKjR_%bjY)TwsQ@XkT&rMvf!u_Ma1&c0~Rky%;yHkTtT;ZpeI4ag3<|yf_t}5cDNUh1_K^cYQ*4A*$yYif%#(lLPKHt-a%jHM zpk2BSO=e;~cDt*fQAPaYs{Z5q|8M|t_rD+E>1KVInX z@4W7>2)3JHm>XD_GD*3IB05-hFh=C{vbN4ZhYACSviWa8CMZY74LcQJ^w=(p+gTXQ ztAE_j>|^6zi>O06noeJ1YbZpX(#Wf~%VU7M742R{hNv1l$V)n6r-!2|W_ww{PP@K<1|hbUy7RVH7v3zX0E`)pOVth3Y5G zchwxXVWvWD4XL-9X6LZ7GB@~qN1^@UreA)elW~<$GWZ?eA)N1zmt9S^5elR^$b%uO zqlz5IknrJQ^`B6XJZ5BMbai$0#Bo%Z4#3pZ-pdy1)md4MHmAGT1;o)+-7Pxj6KwPt zeLXhw#dQ*6dr85F!y3uEx3`CYXliPLs9{0Zy$p|gMd*%;FM;|^riWyxfkIwp3HgLs zU`r5f&TFSF%Q)#6V=l{B+5e_L{=>=ANAr!}1oIjxi^nr=`%lE%kGHz;W~-Od3NzOi zS6?SvCnb?w4h{}B$tULO$fXTX240AqAF)@1Q4uRZgU)a@<_{>bl-RgEC3^A{gN}x# zgq@4@<|0~cR826|Wg$zBaGft(zesPMmWbbG|4&GhI=$|6mI-G46}VY1q0hr0v1>Ev z?oM>O3{X)K&4JvUpv7~Wy}ibh?YJt_xi=Qw1}t{Ac7pDwFR%&6hC3iFsajv$xSwn< zG50z+RS4d%VCJ$gwANQr!aCY+NV*||V_-ONaNPe6X_oJqb>RL{^MyOOV4waK ztDNu|`Wp9xxLyHU=Akn4E*H|I?K5T_1(kgY^?du?-HY7#O{tdO!;9PZnmC^xKY93t zwzI2ON>cXy!W(^g14c43HkZrS>&FR?Fc+6zY)$c8p?@jJ%z0@vo7x(=j7#;D^bHM- zcLSQB&9W?=MR2Wky#jT;V~d**Qb4_-@b98CeS&Aj!L3kt?_0^3?3( zHygW0yxQx9wXw058%&RI`t+*DQ#^PR3mKv0OiVkQtukH#BuA@jL&N3ezQH7d?uXGW z#$&!M#14jFqALlUkG(Hp35kf{uzpLhyQ%5w`Y-~5ix*YzjPsnBSXo0QJ*{A7bU;{2 z>KkN!&7t61qoAEB}eMpTfc26o}L58bU%m2~$nPz3S;>q3mx5r z;o;%(!zUD!Wgc~vV3xkMZua;04-5#HtguNRFvUtN+~#&-50>d!&N9Y^-4~+((_2!G zpy)`Qj4Nx^cy^Ji1^&Umid+1+Ddm7Of@zI6G&yrd8DKSfEr+jQzMs{(mqM`cdV1<^p>#?rx!@w6; zdqY{7;^O0WARMztBZ`A2;?~Tl+0dXP*RITDbaa3fRBN)Wsn2E@wr_66*CtU#ZXx9c zTL}17>8~#bEMFI!YD6rx+*BzZ_TO;og%>|uM}-E84-F2cN?(%j)6&qaP32|Pe{Rkj z`w12d<~lN1dUm$K3t3@D<-k$Vzq|Vg^U8~q`!i&)Oj{i$31Wn+c-zb)OcfQCSdS~6 z7sSLv;80bZk*rUjs@$yzIV{)tOp}EaoM)yk*rF`njf?INjj%lM*^J(1aMFDWA>Phw zoyYNXKb^rpC^s*89w^Sn!4l+t5h;x-guXW$HP{PZ~6I~Kk%5&k2iBNGGZbVujdx1%&?kir&`{z@;YsP zIN9ch!+XHYi>`jXa_7p7eX`G$F1v#56V1pd{??B|h19~#%%XN)_sw79ie!O-aot}0 z>oTaCTic2%D&ke;Uz=$qRdP9kwU9C#E=Eia9uTroAAC>qoT_ zUTv-IGSRHAtsCdrb0SNNi#|_DoR=n#&g_g0`@j~VfW&T=^Y^e&x%ug{ja+g;wr2?X ze@(NJ=|y|+;4Tc+e`2?9EonRK%4b+u?v%wx|Jq9VUSas-MzmmOY!YGAkd&c1r?vdt zR9V!{?(PdBVt&_CX38)r)RHPokNw{hzkka*I5@z*de?Ak%lJbK22xwQlhs|$&QcRo z2TWrL2yhvGry3fFqNRAp#6)INLVs1elvV8@c_$~jIy(V`sne` zvo&s2FPoCJs|x-6{4n<5^V4PJC6*3oP%2U?$t1|2X?X*0C8?l;Yek(K%%C(TXDvFU zh1bC}>2HM;KEW%K;uru({m+B`Ki zEd;i|f1pW^YuilfcaD)F^wRk`1aiou5!{|OV9H(N0unsDYdZxNDz2!m{^vM2*;t@P zg-M!8(LfggHW-6K0jC0R1_80Ol=GfVU+)GZ-r4zWJ2E^H0wWik>FY$BsE}QcJy@Tg zuBqV7c>+arzFXf|6Kn8je_KYDmKqSpQQ#g)i8k|dXk>|soVugi!tV7$SJ%G8CTd1T zDLE+{bLCb$Rc>-nu=6^ZlJ3qMr3fXb6k3^CWu_>?f01KivMGoC`3`yN+cPq7HsXl+ zo}u7@oo;rOv^6O*gHC;CwlP934I6fjan_gr3GoZDj|NRj(l8>%mH8%@wXtWPVf|$+ zkWi|#bF23HVn`TyaY7OA`4FTco->GD51n&2PgnViWdtP2WCu)bnb%z@g}J%;>4(=X zzO%g;+K|!{X)?5n*OS6>~L-Uo^L!L7-Sjg5_~%)-TWa@8(Ru)Yote8R!OKHGScoK~Q#qH?&|+GMh#tfC^@*8xHI zj_~R|Tu7&+lvHKf(=b0}F)OQkOKLv+5UmL_{Br2${);C*5fMupe;^CXJrNr8_dKp1 z1(WbjPL^u?4*30h44R_L)bP#UQ=Mx@hV(LTFsm1(=!`~?+`!U9N8u8PL;L=+oh!?5Ga#--m1HZ zYog7hw9Gxn!)vQJbQi_5+9BRE{f}5Ta|K_qt=tvrq}L4k;@4e zCl&1MQ#=&|gTtr0bB1PSX5MhtKNEJauZlHjElm~g&*p}FF-Zj@1Ye?osei-;b$^Bh zol~5OG)69I9%jM~Ijs|Ku05u&uTsM-!aqS$^oXPtNyBNk;i#jJrDyYo!v)lBv zOEk9Q4_xNMz=$B-=N+F+Q1O8!Xe}G{=OMK;$I5v@7%%6JFF&+j|CJ6lj$*Dkox;LH z5txJo6$Ag(C^7Q#@-7ZeL}3CC*jz@foOiLYTVO2@4>#zjsIv0X42_IjPOiUvx;h7M zbKIsCaSF9sG243I|rJA>uMR~`Z`R_-@Uf_ zy}Cc~QcNd-qdRJw-`RQlZWV|Xj0wCozgMME@p;YDFESbM81>EGnk!w?rvakPeSH#3 zVQXt!;8j*Lza-XU1VG0}YxSOAS)l5SttbyM7ONV&xgf+GkTN=z3KgiZt7vk!*1b+= z<&T^x9C(aM`nuy?H?EPzM*N>YRgZBM9^K2Mzg&9-`z-pC-bhHE}C0F zf1!lqEbvIZ&iP#-VJ-V)JTPaw+#K0gxRKeL$vis1WgwThlp&dEQ zTdL8=#;%C(9PQic9cT+mrIm>;3R^~0)mpSxX_HVOMuS2a1q+6f9jeM^*)*0!_q;9R zos>WpHT2g{Sv-)|mw{|SJY;bsc=8uToIx55|22?2HJu}DD!CU98$Rqa`y@q+aOv$vLlUlUEwx_@vxKp#u(S5SMd z`?gunyVEc){um=Av~$_D`tD!yaJi2*S3?;5i=Vxesc>Xe!DXh}%zS!!_*c4OBhNqx z|4Ps@dce*5k3BB2eXka}kPJ7}@-jxf5DEFF-DOvTW5NA-?2w&>jQojIZp;=OQA&6c zcd>QSovZU#w|uC{6?XbY9}5vNd;%U;c=A_7tfrG1)#u=>Nr$@>ix8fr-x(bN=|HPC;~l( zyGF#VQaBlW(RawTEfCA}ANT^=9Tgc#OGn3LJvy8!_E=L>HZ4NbVi9$X&h29M8z9Mu zj*brI7+FissljPknGIf@fJH#wmyH=@W4WwlODubbzJI9d1qT6cH)8N}%4rAWoy1c& zD}OEnwFTDAbLE19?wud!P}v+GH>73e4z|rUx|im*FHDq|T7^&L$CfHRdL)G;<9qTX zr<9M9)2i>&m!BzbDJUo+mzNj3NQb&6*Con7Tn?LwBs@CW7|Gig0jl)_0k=lO1Mx?0 zyoyWPx^)5gEJQnP;FlfIK{rb_kNZ3*aDvd)qTyOD56yJk>vtM1mKn5OkKb;F@zc`) zi~=GI^vqwy*~yoRKlhdT67Xhloyz;0r}G0FJaQDG!{RPD*9*qrJB-A6xO&8<4BsGyLo03>ePlTZ4DkK7)95sr9gv_ei!S!q)&v2ra6 zMtxpMK+&7Fdxb&ky_uEG{O+B|P^Iy>U2K!y^3vn%i{rf9?9J(_n{-PH9^$N4kbY&i zqrLS|SUOaEW&Ch$A?prhTMzN(`<%LUbMwjq%j~JUpZ=_sne1KOhkX?YURiy^)b|Dg z<7+6y-*x`hDYa&{)HrDtSx*`uZA;mIt{mINDZL7K=?va04vVv@eO)?|xBFsXp+ z@3&<@Hz(`K6> zQd&|IIsvCMGjwq&Z>;F^7hg;pHAOW!IjI_lsgn~$GP=ck0#4g)ZLQB>;Su4o0dY6R zS4*d}|Feb<)N_ll?C$G3IjmhawH~WCQ-*!LS)c;hxQFX;@_^}46PE=SF*`pQ8Drv3 zi7}3E4<+LDc%@^pC7eGziD}$xaaZJ~9jMi#3KZya3H6tk_auVO1zkhK z;-(7hsG?$q#;hl?{;N(Lv^k~xR@KApq;_S+=_=D{NuSF2-8(B}!^MREOM<}ilBy(L zw@klMR_MFUUo?OKJ@EAr7Z*1(H|uqZ5os8a8L|aalpN872*}TmpVnD0?#XpO-2U>Y zwA8lu-g#6+#Mzmk>u!IONmb#n$eg0$Or8Dc@NmFazx^6JlT*lwF+7gnZI6|PMpHXZ z1oc~h-@HvtAU)w+06_$w=jU|v_UlMn)t*?6K-UvSHaZ!&>d4MO8ZeVIHn?`tQP;Z? zZ@TSVCtqLDx;Gb=%0&ecmzCLd$>T>DtG?tHNRprVV_Wm!!2{JMCzV(oc-nw0Jb(<@ zn1R)-l(M_!i+QYH_SL*DqI;z0xVy8bTiKvw{>)t!$H)`*8Q>Rp*yo@ixurCbVKq%P zwaCN}o0FgSK)PToF0QFL@rg+VO+F_fsr&lM$Yi&G=2rIKJ`NSf_14RM#WdnJ&SF`Gr}-vReifr`%&PyU}GAgR6dd;D)+0>x0G+UcG+0kZi8#3;A z@d5&yXJajWj4tZ6hmVhd)YwXd<+d7>qC&ktH`mwK_YDjXPx64WhHY_i(^7k7aq*q; zbyib)4w(nX_r!g5m3j3HT0C|R&fcyrX*nqhsiHnDKgdZ$3dxIR>7`b zSLd`fW8D=nfk|?X4GUFPy1cxKs$uxEzFr#rTXQJ0t-amr{4yzCStsFFI{8hzd?b_B z`{DF&p`n9d%1-h~IUJTsj4@75PG(IW?%?( zwf_Y%v14O<@W4Ce%R{4g?=mwp{&~|B40&PM87Y|=DQcD+55yk8B=tN6{9AJm%{Y?e zvgo|{e@VcFZpi^HEfy@iJ0V|QN|T$3DG6d=Z0tC)p%n+a7(WGCmsN7n*y%%bUB!|^ zw|s#Af+4SpvcHGLNuYAu%^93Y04O~&GSWR$&=_<$k}QsvvRYY<{aYoGRT}jzYs|T; zE0FbV#y2~%sTYjlbXh@I=l)rF-SUzO-=oHsKRh`+Oyu`c)X@nAG-SA~WZ%x?oal;& zoSc`+*Bs5^tDdH2jh*lD9VR_o^!p&hMlKr@5U9q-`JS8Qtrm_2#tjBg{8uo5<8wz_ z#^R?A-g?2{RJ+q!*GOHJb;i&t;=8AaPef77_vrrooRC$<{}&ptn8YFoTxK<_?S@qW zchxuauux@CAlNq@KL~CoLx8+Nxg9amw2k^={|W4?kb>xvU#Rxe+mx>OrwqJLwz~ss zW<*83fK^aMT~$?0$@OOL-qAtyz6O1!0(JUw!82H?oh>RfwLav$Ub3^N)65`Qz96t= z)b--}Y9;zbm~jum(tUD#&brsMevns8blMt9Fw~H%b5^6c7m`hhUkmHk%zCIH-H^ur4FQ8~XgK@4r1Z!BB(Hm+2lamO@I-+foNDi(I3Y=Yki}yMHkv_3zWt6FcXcCX7Ds2K>Pp;4WqckVDLE01( z6T|db&*Sn?Q%qA+UE;z<=#8hau%6!R&_^MDlHfB3R}Im3?_dT>ic(S?aa^DV2bLv$ z!TVLHPceY#Y?U9SnP%Z}293NNTM+wQloV~I+EZU3ME%^@*hJ86Z-2%Y({ghQ6)H_1 z`Kj^xi$pLmQGm~Ze9~iQhKb>-@2w=70{~`2Y^(@aN_gI90+uVk@HhdjPt`^)I)(N1 zmkAe`<=pQbDrh-{(5^E6#SKE*M<@`x&eQ$HTzoUMPpKoqBO@asI$2(@vTK1NPs91O zDoNi;lQVvJLIQ9i03DnnBg4pu!+8#C@|cz!2q8s=b&d(Vj;K%EBL_Y#lWWJY=NuR9 zE79cS97NH(kvtC%3yX`2asmq(W{~P#6Z5~=18}aWj^QWs*60xcrYGIjuMB`QROW4ew{$M37cjMh>kInhE2LN=_ zx2F80>g&*ukb&h7gM&jk*kSwj4q(05)u@sqx}>F#9V<_+NN{m*&CSgT2sowWcuX zCD{7BD(sD2k^yE2KBrIG!otE)y$L$T_7sQ!Vva$Np~%NkMdi#KIqs2)&ph}G4}M$& zD+O*uzIMfY(Ttn8e6(!Z0MXW_j!EBlB6Kq?5ZHF}a+c{`n34q8EzFdcL{cq+0PxMt zvlyQkUme}OoI##U&aYVUg?^a$)-?vrhwlR+EhFQB!|WNlw2Vw&eAA!BRqDAFYM_#* zzKLvNz{gpcovBl7`kpdBTArgQ^Vq+Zh*~T}F@}&+bob0y$I4Oo4pdZ3F{C!H(wQaA z+iYQ+Bz==?-CzO%p;S(ZQ7l_irW#w~=%LVQy_gbf3`ROktG|S}ch-eR$s&6?(V@0Wwu0S;$FAn+fc?ZibDn8@fy{>o*oGsblA3P%=e%Vi zb$RThb#)7D>dOZQl?Ht1p@hHEr+rdK0pz$%mwq}$vvz$CE3=;A;d{kO!_B?F#Y@A4 zb46Cjpttm3b>jyxbh5k%2-=#wP9ExvX1Bhk;W-E9*a)xWQ(eYg234jiW(q{KO$`DP zQPh;3zTHLe;<9uK)4wzRtSKj|@Dlrc8Sd+7`QPoSz%fa=ojV~H^t4DsWVY$z$Iy`W zAJ)2z2#RN3Bb)nqJ$-l(SeJfl-DS9Ro)pRBej01Im=@20wzq0#*~ z99X9jdB#!t1qhln)j}v^Vv`;6y1~nmje`TA!mBM_CkkK(KLD43;z@&?`>LjBvv-jE z(kDNE73*n5wF-M71EVGz85z*m$mwNclSj7iA{8D3*~kr(*qD}DLn~UD0u<8~<>kpk zYoSH4TYPUm!s#q5KLW z`pccVxQL>O`_}U1_Ns+Cs&t}B%yS*_GJV^|B!J-=pUvF4XnW?_haVT6 zOoM-kWXR&@APhoLC6%aQOSUjBXMmiXzT-xnLOp;1z^N^MtD zdGpd#fmAkD!0ruF06=>&YH%EAa}Z(>YtN_;?EYiJJu(`wLWK>}VqX~%&ihZB%hO!% zJ9V$}f_FD@QJ-VU`~`0)Le4rtHf$bi>$vLGJ6mp40s@JUS5#Dag@tcT4Ab(`u85Ul z8$H&gKGb{cWas39()tER{Bw7+n9sxW!ZMSq>Z})n4wDKi!dI?Gxt9vE@23;*J^CWj z0_X;PPfz=e0B|L%5|^hmXXt?IB;b!%$$WcIYs0p_HPy8Q6203B<~i;wQH6Hk|E3Hk zlf!KpqY+uYvyS2;gXDxE^++S{K=q2iF(7vIn(BYol zB^ceBK&!B!;NOaGYkORv?JFTlNkPTM!n}J}Yn+@F6&@cR8#!7S7zXkre|6!30TypD zF_6z$5)%WXU^CgPwAltizGT4)P!h&JPE_{+NJLGER?w)tx;lNoG)YiG&Fv*Lk(YeIoq(*ZgBDfQZuk#VYu(TV&KrgGt)k z%Z~LXsHmzWJ$yL3Ut>93o^z1=>8|jcgY{0IPGQJfY@0qY)S8u*tX~Egq&(@_)u)X} zkxr)XT?`gf|HLP;D%vZVeU?BOk0roQXmB+QvYL&D*#67I z{}lSJqE13labT8s3iw}xiFoHK-CIk(Tx17TI}S}5Zkfy2vGF7@T6JXuBT^&D&Ta{| zp+q1%rxj!0>B+Ph)~JqUg6hU}nO$j><^CHB^Vyo4m#ZTi4s?EMy zsI#>_TjA71!)7e#ysEzyOEbStPDybl=vnGMV30-$2NCDt(azXtP9TEw*TEUsyzpvk zP0h^A?Oq?~8VjPK%3e$sM5KM<0;UPm+>A8S)fe=!r4az#524OlLWMeaAU6OtEnxo+^qf-@iHgTasg$Hf`ST>fW4%m;$DNV zQjZXwpg*Rt54xV1xPA?u=*)G27`E`+l9idL&#CiY<`5cw_Yg=(so$Ow6UFRSiV!SLG#TH2*h?hKvTA*mM~cB?X#y zbM@(c5B_bhDP;%3a_-H+!9hR}X}@F`?*m7Tvk4eW@rTOd8)^SQms!`qGX>}q7FJk1 z5plFErGKmW!YYIiFG^oQ!7~Jy?S7>%ItB%SPzBt2jzDp-?PTj0EjxwSWxr(MkO{}o ztHC>@Wj$X``~It(9wuol=lP4uvtTU$mjV9jhM@D|YdX5R^mK9xj**kuD^L&7nz37r zZo)-R0ohVsSmw}D^KAi=iRaB6S#X{V>fBy+%3EvaT2-Q_v@{G1baY{c%Nm{?5993RG$=cxCA`q8IfpMU&dG&Vvi z%>2f@>2b;s=*YC0D_yzgb#up~@$!lMe^@6B4HoX-6&8KBn7Tyg=a>81Co>~w5A-K= zF)>qcI9!Js^?msamc_yTexa+bjHGNiGZz4I^Xsdo7F7=BWu8NA@&haY9Np+NU7vO; zqgeu+)}&cAeL2=!US6KQxLCmJSYh)a``9D{q)Zr3gCL_4I6)xjZPwFLvcwDEo2IQg6L-W^~74Awhh(tjoWl;#;JcK#C!uk3U~-{T_vhT|H$ zETNYQ>dIx6rQsnVDI~0%oSwu4xtW=5ql38vrC>b+N~Yyn>r=}EMP%g>9#h757rcV1 zs410Jl{M^~6lWJJC@T;37fadNqC-Xhow31VgEzf;b zVU=|g2dvM^yn^S=hgau^0-omqAt@;?H8JaSkqq4DCpPL;t!w!Bz{JxYh~JaV-kktp zbsUyFfon7BG!8Zor(bG{PiSe0JUkS1IDhY~5?-v5T~t)%UJ?fy!ITO$|E=+XHU$Im z-62oyvcj!z6)g;8XlV%v8=Lsfc3@JsWyOk!{DVk*VfMiLCt1)HUZT@CSy=?#B;2;+ z3bbC`9lz_{kCFz+eAsYs#IhFpdV6dBtepX7ssgMbvh^`n2~ZA0B!jIVVhIq{W;YDZRGh2TP^8*dRH6&WxgkI)_8is9|6586h{xKmjXFTNPGVM>1&cE*vlsJ2`26sb68z;>vUDr|73jo`U zkxR2Va;}+}=+mqJrI`R`*`F*S*f_KO+BnOfGrPXL8wdeBxZAnU=64ywMIrHu=bxbVot?eP*BH8osuG!!9^soU-{p)_gK zy%H4Cqrcyh9Mn0wci=4{dg-xmFKHSLI=_P3w7mr zEBZ~ilvxh2ABD8s((Ef=0kZSwhG?D~|BJzY{#%=T0_U)cLX%ztQaEl4yl>c3#I?}%)2B;oeF z8*(Rr;xq_o`1#q%&hxvUyciR}%pCv5W&*(m=XTBB2EM$A2+Qen^(KLa<=@fb5$qhS zu>uaZOR@hTnSFpSqCvs<*x1@T6BF!T&pb*@oV^=0!B9Y-W1b`+c4K`UkG_PD9|H<( z&*N8L#mNBXUg=HDNzIo=#g zg#hhFJ_y8z*Y8(8-K*N3URrPvQ&m-k@l91Zpg+X`R{Fu=!T254_Mg3#?nw{%+&G=) z=J(-~HI`){aGafezLIzw-t^C>8*eJr8v@3YHxORp^MB1Nj%S+4YRrDt0)?0pY`-p64KXK3Q6`_w3D2&-X@V|j_eTg*G*bwips+?VA4eFX|*K66w%3;asMgOx&d3XcCOhyJ$Ib(|v zd*PKk_ugx1mPP*-6c3&+t{^!T#8L+e#=li9w~*>GS#kerz5SN`0sMW0$Vgm!9)8Be z8=lw0JtL~Ry71uk1QttaFs*3aKFMW)?#*+N`>+;=MYO6RrmYRkJV;xykpiGD$01=sRWF}nE z!GQxNDJ`AL>8qUfz(q%Qz*3iyi?FH8oJrKrt=?FMtUF^(k8R0kHLV z-HC$8I|MRqy!}VcuDE1;U%;!x3wbdDL6AgpKuDr)(6gMD3|j*$3XBN8MxR zinldH{KEE2i!t8w=RjmtO0oxSuYpBPwjsXO%(XX=EK>hp13aGIZSxypHdTjX}GC8zzaB`xP*s;8y>>UODe+@gfCLq_?+o zuW_r+<)RnO(5uK@?-9^!z%~&{%u9D)w98qIhK0p!HZ{Y4xkl}^YzOzbt!@Sk?2rNHMNSN@Vx@Pwv!69>u3@t_T*zCKTH3=0dJ;df6uKFa@^ z%x3dih&*aq3RL#}ux1q4i9%_2p!37()qt)5MTGCHX2>QK1 ze!nAFzhZM#X*HdE?7*o7{Ml*+rxquP1K5;9rtA{Dykirfw5;Xf0Syj2eIpj2JObeg zbVA^rQ`UE_Xf>I8Ut|`DhzF(gvJ#Ly8#t}42klC|1#ve5p8LPO4=5;LpdCYJ<@a+Z zv?7PR>%;MS%Z?6~_ndWJO#*Yzo|W3#*5>9awUw1Vx()`5Br0Enh8CQw)?gwu&5P8@ zK|4M%1Xx@?Oh*qqd3f(%!Ekj$|JF0m@$ouKE(s&p4=WP==;;4p@2#V%TDUcCY(PZ> z0YL<}NDD|y>jpu(yF|LB8w?NxDG`vC?(P3Mb1Q_w77QGf<}-KxH{M!}gDzY}M&2+E`#X-i_ipJHAZ|WM*E!jk zn}nA6p=0cqzRL8VJoAdSFLS4IdCSM-*4149`QV1KwdWZy1Nf+}_7tomhkxcQ#`17E z0~wjIxp~XXhTRHZf2A?OSFo|*!PE)$(ue+mqR|Q$&x7e!Z%pqf&Y~C&8_ZvmvZsjz zzesLJ^r%hT)U=cg)ji5_w0`^3WGYdjkt_eFm{6aar=P(XmedC~ekF0F7p}uEu^x>@ z4O$8ulKL&@-(TP=4e?q#DJrT*ze~5z(E*B5AM+^uu;G`5kHffslx24ihisMlj@yy0 z9OUPMD1U!Dkvekrgr;g{vnBY%+uMRd=4Ff?1jUM-T@o2iVkHMV-dyb0nQspe6_vDG z>?fzCy{rytKt8#v5;Wb%?9uI2h@Pcc_r(E{9ZVllWp7I+CTpbE4vD@1t}FGwik$11 zXeLavOs6W25wmVVN=eT|Q-1BZ46%^S< zcAX=ae^E9toYy{-sVXV;$2w)hyqNE^$;Ph@bShPGeUE0$TrPcAFWI$uVQ|g`v86Ln zOVK^c9wyigmq$kvZta^;e%^Zh>qy5!*2NALNwvZ(tc#(=+jb_YP77-@L49>@1K2{1 z4KTi~{3%SUAedlvaiD-BYv0k)b9;Gvtt`*Be!XJ2LP#?D7&Cih6h%QF9rNjvz=+G* zd~jgw&z8`~JtIGI9>hu+nCgz8Ys^(V@xjBpAxX@;+Mc)(FL;H^V)}|8FB_o=95S>m z5#Mb7U6y-4!^eb>Uazv6-~Y_K*F)Tq8-^3a_*px@{1v0!+2<5ji}qRh4`;u1gG^vu zCRb`_iR4DO$G4=<<=@w=WMi6N;`A3^;AT_)6mc7-yWu1kWx8GVfA4&-{07Uj(s?Zo z#!Mg?G6g}xg)ZIB!&14R28qb=$=Ov-mdT^?Yur4Q7wGdLnBA?@nHVS7Be$36|amQEk42a6&&mzYLDWPeWDy@G+$g1cdF6rYfu2lUSy4pLGlyp@1^=verX zc$pxQ+iJVTb0jny>_K=4V=UZ1K?+FYp`UllQdU$^DRb?`1PKY*lq}hK62X&x>{j-KIUzE~r zWb6KbHUHVrI|87LM1KG9ZhQR-Ax3cT$EhbL-T4<1Yx6W&5YC4QP}oq@CMVuYOoaT? zS6Ncx#A36l7{J1WFj!n(Ew|GdBS~AWsT0b|*Be=_fV3~K{^;x3PbJ-re#BhXKRSab zca9F9v^Y9C9GQ3BV`%poDYLeOTX0y|UcBl72L%O!F;~)`M4q?Iq<4OJxI$J4>o!*j zA2o+VFQ;86|Jbf~)_<20a@`g)d+37MRXJ984))yLEN!j9;$ZSlZi1_)R&B~ zJ!n%$J`MKhD&9+KUkW>ku$-8m0y12N%8G`9f*^YC!BQ~+!y9x&ptQ6K5z*~Ij{2|g zZkxnGMps|SE6fe7Xj^BP6xB>fmW-K5OH3@R*XG%plT{;5bN-1FUS<^K)H75JgOh4J zZb=@g!X4>-I5eSP3I86z6aa$>sA22Z-l^&r-j?g$7M(4p8Pe4KPHUpp_5RZ7Su@K0 zhK&r#)F5jQD7W6;)VsEGYxJZ|CZyw{VVM$Ahstkz8z?|2)zr9+aCNGNE6bT_WZL1w z-Qy_^6cl;BLNU5Cc-N6hSh+@N$uy;|dJ?g-)o;DeZpqeZiUaaUphwloX^4nUenHX~ z?+WSoaBYwKH@Vr7bp5JF5A0CZc)GeSJke3U;yvYdaYk+DF7_AX=`n`EOm)4MG0&>o zX1r=dV4yI%R(>H3-;MvSf-kR#i*0)Y0ice#&Ht{nZuuML$31}Na-#VL^xV^WdLV@H zU%J}d)FdV@3d2N$0~P3wPtYAmHq#1Q&Ea3F5E23-&k#`baXdgl`sLfgjY{nKWq4!B z>V01xPOXb`uxcUo$s)dF!Jwuli~Dyz5UxwP^wa{4zhL~*NN!YSV(H*WrSXa@e7oyv zM-28|t*BmGDe)nO*cBSZ5Mrx9qsDWmcgDgYo$>L#>^J-)YU+?|+_WDi>`s!J`YU%qk-nJAdLFjQZ2 z92C)TB}GT5-@fA7y331krJyKJ$K`0hR8U7pw|Z@$x3~J<(iIL4@M$}Xe_U}8)QWv1 z=iAteH-eh743d>`vo?lb0v$n3oeFxNhv$di(L@{Tssi8N*+gR_Bi94ku1-{v*+0d3 zw|SU=1lJ!VhoMq)H}K&sx|8ZWuQHmU-lwOlmgZbEMM>eZe!#wqed9WE(ZmFmnC#~5 z=eoDv`{UI)bVR193XdBhRR4H4Zo>EjvUc>TQu3^a6uc!B2g4Uh*?BA>k{kOXWeI2lj_wOqp`)sriwi;w}Rg zi-nA$HK8)f+9b}rKCLFVnqn}w*(U(GMJ`h z`H%~hSA3aa*k0r-v750Kzv@fV_|tAc8S1BV66pDs)sb!VckabP*%7y1N9#1ZK&6=F z#{OX1?AaCB$63iiKl_iTtM!~oc^tahzNIFKO7k&x=@|DJP!suTlnbsye&Q&gX2#B7 z0p1732G2gH$6K}Z^>xLKe*yOm%3-+7Qm(x?1x*ArlfE|I@ix%(m-%-EA1}moX0oU4 zn=sW4=wa*z?}y$}q^G8$l2hKLGew;SUbg@hgnsp!X8F7%(krvoJRMcmtvj2Uq52K- z%NmqR)t*E8Vi7pIi+hn5E*F^(f6mCbhrq(oun)HWH}242b7h&`N^hl8@U`4e-(%lg zimrUY2*fu#|K)L%vgsvq3i95Rqj63-v1Bh_2M=G-eVgY&H56n=6o4hVjEM=wUTWln z=ol%})kK7D!C0kRwPn;nl8y0|t5;k*+MvdpPbNyh<}Z)fUA!f;=ihv>a_8Z};;mYR zlO3sk>K$I*gSTn}yw67aQR|jW)z4nQ!HK!ZYd$ln6ffybtR79FTAnm;2O%ITJ5lH9 zpP0y6RqOfpAW1pth|2%ecdeET{~>KCcT90`OEd7l%QRl7vM*iv@w5M-D>&PRsW~v< zL3K|aKSZ&dZK$Y-1B^5he<7D9W>z+dmZlQBvG%rxq@;z9!GX$^&dN}45YlJ$3?nTp zuy<KPMxJ)e%;}@QDr_fU;ASCh+T`<{B;ja ziVWiXSd0QMdH{MVwe1Qaj~0`!pjAP>RTNKr5^d--UZzsfvx9T4-=8&P6N2PhQ!dDktPTVqXY3_K)dz^p6M!s{on8K!+bERB)<*h#7}7e?NZASRWdc zEoD|p$JF|t6v(G5VbMsf0l1*2uH9sN?(zCHO^e;)NMk@SZWK|KE79*JuD*5C*o$b4 z_Uspmb2lM3B}KN5c|!4=iRpu$@o@l|`>0c7=Oy<5A^(`eQXR(eUkwu47H-n}<3|LN zF#>;eP^0?@SwT0}@uc^^Tc{qMx#ag<*WvjAAOA`y{gd>wN?T+87qy6p?(pqj5_+0c zt1hJmpVRxBkNLGG`m65nNm{ety@$>S|1N`)?<#|L(9!rf5DC0GCmr~UgXMPH?+Iry zb_E}wIqnaH7h)mWeNOP%>{;_^stG>+rNr;jJ{1?uFS2U-{{8D6_+cF|vo~jRbaDKV z@ISk>SSs`1ZJb+RfVXju6qQ=Fh1HCPi;!=Gm&FCNQ+&~95^|HL^AB)-2sIwAb-&DE zH_y_nda=FR>6Ma#HR6QeMSGOM0llT-&q-2K(+mOf50cMhv7H_(eOy~{Axyf`BJgVI zrN;ShYWVb+&fCt7)4o{SwsPNgQ@hLTmYDRuzBqsBGYW~U^CRZFj=!ChPyjO=-4h* zr6-qdHgb=F$#uQ}YMTA6dHHgUq-cw|+C7MVC5rGmAF}Ax-h(vXL9toSZ0ap3cjFz$ z#DToKaic1yl-K)6FJL6;V|h| zQ7bqJrF5!{)~lZfx|1#XRSwfuOW{%oi=hKKgKex_z!eWkMiH_+|B0J>QG?$Ply3dbQcN1;?36d&Ldxs=cnK>~- zO-)WsO=)-K2y%S2o*oxUo8Q)!4K8BS|C*z9k`%so zp?rI({NxJ*CHqdX0;*+r*xJUXO{2tqiZuIjvZRd7_}-!G8_(gA`ER|w^(BTc_P3X9 z+S^z5#mzf46m&+%6D6ZBb{Me0Ec>yGljO|00E0+T)RZS72G|-?B2m~?Kki(BJnOt^z_Uh8|@=+`< zNhxjp?_>x6W>hHDqG)s@*v*d8HDffa^u*#tAb}(v!x>f<6&P6Rva_tu@Y;X{MnPSd z7sF_po4cy7olYZ9E9U$|=Ncf1BuR!9IGxm3I<3+? zm)T3jKrk{|FweB5KS=q?8t=zXbF`3Y_~r>?^1c_gW$yyf7WSK(&saF9Ab79ywMg=+ zSZ?lO%+V5nf;~MIe1fZ!I9b1cZ(mYk^B}%fbKZwMxG(3$9FxPviG95GsG%Z9^>TU| z%_2*AF|ozKa|dRcJ4{S0k>wrYR5!MldKbE9+F@Ks#^$)xcBH8AYF^Dnk?5LFyt?bJ z$lL&LNyA<6CwGPa8VRpu6zjs<8C_AU4UP?bfyS> z43;_<>ZYTps2LezxGHtulozkuwve>=12HzJqEKXDn)k%is#GDft+kcQX0ii9WkDZ; z06Lr9d4Pua+TF6?7DVCTps|Qb3`!q*-bvWf+!)(j?DMI=BK$V+km zbTXrj37|r^u@=QjbVRY)m|1ULB9^CY6*8DHbv-qmSZ+LAx)Bk4yoD;O`7a3Dvxmz^ zE-zvxZyv)_;7j%gY!V*Z;lZ|7*w0O8o|(?%z?0mFfU$$wWVFEJquWJCJjz_~FfLR& zR+@C)1?rd!4^QZTrk@z(L&uy}pY^Wma?^*_KQC>I#=AwZ19&4myeWy>oNAS}QKu2X zX`6|L&u5MppA%(){bgWm9K&fRG0fZlwcoz~nLfOuoxHBKdfc-s+ zRUPJiH2wAUwQ;wWZzE1qQSD=HVMAxLvgjcu&uVJq?0lHq-s#20Vg@d!J2snYdaYnW zYHZ*!>vf{vc`^^-!CZ^OfR3CC^j3gZ0$TL@>`SvW@QfH3{x0_TvDWP8$MC(WhK3)C zQGV{&?Ys6uY7#ct$)Vb`1s8d|3EMRV3Mi z>|Hd%ehwGl$>ap33>Rf%cZ>1tk26!GX`K--rXG`L5!w)T5mk;yeAS!9n@zPcFM2!Qm;-CAnAz@f>qARLb;n%_w~}gpKCR~}WPBkv6&E)iTE70i7I_BY{tqJVAto*kZa6C|@=NS> zSw%%M_k@H5%YGB-SSczoQAtP*#xKtsC^Fh{lCyezlarD^%F|sdc|N0-0xViK_S50J zdk)6IRjq?3Jflc-VG)Ty7|a16P!|n}|DY~VoJ2NwrS=MEEla;qr{ay@Szd|*4`C3m z1!SU_;9%Y1g1J>KexMOe%+D*(-zdIF5tIl$X5}NK7g#wHWzqXrwe<9+8%&3;-RAl^ z`%H4`kadaZ8)NpcorK>uNklRF`Lma}xQs^QCsP#mDAXLJ5)%|gt~}O~j`AoF%vMwz zu1c)g(o#F~g&`bU!4OI>KC>xk++ir^lthQyP+#vhV6`w0y2(MYd8*6N`@3BYHLq%( zaXW9$f*+{CQ#K)HeKpEPR8+{R{K(VKOXQC>TJJNQa6c^1e~j6^Uaifp>-h3WNNDI_ z+w`&(v-D}yA6l{{Q`2hK3$pdE{|z&1Y@PPrd|2h*-8{uXu77m5lXQ?zAJpxkqBwN_ z>c%Iwu?N$)@?LM;i+%n+M)2YFYvdHt!WbBG|FH8$7Jj(X!YowFFg~Y0@r2F9G)%PK zv%cypNRTg-rTpFV3NeeRU&|t4H_f4u98#{Hr;w$U3i}4i;%TCDC0|_4mOdQ4_Vpk) zeySrv-KwanXboCpWB=9&P=LqBC!Pe!0q2y{#^7XTp0$&a5dZ<=_9Y9*Vn8Gz(W4|* z#){j+tO%AztNCtjJh|l&c5)B{NQA>|uA;wta=iwo;JCu{dgfX`a20{>boVY90iEXK zt0XT4-wVcr&wSLr72$cp8~rgdGAd}oWyo%0I|>pJ^m_>+`;Ka~mwmv_gK+3?J1^u| zxZ1)vvD3h3y1f#3hbyo6b1l~3SA+)}j5?M&ot*J+IchDNny$7@N=zgtr!*hQP4|7n zAMBZSZ}qT|cGl*F#^ai~4T29rvYdtHlv6`b{!ZL=N5W&FgmreU1o9lki~Z6*DX4H5 z9sVRu<`>1SuBLWB*7&c5T^K&VvV3AxE zydTuyum7Mbvd9|1=R$tB&B0So^tkX=l+mYHvK%b6*%*WA!_pU32?7TF3GWsn!@ATqOh?lAc-YKuFl8lUIF%{I34kYm(N*CI(l`us|{N03Pjm}~+cyw~)>0fcO8r|4C zRA%vJurD>gw@STVsTgdFk%}i~->3}%Pqv_#`HTrK?f%ErxA`FLL%z-PJu!dORVMDc zaaavsa%$T2+cYbFx){ST7%$WNcb=}W$40ZW(|7V}3{iU{!|*j(B#kfJq8(YY&^|BA zTh%UuQ7BTSbXYu8XqR2U{DeR0!`W2HzF31tDkR8DC!DX(9-0yl5lqg!69GuiXJqDrk+s@}iP zsH~)mX&M$vyg> z`Y`l#_6r9|O<4N54*WwFdAwjqx0~oN%uh-mBB9wNqTxiIHjvP}{v+Pyig=iMg+&k2 zd8cb&9@UNbPYA@L`SKwqElOuE?ml+id?JfB|L&e^B%9(ji$^R(LM*O@BwxMolGiUJ zqWuus-IEoZ*mJf%hxVoTOR}&)JmZrI#f8W3mC$y3asPU;eQ-*(v*HIZFrtf z^RXXFEP|TBC;Q3iFZ6@J!@qX5g_;_VLI+RXF~pw!&bt_CB^i~szbHRnvgas?5mzu* z8Qh99eS=d3);SQDpEIEQFdbJi)P2+nqsqBmRVf^Qe3RXBHwP$BT8|DfWvqN$_+>R4 zC$W*?2H$p!>ro29LQ1Evh6c!@RHn7`04jwLQmfc|J8x#`#eodb8bU$FkZtzFfN54V zK+yKF@C&i!%GJ&BE+%Btk=GTMt51ZqmGLfIL7e`KM@Xe&+r`ldCgY@!!9Kt%xP1Af zQd3DuZZ{|Aj^ePbpjRzTOG%k+dgWr2mIhqWu=0+j#dq2f!+rG)^&v4#W!Cl08Hr=jCCS z<8fWj?WG3_L}htQIHA(Jlj_syvytml+1i#@2D~}b)*Xp3SrUHb2I2MTWoon=?G^__ z(Q}Ke?zoE)^JTdh(|+^0IZKEmp30X)OC(42R79d`=SNLc1be>;9nx!IQF*D%`e1b} z2<8^1(#h{pK)TzlbtCUp6$wIT_Q&I!LQjmkqD=lQNBh`_rMhz_cydd9bIQ3^GhyEW|E^CA*dW2&Yu?_nHb zVj%bs5K~%MSbOl0h_Jfpy{9x`sYhZkX*1n@mwjGQRgcbA8QfRDbf0_fe2t5Q%&x=D z0KA0Z$fKd+9yxhhZh`nCr@gCLSy=$z^Yuyg!$!t;wh6QjJ}NCKxX;aNcaUiyT`DSh zPLxOY?G^DS*F&dHGXXC(TO_NnVmJBj9AaU_`bi{XEuE-UQgBiT-odWGxMrt5s~{&Q zfUae1Smm@+RZ#FT4HfHB9&8L$@J$ZD@vZl!Uppez`pj~c#ygMO#(|&if@$wb!z8Bal&gfIK(|0JX~l! zSV4*ReoAwm{S}-*TQxS4((jZix^pE}@+@_>@2Ce&Dphk4+u=^Y=e)D@tiR6tQ}hKs zvcr&TRp+!#r{7+0@cJE%_=jG1=RZ;0!1VH*IK8v|Iw~(U84-XQ>4c$@(~s)s|JZ%> zFQ)fTe@5rc750kC&V^MvneMKPC1M!effNIL{PVtc^@GU>pRUwPx4rUT@)(=n68U|l zKL}(GEYXOtW2JWcvPK}Cj=)BT+CGFqIU_YzI<{?-gC2wxY$VJUuz7Y@o5#$_CU?wP z!}^^kWSkV!+3Mwh#DYN?2(d?UYgM$GXQCtib;D!LTi)(3*c+}Fl~b(3bf%$^6c-<< zILdyh-ww!Q;L_&wm?C`Y@46=>>zfP~EgAmEACHT}PfFTQSHJ)Ldjc^VPTD);20Jyi z;XJ(NMyxsiVu6>Ot5&u*;a@|0B3~}&`rTuA=irH(11Z*2VxV!7{!-vwJEko66e|V2pL&HmO!lyxu0LF2r z_hc!Zvz&ZyGw;u?P53&kGFYhIJmh}SSCom@Jj0;!0bmGw!(r(J?Ucz0hoz27q310( zrrp0LSW)^Pj8>REf6i^3l`ci~(Y4=V>|NU@-GdY>tLHM&ayhm8 zEt$~K#cqrBZ!D@0{UD&vlH=qKkjP13prQzH+I6gLOdHU%v-(gbp zu`Veoe&N=oWR{k+)Kqz@Ya-rIZrsK}z7&i&m4locqbuF9jkzu%o4&413>FuCsPK9f zjp$4eJ~;1vQ6Ar)kfXM%_BA%s^VyD(gYD#mBz!!)N_5Qi?XqS)$OL6YU3mB(eqX{2g<6+tpA8T zVQp`@xDWhCFiWo^0p|sydHABj(KQB!x&vL!I7ertPR#*?0GNhyaTysOBO(+_r{}C@ zBxATFN0x`u6+!SZ6uRumf>00+kw z98PtdTkDwX?%1zBKj0u&78HD&o-P`^u&{uO!~w_J<=xvdBKfvw4`96z&}yoOgY|D< zo_7!UXAKkM5%Wa|*>3<*A6gn3Z$ZDRXc8MTM(R{JcuI1oDc~*pDaG0eOZF_ya?_bM zfOQ8=w+aD&Ui{sem+L9Ax|7q>-XfNkmcbvDf{p#)^qU@Y$4WIxK2^$fT=Hf$1{RBl z0ZX32i~dD&v&*SB#s|e$F8G9!43;+*6^ie1j*oh$Mm=PW%8OmeC^j3;(b_=0;`d~} zX&FQ*X1_h|5gty1(3$@p=Eyy&zH&>9Vgc$FH?eTbsY4Eny6LyYJ6(91g7xuEb_t~F zAI;U&!e6PEMq=Nc;ym;DF@?K)IFGjA2wy5V#=yiyKEGQy1Y67!F%#9)6cs^58LM^y zf*jsNs&v=%3wY0Ubu*_#KA@-9SZ|`Hr@oHq0bP90b!4C)+=@-cG^lPk9_)&JTCa90 zHiNs*oeH$6)YLS~{zdkmKYOG%{bP79pFfMcHWm#JTpp5a$m$Q-E(@>_S+FSGM}bo% z8O^&v1Dw7>P(?y5De1!v20q)N)YL~nA(tvXIy@o-zOly>kQEI2BRn?LQhV$cXQj(` zmaajP8I@=iT-T5z98^{5q}WuPNU9E$zT=SkM8f{y77PT%?)r@%jR)UkXA)1&{Pd4+ zoG3hAV&4&u0O%Z~7uZY&2HU>9b94%(LG*Oa^~4^*x){*Q(a!_y>}S^H5y+lDC?dM* z!c(b$LXkwU>+9%zt#6c-jUs!qcouw2e%_5^r}`B6^WK$#5w zy%Xs2Wsh2>ulS$ywD=?aqqy<5mem2Nh^n^jAMAg+D=(w0Ovs1$i89s1)yDl*k8qId zZddHRYLC%}S+416jrEFJ$)`Da1$W$Y3R%V~oitTcXpST*93(`rI*bVk~d?5BDie!zn^{e&v1BEUNMYqc@UcIHEruJN?U#I2b zH6uOmk9C4-tQM`iem*{`MM#d(YiH&KS@j3)_cmi$6wwdy%eR+y!$>oJ1Kk(zQnvH&#GOk|bP!D?w@aDy0Ce|kn(Gc!1w_^41=wLgH*9J36Lg@6Y!Z_~nVBvxMi>L6I z5fyELEu=$-sI+xhAG7-Z7u^5%f*TLhE#^U|!;eQZkI!Ks>V!_r%=K^KlaLT@DJTj# zwR+=aYgF?(Oq*!9x&j_1ltEd*rING7Ci*145eD28h4fdyoROuF0G6tIz~sk|pn1Zc zl}sq7d3BoJ(y1OfyMQ;e{>LJ`C+>K)v)XR4<5>R13wwL_kPy@dkIRD(IbkJ2kNH^E z(!!5`)h4gtmJs!w?W|#wKgct5q}cO54bW5>vUC-RpWf=_<>l@bB%ImX4z!vc-h;X| zNnhtv;RAIRw<_h!-OY^VV<^L&<8#*zNgyP?y@;}TwQxGLUQv(M(^KCPjoWyn1+@iw z7+;_6>gx5F`1q?4EUB>h?>+3{F$T4Ihm`j&k?(yX!@0R!3ky|tM+2*e?g6ml$CP*x z0};HVi?gjcHI~R#V5~TWRS+#IEbMX{$MU!bgHHT(NyS`ugE&adAE1h>blGw3@%jn# z9PR7a~ajjBF_joT$nPN~efcV3rPVfomYYfCDa&hSzE z;VmHvYq9VDQQ3QnpwoUG);)gx2Pte7|C|)r|F^3DpQlZofF7s#TxNUoQ$j?-p;O3xm0=Y35MlBXqtuT@S&>|8<=$j~4ThvleD#h#`*x>pTmM z2J#LsXLOig#CrMoyIsFAxnvWpvN3*P1C;ux@Nfum%j=3hG_gHLG&T0n5+2I{-c!0y z?r7zJ*y^~momO%Z4t;0noQzEX&IFGP_l3eXF+hw)*qtID(m36FQ-ie$6cv9Q0@a0$z zbTMfSj2>MwWFcZ@qwpmqEqkWrUv-3mC@bT1vO6SqOc~T7JDS~ZsG&Z}J1ywC*fG$E zG>{MfM?sRb^CGPKn!os_2J#@-;;Y(C%$OI>&Tj)h4cIm$w{f8|d4z7LYbuTo%U1N5 z@Opcy?q?-8#+!X@+<^6E`}*Lq8LpmYy1hg;9Fov-i=#nPE<31M!&%IE9ebV{v5D?|+Q7vD z0qVFf`ohaW_>qAT5wtY0h(liHxqKFf-XwbQ-iA98v{S89{Q#ArUhRk%*Vz_8 zLYE-pWz1ela9_jGV1;A|#n!b_EwIw{!LDR$80CA5&197G#q5iV{ATO!7?9b3X$^9B zu_z8%|HkqKmp9*Z)T!jI$=Cm$qPPS}H_ZZ*J^1;=i%Xxpy_4Ics%;L~5bkx;lQRS`wazWibVFDq-g%~UmI$o2U6%Eh|o+Pg5e z%(B>8>yRZk&t=|uJ$tRUgq2VD#TiLgZ*w}DfgXI(y8YG{V*bMc3?LYrZ+ma9t<1u%I zrDX#H;FOexa%ZpN;$+;XqgiZ87e!>qJ@ipswGy~MtP5aGx#%nh4ix^#LFf8-3*MZuO9*Ku(H0mxmydvzR+=LU15 zild>zr6*}~SjEc@|JI|-FP;PWNx4{SVk49R$gLLI4eyB%%|Tfy4;9KT@}_AZ_6(g^L6SZ6?L!X1^t zEgGVL5YKrEF9;%o`c`|g97QTens69W(~#@(__(^7*B?eQnH(1unohfuxt&EcDK4>) z5AH&`*FO)gt!)Jd2ATpXhk*11gMH zOX!r8|Lbf2Wu?O23IlF>g1?#@sU6p2VO4Lt$*mw^A;7EB($7r3F^w*v?-JeR<@q}^?Iuddxa7- z8wc+1q7_e4)p&oK6O2SexiYZ<63L8scpsI&ue+U&<}=cf(E^p{`0I5gS;$18Y_R~o zeDmAs3xN%y@5N$L5U6YW=ynGNixXLv77@yEDa*g`3Tk z*KQ4`a54Q$axa^3c#?Oz6$sLh7XS`FTeAH0|K_4BQegCcWu4%nr=LQ*#g)J} zDtc!AOZ$M!;KQQqL&YVTJw^h{u@!=&DVK*INv*0L9I;ZReK+{e)~7tCLgEG)v-or=N01N@`Hxp7RVc$iG~U*$wTfb!I0S-)90AC zL{n7MhlZHhQebuC3xOnwsZ#OuX6c4vMFqC6ih%aJp<0NQl^2-?=LvVQ6gG2$VhvlN zIUP2ensr+gGBiYcH@8daC@CrFfd%;8+CpAVCz{WucBZVapD`Ymc8-=56IqNfx^;el zk#vAMs#V(aa*$88XP<$gZVm(^tUjKoV$Y8FbhABtK}6gp&{E*tU#<-GrJJKq&k$Mt zPAx<9lmL$a58wM3b3ZGDhNEDo&T?pY!qyZ+--1>;LGTjwpq>h zJ3tQxYmEtZywixkO$ouekm{8Iw`brFN!;eTc9Ut*@X?*OXPoVzMS^EaPDii2nukv) zPow|QNKe0#sHpL=S}4pb@(KwlDlepqP!e?a3SK> zZpS0CexD7-++6VpBEWMMTI96D_K=nqQOWj)FLs-tJ*_W9(DCrF(VGpg#20c9;|Iak zQ`a5MAq)gu1OWBSQmRA^w*3ULguEgOi}dIqtMY*2$lNi)FUo9WyS1I6zr;p0`}iCP zn$SjTOchV)nWon09-gn1AxJSn+tD*bPgDsvIT?Z?RXn zn88xO?d7O|PgEie^o|;a6F)wur^{-2Tn+_#YHOwae$N1IBPgRGnM%UJilq|BrrN}o zNy7IFirjW)L-RIE`^E1k!Afe2V%>=Lw}W0MBukBY8PgZ-bV>ZFf%L^D%iG&B7>_AG z?}MxiKvq-Bfs6%_XqW&43jtCQjS`=FQnfGbu%<)zkv147%8#t8t*tXvDMYeVDYATJ z{dJd#&raT1n@BF%-34(o=MyZ{n5r`DE}#wqeZQWSACP{JG~G^O>kzS8Kx>Ix7hhh0 z=X{3pp8o2_BO~65l44n8lYF*uzP^N;^NB__$w+9X8XhjL6Sx&aeU?>TL4Us=| zyi3n}{6s3CxKEx<)dxGzzUB}?CX}3%v=B&=WGKn;}0z2c#1+1bzz(iU7sv) z!B^?aIPBh8t^^1k*AOZwx&5@{HCXwPYYRJ9kbCmLmH{2&_TE8%UNG$*#yfmHy@>|D zxxrE^-%$x6EK*dOt|;>9znm_47|BXk4>cp@b!9Re&4@OaB4!&J&Zq|%s{*~M3dQ}Q z@{>Ehg7M?rhKIWy7cM((E&+F?w|Ag?f}xYY*N+4zKNHXk$z6i7>0*A zXRR`2j1px=nvIn@P4=(Ne){zK^;zUokd)GDy|nm+j*mTuockcO2NZs56U{Zsym!LF z&Ks#nu!Ywb1p4U@iRU5rGq|~^PWKF5Rs2g&c^Ja)2@2uqNy2jni zPOnjZMi4#FG77EZ4sIdQpS@raDRsDzN(jfjut$E5iHY?# z0;+wV5V|oC#@& ztoeF->seY>7FLu*wc7zy$wqI6GJw;>Rcsn-cCdEa61$L*_g+6BWEz z&q1`|pnlZ)%b|=rgRtE)n0_<+>j}Lz;0|YzY;HB6{S%0 z$KdtNEu(v03x5B5gU_FA$_qzPT#dPEVq6C6L52LU?zwaQ@7?pG`TT3Nr`>Z$O|{== zG&g9|F6^Bg?{nY1_mstvInOfBa-HxB2O%d9`^uQRl=u_af%X=bvfP19&I;1c)6x|z zD8Bpz;u5>9n8yIq2T04bz z|EFJlG=y6mA3u2~{8ahXqeYA0!<3o0{?{%2;kwD7hO}k}>63g032O91V^KE+G+86F_?c-|8bOf-B41&2;*bEEipm{gJh`&gN>@ zTuJPwzcQXbkM=0RRhbUxEP0PW|F(7q4Lm5E2RuA|w97Ascm96Q5K zlCS;kF0dJV4m&nlDw8*^isY~nfo&^3hT;_Yxw&guu$^FR)P|cIlOc-90tw)HmcWSW zvH8*D?9|k`xuC`^ydB$k@}%OilepFu`X&oD-Y0y4tBdSZq49 zK4KTWI;1+Btt*_RQN663oaN!+5q;}mY>zOK*{xG9x$&J8>%=lCAd6TWf+ePa34I^K-sJ*5=gDQO zcLChZ%?fFjqBH~9cG$6t_vY;cb9}BKBe}~^Bip3&GlF)PDVdh zJ=ojLU7Zp&HOaViWq&MpqOnlaxHscE`~p8Ml+yKlpQ}=p$^5Wk)USD6z9Um)KDb+F zcgeUnjl+5uy;XVt!+5>xg|?&h?%}ZRVv{kzB_3OrvR5frBlbPXG(EE459X^THuoMA z(kxUm8V%c*s#Q51peniS5|R>K7){?&WJ$VtXGVaWyq})Uo~g>EJO1_C$=G1*>e?G@ff#aExOL7Z(o+h z3qvy165W2P$!CeA_PBmr-(36{M5tJs|G`g}Jf>q$C%GYHG`x*GzW8{Td9u!*h}9`s z@#Gc3!~|>F6*}z-vaUKs{E>QtSO%tyUPP_2`_H)y8~pn$1EN$8zlx!7k(G zwAZD+!HK@Xx=%!zQ{_HnHc?kr0B{2OL=S9sx9Y904Q?DRWzXv|aT|RrgCf-)zUqM* z-qL9yGf*qW#-Pu(?a*V8K>Iwa+2N0xx|ZrA3iqZq1!C{xDyKer$wGB)O0hD_t-(n{ z=8D5}``0kVMMd3aj!=WQbd$e-+N+l2l~3>GtiO59KFnbE!JTYYLIMNB zLZUT9DwHn53J492Xq)*%*9Ct#FSg5mR|q@CD)q!~-HMQ|t80t$KUmma${W1}OC$*x z)QR@zDA`abc@tAl*ZpsBF?4rr&Pxj0z?fmWeER(S`0#_zueiBX=|dsNI3c}a$pp=O`b%jo{X^ZpH1`@~vNKtKeadix zwsP^6DW@&m6F9}mp157~G2OxJljD%qx%E6fR8EnS-rYWp{){~xZmrF&>fY)c zkyxqbDGxr5Yu%IYI(MG)me@W2+(vmcyqi4#4Yml!#yGFTl{w~?J@q5!I;g3fe2oWK z#3rK|%AMXbZH=XUY4uMX)hB1tgK2`>DETzU$}hnrrtRV|dEnFeilw!$wdHqrfVbP& z)RY3;m-6jJx;)+|IVm|`Z2>`&3Ks+fBxjJExk{8Q zl5@^EXYM-}d+hmo9B1D8f9tLP>&03vP*l~e`h|1$*?S)tmc3=}k-&C%N6~K79Eq-r zMo@Y0u}&pLPdA4%ASLZyvJ0%-$ame2oS}9HCrM=EB)`L5=kBzmVp>$ZBhK*5ffP!+ z!gcaxV4=fqKPBj}!ObaceXBqGMrUeC*xR=QMNYZt+v3mY3DE9$&48K(aKOeaydSN1 zk?5WAR(=TyBmLH}h_JATh}I}!bP&F9vS#mG%AWF0xAjk zMVfM&3ai>c>0M|eUkdk60k7Y4K2tO2rqGu3z^qOP^IM1C8RzbAY>OdSq)fL+0{q|D z&)S@eNTSkW``|2L=UeMZu;+mDk;BCxD^e9o4>?MsBG~YLX+(|hJ${vCG^?Z;Ip~o; zw2XD#&Moz5W2UbYf;;T<7X(&rDRuyTp~PX9*`vo{<^9xHh&q>9x_W_PQda@h`RVbo z0&~q)cw>(YjFJ)!W`0ZSQRe1!t#r_B+@jJbSSAo5r=VcbDEio0lQ47HpGa6#B~=kT z@DJzm=W(Y&f44EymG2T=0kJ6;H#cj+6BieLn(C?-`1!9bBl#U>y4*IrQ4Zqb&>f0X zm<<(wM67MXaENdI<>4(IbqFbaW~tvm&@;Vf2}IvN*e*KXsj@**EL&BcogRiP}M z2SJ7Ljtn}_>@ze_{bg$K!mNumu^;Sc-vqQbo!Lok*`KkXz{;gVyLhURfrySc8Mm_A zHDT6I=#U{LQwzyR?Y4q)|MVPOAd=*E$6mzMt*x&g9=Zze*sfTqWd6pD{gy*N+{dS$ zXg~2VUs^&&(EecO^>VDa1&}r1Q(3H8HfkUa)N;i)ziPaaOvoP`1lxY6gGbW+UCZ;# zZc~PhvJ#~myf6HZ;?h)G70TmF#KuLi4@-X=*_6V*bLS8EDnB?mrr&CO`AL$#vd0TD zmf897cKYid()Y&~CgLOQt*s>`WIClCOouC=LE`rEFxqr+?-_dkp;h&{MV)jD{>v4b zrAuimNY<7X*oeeV;o9JG5FYC#As-7VW zrcrBo6o4MR1r)f`#+@Ilg&puvqed@MXZ zmnF>vNs`S4xokU6g2VldrQxJPg0W9kFKDP;H=4s&^5=Pq$~U`m#Bk|kqjGEn$7;v=eB%G?jhNG_vwj6u&vm#Vhy5VdPCpGYwamk z(awE|!3jcBkO))p$D(JxI=0MtA{7gbHL+uiB2H*GdVEl=!oqh~N|Tk8W!>2RJ;kX# zu}{rMR8L=j{kHMv0IgC&!e!%2%h~o-wHf^m=~_hEd*j1!kmDZg(-wrP!Av-%;k@0YKzY58y+>tbP=;!x@hz{Bk z@VtM90&|&=MgDULv|C&B?bE0ClT5cJ>Fq;>7C$_Jo!Q=^kJxf6MI5}XETWQ}b>Rfe z1(t0^1R|~6zX$}X99Vt&5Oe3~3cud;(r23|Pu3O|{38lMbzfjSB%PrgNFhAB=T9k# zoP*tORL9*w-(qnqB#l5%Y-cXA^h!74@LKPPSzWcLa!sG)w7#BRIszA(AVZg2M6RWq zrKJ!4t^M5&6B-Y0N@9_nt}MiQ*D_yVET)Gj?9$g7RW5q(Ev>W1*%Asz?+vYH8~w4r z4Pqn(qn176mHW+k8UCBi5vb{Dv`xlr_k34Ue0)o#X1>Fm(YZG68Va?Vya?& z+b7+-)tUPg6lXm3=C`OILCiPpVrI~m=Lg84-eLI?Ads*aBT?nheX}iN-b*GTYq%`T z9eos=(0JhLIxyrse{I2EMx!9gUobQ><0hqxCB&lCXV0{~D`!X%_C&Z!rlzfq`vj?3 zJM9<1dSz)<9CffeyMVfqy zr0MMxMN(p7K%2=aE@+pyI<0qE^oe;Qu%ZikL*sh1_71A1QpCG`Gca*q5y4nW$!{aQ zW#JqiePsB~r7E3HETd^(Gt5FsM$Ie-%0|Z)a;#?wnV6VpMB#1>TOu6}YayJHqPEJi z>hfrOlmqw0GtYxf!)LpgnZguQuZIZGy2znr=8)tpe)qOyn-n#U z=a3vVw;jo5#ncHD@0e`e&Tm)@In={$?wj)O>^BUd(7y7ZK-%!p??}UXYB~=jqw0kV z7qDq2UcMU`7&t>}s7%7r8wMuJr_nhl9k~ssNE4X39-SWb*VAL>U=PX0wCZ@;%|erx ze&vy~Fw<~PK%8$SERdF#Q4Jq@C!nb_wSM)>e7lnyPQ-_R}&U-MuBJ??qY!nehDP%N&kf6ums-S zOUEbl=4s6>AHr1h>Fcx)sT4qia`70eaU>$DjGuUP?ie5`^T9cWJpR;%gS?p=QUd>h z(k$aLqv4|mFV3-pZ$9Sq6>as$N0!lOe=?(kheaG^u`r(E56=uK|KK%wjIx1H6wh^z z>sbbRlq=?gJrJ`B)wJjoeE{|)ETP3EHv_47bDp81?H@pdi55EAIPk({BY_4OA6C9u7fnO$ZaZEpLq-<5sxR*3;)Fw^?2)xJkbMVK z`5pu$jH<`8?=Y*suyD~^wb)i7RH2+)9P@zgVMq6s_z%xT6QmNKL24c?ztt4*3!xJy zV_{BHZO>mkf9R6Kal^X)VE0n~AcXsZH_b&mq6T4=CS$GTSy`X&OOungw%#=D6!*eg zUtc{hlH55bp^yZOZgn5dU)f&r z`;aDYu;}FQ_n$ANR>;cTtpdj|@}`BX;+sGYy0ofn@KC>Qtf zW6n}wZcvbYUwu6T^ppbb;pNx1vmL7zfqf$0_;*fQ3>6%jFBJ1xxri?GQDqwt#bH{` zfMOe$!+d2#C4Ua8n04(E63r3Z5JcO%%(Ga0c>n$uG4Z{GH!xNb5)-(rI@L>)jZnXw z+~1}o*`gT7IX*#5#RlrxhxEk(s^06@KgQWFYQ1)mmzUSzw4(Kx=`8Q>^QVkBb*dPG zsF20}_0G8y2n1xLu>BgFCJdv7W9>Aw`G0{kJ@vS+)5jz2SgPVS)=#Sq2yJOPwvNsC z6dsvTC{ijE5PTdOSjKkgnap}VKLBxIzFDVwzPXRTKi+f9G`h9B;$f8cPS`GIBeX^s zMKU_Nr_JCzdnKkQ!>TkfGj^!^;t0>(fm!nCkH3p;jZc5kE+V0`9yIygySr+Gd3h&fvz|Z2&3{TJ z?qN5+3yQn)0`rMZ3{`tvXW$L1!u9cy6ziq93rFEawUEs+YY@F`ie8#%EpN25MfC(B{)Xwf{M7i&1 zdIgZC1VzQG;>k*3fzxPW2tR#V(*lUkN*3h3OOY5z)(3@#1lBgVG*Zi_&Cju;k)h2Dw&Q z-moXv1ej)3mDg}0n=3wJC&i1T6dp^2_$&1Zzbaj(FouI=devl*GBKE zWNDF!_(fiy1>_(osd;4$e@2*o!{_iKpWZ})cO<0ij=QJRHOmu&?aI3g4rg|kWW31GhDV^=o$yoUHFaJ+1WdH{meTSB3T7psCc)oRKd$W z@p0X;W}6_hf5)Qd3ym|WG6OMV@xd^Au_HN)oFA*Ys&aOsS^t%UJZ{5--xL&rA~m?D z)7I%^BDtTe1;aQTkiDl0MAts9(My*ub!QFVB&J$?(C>D%5a#1R=v)3MPn9W@If?^u z8>sXED6$!7CGc4PK3D2#W7M6j?bhC9L6j)XYrpeK^fCp9uATu?+H;r7lf7$Ch-lf5 zTL_ee;Ea~wQHExmArpa5jZGtR=a1*Ef>K#ya|^WT*Y+|$cH{?pI=p$)Qd2|KhwAR? z631TMop~_mye%Uq$HU3V3E&e?;q{REIj63SX|=+hIS9Ne&<}&{m2+{97X!S*4I;sA z1ATo3N&NlIC8a`HDjHFcd8lM+gRMoTBI7u#<^lWOP9gS9&b8)_=EIJ$7ba2$OieHu~OfE!#am}QL8;fcNB z4rcmc03tm+N`alp^v^>rtir66v&zf#W-BBm0YsR|6F zI%y(+Y(So$;|V^)w8+@)$(0Ap?L-=}u*3cv-(SgvYp3P23zD zBqC=o+#E^Lk=^Av_jQ^f&N)*Go87hN%V~V-JoA3{8SmFDLncSn_M}Z=$jA)i?&5ja zUO=AGxN-`vydn^_;uo@>BpY&+vol?ISwM4`NsP_yDskbaMR$i%$A0+9gDG!*s&OLn z_|NVK7yHse#~PeBem(ItbSsqVV=g;KA-zFLFcoHdKVd~%1vWq-b{*FbL-N6DRW>;SQDE=ba^eu(!}VRSBMw# z;3`&at#Xuz-pB0Y;HCR_nhtevUOZkcV5ys8L`W#c&X2DAOFl^Uy1_MOFRf*HS^n@HS!T1 zduTPBpil?zK2xo`hmk&)_e>lI0VJyMDkmj=o%{t-NNe$gY4_ZMvL`*C1y=&FaO{HK4rj%^C{ z8_ubl7g;T918_zvVr$%OIs7S^{mP4o@Nnhy`!pYi(Q}*g8R`XqpZ*vRGttw#2yM;+ z+g>oK6r^hQGMe|i3dF)^F`1N4$tx&zfktY9(S9_?<|8m%*kWit!58cqkWO>HhHaCn zvdTAPV{;WsMLV%w8ojA+3`J;z0DXNM9)84O#;h zOfbuow{NaZ_96l?G1G@fX+?Jx8gJ5g6o!&LKa2VBIHN(Bl?u82>C-1|M&!}X;ix1D z6KMe5P;uNpEj?oML#?05d7gX)Q~r||b|S*UEqg1*SGI>aK4Z>uz*9c9O)!TzIoc?N zy|@^`gSV&Gx6zM-0}_0cR`CtJCRL>5VBf|5Y?`S$IbSwMI=ZJ>6SJ#@~ac?$ep(qY|R{$m8N>$m{x#ulS zOC-Ylmcy*Csdtgjp;N$MW6cURt7vQcn0&SO1LTW`hcmD5T5OXpa|WU}Rj@Dv@F_Zx zP9SdbASEaP#eB4zf&Gt^uExTxpX*;zI^h>va|uN`m&)O$5)KVcub4MAu}G>h8l1*V z{Erd!D|U0~22?b;U4NFwoa=WQSMi?)O(Uw9jq9V?D*{uN|0)6E`xNE2&$F^!>N?o~ z*dr8r66K;?N5^VpGz#l;b2U)gLtqgUFQ1aAnwxl=Y-~JBv)EK$pToUr#_QQHe-)Bw zzcM8eS;Lnnw*RiW{!k2V7tup&HteQ27R>o=WXcU^}gQ5WXS)JQk z8UOec3nt?iVt#_n!OG%XGsUb5llcN1g~W^euEyV@pW;Wvm}}vT+9dr!$<3-k!s-E$ z9D#_3*KenHokO&1I(ubQr8cylrI=-p0^O*5@qq?*TZ~qz+v&Jkzl)Zprs~-uo+l9K zwC*f3qDlajJ2MpSVz;HaY$MaU;}eopvhxj!FqeP_{|kgg{}$I?5z8pdQM%UbAx+D{ z&d$!ob!gP3n%GqDq?w+BG_35YR>giD6!$3o+qszU0QP#sw#@9~QD}?#5xubX6R$Wl zo(K9=0e2UV)lpP*bZZf2VBSA>j6HqNQ;ztt@Gf45upoo~^3Yatvw}G0sIsO1h252b zr{;JnP&WKuHx=khGq=9y6dd<4j||`l^8f9r;OE!0>q{0bM9K8pyB3cU+%aPHJkN-D zW2T770R7Tf4JD6dNl}p>HAh2#quGLo1O%`V0v5GUNfnsyf*KuF6(-ohWVyMP>HCV`uS1Hp+-F;pA5Q-$|JQP zP91dFhNZ=mz{kfY{Jo)xymd~&BS_lU6JJ`)nxp?YX|&(( z+8DB^mVphNJE2efgNGwMudDKXOY9bNF1t2ED53dIOPN(m&%ofC2RY~Cj~!vTg-hO! z{c+rK6qxr3n|Yb7wn))sfimeVVwR@bKIftIuo~9X2N|Y41cR22E>4Ss#m^sL*53D; zW0!a_AII^|zs=~8530^<@NgV2US%dl-&V0d&Bwk+pf{!`WjFN5@&{`xrCdTxb(o_^f!O9GQ*?o*;j|BQJ54v!op6Ut1~cUz z&mdxi9dXX;95Ign9KiedKvehArfUy`t}CHbts~P4No?q?{!fGFr>3TWTZ48q?y09f zhgwRpOR~4%FR-fnbclb=_bGkv)imwwzJWz>Lbc&+4C5r%p^>T1R|d&*N|ov0`6lI z0DW(36p#z*6{Vyw9D`&Dl)OvpC05CmN<4OP- z&S>LL7lU|XSEEN0Z+6!OO-q-1@?9SHcqa>%W~8Tk5V&kpt8yYNp)Q#qKMQy%p!kob zLz;Kz4gl7Wc_&~n`SqHJH%?HJz`_2qQez1Cwun;mUT|4*ZGTVeM+3tn`0^Ewby@kf zce4s$vvs7 zZ(>hzqOTuK>vY^uYYXy_TUZCFl5Ghz&wg0nfHZ2Yk+>SG`y2~e9Hl%oNoikr`URiz z6qe?}=GSE&D_&myz;l$!t+|=BLq)nH35}sygGEB_8*gDTCe_++`CPsqSJ9uh#U!Hu zr6l`h{)gKI&5)3LvQe-UObbVOs~v83pN~M1h`8q4AC_40EDja)*0-$!h$T$PHMReI z|Loa@-30F}W*W_ZVH7MAwM3NimExHBR4`q)W8of%GgAR`7N8rfd_4K!0eG0r3?<|e z#6mnav^j@Evvl=ZBslElA+jEpK8x`>n{sW5+1^OB_7a>srl zzMir|odtO`UmSQ18OCR`H5_=PEr1Bf$lrWT9ayJTbknAR3_uyl5@nm!0Z60r)erYG zG>DR9A;on?8Q@0#3Vq@m8&wcOrXB<$Hu$=2_EA04v4d}K+F?rnQ~5doGbOb~@k20C zv;LCz_Jv{ux-5)iz^rqi++o-|J9>GjpjbXZQpEe@FjY8*zWQLHV7&Znk%Yp_%<@ea z7Vs~%S>GNu>q@5a@a7|49ABJ#Xq6?`dgwYgEFoDD`Ub{@!`6WleA=G%Cs2GaYK`7b zS=L(0{iG5`;N|t(3$Nc?c25I2q)ilx3F+AQZ0cXT>ylu3_EpdN3tLv=$e?21mX|+! z_UuKfi=4TvtgPYhSAx+sYpS>oo@9Ry)=xnvarZd$T6!65E{~k z-i&Vh7x^x_2~5#7+uK1q0_{P*ge<}2CvjlN9C(pC56JzDe0xFo=p;nzpjnrnAoaH8 zx*^<+%g&ciNiFYY2V&e7ir1GpE%s>rSYU;;|e&+_i-YTKpov|#G zkaO^+?EP0qg4^5c{(^`Kh^Bxs#(Czf>iG&q2b8i0|mwc@a)L(;Ei0er&aYS$bwfZWG=a`FPuyzNfbkQ-+2R z)bx>Na~yT+^yfLtF(NPZk)=4f>t-)3rV(pQ0cfb6Q4wUTV&ipR>N3L-*EmI3n2vNb zyV0Jau&@pXQ4m$>WyDSU1&Tc#=*4a(xejDS!YXv_6%J}tHHf1@O+Z+B@=SqzyAnc?Vmr6tRb2D9hRI6EZUM< z`38ZVpT4YQ_37&*5PRe#WVG_k#j2h@jZHf3N$`4qt4_-=l!;iwUNk;2k&DfGE+IHy zD&cFva^cG!DklNjME(9QxlWs`5<4yN1^Pe%0s7u5aX;3&@pTfw z6`XKJU#_f#K2Ath_BU0k@|wwH)tjF}1U{U1$~7d-Fw@m#*By9^GhVw=ib>)HK^0&- z-=n)8Vxf|$qcWYQrTLD|O-MP&N;>MM`{17cm0I_x*I{ZIsTz+_6*t0``a2{UIo!T_ ztYlSC2sx*V#5it8PU$|^z^1|4m=y#s0q;Q<)6#CngWw0JUG`(5-4AN=C}2Z{H58g| z)MufzoC=zcAIV!@yS1;#w7qkCw=Tc_ZEru2T=_uw2;6_Jk`-W#lMa{{a^oUUO8 zwY6o@56+hPsS*ACmoCAh0G7(4+IDS*MmVkxgeexo#rw^?+v^@L>_M!X(-Zm`bxZCw zf%e`#ddvC#%#^g`?R?pEsNDklBj2pui#ld= zOK{z>r}!p1kW>0?=lkHtr+d8hCEZNc$llm=&%gOPi+U4q!RLDu_oV@X0Jl3e|Q z@>&o%Ii-fv8J}P32r-w@e(rJK3`$A5cV5h>-BdwFq$vtTsW2)v1WAg8BxvB%AZLj~ zE`*0Et#;#VE%XZ~ON{%jhGuG(@&Sh!%iVpQj7`XmTlw;Na5uq-uWQbb9IN2P#{Z8H z5kT>o!-WgA2TSBrHW1VZkSyicG)}qN9mS`#(*hq1s3tVkZ&!(4)=sxZg*no_mO4)V zUlD-AchSG4NQw@Vye%Q6TpG-M;ZW`@;{ES(!)bbCO|6BwIr;G$yoasx^vkP}FRvc5 zp`QZbJNzDC-B9EdliJ(cfhFAa!Mxpv!fxpSDGB!L*B{$H{?Y`gvGE_gk_OpNzV#}* z>S17v$pXvI)T5*I@(Oc7gq&$hD}RiiXJUOZToZVvwr>=0-n`R^^eOCS+_-!^TBQl=w=(t@6sBYl^Cg8nUIW=5($?Z zzh=C6)nI!@3a_~-b!YxJm#szWj`-D@9Z;rdfsA4dSXzeCkr#X3KO?vd?oLXj=YU|S zXJXQuYlSwHdCpp0r&Da*SzBv(@U{)_BriVZia^KkR4-iBI3yB5ip0EWL{kO%zGz#k zo{9A|ATcW`5R~Vv0OMq__35rO4HZTtt9nU#yd4mnEk_4>0U;jFv+MU@PzazBy88Ow z_rWRplMz3p7KVZRz<}nskMEb2WbWZ_$(hjT#AmhgkIU^Yd=b4J~f;=AJ!9 zjNwxErmuNG1wp408G8ZX1F|SPduw%cXJ}t<)Lfd#4y83SD!!Ncwz^KdVDY?KYa{YYZJ2`BOKqCYfR1vFYH4TFE5|<_u3fff&YTI zF-LqqepIRh-hA?%7d`lxAK$}X4gAJ3&=3iuam-W0KxfM4V@W~u zq!35;OX5+~9x=JCKsAA+Dg5 z7E}wucZ3dRYW*W1p%}08TiaTL!ag-|U%o<8W+Xojjq}#xaJtDC&z&76mCU}L9zgv{ zbRVAh2ahFJ zo3>g4doU6UU(~Je@zMfN->mvdEGmN;>dsNWxp(#K@0d?})k7}&-(!9m4(AN#QcF|3 zynrG&HAPx`Lrt)?WK&??lx*`ytoU?=a-C&uyLm9m-IyD4o1Eh^nVkh;2E{GRwE($S z2Zp`zy$_*?TdoTfGFO*un;^=Hz2@fTdf^n75c)#oMJmu8WIOJzoyNb^k@_IcDtc&% z%gmINm!DUVb!sB`O}KiV^JD}19S0{jJE_RT1U_+ z)7JJ}=wPR3bQ$&p4+5y4^xXi*O`ruCJi^=#{kHb13-%`J*~)GtpN;))>WW7-KhR2d zcTE%IQW<_ zbQ5MBJl^M6=KvvmDk}hSrd}hNJGO{<^rl6!V6;9@60@1dFvbBxjn}O zu)iOFj0HCQ@n6)UZ@$D_Va56Ce}U2czbhdDLEOBrgyjlHdtAYO(StYv(^A}jGku;C z=mApzi1*B|?*SrQYCE^1JM+7bXe`5U{FAsMUoxwKqS>*r8}3c?R%~qI$I%*a-hK|C zJ)W=^sf;yf?9Rm5URfz}KdggqC`HyL&*sd4Bqm_e(zK1JP~PND67prSD;H z68=SjaS}#lL}p$!^6dpOlTYT z)<5e{#3HW$qkJqnIxi9#`R)?WJZ8!^+{26|*bg-^phMy!5AGRLQsQ{!G<>nU_)w73 ze}RJ97DK_rk6G{K_5kazjnBr%Psv2wc!Rm|GW(y;-C#x4eS#Gz7Y-ruLXz=LmzLyY#H2!DDOap(0LH!eWD`2N{R+cDT`&64~LZY&D=e>&Vh zwY{ujuE3S5ghzSfZ5w8h7|EwKIIb4|q=vkJ`r=y|`u1Ct=KC#Z|IW8P-rq48$bZSt z{qy+a^&^b-y6NX)a-5I0POdexISai(OS-Gf>IJTg;g9k0 z1LwP~F1`%ty6Pd~{a3D}rnW|E(Wi}iQdO?ZLQ`FF@-$FiKVxA&Go1g6;^V4XgS1Ld z0w7|Dctb|>yH4xZw4(1wZ|9)mojX^qT(Pmi)elRLG8%-5oA2wqy)dek6-5v$a;g!= z^OE4>#{ifMapOPWqRBL0KJDUSk=;VoFSn!D z@VV8iMyqUR=7Dmf!;o$;=FwmV1XPJx+qZzt%7uGIKCSabY}{$Loz?8BVW5m)(_Hmz z;paY!{i^;E9b#{GCXi0O(7Cmz=k1?mDCe4GnhrEp<+KCn0VdNbi3pb`Cc#o84R~ql zTKAhSy_N)aR;is!bsIHe;cNEEJD3r5%W3m}#H5Z74qWGSO5NS=sj5nvhDW5$|G}Hv z+$8PG(p#+m0`y19S;`rfk$h**V{RJuOTT}+Um8Ywr}_GBwTNSmq|b_9INrMcKOnvy zU+2&An!%@)$*dbow$h`$wa`A%Tkhd~1`-mV^QKBu<4z|zPOhud7W{Hi40DT)f`pZC z2!$g>vy&G*yvx!xKpz#8pnFZ&=UH|(GU*lhW9H*|l~Oh)B@PBoKUW5mTIl{O0~G^! zW;p0rJoBio-une_d8k~)T=)3#$eq<$oa?qY;6CQCr~Qyn{<`utP;h4_1pHUFInQE0e*A&0;QBx6)4uoq1B+Z`P=@R8%uW?- z=(*V!V-W$c7ZlJG89R?oo~Dz#s3U)T)BlkqvD-Z}F>}+Z%de&}RM>Ily|VuWxBU(K zEHwq-T-@CTv)uqHuCZwycK{TONr@FD|2!=-(a&arVw!qquI1q2>2qLB7OU`L%gf6v zf!MJ6_&M@uuL9TeC($GHjLG-6RLG|3NRPJ?!b+p~b;3%3N(|f* zlD(ksDeNP_+PH7~S{>7Cc(JQESLn~{yvo6aE*78^HZV6I0Qr9pCFZV&E_!7t7_rP_ zhvipz`Y&!UlYfa%Nl2K?s}ITx48q(YumI3+Q*{m6=6a*`$&X9!L%049IgcWk^ZrR! zde2N=aiCNi8NeXW-&3bRxr%d^F(LLcyGTZZtAN!*!-v|9)!etxN`EoEZALKn^>GE6 zp5Id~NB4l4k)P%=+kA+m26lz)rZEa6frs4N*5Srkf|gUSh1PRui6S5l9@+6fMZHDjN$b{<{c)3gaQo!os2O z!pEa^cf1NLJ59T{GVrN|E^dt0J^KX@)BrII1wYVqC@U-b9b=|fJzDh!z5E-MJSdB{d}K+*iJYlk5$pl(sa!d54r}-AHk|Y z%Ev~d3DS}-m6xURDrKHsXFHXjXDNV@q7b3ZG4*Dx%;*51lpT(ZA@g!$EX z)Pn}|&G!KAn`1tB!Eyxk z2H1p~BYE8#x2RmU zmJxm5Z$-UPIrA$DxEU%5il)MnPR{lX^?ppFY7%uhZ{%iNa}@M_3$8mm_LMe9XsE06 z_4GR|4uG|PTAvtVu%g`JZuQ&qSE73S!5RaA-*-O#Bp5xE%@Z=A!*ka3VuODwaQfg3baahCq`6oLxMSTs!8E1R+73mG$;`8(?_lXBW69skNgB2|>yD znqaJ{w+BQ;k!!n9H+C;~-EAPDwpunaJ6@M<$t!A8MviAZE)BSAfCP)?)9s3=1eGUt z_C#HTZYmFfh3q)|={d+w2t+zk)%2m=d-w=&dLd^SwiseQJaQ{1tUQ69WUBHcFw$$~ zT3P4d<$YxWA23FLfV53-wTDU)fz6^wui9QegfS^%^b%^mp{mdTVa>w6pCz zqsPkjx}eS0J4MrndU~25->|5M`Z`GPQM3Sesf1&9>{ada5ols9zI07$5je3rJQo%FD=y6k~`Y4 zImkk_LcSyWtXFwnYVy{JxUW>#3J39d73nKG0zVep|e@+>JK8*#}{Pbeh)@)6M< zl99>E3e0n25yzs^;nvSPqo3PeJv;f5l$D;o$YptGbQzJXl-re?kNI3DCa(gU+4>8H z&8#E56MHY=D-`RR-tjN*`A2|g0$2dU={TYFj}jDp21>2^(tE>An#%*|+`TguQ{E6< z{_qS@ojGWsj>@taD6H6C4Pt+WcY)aQwhexa=;iF3)QRbFj+`%QcLg<$pQ4`NC=S!K zTx7@ep?j;onB$I(_Vg|>i{?L7`nc{)aP0~IiqV)AXi^qCGi0XUWrGdY1Zr~vlpHp` z@*$GFw=rj1F?6oFB&hctliI=j5agcPig@F%Fq!OAte;Svb)1eBBmfVbf#zeVwc{!F zeF^+WPO4o5T*e!w6S{v=BxAv!Wu~ zMQEz}me)voI)F;t(=E}bBU$?}m*zQFJ2+|ou(tSL>O5q!Y5gf7lXU!N1*^%}ZJ7%? zekj|?zD_U;;kNkt=2ul;L!{? zJJ?$cm$;l1jvF8kdvv2gPb@rU;pyvUYPF_B_{YXWAoIV`U=!tS;>rUKh@b6&mqxjT1* zF|)<8QayJ0jI@^BufS*#@n%#q_YElz3btisUHWoVUQoa-;@x^TaIUM=%GtTZ-`}4g z_CJeazHom14}3WAiw>j`cAqGvZ&nvSOW8Lu7y>?6?M+Q!efoJ|pi-3Z`y;odA0eatPyf@_SGK{OPZ|3J>NhGGyQ$!41tMo$hx8eDY}0|{9=t5IxRQc2 z&{jCxEw`v(Mv>zc!pa|q$vX#T>E=NC1-tj$!rXQ>+kc@p$ABV{^GTSrA@HmONoYBa zgJ}Po{JbVfk?-EYXXa`L0%EvNeEcIC(Z+(sGg*AQme#rN-~R}X1%OZNxm?>csCkOA zeXG#y|oJ+o47z;onuy(8W9oq{7y&A9kJGe z-u~Xn>FMEe2f3j2tF*MHf5>&Z|Ap0>CSF4$&4rzPnH{~C9k=G@$MubkRh=W=AfhAj zD7hyftQNk+XWRKT0i^RGInmoBZ5{k@LE+gj;OQIAX&TSVl86Gslh&FVkf-oi4uM>%FV=}8@>h5b z%@J5&{I&;E^4+_4!JJBuV%p zZ))+gm6SeC&g)>4~sJeM)uN5DwxAa$l0;0Ly8J^XZ zxlBzM9;aARaue{EL=c7&=pDUN!zE<{XX0ql%OLu?X+N=&WKs5a2pY0QvBst!>!Q}z zQxJ=Ln>4z*3@Ij-nVH|Tm@M4M?R^{@yx+qKbSs+vUP8qt*faa#n130e=nIxifOcuB zMUQ5|XXl8~H38Ua*A`#s@w{Wh@W?TGOb_I-9y4_tqsKhp&02Sbpy!_?nR^LTKCqpi z#-Hhi=m~3*15N$x8;QtY^G8zlKjk<>_pQ2oXFb2OhyJYV>N-8(S`hBf_s)o*2*8X1 zZQ7sIo-5W!-FfX^#$XPF{nS7T7?6`J?@*7Q{rRK66~m6qpj=CBH$$+GSs8z)scvis zV~2?$w*?G1GGb@6V`yprAHUh1KShcq{Uzf)JmZ#P*&qmf0_GW zuF^n`TOGKFo!(yPYO)-x&>xDeF;mVoCjW8^J4TT)0wlBbe>j1BcPs+KeSm^qj2_WX zK*X8cp=i0t3qsE3;J+{K0)-e+5sUcA=^6 zwV8&E*PfeOIfy{pJ5VL9Q)p;&R~-`#Lo#3gU$NqKZpz=kf8C~~tc8l-@65h5QObkz zJ)=8VW{Kd|Kh37?x@Vp)dies6`Ax$K@0TQuAR2*k_{ZU8VET1u4nO`-7$M}e=IKku zV=+kE+G2S)OU9Ozg9mF=d2({%r@kw!KgIvi?4qPZDD~_ z4?ga0R#`s!b*5i)e^5pYC0-Dw&ofJs)#7(xEN^WN$I|i+L|IR!0)G-%{FGQ&ido9d zkX}fen?o=K!qDO(T+Fyft!gw+Mf(V<0GS*rLJSt9Lj;T3&Gq#ZJWu2w2g;uD{9_fv zfS3t%!LBJm%iY_Ek*5!~b{dsysp53f+bDmFzZ6NHye=Wkqn)%DjW|TQZd<-6u<*5T zZQ8*BGYI}d^P#Q9S{DWgc;wnx`S!+Fv#;E}#P6C6^et+9ioNbd53T+}%i+9LQR$}F z`ogBZAnm!hS5GJG>acRSt$ywtC9vXKJ2QafPYx9OXoNslNv8`@lC;dddz$-`bIbTk zS#wj|u1%`>>Tci21U81!{7xG$UPrsx)AOlgKQO$US^CJ)kphzL& zeL@(;k)18#4Q!V_RSi^o`m7EP4g!665BtfKx%pERVixm6WUd|_9exQE%FfRx5djhE zji_&NMcend;wKU#zb0uKQ6yu5SR*LR`N+r|jM3T}8eq{i9d{Regn0x72WxA^vD*qA zvI+%FacRIgwQ%`i$J^Ic;K0nJoJ=sXjM&`URA$jIzA)RCpB9}JNN}0capYNYa$KF9 z?ebSO?6y)P{AK4&FbGdcsSBVMUegLTWckaW&cx8r^wFb7mX^BAL7nUA8t|?OD!+XB zU<{815{5LWONe-D50++WmeaBbmcPufxb+mhh8z{~=E$DDu(L~X;R1LhlDQmdK1HEB z&AMv?E^0eR^x?G?fe}nvmuqLaS?HIMXL4l&#|1u|QT?AvdjJ0#Oq?Em1Tz6v*;C}) zj*ok}v^$%d+dk2NgIugp|H9$|m((qgQLd@FG=j?xPM$Y=H3x-D=5r$Y3$sbkQ1&$@6DI>a(EKp9URiSvWuDizPY)| z&dJGKE>n+cZSevRh{lw5YJ92^(^+{h3~^~Cujs}78MJ8FG~Mfa(2%bX7_QLc{n}Mp z$Sel-#sf)4AvCwH{>~SVPf8k=k_an({2)l&pEIduqY(V`ii>VK*>BZ0Ykc#J0e(3c zvO2ZDXG@epMZS6!6tv!tWV%x8r<9@ygnX?EMgf{tSx_mtSE#2brgomH!NM)vGIu*? zJc2T5R8}D)laZerU2+$`GXCPqT`nF1A*Bc}EG(F)S+JmkzZphW=1iVt4*XZd9pu#*VAfJ^bU)YLXRIl!8zKAiw9g#_Q~v_tI*t~&-cOLx z;y8A7xFzBpYNX|DL1$%i{QXJ^hbsqd6|R#%~it*s*3cU^AQP6OpaPx+B05xMrl2z&&1?}*E2^M z!h5xkNaNI%E*5YdHbqO)cx1;+%oxAO=I3xPMpp4)_$E;Py@N4weh!I@WY%WpuB=g< zM)<6YQzmjE1@*b#Aem}I$Es?M7S~yE$Vs;1V5X|$?yfBI>~NOZ1;zG>&q`ttI2fT7 z16;FW?}$Aw#b5U%aJQ^Q#82+2a6tuKoApMDsHU{yT0SP81+woOm`k2`RBLBR7V(I} z!ZmbOPBPC^f_Yic(c^0_OEIVCevM(+ zTFMK-Q(FYZI(gd0a)J^a9AO!zQreDZRd1zQ9aa<_e%H<~-^OVeOSr7B@`ayVk= z8Li;_q*WWw$I1q2=SA(Mv1Mw_(ohZg$6DTt6~4XutPN7jHcibJ*~r3PJZBbULfqbX z&Visu-)od~X+%K3oA9XnP_i;oc`JYSsBl3@ zCrOzT>CW|Wswq10E*ia{$3wlIU3w#PP{D3JTeYu0(QK!@$vmGRHZHWcXpkZ_NOA60 zc9e@jSj(k3IK z`@1+OQT*e|>Xy-1r0x95mgO6k&j_ZELs=aCy_8KQx+sbxB@&t7v+*FHZ@779 zde}zNDtE=}H=Z0~5mgOx<|Vb=xj{`_8sg$pen#+UZXv8T?ih4g^vq!F$UuJ&U#bO+ z2Pvs(S4k_&K$wy5>+aksp}j18CB61sB%`t+mWX%W-CZ#ao76`$YmBu3@;daAt32%(nspqcwUo^`Z zJnl6$wg1+hyXHv{W}Q!)*~Ij@p5;r_8HGt4{i?cd30eaoOYYg*+Q>8Li3fe1yvm#e zBBE}FI*y&^NbvM5r)2wrR@HpZAf@pv6Q?W#n@hspL>&B%GG6K3WA?bt9*j++hn82@ zAXm(1-}ryJJNI{{`#+A~_uZ{@ySr0vbh<4SO358jS`jLzPzi;h19BKiHqBvmQ@-WU zK^QYcQ;4OpId0t|gw3It!;tf7&TI~w?`!Vw5BJ~jb?v&iu3gOM{r()@@7MGBe7yWL zqa$Oa6j@xGrIPL0-z0R3B$!G=Eq-yaO&P3^v0)d{ZX~{S@erd1CSGjSeSd%&ldPn6 zbJWx|>qVUIg{zK17pznSwzw{C_#%2V5oOVoLzuxb9$e>vAx*L;j#r)^liAF~?Ns zshpnKIh}#Ym9WTCWv%lkNiH({o05twAvv+D#v$i$;j`2_EBTjQu75Kr8&UVhuQ~h2 z#0I0wY29vXL^aC@zVs8fk_Q)*GIt+2O4o>!FS(rThBLZGOncw*SYGl7zaiA3%hj+u zr{_{zLxu6B`j+A5L0LN-LHSg5$+Og59mOx;^n}T2oU~mTu!$bbCy0s{3Co2$h&B6! z>(t=HJ061K*O2)l*3+Wrn&i+}PZvX(y)_-tA0)UrF6&?1&-xtNo!N8U<<|aKV{$pi z*jOoz<9vtLDs6lacwrtS^I2ataxF_yZ}l`k(?r=`px+ClW>4k>b@_ zk9%3szby*CleP1=er`F})Y~olJkDx_&dkpICGyGP6aEK>`pidOnICEm=j_;N0)PEC z6f3$YVQ(1Wy^3E&pe#M)3i*IF;u*2$}4mK2L#!9=YZNL1rHk>_lQ-6(+JCe^Q%DTG{aw9p55tO zV7Z!owDzrpu>Sp6=Dp3YDoSxmo<>GgW`r7MDTFj-kD>@s^DYXtYKY5iK_|u<2F8#1 z?Wed;SE(3fleS$nS<^u{baTK*tunwJ2T#%`UOFW|1z4;RItLWW->jf7mClA|!G_@q z8h!CXW5u6z&UIUbVx?+c8%lh3i)!Jf&q`+M3Efm)L2mE>pA$}97M?cAG~J>t(P#dvw~y0b2hwaJuw-IGJE z8Lb?x$SI2buhwkNQ;+uiV;=q|bOWM`WAv>2VbNr>BBgZa-p|o`n3a9uVu5G3;)>5= z_Z!0nM5HPuP;Vuh61qGeJiwKlXvNka>n;tmjeI$oE-qV?Vt!4bl|K}i0b6?zq%D?3 zG;F)(XERrmPDw5f$4sD*bRTXphwN!fQH`hvschRW?OuZwvbiTS>uUNVR#OGC#);yD zRbD8SE(hE7)T>Z5+UxF@u&C`rgQFVPITrxAX2A-}Y;JC9if5(VK%@WAcI|``xPc@% zV=G%|yyYC-ZNDFi9Ubg~Rd1=EO6Eo9`>qQj;j)?S!<19<@$tlte3QP$z2$ZFb*8|Y z{Z1<32T`Z(fuYAXijwqdIBgX$UZLKe?=f#d-HrD5QD=8{mQsiMAOoM`R|UP1MAYXu zo_waG9c;hV=R`&;!-3y-o4xBMsg4q%`wOSh_GQ@HeAYcpw_SRR#g1-;!9(5&dA=rQ zd-;=|jiqf9IYjM{vU9I4oKMs)dzqmCT&n=VTL-d7{S$IaOG~0NWddcHa$L@DA8tur z29Te@JtdP*Y)*#EK$8H3;V1l^ndz7}Uk2LjkG^KyrHU37bgllw52>|;<67q zX~Ti^Fb79i(z%OE#09iHc7mfXdClGZ2rl4CqXx7+@@xt5(-<6rfWPDX$*SKrQ|WXW zgI^_ue?nGS;Dj&3@QMqf=HI?&{{*`;bD}SADJsElke#H-WOXROobQlcy4LsTLHuDS z2BvRG(0GG3+5RmP)e+6>h;6!4ky{NN#$9v+dK|MqS6}-CamYi#2fbC4Cn7tkx4M#a z*BL%hlhzhk)rUQNc=pW#TE#NO`jR-)|0qfUddIcL=psCC-h55{`tf5bSU^rFD9}YK z*|)g0Ow{zeaA?H?wq5VWE{Y-FfwX33)ev+BBHAYk zU^lxqkMm(07|6MraKx_1Cj^cW*ksNfgI>}=cI$cKQN^W?UDu!!h46X0W!%P z^;U~>uC`~}x6)6Y1Ornsb|NWZaW_<}uG@Ys^Be*xZ@BZW?WNjBkJhh+UIrob!ad=$ zdFP7Bh<%=KtqtUHUG>dP)iG03-#@xe(k5H_fZlk`P*;ss2e7=CIyxX49!`4iKF*Qd z&Kc)^o@DUqr%k ztn+g2)`iGTbk$0Qyg1|_c&Z$xtgH-lnj0mw0SHAgKLyWM@;>d#5BfBZ?>xcBvPmwh;@f1fHK&ns& zSOa6)zVbYysfplR8FTdx#|!kS zbhE`>qVM5pq{mSa_W51|Umy3GF@sub8ugL9o?${_9BXZq0uqvY6%20N=%YJ0%^o_)&ZHkp^n`R+GRi!C{N zP}?(e@;6aS(ZPny+jj5kGGW!$*0OC6TC3Lo5dkpz_A;N zHo>w8C^d~@=*TJG(R?Cv73?k0Wn&S?T|f3E_8>m+!w)~K(=$1ve;I{JG8^SoA;Q-4 z$7Hl!9lRWpSZS~l0{DdR_ITBfZ=S#Ba1On#fx3PBrf%AeA@~6vP(!_k4>{2G6ylxg z$CON;jCFsH(Gw;qvAvr|PbpT|=d$!jx+D_G@5I82eeToRq*GWCDMO#FH!gk%dvu0j ziCVnY>>(K`qlpd#_K0Kd1+TU(&l3|{R!laE@8S35$HW7dZXa(n^X_7w%9nu|JFP7B z1U;kWG>M!VnJS7s)3<`d*3{6(mZp#a_MDy*)%s085xZu)J zZs9$@{=&RWcRq?lax*7+xw`HND*_(arOco(mb0#sOE^9<=e~2$pJnl*N#>= zXTFU?fnH>7!)z5+;?h|tQi4|-b9RCwd*HdTs&&?IA5Uw%`8w-#3x+Z<-Y=*f(bSLH zxlyqT`iXV*l6~dXZSo36TB&Y;(LY)VgVgrj=;?=Y<_fOz#lyt59+8ki&`w9SdV_np z zQ6F!zX{^9NEH0^6$gnqg7@ImJsj+e9bSC@bGbO1(*+673z6xedyi|;3<_5#UiDyUm zp0=RI7d8F(3b8QOJiGm{iP)W%st1N7pjlPmcdcO&r>@H~d@dkSID`Ez6!8-KBk{3j z7<>m9nD3*1OMC;>$98Iac*`GhNnGO%{E!+ExQ1*^?Nk*i7>Ix6|LV)}_3FAUQgznG T5?6D@2WE2C;!LiA^I!i3jR{9W literal 65657 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

J-t(C?D*vNbSd$Mpldw%6G^3`$t6)P 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 O*JNyHeySLKDeAvN*8HCU From 67c60ff225f5aed5a07414ee0d318d803a6c9716 Mon Sep 17 00:00:00 2001 From: Hawkynt Date: Wed, 28 Oct 2026 06:49:48 +0100 Subject: [PATCH 34/34] # reject ambiguous firmware payloads before writing an image Adding a second named payload to Intel HEX could silently discard a file because the shared image builder kept only the first payload in filesystem enumeration order. Reject multiple non-metadata payloads, including an empty first payload, before any output is written. Tests verify creation refusal and byte-preserving add refusal. Verified all three regressions fail before the fix and pass after it, 93 focused tests, 34280 required tests with 1582 skips and no failures, and three package documentation checks. --- .../FirmwareHex/FirmwareHexPayloadTests.cs | 37 +++++++++++++++++++ .../FirmwareHexWriter.cs | 5 ++- 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 Compression.Tests/FirmwareHex/FirmwareHexPayloadTests.cs diff --git a/Compression.Tests/FirmwareHex/FirmwareHexPayloadTests.cs b/Compression.Tests/FirmwareHex/FirmwareHexPayloadTests.cs new file mode 100644 index 000000000..bbbc898af --- /dev/null +++ b/Compression.Tests/FirmwareHex/FirmwareHexPayloadTests.cs @@ -0,0 +1,37 @@ +using Compression.Registry; +using FileFormat.FirmwareHex; + +namespace Compression.Tests.FirmwareHex; + +[TestFixture] +public sealed class FirmwareHexPayloadTests { + [TestCase(false)] + [TestCase(true)] + public void Create_RejectsMultiplePayloadsWithoutWritingOutput(bool emptyFirst) { + var descriptor = new IntelHexFormatDescriptor(); + using var output = new MemoryStream(); + var inputs = new[] { + ArchiveInputInfo.InMemory("first.bin", emptyFirst ? [] : [1, 2, 3]), + ArchiveInputInfo.InMemory("second.bin", [9, 8, 7]), + }; + + Assert.Throws(() => + ((IArchiveCreatable)descriptor).Create(output, inputs, new FormatCreateOptions())); + Assert.That(output.Length, Is.Zero); + } + + [Test] + public void Add_RejectsAnotherPayloadWithoutChangingTheArchive() { + var descriptor = new IntelHexFormatDescriptor(); + using var archive = new MemoryStream(); + ((IArchiveCreatable)descriptor).Create(archive, + [ArchiveInputInfo.InMemory("firmware.bin", [1, 2, 3])], new FormatCreateOptions()); + var original = archive.ToArray(); + archive.Position = 0; + + Assert.Throws(() => ((IArchiveModifiable)descriptor).Add(archive, + [ArchiveInputInfo.InMemory("ADD.BIN", [9, 8, 7])])); + Assert.That(archive.ToArray(), Is.EqualTo(original)); + } + +} diff --git a/Hawkynt.FileFormats.FileSystems/FileFormats/FileFormat.FirmwareHex/FirmwareHexWriter.cs b/Hawkynt.FileFormats.FileSystems/FileFormats/FileFormat.FirmwareHex/FirmwareHexWriter.cs index 6c7d4b452..1c9e17266 100644 --- a/Hawkynt.FileFormats.FileSystems/FileFormats/FileFormat.FirmwareHex/FirmwareHexWriter.cs +++ b/Hawkynt.FileFormats.FileSystems/FileFormats/FileFormat.FirmwareHex/FirmwareHexWriter.cs @@ -33,6 +33,7 @@ public static class FirmwareHexWriter { /// one the reader rendered. An input list with no payload describes an image /// with no data, which both formats can write. ///

+ /// More than one non-metadata payload is supplied. public static FirmwareImage ImageFrom(IReadOnlyList inputs, string sourceFormat) { ArgumentNullException.ThrowIfNull(inputs); @@ -53,7 +54,9 @@ public static FirmwareImage ImageFrom(IReadOnlyList inputs, st } // Any other single input is the payload, whatever it is called: a caller // converting a .bin into a .hex should not have to rename it first. - payload ??= input.ReadContent(); + if (payload is not null) + throw new InvalidDataException($"{sourceFormat} represents one logical firmware payload; multiple payload inputs are ambiguous."); + payload = input.ReadContent(); } List<(uint Address, byte[] Data)> segments;
/// Opens the raw virtual disk content as a seekable read/write stream. /// Uses format-specific stream wrappers (VhdStream, VhdxStream, etc.) that diff --git a/Compression.NativeUI/ViewModels/MainViewModel.cs b/Compression.NativeUI/ViewModels/MainViewModel.cs index 1ded0cc36..af0a2ada9 100644 --- a/Compression.NativeUI/ViewModels/MainViewModel.cs +++ b/Compression.NativeUI/ViewModels/MainViewModel.cs @@ -2074,8 +2074,10 @@ internal void HandleFileDrop(string[] files) { // Per-file + cumulative-size check via optional constraints. if (ops is Compression.Registry.IArchiveWriteConstraints constraints) { long cumulative = 0; + // The entry lands in the folder being browsed, as AddFilesToArchiveImpl names it. + var folderPrefix = _currentFolder.Length > 0 ? _currentFolder.TrimEnd('/') + "/" : ""; foreach (var path in files) { - var entryName = System.IO.Path.GetFileName(path); + var entryName = folderPrefix + System.IO.Path.GetFileName(path); var size = new FileInfo(path).Exists ? new FileInfo(path).Length : 0; cumulative += size; var input = new Compression.Registry.ArchiveInputInfo(path, entryName, IsDirectory: false); diff --git a/Compression.NativeUI/Views/PartitionsWindow.cs b/Compression.NativeUI/Views/PartitionsWindow.cs index 2a1f78f7c..5005e789f 100644 --- a/Compression.NativeUI/Views/PartitionsWindow.cs +++ b/Compression.NativeUI/Views/PartitionsWindow.cs @@ -268,8 +268,8 @@ private void RefreshGrid() { var partitions = editor.ListPartitions(); var rows = partitions.Select(p => { - var startLba = p.StartOffset / PartitionEditor.SectorSize; - var endLba = (p.StartOffset + p.Size) / PartitionEditor.SectorSize - 1; + var startLba = p.StartOffset / editor.LogicalSectorSize; + var endLba = (p.StartOffset + p.Size) / editor.LogicalSectorSize - 1; return new PartitionRow { Index = p.Index, IndexDisplay = p.Index.ToString(), diff --git a/Compression.Tests/DiskImage/PartitionSchemeDetectionTests.cs b/Compression.Tests/DiskImage/PartitionSchemeDetectionTests.cs new file mode 100644 index 000000000..f0c73bd31 --- /dev/null +++ b/Compression.Tests/DiskImage/PartitionSchemeDetectionTests.cs @@ -0,0 +1,317 @@ +#pragma warning disable CS1591 +using System.Buffers.Binary; +using Compression.Core.DiskImage; + +namespace Compression.Tests.DiskImage; + +/// +/// Partition schemes beyond the classic 512-byte MBR/GPT: 4Kn GPT, GPT recovery from the backup +/// header, protective/hybrid MBRs, EBR chains, and the checksummed labels of SGI, Sun, BSD and +/// Amiga. The labels here are laid out field by field from the scheme's specification; the +/// external fixture checks the same readers against labels parted and sfdisk write. +/// +[TestFixture] +public class PartitionSchemeDetectionTests { + + [SetUp] + public void EnsureRegistered() => Compression.Lib.FormatRegistration.EnsureInitialized(); + + /// A zeroed, writable stream whose buffer the test may patch through GetBuffer. + private static MemoryStream Buffered(long length) { + var bytes = new byte[length]; + return new MemoryStream(bytes, 0, bytes.Length, writable: true, publiclyVisible: true); + } + + private static MemoryStream GptDisk(int sectorSize, long sectors, params (long Start, long Length)[] parts) { + var ms = Buffered(sectors * sectorSize); + var editor = PartitionEditor.CreateEmpty(ms, PartitionScheme.Gpt, sectorSize); + foreach (var (start, length) in parts) + editor.AddPartition(start * sectorSize, length * sectorSize, PartitionType.Linux, "p"); + return ms; + } + + // ── GPT ──────────────────────────────────────────────────────────── + + [TestCase(512)] + [TestCase(4096)] + [Category("HappyPath")] + public void Given_GptAtBlockSize_When_Detected_Then_SectorSizeAndOffsetsFollowIt(int sectorSize) { + using var disk = GptDisk(sectorSize, 4096, (256, 512)); + var head = disk.ToArray().AsSpan(0, GptParser.ProbeBytes); + + Assert.That(GptParser.DetectSectorSize(head), Is.EqualTo(sectorSize)); + var detection = PartitionTableDetector.Detect(disk); + Assert.That(detection.Scheme, Is.EqualTo("GPT")); + Assert.That(detection.SectorSize, Is.EqualTo(sectorSize)); + Assert.That(detection.Partitions.Single().StartOffset, Is.EqualTo(256L * sectorSize)); + Assert.That(detection.Partitions.Single().Size, Is.EqualTo(512L * sectorSize)); + } + + [Test, Category("HappyPath")] + public void Given_4KnGpt_When_Written_Then_EntryArrayTakesFourBlocksAndUsableRangeFollows() { + using var disk = GptDisk(4096, 1024); + var info = GptParser.ReadHeader(disk)!; + Assert.That(info.SectorSize, Is.EqualTo(4096)); + Assert.That(info.FirstUsableLba, Is.EqualTo(6UL), "LBA0 MBR + LBA1 header + 4 blocks of 128x128-byte entries"); + Assert.That(info.LastUsableLba, Is.EqualTo(1024UL - 6), "backup array (4) + backup header (1) at the end"); + Assert.That(info.AlternateLba, Is.EqualTo(1023UL)); + } + + [Test, Category("Boundary")] + public void Given_GptPartitionEndingAtLastUsableLba_When_Added_Then_AcceptedButOneMoreSectorRefused() { + using var disk = GptDisk(512, 2048); + var editor = new PartitionEditor(disk); + var lastUsable = 2048 - 34; // 32 backup-array sectors + backup header, then the LBA before + Assert.That(() => editor.AddPartition(1024L * 512, (lastUsable - 1024 + 2) * 512L, PartitionType.Linux, null), + Throws.InvalidOperationException, "would run into the backup entry array"); + editor.AddPartition(1024L * 512, (lastUsable - 1024 + 1) * 512L, PartitionType.Linux, null); + Assert.That(editor.Verify().IsValid, Is.True, string.Join("; ", editor.Verify().Issues)); + Assert.That(() => editor.AddPartition(33L * 512, 8 * 512, PartitionType.Linux, null), + Throws.InvalidOperationException, "LBA 33 is the primary entry array"); + } + + [Test, Category("Boundary")] + public void Given_GptWith128Entries_When_129thAdded_Then_Refused() { + using var disk = GptDisk(512, 34 + 128 + 34 + 8); + var editor = new PartitionEditor(disk); + for (var i = 0; i < 128; ++i) + editor.AddPartition((34L + i) * 512, 512, PartitionType.Linux, null); + Assert.That(PartitionTableDetector.Detect(disk).Partitions, Has.Count.EqualTo(128)); + Assert.That(() => editor.AddPartition((34L + 128) * 512, 512, PartitionType.Linux, null), Throws.InvalidOperationException); + } + + [Test, Category("ErrorHandling")] + public void Given_PrimaryHeaderCrcBroken_When_Parsed_Then_TheBackupHeaderIsUsed() { + using var disk = GptDisk(512, 4096, (2048, 1024)); + disk.Position = 512 + 40; // FirstUsableLBA inside the primary header: breaks its CRC + disk.WriteByte(0x77); + + var detection = PartitionTableDetector.Detect(disk); + Assert.That(detection.Scheme, Is.EqualTo("GPT")); + Assert.That(detection.Partitions.Single().StartOffset, Is.EqualTo(2048L * 512)); + Assert.That(GptParser.ReadHeader(disk)!.FromBackup, Is.True); + } + + [Test, Category("ErrorHandling")] + public void Given_PrimaryEntryArrayCorrupted_When_Parsed_Then_TheBackupArrayIsUsed() { + using var disk = GptDisk(512, 4096, (2048, 1024)); + disk.Position = 2 * 512 + 32; // first entry's FirstLBA + disk.Write(new byte[8]); + + var part = PartitionTableDetector.Detect(disk).Partitions.Single(); + Assert.That(part.StartOffset, Is.EqualTo(2048L * 512)); + } + + [Test, Category("ErrorHandling")] + public void Given_BothGptHeadersBroken_When_Detected_Then_NoTableAndNoProtectiveEntryListed() { + using var disk = GptDisk(512, 4096, (2048, 1024)); + disk.Position = 512 + 40; disk.WriteByte(0x77); + disk.Position = 4095L * 512 + 40; disk.WriteByte(0x77); + disk.Position = 512; disk.Write(new byte[8]); // and no signature for the lenient reading either + + var detection = PartitionTableDetector.Detect(disk); + Assert.That(detection.Partitions, Is.Empty, "a protective 0xEE entry describes the GPT, not data"); + } + + [Test, Category("EdgeCase")] + public void Given_HybridMbr_When_Detected_Then_GptWins() { + using var disk = GptDisk(512, 4096, (2048, 1024)); + // Hybrid: a real MBR entry mirroring the GPT partition beside the 0xEE entry. + var entry = disk.GetBuffer().AsSpan(0x1BE + 16, 16); + entry[4] = 0x83; + BinaryPrimitives.WriteUInt32LittleEndian(entry[8..], 2048); + BinaryPrimitives.WriteUInt32LittleEndian(entry[12..], 1024); + + var detection = PartitionTableDetector.Detect(disk); + Assert.That(detection.Scheme, Is.EqualTo("GPT")); + Assert.That(detection.Partitions.Single().Source, Is.EqualTo("GPT")); + } + + [Test, Category("EdgeCase")] + public void Given_GptEntryWithInvertedRange_When_Parsed_Then_Skipped() { + using var disk = GptDisk(512, 4096, (2048, 1024), (3072, 16)); + // Invert the second entry and recompute both CRCs through a re-read of the editor's own layout. + var bytes = disk.GetBuffer(); + var slot = bytes.AsSpan(2 * 512 + 128, 128); + BinaryPrimitives.WriteUInt64LittleEndian(slot[40..], 100); // LastLBA < FirstLBA + FixGptCrcs(bytes, 512, 4096); + + Assert.That(PartitionTableDetector.Detect(disk).Partitions, Has.Count.EqualTo(1)); + } + + private static void FixGptCrcs(byte[] disk, int sectorSize, long sectors) { + foreach (var headerLba in new[] { 1L, sectors - 1 }) { + var header = disk.AsSpan((int)(headerLba * sectorSize), 92); + var entriesLba = BinaryPrimitives.ReadUInt64LittleEndian(header[72..]); + var count = BinaryPrimitives.ReadUInt32LittleEndian(header[80..]); + var size = BinaryPrimitives.ReadUInt32LittleEndian(header[84..]); + if (headerLba != 1) // keep the backup array identical to the primary + disk.AsSpan(2 * sectorSize, (int)(count * size)).CopyTo(disk.AsSpan((int)((long)entriesLba * sectorSize))); + var crc = Compression.Core.Checksums.Crc32.Compute(disk.AsSpan((int)((long)entriesLba * sectorSize), (int)(count * size))); + BinaryPrimitives.WriteUInt32LittleEndian(header[88..], crc); + header.Slice(16, 4).Clear(); + BinaryPrimitives.WriteUInt32LittleEndian(header[16..], Compression.Core.Checksums.Crc32.Compute(header)); + } + } + + // ── MBR / EBR ────────────────────────────────────────────────────── + + [Test, Category("HappyPath")] + public void Given_ExtendedPartitionWithTwoLogicals_When_Detected_Then_BothLogicalsListedAtTheirAbsoluteOffsets() { + using var disk = Buffered(8192 * 512); + var editor = PartitionEditor.CreateEmpty(disk, PartitionScheme.Mbr); + editor.AddPartition(2048L * 512, 6000L * 512, PartitionType.ExtendedLba, null); + editor.AddLogicalPartition(2049L * 512, 1000L * 512, PartitionType.Linux, null); + editor.AddLogicalPartition(4000L * 512, 1000L * 512, PartitionType.Fat32Lba, null); + + var parts = PartitionTableDetector.Detect(disk).Partitions; + Assert.That(parts.Select(p => (p.Source, p.StartOffset)), + Is.EqualTo(new[] { ("EBR", 2049L * 512), ("EBR", 4000L * 512) })); + } + + [Test, Category("ErrorHandling")] + public void Given_EbrChainThatLoops_When_Detected_Then_TheWalkStops() { + using var disk = Buffered(8192 * 512); + var editor = PartitionEditor.CreateEmpty(disk, PartitionScheme.Mbr); + editor.AddPartition(2048L * 512, 6000L * 512, PartitionType.ExtendedLba, null); + editor.AddLogicalPartition(2049L * 512, 1000L * 512, PartitionType.Linux, null); + // Point the first EBR's link back at itself. + var link = disk.GetBuffer().AsSpan(2048 * 512 + 0x1BE + 16, 16); + link[4] = 0x05; + BinaryPrimitives.WriteUInt32LittleEndian(link[8..], 0); + BinaryPrimitives.WriteUInt32LittleEndian(link[12..], 1); + + Assert.That(PartitionTableDetector.Detect(disk).Partitions, Has.Count.EqualTo(1)); + } + + // ── SGI / Sun / BSD / Amiga ──────────────────────────────────────── + + [Test, Category("HappyPath")] + public void Given_SgiVolumeHeader_When_Detected_Then_PartitionsListedWithoutTheWholeVolume() { + var disk = new byte[8 * 1024 * 1024]; + var s = disk.AsSpan(0, 512); + BinaryPrimitives.WriteUInt32BigEndian(s, 0x0BE5A941); + void Part(int slot, uint blocks, uint first, uint type) { + var r = disk.AsSpan(312 + slot * 12); + BinaryPrimitives.WriteUInt32BigEndian(r, blocks); + BinaryPrimitives.WriteUInt32BigEndian(r[4..], first); + BinaryPrimitives.WriteUInt32BigEndian(r[8..], type); + } + Part(0, 4096, 2048, 0x83); + Part(8, 2048, 0, 0); // volume header + Part(10, 16384, 0, 6); // whole volume + uint sum = 0; + for (var i = 0; i < 512; i += 4) sum += BinaryPrimitives.ReadUInt32BigEndian(s[i..]); + BinaryPrimitives.WriteUInt32BigEndian(s[504..], unchecked(0u - sum)); + + var detection = PartitionTableDetector.Detect(disk); + Assert.That(detection.Scheme, Is.EqualTo("SGI")); + Assert.That(detection.Partitions.Select(p => (p.StartOffset, p.Size)), + Is.EqualTo(new[] { (2048L * 512, 4096L * 512), (0L, 2048L * 512) })); + using var ms = new MemoryStream(disk); + Assert.That(PartitionTableDetector.HasSelfValidatingLabel(ms), Is.True); + + disk[400] ^= 1; // checksum now wrong + Assert.That(PartitionTableDetector.Detect(disk).Scheme, Is.EqualTo("None")); + } + + [Test, Category("HappyPath")] + public void Given_SunLabel_When_Detected_Then_SlicesPlacedByCylinderGeometry() { + var disk = new byte[16 * 1024 * 1024]; + var s = disk.AsSpan(0, 512); + BinaryPrimitives.WriteUInt32BigEndian(s[128..], 1); // vtoc version + BinaryPrimitives.WriteUInt16BigEndian(s[140..], 8); // nparts + BinaryPrimitives.WriteUInt16BigEndian(s[142..], 0x83); // slice a: Linux native + BinaryPrimitives.WriteUInt16BigEndian(s[150..], 5); // slice c: whole disk + BinaryPrimitives.WriteUInt32BigEndian(s[188..], 0x600DDEEE); // vtoc sanity + BinaryPrimitives.WriteUInt16BigEndian(s[436..], 16); // ntrks + BinaryPrimitives.WriteUInt16BigEndian(s[438..], 63); // nsect + BinaryPrimitives.WriteUInt32BigEndian(s[444..], 2); // a: start cylinder 2 + BinaryPrimitives.WriteUInt32BigEndian(s[448..], 1008 * 5); + BinaryPrimitives.WriteUInt32BigEndian(s[464..], 1008 * 32); + BinaryPrimitives.WriteUInt16BigEndian(s[508..], 0xDABE); + ushort xor = 0; + for (var i = 0; i < 510; i += 2) xor ^= BinaryPrimitives.ReadUInt16BigEndian(s[i..]); + BinaryPrimitives.WriteUInt16BigEndian(s[510..], xor); + + var detection = PartitionTableDetector.Detect(disk); + Assert.That(detection.Scheme, Is.EqualTo("Sun")); + var part = detection.Partitions.Single(); + Assert.That(part.StartOffset, Is.EqualTo(2L * 16 * 63 * 512)); + Assert.That(part.Size, Is.EqualTo(1008L * 5 * 512)); + } + + [Test, Category("HappyPath")] + public void Given_StandaloneBsdDisklabel_When_Detected_Then_SlicesListedFromTheWholeDisk() { + var disk = new byte[8 * 1024 * 1024]; + var l = disk.AsSpan(512, 148 + 8 * 16); + BinaryPrimitives.WriteUInt32LittleEndian(l, 0x82564557); + BinaryPrimitives.WriteUInt32LittleEndian(l[40..], 512); + BinaryPrimitives.WriteUInt32LittleEndian(l[132..], 0x82564557); + BinaryPrimitives.WriteUInt16LittleEndian(l[138..], 8); + void Part(int slot, uint size, uint offset, byte fstype) { + var r = disk.AsSpan(512 + 148 + slot * 16); + BinaryPrimitives.WriteUInt32LittleEndian(r, size); + BinaryPrimitives.WriteUInt32LittleEndian(r[4..], offset); + r[12] = fstype; + } + Part(0, 4096, 64, 7); // a: 4.2BSD + Part(1, 2048, 4160, 1); // b: swap + Part(2, 16384, 0, 0); // c: whole disk + ushort xor = 0; + for (var i = 0; i < l.Length; i += 2) xor ^= BinaryPrimitives.ReadUInt16LittleEndian(l[i..]); + BinaryPrimitives.WriteUInt16LittleEndian(l[136..], xor); + + var detection = PartitionTableDetector.Detect(disk); + Assert.That(detection.Scheme, Is.EqualTo("BSD")); + Assert.That(detection.Partitions.Select(p => (p.Name, p.StartOffset)), + Is.EqualTo(new[] { ("a", 64L * 512), ("b", 4160L * 512) })); + + disk[512 + 132] ^= 1; + Assert.That(PartitionTableDetector.Detect(disk).Scheme, Is.EqualTo("None"), "without d_magic2 the label proves nothing"); + } + + [Test, Category("HappyPath")] + public void Given_AmigaRigidDiskBlock_When_Detected_Then_PartitionsPlacedByDosEnvecGeometry() { + var disk = new byte[16 * 1024 * 1024]; + void Checksum(Span block) { + var longs = BinaryPrimitives.ReadUInt32BigEndian(block[4..]); + block.Slice(8, 4).Clear(); + uint sum = 0; + for (var i = 0; i < longs; ++i) sum += BinaryPrimitives.ReadUInt32BigEndian(block[(i * 4)..]); + BinaryPrimitives.WriteUInt32BigEndian(block[8..], unchecked(0u - sum)); + } + var rdb = disk.AsSpan(1 * 512, 512); // RDSK in block 1 + BinaryPrimitives.WriteUInt32BigEndian(rdb, 0x5244534B); + BinaryPrimitives.WriteUInt32BigEndian(rdb[4..], 64); + BinaryPrimitives.WriteUInt32BigEndian(rdb[16..], 512); + BinaryPrimitives.WriteUInt32BigEndian(rdb[24..], 0xFFFFFFFF); + BinaryPrimitives.WriteUInt32BigEndian(rdb[28..], 3); // partition list → block 3 + BinaryPrimitives.WriteUInt32BigEndian(rdb[32..], 0xFFFFFFFF); + Checksum(rdb); + + var part = disk.AsSpan(3 * 512, 512); + BinaryPrimitives.WriteUInt32BigEndian(part, 0x50415254); + BinaryPrimitives.WriteUInt32BigEndian(part[4..], 64); + BinaryPrimitives.WriteUInt32BigEndian(part[16..], 0xFFFFFFFF); // no next + part[36] = 3; "DH0"u8.CopyTo(part[37..]); + void Env(int i, uint v) => BinaryPrimitives.WriteUInt32BigEndian(disk.AsSpan(3 * 512 + 128 + i * 4), v); + Env(0, 16); Env(1, 128); Env(3, 2); Env(5, 32); Env(9, 4); Env(10, 99); Env(16, 0x444F5301); + Checksum(part); + + var detection = PartitionTableDetector.Detect(disk); + Assert.That(detection.Scheme, Is.EqualTo("RDB")); + var p = detection.Partitions.Single(); + Assert.That(p.Name, Is.EqualTo("DH0")); + Assert.That(p.TypeName, Is.EqualTo("DOS1")); + Assert.That(p.StartOffset, Is.EqualTo(4L * 2 * 32 * 512)); + Assert.That(p.Size, Is.EqualTo(96L * 2 * 32 * 512)); + } + + [Test, Category("EdgeCase")] + public void Given_ZeroedDisk_When_Detected_Then_NoSchemeClaimsIt() { + Assert.That(PartitionTableDetector.Detect(new byte[1024 * 1024]).Scheme, Is.EqualTo("None")); + using var ms = new MemoryStream(new byte[1024 * 1024]); + Assert.That(PartitionTableDetector.HasSelfValidatingLabel(ms), Is.False); + } +} diff --git a/Compression.Tests/DiskImage/PartitionedDiskExternalTests.cs b/Compression.Tests/DiskImage/PartitionedDiskExternalTests.cs new file mode 100644 index 000000000..538c46dc2 --- /dev/null +++ b/Compression.Tests/DiskImage/PartitionedDiskExternalTests.cs @@ -0,0 +1,258 @@ +#pragma warning disable CS1591 +using System.Globalization; +using Compression.Core.DiskImage; +using Compression.Registry; +using FileFormat.PartitionedDisk; + +namespace Compression.Tests.DiskImage; + +/// +/// The partitioned-disk reader and writer against the reference Linux tools: tables written here +/// must pass sgdisk -v / sfdisk --verify and read back in parted; filesystems +/// edited in place inside a partition must pass fsck.vfat / e2fsck; and the +/// tables sfdisk, fdisk -b 4096 and parted (bsd, amiga, sun, dvh) write must +/// be read at the offsets those tools report. Skipped when WSL (or, off Windows, a tool) is missing. +/// +[TestFixture] +[Category("ExternalFsInterop")] +public class PartitionedDiskExternalTests { + + private static readonly PartitionedDiskFormatDescriptor Desc = new(); + private string _dir = null!; + + [SetUp] + public void SetUp() { + Compression.Lib.FormatRegistration.EnsureInitialized(); + this._dir = Path.Combine(Path.GetTempPath(), "cwb_pdisk_ext_" + Guid.NewGuid().ToString("N")[..10]); + Directory.CreateDirectory(this._dir); + } + + [TearDown] + public void TearDown() { + try { Directory.Delete(this._dir, recursive: true); } catch { /* best effort */ } + } + + private static void RequireTools(params string[] tools) { + if (!FsInteropToolbox.WslAvailable) Assert.Ignore("WSL is not available."); + foreach (var tool in tools) + if (!FsInteropToolbox.WslHasTool(tool)) + Assert.Ignore($"'{tool}' is not installed in the WSL distro."); + } + + /// Runs a bash script in the fixture directory; returns stdout+stderr, asserting success unless told otherwise. + private string Bash(string script, bool mustSucceed = true) { + var dir = FsInteropToolbox.WinToWsl(this._dir); + var result = FsInteropToolbox.RunWsl($"set -e; export LC_ALL=C; cd {dir}; {script}"); + var output = result.StdOut + result.StdErr; + if (mustSucceed) + Assert.That(result.ExitCode, Is.EqualTo(0), $"script failed:\n{script}\n{output}"); + return output; + } + + private string PathOf(string name) => Path.Combine(this._dir, name); + + private static FormatCreateOptions Options(string scheme, int sectorSize = 512) => new() { + FormatSpecific = new(StringComparer.OrdinalIgnoreCase) { + ["scheme"] = scheme, ["sectorSize"] = sectorSize.ToString(CultureInfo.InvariantCulture), + }, + }; + + private void CreateDisk(string disk, string scheme, int sectorSize, params string[] images) { + using var fs = File.Create(this.PathOf(disk)); + Desc.Create(fs, [.. images.Select(i => new ArchiveInputInfo(this.PathOf(i), i, false))], Options(scheme, sectorSize)); + } + + /// Partition (start, size) in bytes as parted -m unit B print reports them. + private List<(long Start, long Size)> PartedLayout(string disk) { + var output = this.Bash($"parted -s -m {disk} unit B print"); + var result = new List<(long, long)>(); + foreach (var line in output.Split('\n')) { + var f = line.Trim().Split(':'); + if (f.Length < 4 || !int.TryParse(f[0], out _)) continue; + result.Add((long.Parse(f[1].TrimEnd('B'), CultureInfo.InvariantCulture), + long.Parse(f[3].TrimEnd('B'), CultureInfo.InvariantCulture))); + } + return result; + } + + // ── Create, verified by the table tools and fsck ─────────────────── + + [Test] + public void Given_VfatAndExt4Images_When_CreatedAsGpt_Then_SgdiskSfdiskPartedAndFsckAgree() { + RequireTools("sgdisk", "sfdisk", "parted", "mkfs.vfat", "fsck.vfat", "mkfs.ext4", "e2fsck", "mcopy"); + this.Bash( + "echo dos > dos.txt; mkdir -p root && echo linux > root/linux.txt; " + + "mkfs.vfat -C a.img 8192 >/dev/null; mcopy -i a.img dos.txt ::/DOS.TXT; " + + "truncate -s 12M b.img; mkfs.ext4 -q -F -d root b.img"); + + this.CreateDisk("disk.img", "gpt", 512, "a.img", "b.img"); + + var sgdisk = this.Bash("sgdisk -v disk.img"); + Assert.That(sgdisk, Does.Contain("No problems found"), sgdisk); + var sfdisk = this.Bash("sfdisk --verify disk.img"); + Assert.That(sfdisk, Does.Contain("No errors detected"), sfdisk); + + var layout = this.PartedLayout("disk.img"); + var ours = PartitionTableDetector.Detect(File.ReadAllBytes(this.PathOf("disk.img"))).Partitions; + Assert.That(layout, Is.EqualTo(ours.Select(p => (p.StartOffset, p.Size)).ToList())); + Assert.That(layout[0].Start, Is.EqualTo(1024 * 1024)); + + var p1 = ours[0]; var p2 = ours[1]; + this.Bash( + $"dd if=disk.img of=p1.img bs=512 skip={p1.StartOffset / 512} count={p1.Size / 512} status=none; " + + $"dd if=disk.img of=p2.img bs=512 skip={p2.StartOffset / 512} count={p2.Size / 512} status=none; " + + "fsck.vfat -n p1.img; e2fsck -fn p2.img; test \"$(mcopy -i disk.img@@1M ::/DOS.TXT -)\" = dos"); + } + + [Test] + public void Given_VfatImage_When_CreatedAsMbr_Then_SfdiskVerifiesAndTheTypeIsFat32() { + RequireTools("sfdisk", "mkfs.vfat", "fsck.vfat", "mcopy"); + this.Bash("echo dos > dos.txt; mkfs.vfat -C a.img 8192 >/dev/null; mcopy -i a.img dos.txt ::/DOS.TXT"); + + this.CreateDisk("disk.img", "mbr", 512, "a.img"); + + var verify = this.Bash("sfdisk --verify disk.img"); + Assert.That(verify, Does.Contain("No errors detected"), verify); + var dump = this.Bash("sfdisk --dump disk.img"); + Assert.That(dump, Does.Contain("label: dos")); + Assert.That(dump, Does.Match(@"start=\s*2048, size=\s*16384, type=c"), dump); + Assert.That(this.Bash("mcopy -i disk.img@@1M ::/DOS.TXT -").Trim(), Is.EqualTo("dos")); + } + + [Test] + public void Given_VfatImage_When_CreatedAs4KnGpt_Then_FdiskWith4096ByteSectorsReadsTheSameLayout() { + RequireTools("fdisk", "mkfs.vfat", "mcopy"); + this.Bash("echo dos > dos.txt; mkfs.vfat -S 4096 -C a.img 8192 >/dev/null; mcopy -i a.img dos.txt ::/DOS.TXT"); + + this.CreateDisk("disk.img", "gpt", 4096, "a.img"); + + var listing = this.Bash("fdisk -b 4096 -l disk.img"); + Assert.That(listing, Does.Contain("Disklabel type: gpt"), listing); + Assert.That(listing, Does.Match(@"disk\.img1\s+256\s+2303\s+2048"), listing); // 1 MiB / 4096 = LBA 256, 8 MiB = 2048 blocks + Assert.That(listing, Does.Not.Contain("corrupt").And.Not.Contain("mismatch"), listing); + Assert.That(this.Bash("mcopy -i disk.img@@1M ::/DOS.TXT -").Trim(), Is.EqualTo("dos")); + } + + // ── Read what the tools write ────────────────────────────────────── + + [Test] + public void Given_FdiskGptAt4096ByteSectors_When_Listed_Then_TheVfatPartitionBrowses() { + RequireTools("fdisk", "mkfs.vfat", "mcopy"); + this.Bash( + "echo dos > dos.txt; truncate -s 24M disk.img; " + + "printf 'g\\nn\\n1\\n256\\n+8M\\nw\\n' | fdisk -b 4096 disk.img >/dev/null; " + + "mkfs.vfat -S 4096 -C p1.img 8192 >/dev/null; mcopy -i p1.img dos.txt ::/DOS.TXT; " + + "dd if=p1.img of=disk.img bs=4096 seek=256 conv=notrunc status=none"); + + var detection = PartitionTableDetector.Detect(File.ReadAllBytes(this.PathOf("disk.img"))); + Assert.That(detection.Scheme, Is.EqualTo("GPT")); + Assert.That(detection.SectorSize, Is.EqualTo(4096)); + Assert.That(detection.Partitions.Single().StartOffset, Is.EqualTo(256L * 4096)); + Assert.That(Compression.Lib.FormatDetector.Detect(this.PathOf("disk.img")).ToString(), Is.EqualTo("PartitionedDisk")); + Assert.That(Compression.Lib.ArchiveOperations.List(this.PathOf("disk.img"), null).Select(e => e.Name), + Has.Some.Match("^Partition1_.*/DOS\\.TXT$")); + } + + [Test] + public void Given_SfdiskExtendedWithLogicals_When_Listed_Then_LogicalsAppearAtSfdiskOffsets() { + RequireTools("sfdisk", "mkfs.vfat", "mcopy"); + this.Bash( + "echo dos > dos.txt; truncate -s 32M disk.img; " + + "printf 'label: dos\\nstart=2048, size=8192, type=83\\nstart=10240, type=5\\nstart=12288, size=8192, type=c\\nstart=22528, size=8192, type=83\\n' | sfdisk -q disk.img; " + + "mkfs.vfat --offset=12288 disk.img 4096 >/dev/null; mcopy -i disk.img@@6M dos.txt ::/DOS.TXT"); + + var dump = this.Bash("sfdisk --dump disk.img"); + var sfdiskStarts = System.Text.RegularExpressions.Regex.Matches(dump, @"start=\s*(\d+), size=\s*(\d+), type=(\w+)") + .Where(m => m.Groups[3].Value != "5") + .Select(m => (long.Parse(m.Groups[1].Value, CultureInfo.InvariantCulture) * 512, long.Parse(m.Groups[2].Value, CultureInfo.InvariantCulture) * 512)) + .ToList(); + var ours = PartitionTableDetector.Detect(File.ReadAllBytes(this.PathOf("disk.img"))).Partitions; + Assert.That(ours.Select(p => (p.StartOffset, p.Size)), Is.EquivalentTo(sfdiskStarts), dump); + Assert.That(ours.Count(p => p.Source == "EBR"), Is.EqualTo(2)); + Assert.That(Compression.Lib.ArchiveOperations.List(this.PathOf("disk.img"), null).Select(e => e.Name), + Has.Some.Match("/DOS\\.TXT$")); + } + + [TestCase("bsd")] + [TestCase("amiga")] + [TestCase("sun")] + [TestCase("dvh")] + public void Given_PartedLabel_When_Detected_Then_PartitionsMatchPartedsOffsets(string label) { + RequireTools("parted"); + // mkpart's leading arguments differ per label: amiga takes a name, dvh a primary/logical + // kind plus a filesystem type, bsd and sun only a filesystem type. + var kind = label switch { "amiga" => "primary", "dvh" => "primary ext2", _ => "ext2" }; + var made = this.Bash( + $"truncate -s 64M disk.img; parted -s disk.img mklabel {label}; " + + $"parted -s disk.img mkpart {kind} 4MiB 24MiB; parted -s disk.img mkpart {kind} 24MiB 44MiB; " + + "xxd -l 1536 disk.img | grep -v ' 0000 0000 0000 0000 0000 0000 0000 0000 '", mustSucceed: false); + var parted = this.PartedLayout("disk.img"); + if (parted.Count == 0) Assert.Ignore($"parted cannot write a {label} label here: {made}"); + + var bytes = File.ReadAllBytes(this.PathOf("disk.img")); + var detection = PartitionTableDetector.Detect(bytes); + var expectedScheme = label switch { "bsd" => "BSD", "amiga" => "RDB", "sun" => "Sun", _ => "SGI" }; + Assert.That(detection.Scheme, Is.EqualTo(expectedScheme), made); + // parted lists the label's bookkeeping slices (SGI volume header, Sun whole-disk) as partitions + // too; every data partition we report must be one parted reports, and each of parted's two + // mkpart partitions must be among ours. + var ours = detection.Partitions.Select(p => (p.StartOffset, p.Size)).ToList(); + Assert.That(parted, Is.SupersetOf(ours.Where(o => parted.Contains(o)))); + var dataPartitions = parted.Where(p => p.Start >= 1024 * 1024 - 64 * 1024 && p.Size >= 16L * 1024 * 1024).ToList(); + Assert.That(dataPartitions, Has.Count.EqualTo(2), $"{string.Join(", ", parted)}\n{made}"); + Assert.That(ours, Is.SupersetOf(dataPartitions), $"ours: {string.Join(", ", ours)}; parted: {string.Join(", ", parted)}"); + + using var ms = new MemoryStream(bytes); + Assert.That(PartitionTableDetector.HasSelfValidatingLabel(ms), Is.True); + Assert.That(Compression.Lib.FormatDetector.Detect(this.PathOf("disk.img")).ToString(), Is.EqualTo("PartitionedDisk")); + } + + // ── Edit in place, verified by fsck ──────────────────────────────── + + [Test] + public void Given_SfdiskGptWithVfatAndExt4_When_FilesEditedInPlace_Then_FsckIsCleanAndTheToolsReadTheEdits() { + RequireTools("sfdisk", "sgdisk", "mkfs.vfat", "fsck.vfat", "mkfs.ext4", "e2fsck", "debugfs", "mcopy"); + this.Bash( + "mkdir -p rootfs && echo linux > rootfs/linux.txt && echo gone > rootfs/gone.txt && echo dos > dos.txt; " + + "truncate -s 24M disk.img; " + + "printf 'label: gpt\\nstart=2048, size=16384, type=EBD0A0A2-B9E5-4433-87C0-68B6B72699C7\\nstart=18432, size=28672, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4\\n' | sfdisk -q disk.img; " + + "mkfs.vfat --offset=2048 disk.img 8192 >/dev/null; mcopy -i disk.img@@1M dos.txt ::/DOS.TXT; " + + "mkfs.ext4 -q -F -d rootfs -E offset=9437184 disk.img 14M"); + + var path = this.PathOf("disk.img"); + var before = File.ReadAllBytes(path); + var parts = PartitionTableDetector.Detect(before).Partitions; + string fat, ext; + using (var fs = new FileStream(path, FileMode.Open, FileAccess.ReadWrite)) { + fat = PartitionedDiskLister.MakePartitionPrefix(parts[0]); + ext = PartitionedDiskLister.MakePartitionPrefix(parts[1]); + Desc.Add(fs, [ + ArchiveInputInfo.InMemory($"{fat}/NEW.TXT", "added to fat\n"u8.ToArray()), + ArchiveInputInfo.InMemory($"{ext}/added.txt", "added to ext4\n"u8.ToArray()), + ]); + Desc.Remove(fs, [$"{ext}/gone.txt"]); + } + + var after = File.ReadAllBytes(path); + Assert.That(after.Length, Is.EqualTo(before.Length)); + // Nothing outside the two partitions moved: table, gaps and backup GPT are byte-identical. + var inside = new bool[after.Length]; + foreach (var p in parts) Array.Fill(inside, true, (int)p.StartOffset, (int)p.Size); + for (var i = 0; i < after.Length; ++i) + if (!inside[i] && after[i] != before[i]) + Assert.Fail($"byte {i} outside the partitions changed"); + + var output = this.Bash( + "sgdisk -v disk.img; " + + $"dd if=disk.img of=p1.img bs=512 skip={parts[0].StartOffset / 512} count={parts[0].Size / 512} status=none; " + + $"dd if=disk.img of=p2.img bs=512 skip={parts[1].StartOffset / 512} count={parts[1].Size / 512} status=none; " + + "fsck.vfat -n p1.img; e2fsck -fn p2.img; " + + "echo FAT=$(mcopy -i disk.img@@1M ::/NEW.TXT -); " + + "echo EXT=$(debugfs -R 'cat /added.txt' p2.img 2>/dev/null); " + + "echo GONE=$(debugfs -R 'ls -l /' p2.img 2>/dev/null | grep -c gone.txt || true)"); + Assert.That(output, Does.Contain("No problems found"), output); + Assert.That(output, Does.Contain("FAT=added to fat"), output); + Assert.That(output, Does.Contain("EXT=added to ext4"), output); + Assert.That(output, Does.Contain("GONE=0"), output); + } +} diff --git a/Compression.Tests/DiskImage/PartitionedDiskWriteTests.cs b/Compression.Tests/DiskImage/PartitionedDiskWriteTests.cs new file mode 100644 index 000000000..f41973b5e --- /dev/null +++ b/Compression.Tests/DiskImage/PartitionedDiskWriteTests.cs @@ -0,0 +1,279 @@ +#pragma warning disable CS1591 +using System.Buffers.Binary; +using Compression.Core.DiskImage; +using Compression.Registry; +using FileFormat.PartitionedDisk; + +namespace Compression.Tests.DiskImage; + +/// +/// Write support of the raw partitioned-disk format: files edited inside a partition's +/// filesystem in place, partitions added and deleted, fresh disks created from partition images — +/// and every refusal leaving the image byte-identical. +/// +[TestFixture] +public class PartitionedDiskWriteTests { + + [SetUp] + public void EnsureRegistered() => Compression.Lib.FormatRegistration.EnsureInitialized(); + + private static readonly PartitionedDiskFormatDescriptor Desc = new(); + + private static byte[] FatImage(string name, byte[] content, int sectors = 2880) { + var writer = new FileSystem.Fat.FatWriter(); + writer.AddFile(name, content); + return writer.Build(totalSectors: sectors); + } + + private static MemoryStream CreateDisk(string scheme, int sectorSize, params (string Name, byte[] Data)[] images) { + var ms = new MemoryStream(); + var options = new FormatCreateOptions { FormatSpecific = new(StringComparer.OrdinalIgnoreCase) { + ["scheme"] = scheme, ["sectorSize"] = sectorSize.ToString(System.Globalization.CultureInfo.InvariantCulture) } }; + Desc.Create(ms, [.. images.Select(i => ArchiveInputInfo.InMemory(i.Name, i.Data))], options); + return ms; + } + + private static string Prefix(Stream disk, int index) { + var detection = PartitionTableDetector.Detect(disk); + return PartitionedDiskLister.MakePartitionPrefix(detection.Partitions[index]); + } + + private static byte[] ReadEntry(Stream disk, string name) { + using var s = Desc.OpenEntry(disk, name, null); + using var ms = new MemoryStream(); + s.CopyTo(ms); + return ms.ToArray(); + } + + // ── Create ───────────────────────────────────────────────────────── + + [TestCase("gpt", 512)] + [TestCase("gpt", 4096)] + [TestCase("mbr", 512)] + [Category("HappyPath")] + public void Given_TwoFatImages_When_Created_Then_EachPartitionListsItsFileAtMiBAlignment(string scheme, int sectorSize) { + using var disk = CreateDisk(scheme, sectorSize, + ("boot.img", FatImage("A.TXT", "first"u8.ToArray())), + ("data.img", FatImage("B.TXT", "second"u8.ToArray()))); + + var detection = PartitionTableDetector.Detect(disk); + Assert.That(detection.Scheme, Is.EqualTo(scheme.ToUpperInvariant())); + Assert.That(detection.SectorSize, Is.EqualTo(sectorSize)); + Assert.That(detection.Partitions, Has.Count.EqualTo(2)); + Assert.That(detection.Partitions[0].StartOffset, Is.EqualTo(1024 * 1024)); + Assert.That(detection.Partitions[1].StartOffset % (1024 * 1024), Is.Zero); + + var names = Desc.List(disk, null).Select(e => e.Name).ToList(); + Assert.That(names, Has.Some.Match("^Partition1_[^/]+/A\\.TXT$"), string.Join(", ", names)); + Assert.That(names, Has.Some.Match("^Partition2_[^/]+/B\\.TXT$"), string.Join(", ", names)); + + var verify = new PartitionEditor(disk).Verify(); + Assert.That(verify.IsValid, Is.True, string.Join("; ", verify.Issues)); + } + + [Test, Category("HappyPath")] + public void Given_GptCreate_When_InputNamed_Then_PartitionNameIsTheStem() { + using var disk = CreateDisk("gpt", 512, ("Partition1_EFI.raw", new byte[4096])); + var part = PartitionTableDetector.Detect(disk).Partitions.Single(); + Assert.That(part.Name, Is.EqualTo("EFI.raw".Replace(".raw", ""))); + Assert.That(part.Size, Is.EqualTo(4096)); + } + + [Test, Category("ErrorHandling")] + public void Given_LooseTextFile_When_Created_Then_Refused() { + Assert.That(() => CreateDisk("gpt", 512, ("PROBE.TXT", "hello"u8.ToArray())), Throws.ArgumentException); + } + + [Test, Category("ErrorHandling")] + public void Given_NoInputs_When_Created_Then_Refused() { + Assert.That(() => CreateDisk("gpt", 512), Throws.ArgumentException); + } + + [Test, Category("Boundary")] + public void Given_FiveImagesOnMbr_When_Created_Then_RefusedBecauseOnlyFourPrimariesExist() { + var images = Enumerable.Range(1, 5).Select(i => ($"p{i}.raw", new byte[512])).ToArray(); + Assert.That(() => CreateDisk("mbr", 512, images), Throws.ArgumentException); + Assert.That(() => CreateDisk("mbr", 512, images[..4]), Throws.Nothing); + } + + [TestCase("scheme", "apm")] + [TestCase("sectorSize", "1024")] + [Category("ErrorHandling")] + public void Given_UnknownOption_When_Created_Then_Refused(string key, string value) { + var options = new FormatCreateOptions { FormatSpecific = new(StringComparer.OrdinalIgnoreCase) { [key] = value } }; + Assert.That(() => Desc.Create(new MemoryStream(), [ArchiveInputInfo.InMemory("a.raw", new byte[512])], options), + Throws.ArgumentException); + } + + // ── Edit inside a partition ──────────────────────────────────────── + + [TestCase("gpt", 512)] + [TestCase("gpt", 4096)] + [TestCase("mbr", 512)] + [Category("HappyPath")] + public void Given_FatPartition_When_FileAdded_Then_ItReadsBackAndBytesOutsideThePartitionAreUntouched(string scheme, int sectorSize) { + using var disk = CreateDisk(scheme, sectorSize, + ("a.img", FatImage("A.TXT", "first"u8.ToArray())), + ("b.img", FatImage("B.TXT", "second"u8.ToArray()))); + var before = disk.ToArray(); + var p1 = PartitionTableDetector.Detect(disk).Partitions[0]; + var prefix = Prefix(disk, 0); + + Desc.Add(disk, [ArchiveInputInfo.InMemory($"{prefix}/NEW.TXT", "added in place"u8.ToArray())]); + + Assert.That(ReadEntry(disk, $"{prefix}/NEW.TXT"), Is.EqualTo("added in place"u8.ToArray())); + Assert.That(ReadEntry(disk, $"{prefix}/A.TXT"), Is.EqualTo("first"u8.ToArray())); + var after = disk.ToArray(); + Assert.That(after.Length, Is.EqualTo(before.Length)); + Assert.That(after.AsSpan(0, (int)p1.StartOffset).SequenceEqual(before.AsSpan(0, (int)p1.StartOffset)), Is.True, + "bytes before the partition changed"); + var end = (int)(p1.StartOffset + p1.Size); + Assert.That(after.AsSpan(end).SequenceEqual(before.AsSpan(end)), Is.True, "bytes after the partition changed"); + } + + [Test, Category("HappyPath")] + public void Given_FatPartition_When_FileRemoved_Then_ItIsGoneAndTheOtherStays() { + using var disk = CreateDisk("gpt", 512, ("a.img", FatImage("A.TXT", "first"u8.ToArray()))); + var prefix = Prefix(disk, 0); + Desc.Add(disk, [ArchiveInputInfo.InMemory($"{prefix}/KEEP.TXT", "keep"u8.ToArray())]); + + Desc.Remove(disk, [$"{prefix}/A.TXT"]); + + var names = Desc.List(disk, null).Select(e => e.Name).ToList(); + Assert.That(names, Has.None.EqualTo($"{prefix}/A.TXT")); + Assert.That(names, Has.Some.EqualTo($"{prefix}/KEEP.TXT")); + } + + [Test, Category("ErrorHandling")] + public void Given_RawPartitionWithoutFilesystem_When_FileAdded_Then_RefusedAndByteIdentical() { + using var disk = CreateDisk("gpt", 512, ("blob.raw", new byte[64 * 1024])); + var before = disk.ToArray(); + var prefix = Prefix(disk, 0); + + Assert.That(() => Desc.Add(disk, [ArchiveInputInfo.InMemory($"{prefix}/X.TXT", "x"u8.ToArray())]), + Throws.InvalidOperationException); + Assert.That(disk.ToArray(), Is.EqualTo(before)); + } + + [Test, Category("Boundary")] + public void Given_FullFatVolume_When_OversizedFileAdded_Then_RefusedAndByteIdentical() { + using var disk = CreateDisk("gpt", 512, ("a.img", FatImage("A.TXT", "first"u8.ToArray()))); + var before = disk.ToArray(); + var prefix = Prefix(disk, 0); + + Assert.That(() => Desc.Add(disk, [ArchiveInputInfo.InMemory($"{prefix}/BIG.BIN", new byte[4 * 1024 * 1024])]), + Throws.InvalidOperationException); + Assert.That(disk.ToArray(), Is.EqualTo(before)); + } + + [Test, Category("ErrorHandling")] + public void Given_TwoEditsWhereTheSecondFails_When_Added_Then_NeitherIsWritten() { + using var disk = CreateDisk("gpt", 512, + ("a.img", FatImage("A.TXT", "first"u8.ToArray())), + ("blob.raw", new byte[64 * 1024])); + var before = disk.ToArray(); + + Assert.That(() => Desc.Add(disk, [ + ArchiveInputInfo.InMemory($"{Prefix(disk, 0)}/OK.TXT", "fine"u8.ToArray()), + ArchiveInputInfo.InMemory($"{Prefix(disk, 1)}/NO.TXT", "refused"u8.ToArray()), + ]), Throws.InvalidOperationException); + Assert.That(disk.ToArray(), Is.EqualTo(before)); + } + + [Test, Category("ErrorHandling")] + public void Given_NameOutsideAnyPartition_When_Removed_Then_FileNotFoundAndByteIdentical() { + using var disk = CreateDisk("gpt", 512, ("a.img", FatImage("A.TXT", "first"u8.ToArray()))); + var before = disk.ToArray(); + Assert.That(() => Desc.Remove(disk, ["Partition9_Nope/A.TXT"]), Throws.InstanceOf()); + Assert.That(disk.ToArray(), Is.EqualTo(before)); + } + + // ── Whole partitions ─────────────────────────────────────────────── + + [TestCase("gpt")] + [TestCase("mbr")] + [Category("HappyPath")] + public void Given_TwoPartitions_When_FirstRemovedWhole_Then_TableShrinksAndItsSectorsStay(string scheme) { + using var disk = CreateDisk(scheme, 512, + ("a.img", FatImage("A.TXT", "first"u8.ToArray())), + ("b.img", FatImage("B.TXT", "second"u8.ToArray()))); + var first = PartitionTableDetector.Detect(disk).Partitions[0]; + var payload = disk.ToArray().AsSpan((int)first.StartOffset, (int)first.Size).ToArray(); + + Desc.Remove(disk, [Prefix(disk, 0)]); + + var left = PartitionTableDetector.Detect(disk).Partitions; + Assert.That(left, Has.Count.EqualTo(1)); + Assert.That(Desc.List(disk, null).Select(e => e.Name), Has.Some.EndsWith("/B.TXT")); + Assert.That(disk.ToArray().AsSpan((int)first.StartOffset, (int)first.Size).ToArray(), Is.EqualTo(payload)); + Assert.That(new PartitionEditor(disk).Verify().IsValid, Is.True); + } + + [Test, Category("HappyPath")] + public void Given_GptDisk_When_FilesystemImageAddedAtRoot_Then_ItBecomesAMiBAlignedPartition() { + using var disk = CreateDisk("gpt", 512, ("a.img", FatImage("A.TXT", "first"u8.ToArray()))); + disk.SetLength(disk.Length + 4 * 1024 * 1024); + new PartitionEditor(disk).Reload(); + // Re-home the backup GPT at the new end: a grown disk is what partition tools hand over. + var grow = new PartitionEditor(disk); + grow.ConvertGptToMbr(); + grow.ConvertMbrToGpt(); + + Desc.Add(disk, [ArchiveInputInfo.InMemory("second.img", FatImage("N.TXT", "new"u8.ToArray()))]); + + var parts = PartitionTableDetector.Detect(disk).Partitions; + Assert.That(parts, Has.Count.EqualTo(2)); + Assert.That(parts[1].StartOffset % (1024 * 1024), Is.Zero); + Assert.That(Desc.List(disk, null).Select(e => e.Name), Has.Some.EndsWith("/N.TXT")); + Assert.That(new PartitionEditor(disk).Verify().IsValid, Is.True); + } + + [Test, Category("Boundary")] + public void Given_NoFreeSpace_When_ImageAddedAtRoot_Then_RefusedAndByteIdentical() { + using var disk = CreateDisk("gpt", 512, ("a.img", FatImage("A.TXT", "first"u8.ToArray()))); + var before = disk.ToArray(); + Assert.That(() => Desc.Add(disk, [ArchiveInputInfo.InMemory("second.img", FatImage("N.TXT", "new"u8.ToArray()))]), + Throws.InvalidOperationException); + Assert.That(disk.ToArray(), Is.EqualTo(before)); + } + + [Test, Category("ErrorHandling")] + public void Given_LooseFileAtRoot_When_Added_Then_RefusedAndByteIdentical() { + using var disk = CreateDisk("gpt", 512, ("a.img", FatImage("A.TXT", "first"u8.ToArray()))); + var before = disk.ToArray(); + Assert.That(() => Desc.Add(disk, [ArchiveInputInfo.InMemory("notes.txt", "text"u8.ToArray())]), + Throws.InvalidOperationException); + Assert.That(disk.ToArray(), Is.EqualTo(before)); + } + + // ── Staged window ────────────────────────────────────────────────── + + [Test, Category("Boundary")] + public void Given_StagedWindow_When_WritePastItsEnd_Then_ThrowsAndDiskUntouched() { + var bytes = Enumerable.Range(0, 8192).Select(i => (byte)i).ToArray(); + using var disk = new MemoryStream(bytes.ToArray()); + var window = new StagedWindowStream(disk, 1024, 4096); + window.Position = 4090; + Assert.That(() => window.Write(new byte[7]), Throws.InstanceOf()); + Assert.That(() => window.SetLength(4097), Throws.InstanceOf()); + window.SetLength(100); // shrinking is a no-op + Assert.That(disk.ToArray(), Is.EqualTo(bytes)); + } + + [Test, Category("HappyPath")] + public void Given_StagedWrites_When_ReadBackBeforeCommit_Then_SeenOnlyThroughTheWindowUntilCommitted() { + var bytes = new byte[16384]; + using var disk = new MemoryStream(bytes.ToArray()); + var window = new StagedWindowStream(disk, 4096, 8192); + window.Position = 4094; // straddles a page boundary + window.Write([1, 2, 3, 4]); + window.Position = 4094; + var back = new byte[4]; + window.ReadExactly(back); + Assert.That(back, Is.EqualTo(new byte[] { 1, 2, 3, 4 })); + Assert.That(disk.ToArray(), Is.EqualTo(bytes), "nothing reaches the disk before Commit"); + + window.Commit(); + Assert.That(disk.ToArray().AsSpan(4096 + 4094, 4).ToArray(), Is.EqualTo(new byte[] { 1, 2, 3, 4 })); + } +} diff --git a/Compression.Tests/NativeUI/ShellContentDetectionTests.cs b/Compression.Tests/NativeUI/ShellContentDetectionTests.cs index 5590561ef..1cfafbcbd 100644 --- a/Compression.Tests/NativeUI/ShellContentDetectionTests.cs +++ b/Compression.Tests/NativeUI/ShellContentDetectionTests.cs @@ -59,16 +59,30 @@ private string Write(string name, byte[] data) { } [Test] - public void GivenAnImgHoldingAPartitionTable_WhenOpened_ThenItIsAPartitionedDiskAndNothingIsOfferedToBeAddedToIt() { + public void GivenAnImgHoldingAPartitionTable_WhenOpened_ThenItIsAPartitionedDiskWhoseAddsGoThroughThePartitions() { this._model.Open(this.Write("disk.img", PartitionedDisk())); Assert.Multiple(() => { Assert.That(this._model.Format, Is.EqualTo("PartitionedDisk"), "the extension says FAT; the bytes say whole disk"); - Assert.That(this._model.AddFilesCommand.CanExecute(null), Is.False, - "adding through the FAT writer would write a FAT structure over the partition table"); + // Adding is offered, but through the partition-aware writer — never the FAT writer, which + // would write a FAT structure over the partition table. + Assert.That(this._model.AddFilesCommand.CanExecute(null), Is.True); + Assert.That(FormatRegistry.GetArchiveOps("PartitionedDisk"), Is.InstanceOf()); }); } + [Test] + public void GivenAPartitionedDisk_WhenAFileIsDroppedIntoAPartitionFolder_ThenTheDropIsAccepted() { + var disk = PartitionedDisk(); + this._model.Open(this.Write("disk.img", disk)); + this._model.CurrentFolder = PartitionedDiskLister.MakePartitionPrefix(PartitionTableDetector.Detect(disk).Partitions[0]); + var dropped = this.Write("any.txt", "dropped"u8.ToArray()); + + var (allowed, _) = this._model.EvaluateDropAgainstCurrentArchive([dropped]); + + Assert.That(allowed, Is.True); + } + [Test] public void GivenAnImgHoldingABareFatVolume_WhenOpened_ThenFilesCanBeAddedToIt() { this._model.Open(this.Write("floppy.img", Fat())); diff --git a/Compression.Tests/Nested/NestedStreamResolverTests.cs b/Compression.Tests/Nested/NestedStreamResolverTests.cs index 94ac99129..732174fc3 100644 --- a/Compression.Tests/Nested/NestedStreamResolverTests.cs +++ b/Compression.Tests/Nested/NestedStreamResolverTests.cs @@ -111,4 +111,28 @@ public void ResolveDeepest_AddFileThroughResolved_AppearsOnReRead() { Assert.Ignore("FAT descriptor does not implement IArchiveModifiable (unexpected)"); } } + + [TestCase("gpt", 512)] + [TestCase("gpt", 4096)] + [TestCase("mbr", 512)] + [Category("HappyPath")] + public void Given_RawPartitionedDisk_When_Resolved_Then_TheFilesystemInsideThePartitionIsReturned(string scheme, int sectorSize) { + var fatWriter = new FileSystem.Fat.FatWriter(); + fatWriter.AddFile("inner.txt", "inside a partition"u8.ToArray()); + var fat = fatWriter.Build(totalSectors: 2880); + using var disk = new MemoryStream(); + new FileFormat.PartitionedDisk.PartitionedDiskFormatDescriptor().Create(disk, [ArchiveInputInfo.InMemory("fat.img", fat)], + new FormatCreateOptions { FormatSpecific = new(StringComparer.OrdinalIgnoreCase) { + ["scheme"] = scheme, ["sectorSize"] = sectorSize.ToString(System.Globalization.CultureInfo.InvariantCulture) } }); + + var resolution = NestedStreamResolver.ResolveDeepest(disk); + + Assert.That(resolution, Is.Not.Null, "a raw disk's partitions must be descended into"); + Assert.That(resolution!.InnerDescriptor.Id, Is.EqualTo("Fat")); + Assert.That(resolution.NestingPath, Does.Contain($"{scheme.ToUpperInvariant()} partition 0")); + Assert.That(resolution.InnerStream.Length, Is.EqualTo(fat.Length)); + resolution.InnerStream.Position = 0; + Assert.That(FormatRegistry.GetArchiveOps("Fat")!.List(resolution.InnerStream, null).Select(e => e.Name), + Has.Some.EqualTo("inner.txt")); + } } diff --git a/README.md b/README.md index bc4c13f2b..094cc1dff 100644 --- a/README.md +++ b/README.md @@ -457,6 +457,10 @@ checks. **Partition-table support.** `MbrParser`, `GptParser` and `PartitionTypeDatabase` feed recursive descent so `--recursive` can follow disk image → partition table → filesystem → nested format. +Raw disk images (`PartitionedDisk`) read GPT (512- and 4096-byte LBAs), MBR with logical +partitions, APM, BSD disklabels, Amiga RDB, Sun and SGI labels; files inside a partition are edited +in place by that partition's filesystem editor, GPT/MBR partitions are added and removed, and new +disks are created from partition images — see [ARCHITECTURE.md](ARCHITECTURE.md). Analysis does not change format ownership: discovering JPEG data inside damaged storage does not make JPEG an archive format, and finding an ext superblock does not make `Compression.Analysis` the ext diff --git a/docs/catalogue/our-registry.csv b/docs/catalogue/our-registry.csv index 22ea9daba..a9ce7575f 100644 --- a/docs/catalogue/our-registry.csv +++ b/docs/catalogue/our-registry.csv @@ -395,7 +395,7 @@ format,Paragon,Paragon Backup,Archive,,.pbf,Y,Y,Y,Y format,ParallelsHdd,Parallels Disk (HDS),Archive,,.hds,Y,Y,, format,Parquet,Apache Parquet,Archive,,.parquet,Y,Y,, format,Partclone,partclone (Clonezilla),Archive,,.aa;.img,Y,Y,Y, -format,PartitionedDisk,Partitioned disk image,Archive,,,Y,Y,, +format,PartitionedDisk,Partitioned disk image,Archive,,,Y,Y,Y,Y format,Pbp,PSP PBP Archive,Archive,,.pbp,Y,Y,Y, format,Pc98,NEC PC-98 DOS,Archive,Y,.hdm;.fdi;.d88,Y,Y,Y,Y format,Pcap,PCAP (libpcap capture),Archive,,.pcap;.cap,Y,Y,, diff --git a/docs/catalogue/reference-formats.csv b/docs/catalogue/reference-formats.csv index 0184360a7..0fecd73e3 100644 --- a/docs/catalogue/reference-formats.csv +++ b/docs/catalogue/reference-formats.csv @@ -243,10 +243,10 @@ ISO 9660 (Joliet / Rock Ridge / El Torito),iso;cdr;bin;cd-rom;cdi;dmg;img;joliet High Sierra format,iso;hsf,filesystem,,,,,,,Y,1,,,,,,MISSING,, UDF,udf;cdr;dmg;iso;toast,filesystem,R,,,,,fmt/1738;fmt/1739;fmt/1757,Y,3,https://www.osta.org/specs/,Udf,Y,Y,Y,PARTIAL,,type-1 partition maps only: no VAT/sparable/metadata partitions (UDF 1.50+ media) (UdfReader.cs) Roxio Toast image,toast,disk-image,,,,,,,Y,1,,Iso;Hfs,Y,Y,Y,OK,, -Apple Partition Map,apm;bin;cdr;dmg;img;iso;toast,partition-table,R,,,,,fmt/1740;fmt/1741;fmt/1757,,2,https://en.wikipedia.org/wiki/Apple_Partition_Map,PartitionedDisk,Y,,,READ-ONLY-BUT-SOURCES-WRITE,pdisk / hdiutil, -MBR partition table,mbr,partition-table,R,,,R,,,,2,https://en.wikipedia.org/wiki/Master_boot_record,PartitionedDisk,Y,,,READ-ONLY-BUT-SOURCES-WRITE,fdisk / sfdisk, -GUID Partition Table (GPT),gpt,partition-table,R,,,D,,,,2,https://uefi.org/specifications,PartitionedDisk,Y,,,READ-ONLY-BUT-SOURCES-WRITE,sgdisk / gdisk, -Raw disk image,img;raw;dd;bin,disk-image,,,,,,,Y,1,,PartitionedDisk,Y,,,READ-ONLY-BUT-SOURCES-WRITE,dd, +Apple Partition Map,apm;bin;cdr;dmg;img;iso;toast,partition-table,R,,,,,fmt/1740;fmt/1741;fmt/1757,,2,https://en.wikipedia.org/wiki/Apple_Partition_Map,PartitionedDisk,Y,Y,Y,PARTIAL,pdisk / hdiutil,APM tables are read and their partitions' filesystems edited in place; APM entries cannot be created; added or removed +MBR partition table,mbr,partition-table,R,,,R,,,,2,https://en.wikipedia.org/wiki/Master_boot_record,PartitionedDisk,Y,Y,Y,OK,fdisk / sfdisk,incl. EBR logical partitions; create/modify checked with sfdisk --verify and fsck +GUID Partition Table (GPT),gpt,partition-table,R,,,D,,,,2,https://uefi.org/specifications,PartitionedDisk,Y,Y,Y,OK,sgdisk / gdisk,512- and 4096-byte LBAs; create/modify checked with sgdisk -v; sfdisk --verify and fdisk -b 4096 +Raw disk image,img;raw;dd;bin,disk-image,,,,,,,Y,1,,PartitionedDisk,Y,Y,Y,OK,dd,files inside partitions are edited in place by the partition filesystem's own editor Linux LVM2,lvm,partition-table,R,,,,,,,1,,,,,,MISSING,,https://man7.org/linux/man-pages/man8/lvm.8.html Android logical partitions (super.img),img;lp,partition-table,R,,,,,,,1,,,,,,MISSING,,https://source.android.com/docs/core/ota/dynamic_partitions Apple Disk Image (UDIF),dmg;smi;img,disk-image,R,,,R,R,fmt/1071,Y,5,https://en.wikipedia.org/wiki/Apple_Disk_Image,Dmg,Y,Y,Y,OK,, From 2cb06be81531c32872453d9605f084593a6dc175 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 26 Oct 2026 12:06:10 +0000 Subject: [PATCH 16/34] * refresh the archive browser screenshot --- docs/screenshots/archive-browser.png | Bin 66518 -> 65657 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/screenshots/archive-browser.png b/docs/screenshots/archive-browser.png index de88d7dd43f38a733fe5557dd0e66b254aa140ee..e3795c0d04a8041389e0aaa3edc8cc5c89e36820 100644 GIT binary patch literal 65657 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

J-t(C?D*vNbSd$Mpldw%6G^3`$t6)P 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 O*JNyHeySLKDeAvN*8HCU literal 66518 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 Kj%4xcAO8X%gq{lk From ad124fa96b2cd5af165c699285851fa5a341f13f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 26 Oct 2026 12:06:16 +0000 Subject: [PATCH 17/34] * refresh the application screenshot --- screenshots/main-window.png | Bin 65657 -> 66510 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/screenshots/main-window.png b/screenshots/main-window.png index e3795c0d04a8041389e0aaa3edc8cc5c89e36820..a8d91cb6875fd05a6e6bed08a6f241f4bb446625 100644 GIT binary patch literal 66510 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 z zQ6F!zX{^9NEH0^6$gnqg7@ImJsj+e9bSC@bGbO1(*+673z6xedyi|;3<_5#UiDyUm zp0=RI7d8F(3b8QOJiGm{iP)W%st1N7pjlPmcdcO&r>@H~d@dkSID`Ez6!8-KBk{3j z7<>m9nD3*1OMC;>$98Iac*`GhNnGO%{E!+ExQ1*^?Nk*i7>Ix6|LV)}_3FAUQgznG T5?6D@2WE2C;!LiA^I!i3jR{9W literal 65657 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

J-t(C?D*vNbSd$Mpldw%6G^3`$t6)P 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 O*JNyHeySLKDeAvN*8HCU From fc8ce06787c403199d76736e8e9c59c7b39bbed5 Mon Sep 17 00:00:00 2001 From: Hawkynt Date: Mon, 26 Oct 2026 13:06:53 +0100 Subject: [PATCH 18/34] * regenerate references --- Compression.Core/README.md | 2 +- docs/catalogue/GAPS.md | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Compression.Core/README.md b/Compression.Core/README.md index 483e87991..e918da817 100644 --- a/Compression.Core/README.md +++ b/Compression.Core/README.md @@ -205,7 +205,7 @@ Use the concrete version you intend to consume; this document does not predict a -Every public and protected member of all 728 types, generated from the built assembly and its XML documentation, is in [REFERENCE.md](https://github.com/Hawkynt/CompressionWorkbench/blob/main/Compression.Core/REFERENCE.md). +Every public and protected member of all 734 types, generated from the built assembly and its XML documentation, is in [REFERENCE.md](https://github.com/Hawkynt/CompressionWorkbench/blob/main/Compression.Core/REFERENCE.md). diff --git a/docs/catalogue/GAPS.md b/docs/catalogue/GAPS.md index 9bb95c1b8..f04ba09fa 100644 --- a/docs/catalogue/GAPS.md +++ b/docs/catalogue/GAPS.md @@ -24,9 +24,9 @@ exists. It warns when a capability letter has drifted from the live descriptor. | Gap type | Rows | Meaning | | --- | ---: | --- | | `MISSING` | 240 | a source lists it; no descriptor or building block covers it | -| `READ-ONLY-BUT-SOURCES-WRITE` | 24 | we only read it; a source or the format's own tool writes it | -| `PARTIAL` | 40 | covered, but the reader handles only part of the format (see the table below) | -| `OK` | 221 | covered with no known gap against what the sources list | +| `READ-ONLY-BUT-SOURCES-WRITE` | 20 | we only read it; a source or the format's own tool writes it | +| `PARTIAL` | 41 | covered, but the reader handles only part of the format (see the table below) | +| `OK` | 224 | covered with no known gap against what the sources list | | `OURS-ONLY` | 458 | in our registry, listed by none of the seven sources | ### Per source @@ -36,13 +36,13 @@ read-only rows. | Source | Rows it lists | Mapped | % | OK | | --- | ---: | ---: | ---: | ---: | -| 7-Zip (formats + codecs) | 96 | 89 | 92.7 | 72 | +| 7-Zip (formats + codecs) | 96 | 89 | 92.7 | 74 | | libarchive (formats + filters) | 28 | 27 | 96.4 | 23 | | XADMaster / The Unarchiver | 126 | 95 | 75.4 | 78 | -| binwalk v3 | 79 | 48 | 60.8 | 36 | +| binwalk v3 | 79 | 48 | 60.8 | 38 | | Universal Extractor 2 | 124 | 71 | 57.3 | 48 | | PRONOM | 87 | 63 | 72.4 | 43 | -| Wikipedia | 266 | 156 | 58.6 | 117 | +| Wikipedia | 266 | 156 | 58.6 | 118 | The libarchive and 7-Zip lists are almost fully covered. The rows still open are libarchive's grzip, plus seven 7-Zip rows: LVM, Android `super.img` (LP), AVB, TE, the Swap2/Swap4 filter, PKWARE strong From 1cfcb4f8a11967be613694bbf2d1159a9172b6db Mon Sep 17 00:00:00 2001 From: Hawkynt Date: Mon, 26 Oct 2026 13:06:56 +0100 Subject: [PATCH 19/34] # partition add counts in the disk's own sector size on 4Kn disks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Symptom: `cwb partition add --start/--length` and the shell's Add Partition dialog multiplied LBAs by 512, so on a 4Kn GPT disk a partition asked for at LBA 256 landed at byte 131072 instead of 1 MiB, and the dialog suggested start sector 2048 (8 MiB) and described 512-byte sectors. Root cause: both used the PartitionEditor.SectorSize constant instead of the block size the disk's GPT validated at. Fix: PartitionOperations.List reports the disk's logical sector size and the new AddBySectors converts LBAs with it; the CLI uses it and says which sector size applied. The dialog takes the editor's LogicalSectorSize: the default start is 1 MiB in that size (2048 or 256) and the hints name it. Sourcing: no format work; UEFI 2.10 §5.3 counts every LBA in the logical block size. Tests: AddBySectors on 512- and 4096-byte GPT disks, the last-usable-LBA boundary on a 4Kn disk (refusal leaves it byte-identical), negative/empty ranges. --- Compression.CLI/Program.cs | 15 ++--- Compression.Lib/PartitionOperations.cs | 21 ++++++- .../Views/PartitionDialogs.cs | 24 ++++---- .../Views/PartitionsWindow.cs | 2 +- Compression.Tests/Cli/PartitionCliTests.cs | 57 +++++++++++++++++++ 5 files changed, 98 insertions(+), 21 deletions(-) diff --git a/Compression.CLI/Program.cs b/Compression.CLI/Program.cs index 950e91cc1..7d8090e98 100644 --- a/Compression.CLI/Program.cs +++ b/Compression.CLI/Program.cs @@ -2592,15 +2592,16 @@ List all partitions in a disk image. Auto-detects the host container }); var partitionAddImageArg = new Argument("image") { Description = "Disk image to modify" }; -var partitionAddStartOpt = new Option("--start") { Description = "Start sector (LBA, multiplied by 512 internally)", Required = true }; -var partitionAddLengthOpt = new Option("--length") { Description = "Length in sectors", Required = true }; +var partitionAddStartOpt = new Option("--start") { Description = "Start sector (LBA, in the disk's logical sectors: 512 B, or 4096 B on a 4Kn GPT disk)", Required = true }; +var partitionAddLengthOpt = new Option("--length") { Description = "Length in the disk's logical sectors", Required = true }; var partitionAddTypeOpt = new Option("--type") { Description = "Partition type: Linux, Fat32Lba, NtfsExfat, EfiSystem, ExtendedLba, …", Required = true }; var partitionAddLabelOpt = new Option("--label") { Description = "Partition label (GPT only)" }; var partitionAddCmd = new Command("add", """ Add a primary or logical partition. If an extended container exists and the range falls inside it, the new entry is added as a logical partition. - Sectors are 512 bytes each. + Sectors are the disk's logical sectors, as fdisk and sgdisk count them: + 512 bytes, or 4096 bytes on a 4Kn GPT disk. Example: cwb partition add disk.vhd --start 2048 --length 2048 --type Linux cwb partition add disk.img --start 4096 --length 1024 --type Fat32Lba --label boot @@ -2610,10 +2611,10 @@ cwb partition add disk.img --start 4096 --length 1024 --type Fat32Lba --label bo if (!image.Exists) { Console.Error.WriteLine($"File not found: {image.FullName}"); return 1; } try { var type = PartitionOperations.ParseType(ctx.GetValue(partitionAddTypeOpt)!); - var startBytes = ctx.GetValue(partitionAddStartOpt) * PartitionEditor.SectorSize; - var lengthBytes = ctx.GetValue(partitionAddLengthOpt) * PartitionEditor.SectorSize; - PartitionOperations.Add(image.FullName, startBytes, lengthBytes, type, ctx.GetValue(partitionAddLabelOpt)); - Console.WriteLine($"Added {type} partition: start={startBytes:N0} bytes, size={lengthBytes:N0} bytes."); + var startLba = ctx.GetValue(partitionAddStartOpt); + var lengthSectors = ctx.GetValue(partitionAddLengthOpt); + var sectorSize = PartitionOperations.AddBySectors(image.FullName, startLba, lengthSectors, type, ctx.GetValue(partitionAddLabelOpt)); + Console.WriteLine($"Added {type} partition: start={startLba * sectorSize:N0} bytes, size={lengthSectors * sectorSize:N0} bytes ({sectorSize}-byte sectors)."); return 0; } catch (Exception ex) { Console.Error.WriteLine($"Add failed: {ex.Message}"); diff --git a/Compression.Lib/PartitionOperations.cs b/Compression.Lib/PartitionOperations.cs index 141b36e37..de9fa2eee 100644 --- a/Compression.Lib/PartitionOperations.cs +++ b/Compression.Lib/PartitionOperations.cs @@ -43,9 +43,11 @@ public sealed record PartitionInfo( /// Snapshot returned by : the detected scheme plus all /// primary and logical entries currently on disk. ///