diff --git a/Compression.Tests/Compression.Tests.csproj b/Compression.Tests/Compression.Tests.csproj
index 023dbbe39..5cf8945c9 100644
--- a/Compression.Tests/Compression.Tests.csproj
+++ b/Compression.Tests/Compression.Tests.csproj
@@ -146,6 +146,10 @@
ZfsVectors.%(Filename)%(Extension)
+
+
+ Uc2Vectors.%(Filename)%(Extension)
+ G722Vectors.%(Filename)%(Extension)
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/Compression.Tests/Uc2/ReferenceVectors/LICENSE.txt b/Compression.Tests/Uc2/ReferenceVectors/LICENSE.txt
new file mode 100644
index 000000000..0a041280b
--- /dev/null
+++ b/Compression.Tests/Uc2/ReferenceVectors/LICENSE.txt
@@ -0,0 +1,165 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/Compression.Tests/Uc2/ReferenceVectors/README.md b/Compression.Tests/Uc2/ReferenceVectors/README.md
new file mode 100644
index 000000000..f01892952
--- /dev/null
+++ b/Compression.Tests/Uc2/ReferenceVectors/README.md
@@ -0,0 +1,18 @@
+# UltraCompressor II reference archives
+
+Written by the original UltraCompressor II (r2, PRO 2.3 and 2.37 beta, run in DOSBox-X), never by
+CompressionWorkbench. Taken unmodified from `testdata/oracle/` of
+[klauspost/uc2](https://github.com/klauspost/uc2) at revision
+`53622f89ada1cf0d4a69f91b653bdd44ec5b0f92` (LGPL-3.0, `LICENSE.txt` beside this file); `README.txt`
+is that directory's own description of how each archive was made, and `sums.txt` lists the first 8
+bytes of the SHA-256 of every stored file revision.
+
+| File | Made by | What it pins |
+| --- | --- | --- |
+| `r2_def.uc2` | UC r2 `UC A C:\DEF *.*` | default method, custom masters over the supermaster |
+| `r2_prot.uc2` | UC r2 `UC A -S -P` | damage-protection area after the component; subdirectories |
+| `r2_rev.uc2` | UC r2 `UC A` then `UC A -I` twice | three revisions of one name; the newest is listed |
+| `r2_tf.uc2`, `r2_tst.uc2`, `r2_tt.uc2` | UC r2 `-TF`, `-TST`, `-TT` | fast, tight and super-tight levels (the tight levels may pick delta methods) |
+| `v23_def.uc2` | UC PRO 2.3 | made-by 203 |
+| `v237b_lfn.uc2` | UC 2.37b `UC A -S` | `AIP:Win95 LongN` tags, CP437 names (`Café crème.txt`) |
+| `v237b_tt.uc2` | UC 2.37b `-TT` | 2.37b's encoder at the tight level |
diff --git a/Compression.Tests/Uc2/ReferenceVectors/README.txt b/Compression.Tests/Uc2/ReferenceVectors/README.txt
new file mode 100644
index 000000000..d561483d1
--- /dev/null
+++ b/Compression.Tests/Uc2/ReferenceVectors/README.txt
@@ -0,0 +1,36 @@
+Archives made by the original UltraCompressor II in DOSBox-X by
+TestOracleUC2ToGo (UC2_DOSBOX=... UC2_ORACLE_SAVE=1 go test -run OracleUC2ToGo).
+The corpus is generated by oracleCorpus in oracle_test.go; all files have
+mtime 2025-01-02 03:04:06 local time (VERS.TXT revision n: +n hours).
+Commands run in the source directory with UC2_ANONYMOUS=1, UC2_OK=OFF.
+sums.txt lists "archive sha256[:8] revision name" for every file revision
+(names UTF-8, decoded from CP437). "UC T -F" of all three versions accepts
+every archive. DOSBox-X on Windows converts mtimes to DOS time with the
+current UTC offset, so stored times may differ by the DST offset. Re-running
+with the same UTC offset reproduces the archives byte for byte.
+
+Versions: r2 = UC2 revision 2 (uc2r2.exe), v23 = UC2 PRO 2.3 (uc2pro.exe),
+v237b = UC 2.37 beta (uc237b.exe, stores Win95 long names).
+
+r2_def.uc2 8100 bytes UC r2: UC A C:\DEF *.*
+r2_prot.uc2 8751 bytes UC r2: UC A -S -P C:\PROT *.*
+r2_rev.uc2 286 bytes UC r2: UC A C:\REV *.* ; UC A -I C:\REV *.* ; UC A -I C:\REV *.*
+r2_tf.uc2 8196 bytes UC r2: UC A -S -TF C:\TF *.*
+r2_tn.uc2 8190 bytes UC r2: UC A -S -TN C:\TN *.*
+r2_tst.uc2 8078 bytes UC r2: UC A -S -TST C:\TST *.*
+r2_tt.uc2 8090 bytes UC r2: UC A -S -TT C:\TT *.*
+v237b_def.uc2 8104 bytes UC 2.37b: UC A C:\DEF *.*
+v237b_lfn.uc2 544 bytes UC 2.37b: UC A -S C:\LFN *.*
+v237b_prot.uc2 8751 bytes UC 2.37b: UC A -S -P C:\PROT *.*
+v237b_rev.uc2 286 bytes UC 2.37b: UC A C:\REV *.* ; UC A -I C:\REV *.* ; UC A -I C:\REV *.*
+v237b_tf.uc2 8196 bytes UC 2.37b: UC A -S -TF C:\TF *.*
+v237b_tn.uc2 8192 bytes UC 2.37b: UC A -S -TN C:\TN *.*
+v237b_tst.uc2 8074 bytes UC 2.37b: UC A -S -TST C:\TST *.*
+v237b_tt.uc2 8088 bytes UC 2.37b: UC A -S -TT C:\TT *.*
+v23_def.uc2 8104 bytes UC 2.3: UC A C:\DEF *.*
+v23_prot.uc2 8751 bytes UC 2.3: UC A -S -P C:\PROT *.*
+v23_rev.uc2 286 bytes UC 2.3: UC A C:\REV *.* ; UC A -I C:\REV *.* ; UC A -I C:\REV *.*
+v23_tf.uc2 8196 bytes UC 2.3: UC A -S -TF C:\TF *.*
+v23_tn.uc2 8192 bytes UC 2.3: UC A -S -TN C:\TN *.*
+v23_tst.uc2 8074 bytes UC 2.3: UC A -S -TST C:\TST *.*
+v23_tt.uc2 8088 bytes UC 2.3: UC A -S -TT C:\TT *.*
diff --git a/Compression.Tests/Uc2/ReferenceVectors/r2_def.uc2 b/Compression.Tests/Uc2/ReferenceVectors/r2_def.uc2
new file mode 100644
index 000000000..3610a1bd6
Binary files /dev/null and b/Compression.Tests/Uc2/ReferenceVectors/r2_def.uc2 differ
diff --git a/Compression.Tests/Uc2/ReferenceVectors/r2_prot.uc2 b/Compression.Tests/Uc2/ReferenceVectors/r2_prot.uc2
new file mode 100644
index 000000000..8d689f3c3
Binary files /dev/null and b/Compression.Tests/Uc2/ReferenceVectors/r2_prot.uc2 differ
diff --git a/Compression.Tests/Uc2/ReferenceVectors/r2_rev.uc2 b/Compression.Tests/Uc2/ReferenceVectors/r2_rev.uc2
new file mode 100644
index 000000000..fc0a4906f
Binary files /dev/null and b/Compression.Tests/Uc2/ReferenceVectors/r2_rev.uc2 differ
diff --git a/Compression.Tests/Uc2/ReferenceVectors/r2_tf.uc2 b/Compression.Tests/Uc2/ReferenceVectors/r2_tf.uc2
new file mode 100644
index 000000000..55cd1601f
Binary files /dev/null and b/Compression.Tests/Uc2/ReferenceVectors/r2_tf.uc2 differ
diff --git a/Compression.Tests/Uc2/ReferenceVectors/r2_tst.uc2 b/Compression.Tests/Uc2/ReferenceVectors/r2_tst.uc2
new file mode 100644
index 000000000..a43375e8d
Binary files /dev/null and b/Compression.Tests/Uc2/ReferenceVectors/r2_tst.uc2 differ
diff --git a/Compression.Tests/Uc2/ReferenceVectors/r2_tt.uc2 b/Compression.Tests/Uc2/ReferenceVectors/r2_tt.uc2
new file mode 100644
index 000000000..4e24dd9bf
Binary files /dev/null and b/Compression.Tests/Uc2/ReferenceVectors/r2_tt.uc2 differ
diff --git a/Compression.Tests/Uc2/ReferenceVectors/sums.txt b/Compression.Tests/Uc2/ReferenceVectors/sums.txt
new file mode 100644
index 000000000..e16b86ab8
--- /dev/null
+++ b/Compression.Tests/Uc2/ReferenceVectors/sums.txt
@@ -0,0 +1,213 @@
+r2_def.uc2 fe698381b5867e9a 0 HELLO.TXT
+r2_def.uc2 e3b0c44298fc1c14 0 EMPTY.DAT
+r2_def.uc2 55e7449bfafaeb29 0 TEXT.TXT
+r2_def.uc2 6ac9c2f2f37dffed 0 SOURCE.C
+r2_def.uc2 7def5a7e066a3a2e 0 BINARY.BIN
+r2_def.uc2 ce7d9ba00521670a 0 RANDOM.BIN
+r2_def.uc2 9f1dcbc35c350d60 0 ZEROS.BIN
+r2_def.uc2 40aff2e9d2d8922e 0 ALLBYTES.BIN
+r2_def.uc2 c2e59f96175d054a 0 SOUND.WAV
+r2_prot.uc2 fe698381b5867e9a 0 HELLO.TXT
+r2_prot.uc2 e3b0c44298fc1c14 0 EMPTY.DAT
+r2_prot.uc2 55e7449bfafaeb29 0 TEXT.TXT
+r2_prot.uc2 6ac9c2f2f37dffed 0 SOURCE.C
+r2_prot.uc2 7def5a7e066a3a2e 0 BINARY.BIN
+r2_prot.uc2 ce7d9ba00521670a 0 RANDOM.BIN
+r2_prot.uc2 9f1dcbc35c350d60 0 ZEROS.BIN
+r2_prot.uc2 40aff2e9d2d8922e 0 ALLBYTES.BIN
+r2_prot.uc2 c2e59f96175d054a 0 SOUND.WAV
+r2_prot.uc2 c99fcad02327e45f 0 SUB/NESTED.TXT
+r2_prot.uc2 f146e9b5474872cc 0 SUB/DEEP/DEEPER.TXT
+r2_rev.uc2 fbdf08b036b1d798 2 VERS.TXT
+r2_rev.uc2 b024953a6dc0f1d0 0 ÉTÉ.TXT
+r2_rev.uc2 304d27f074f1fb66 1 VERS.TXT
+r2_rev.uc2 f3adfa173b453df1 0 NEW.TXT
+r2_rev.uc2 aa08feae8030e6d7 0 VERS.TXT
+r2_tf.uc2 fe698381b5867e9a 0 HELLO.TXT
+r2_tf.uc2 e3b0c44298fc1c14 0 EMPTY.DAT
+r2_tf.uc2 55e7449bfafaeb29 0 TEXT.TXT
+r2_tf.uc2 6ac9c2f2f37dffed 0 SOURCE.C
+r2_tf.uc2 7def5a7e066a3a2e 0 BINARY.BIN
+r2_tf.uc2 ce7d9ba00521670a 0 RANDOM.BIN
+r2_tf.uc2 9f1dcbc35c350d60 0 ZEROS.BIN
+r2_tf.uc2 40aff2e9d2d8922e 0 ALLBYTES.BIN
+r2_tf.uc2 c2e59f96175d054a 0 SOUND.WAV
+r2_tf.uc2 c99fcad02327e45f 0 SUB/NESTED.TXT
+r2_tf.uc2 f146e9b5474872cc 0 SUB/DEEP/DEEPER.TXT
+r2_tn.uc2 fe698381b5867e9a 0 HELLO.TXT
+r2_tn.uc2 e3b0c44298fc1c14 0 EMPTY.DAT
+r2_tn.uc2 55e7449bfafaeb29 0 TEXT.TXT
+r2_tn.uc2 6ac9c2f2f37dffed 0 SOURCE.C
+r2_tn.uc2 7def5a7e066a3a2e 0 BINARY.BIN
+r2_tn.uc2 ce7d9ba00521670a 0 RANDOM.BIN
+r2_tn.uc2 9f1dcbc35c350d60 0 ZEROS.BIN
+r2_tn.uc2 40aff2e9d2d8922e 0 ALLBYTES.BIN
+r2_tn.uc2 c2e59f96175d054a 0 SOUND.WAV
+r2_tn.uc2 c99fcad02327e45f 0 SUB/NESTED.TXT
+r2_tn.uc2 f146e9b5474872cc 0 SUB/DEEP/DEEPER.TXT
+r2_tst.uc2 fe698381b5867e9a 0 HELLO.TXT
+r2_tst.uc2 e3b0c44298fc1c14 0 EMPTY.DAT
+r2_tst.uc2 55e7449bfafaeb29 0 TEXT.TXT
+r2_tst.uc2 6ac9c2f2f37dffed 0 SOURCE.C
+r2_tst.uc2 7def5a7e066a3a2e 0 BINARY.BIN
+r2_tst.uc2 ce7d9ba00521670a 0 RANDOM.BIN
+r2_tst.uc2 9f1dcbc35c350d60 0 ZEROS.BIN
+r2_tst.uc2 40aff2e9d2d8922e 0 ALLBYTES.BIN
+r2_tst.uc2 c2e59f96175d054a 0 SOUND.WAV
+r2_tst.uc2 c99fcad02327e45f 0 SUB/NESTED.TXT
+r2_tst.uc2 f146e9b5474872cc 0 SUB/DEEP/DEEPER.TXT
+r2_tt.uc2 fe698381b5867e9a 0 HELLO.TXT
+r2_tt.uc2 e3b0c44298fc1c14 0 EMPTY.DAT
+r2_tt.uc2 55e7449bfafaeb29 0 TEXT.TXT
+r2_tt.uc2 6ac9c2f2f37dffed 0 SOURCE.C
+r2_tt.uc2 7def5a7e066a3a2e 0 BINARY.BIN
+r2_tt.uc2 ce7d9ba00521670a 0 RANDOM.BIN
+r2_tt.uc2 9f1dcbc35c350d60 0 ZEROS.BIN
+r2_tt.uc2 40aff2e9d2d8922e 0 ALLBYTES.BIN
+r2_tt.uc2 c2e59f96175d054a 0 SOUND.WAV
+r2_tt.uc2 c99fcad02327e45f 0 SUB/NESTED.TXT
+r2_tt.uc2 f146e9b5474872cc 0 SUB/DEEP/DEEPER.TXT
+v237b_def.uc2 fe698381b5867e9a 0 HELLO.TXT
+v237b_def.uc2 e3b0c44298fc1c14 0 EMPTY.DAT
+v237b_def.uc2 55e7449bfafaeb29 0 TEXT.TXT
+v237b_def.uc2 6ac9c2f2f37dffed 0 SOURCE.C
+v237b_def.uc2 7def5a7e066a3a2e 0 BINARY.BIN
+v237b_def.uc2 ce7d9ba00521670a 0 RANDOM.BIN
+v237b_def.uc2 9f1dcbc35c350d60 0 ZEROS.BIN
+v237b_def.uc2 40aff2e9d2d8922e 0 ALLBYTES.BIN
+v237b_def.uc2 c2e59f96175d054a 0 SOUND.WAV
+v237b_lfn.uc2 45657ab604a2607f 0 A long file name.txt
+v237b_lfn.uc2 5e0449c363c3edd1 0 lower.txt
+v237b_lfn.uc2 bbe2f841ea9788d9 0 multi.part.name.txt
+v237b_lfn.uc2 52b46cae40507d13 0 UPPER.TXT
+v237b_lfn.uc2 a23caf9bd14c14ee 0 Café crème.txt
+v237b_lfn.uc2 dcadeab3cdface01 0 Long Directory Name/Another Long Name.dat
+v237b_prot.uc2 fe698381b5867e9a 0 HELLO.TXT
+v237b_prot.uc2 e3b0c44298fc1c14 0 EMPTY.DAT
+v237b_prot.uc2 55e7449bfafaeb29 0 TEXT.TXT
+v237b_prot.uc2 6ac9c2f2f37dffed 0 SOURCE.C
+v237b_prot.uc2 7def5a7e066a3a2e 0 BINARY.BIN
+v237b_prot.uc2 ce7d9ba00521670a 0 RANDOM.BIN
+v237b_prot.uc2 9f1dcbc35c350d60 0 ZEROS.BIN
+v237b_prot.uc2 40aff2e9d2d8922e 0 ALLBYTES.BIN
+v237b_prot.uc2 c2e59f96175d054a 0 SOUND.WAV
+v237b_prot.uc2 c99fcad02327e45f 0 SUB/NESTED.TXT
+v237b_prot.uc2 f146e9b5474872cc 0 SUB/DEEP/DEEPER.TXT
+v237b_rev.uc2 fbdf08b036b1d798 2 VERS.TXT
+v237b_rev.uc2 b024953a6dc0f1d0 0 ÉTÉ.TXT
+v237b_rev.uc2 304d27f074f1fb66 1 VERS.TXT
+v237b_rev.uc2 f3adfa173b453df1 0 NEW.TXT
+v237b_rev.uc2 aa08feae8030e6d7 0 VERS.TXT
+v237b_tf.uc2 fe698381b5867e9a 0 HELLO.TXT
+v237b_tf.uc2 e3b0c44298fc1c14 0 EMPTY.DAT
+v237b_tf.uc2 55e7449bfafaeb29 0 TEXT.TXT
+v237b_tf.uc2 6ac9c2f2f37dffed 0 SOURCE.C
+v237b_tf.uc2 7def5a7e066a3a2e 0 BINARY.BIN
+v237b_tf.uc2 ce7d9ba00521670a 0 RANDOM.BIN
+v237b_tf.uc2 9f1dcbc35c350d60 0 ZEROS.BIN
+v237b_tf.uc2 40aff2e9d2d8922e 0 ALLBYTES.BIN
+v237b_tf.uc2 c2e59f96175d054a 0 SOUND.WAV
+v237b_tf.uc2 c99fcad02327e45f 0 SUB/NESTED.TXT
+v237b_tf.uc2 f146e9b5474872cc 0 SUB/DEEP/DEEPER.TXT
+v237b_tn.uc2 fe698381b5867e9a 0 HELLO.TXT
+v237b_tn.uc2 e3b0c44298fc1c14 0 EMPTY.DAT
+v237b_tn.uc2 55e7449bfafaeb29 0 TEXT.TXT
+v237b_tn.uc2 6ac9c2f2f37dffed 0 SOURCE.C
+v237b_tn.uc2 7def5a7e066a3a2e 0 BINARY.BIN
+v237b_tn.uc2 ce7d9ba00521670a 0 RANDOM.BIN
+v237b_tn.uc2 9f1dcbc35c350d60 0 ZEROS.BIN
+v237b_tn.uc2 40aff2e9d2d8922e 0 ALLBYTES.BIN
+v237b_tn.uc2 c2e59f96175d054a 0 SOUND.WAV
+v237b_tn.uc2 c99fcad02327e45f 0 SUB/NESTED.TXT
+v237b_tn.uc2 f146e9b5474872cc 0 SUB/DEEP/DEEPER.TXT
+v237b_tst.uc2 fe698381b5867e9a 0 HELLO.TXT
+v237b_tst.uc2 e3b0c44298fc1c14 0 EMPTY.DAT
+v237b_tst.uc2 55e7449bfafaeb29 0 TEXT.TXT
+v237b_tst.uc2 6ac9c2f2f37dffed 0 SOURCE.C
+v237b_tst.uc2 7def5a7e066a3a2e 0 BINARY.BIN
+v237b_tst.uc2 ce7d9ba00521670a 0 RANDOM.BIN
+v237b_tst.uc2 9f1dcbc35c350d60 0 ZEROS.BIN
+v237b_tst.uc2 40aff2e9d2d8922e 0 ALLBYTES.BIN
+v237b_tst.uc2 c2e59f96175d054a 0 SOUND.WAV
+v237b_tst.uc2 c99fcad02327e45f 0 SUB/NESTED.TXT
+v237b_tst.uc2 f146e9b5474872cc 0 SUB/DEEP/DEEPER.TXT
+v237b_tt.uc2 fe698381b5867e9a 0 HELLO.TXT
+v237b_tt.uc2 e3b0c44298fc1c14 0 EMPTY.DAT
+v237b_tt.uc2 55e7449bfafaeb29 0 TEXT.TXT
+v237b_tt.uc2 6ac9c2f2f37dffed 0 SOURCE.C
+v237b_tt.uc2 7def5a7e066a3a2e 0 BINARY.BIN
+v237b_tt.uc2 ce7d9ba00521670a 0 RANDOM.BIN
+v237b_tt.uc2 9f1dcbc35c350d60 0 ZEROS.BIN
+v237b_tt.uc2 40aff2e9d2d8922e 0 ALLBYTES.BIN
+v237b_tt.uc2 c2e59f96175d054a 0 SOUND.WAV
+v237b_tt.uc2 c99fcad02327e45f 0 SUB/NESTED.TXT
+v237b_tt.uc2 f146e9b5474872cc 0 SUB/DEEP/DEEPER.TXT
+v23_def.uc2 fe698381b5867e9a 0 HELLO.TXT
+v23_def.uc2 e3b0c44298fc1c14 0 EMPTY.DAT
+v23_def.uc2 55e7449bfafaeb29 0 TEXT.TXT
+v23_def.uc2 6ac9c2f2f37dffed 0 SOURCE.C
+v23_def.uc2 7def5a7e066a3a2e 0 BINARY.BIN
+v23_def.uc2 ce7d9ba00521670a 0 RANDOM.BIN
+v23_def.uc2 9f1dcbc35c350d60 0 ZEROS.BIN
+v23_def.uc2 40aff2e9d2d8922e 0 ALLBYTES.BIN
+v23_def.uc2 c2e59f96175d054a 0 SOUND.WAV
+v23_prot.uc2 fe698381b5867e9a 0 HELLO.TXT
+v23_prot.uc2 e3b0c44298fc1c14 0 EMPTY.DAT
+v23_prot.uc2 55e7449bfafaeb29 0 TEXT.TXT
+v23_prot.uc2 6ac9c2f2f37dffed 0 SOURCE.C
+v23_prot.uc2 7def5a7e066a3a2e 0 BINARY.BIN
+v23_prot.uc2 ce7d9ba00521670a 0 RANDOM.BIN
+v23_prot.uc2 9f1dcbc35c350d60 0 ZEROS.BIN
+v23_prot.uc2 40aff2e9d2d8922e 0 ALLBYTES.BIN
+v23_prot.uc2 c2e59f96175d054a 0 SOUND.WAV
+v23_prot.uc2 c99fcad02327e45f 0 SUB/NESTED.TXT
+v23_prot.uc2 f146e9b5474872cc 0 SUB/DEEP/DEEPER.TXT
+v23_rev.uc2 fbdf08b036b1d798 2 VERS.TXT
+v23_rev.uc2 b024953a6dc0f1d0 0 ÉTÉ.TXT
+v23_rev.uc2 304d27f074f1fb66 1 VERS.TXT
+v23_rev.uc2 f3adfa173b453df1 0 NEW.TXT
+v23_rev.uc2 aa08feae8030e6d7 0 VERS.TXT
+v23_tf.uc2 fe698381b5867e9a 0 HELLO.TXT
+v23_tf.uc2 e3b0c44298fc1c14 0 EMPTY.DAT
+v23_tf.uc2 55e7449bfafaeb29 0 TEXT.TXT
+v23_tf.uc2 6ac9c2f2f37dffed 0 SOURCE.C
+v23_tf.uc2 7def5a7e066a3a2e 0 BINARY.BIN
+v23_tf.uc2 ce7d9ba00521670a 0 RANDOM.BIN
+v23_tf.uc2 9f1dcbc35c350d60 0 ZEROS.BIN
+v23_tf.uc2 40aff2e9d2d8922e 0 ALLBYTES.BIN
+v23_tf.uc2 c2e59f96175d054a 0 SOUND.WAV
+v23_tf.uc2 c99fcad02327e45f 0 SUB/NESTED.TXT
+v23_tf.uc2 f146e9b5474872cc 0 SUB/DEEP/DEEPER.TXT
+v23_tn.uc2 fe698381b5867e9a 0 HELLO.TXT
+v23_tn.uc2 e3b0c44298fc1c14 0 EMPTY.DAT
+v23_tn.uc2 55e7449bfafaeb29 0 TEXT.TXT
+v23_tn.uc2 6ac9c2f2f37dffed 0 SOURCE.C
+v23_tn.uc2 7def5a7e066a3a2e 0 BINARY.BIN
+v23_tn.uc2 ce7d9ba00521670a 0 RANDOM.BIN
+v23_tn.uc2 9f1dcbc35c350d60 0 ZEROS.BIN
+v23_tn.uc2 40aff2e9d2d8922e 0 ALLBYTES.BIN
+v23_tn.uc2 c2e59f96175d054a 0 SOUND.WAV
+v23_tn.uc2 c99fcad02327e45f 0 SUB/NESTED.TXT
+v23_tn.uc2 f146e9b5474872cc 0 SUB/DEEP/DEEPER.TXT
+v23_tst.uc2 fe698381b5867e9a 0 HELLO.TXT
+v23_tst.uc2 e3b0c44298fc1c14 0 EMPTY.DAT
+v23_tst.uc2 55e7449bfafaeb29 0 TEXT.TXT
+v23_tst.uc2 6ac9c2f2f37dffed 0 SOURCE.C
+v23_tst.uc2 7def5a7e066a3a2e 0 BINARY.BIN
+v23_tst.uc2 ce7d9ba00521670a 0 RANDOM.BIN
+v23_tst.uc2 9f1dcbc35c350d60 0 ZEROS.BIN
+v23_tst.uc2 40aff2e9d2d8922e 0 ALLBYTES.BIN
+v23_tst.uc2 c2e59f96175d054a 0 SOUND.WAV
+v23_tst.uc2 c99fcad02327e45f 0 SUB/NESTED.TXT
+v23_tst.uc2 f146e9b5474872cc 0 SUB/DEEP/DEEPER.TXT
+v23_tt.uc2 fe698381b5867e9a 0 HELLO.TXT
+v23_tt.uc2 e3b0c44298fc1c14 0 EMPTY.DAT
+v23_tt.uc2 55e7449bfafaeb29 0 TEXT.TXT
+v23_tt.uc2 6ac9c2f2f37dffed 0 SOURCE.C
+v23_tt.uc2 7def5a7e066a3a2e 0 BINARY.BIN
+v23_tt.uc2 ce7d9ba00521670a 0 RANDOM.BIN
+v23_tt.uc2 9f1dcbc35c350d60 0 ZEROS.BIN
+v23_tt.uc2 40aff2e9d2d8922e 0 ALLBYTES.BIN
+v23_tt.uc2 c2e59f96175d054a 0 SOUND.WAV
+v23_tt.uc2 c99fcad02327e45f 0 SUB/NESTED.TXT
+v23_tt.uc2 f146e9b5474872cc 0 SUB/DEEP/DEEPER.TXT
diff --git a/Compression.Tests/Uc2/ReferenceVectors/v237b_lfn.uc2 b/Compression.Tests/Uc2/ReferenceVectors/v237b_lfn.uc2
new file mode 100644
index 000000000..6e7e374f5
Binary files /dev/null and b/Compression.Tests/Uc2/ReferenceVectors/v237b_lfn.uc2 differ
diff --git a/Compression.Tests/Uc2/ReferenceVectors/v237b_tt.uc2 b/Compression.Tests/Uc2/ReferenceVectors/v237b_tt.uc2
new file mode 100644
index 000000000..8015334b0
Binary files /dev/null and b/Compression.Tests/Uc2/ReferenceVectors/v237b_tt.uc2 differ
diff --git a/Compression.Tests/Uc2/ReferenceVectors/v23_def.uc2 b/Compression.Tests/Uc2/ReferenceVectors/v23_def.uc2
new file mode 100644
index 000000000..b389fbda5
Binary files /dev/null and b/Compression.Tests/Uc2/ReferenceVectors/v23_def.uc2 differ
diff --git a/Compression.Tests/Uc2/Uc2Tests.cs b/Compression.Tests/Uc2/Uc2Tests.cs
new file mode 100644
index 000000000..a07412ca9
--- /dev/null
+++ b/Compression.Tests/Uc2/Uc2Tests.cs
@@ -0,0 +1,216 @@
+using System.Reflection;
+using System.Security.Cryptography;
+using Compression.Registry;
+using FileFormat.Uc2;
+
+namespace Compression.Tests.Uc2;
+
+///
+/// UltraCompressor II: archives the original UC2 r2 / 2.3 / 2.37b wrote (Uc2/ReferenceVectors),
+/// the format's golden vectors, our writer, and unuc2 (an independent decoder) as the oracle for
+/// what we write.
+///
+[TestFixture]
+public class Uc2Tests {
+
+ private string _tmpDir = null!;
+
+ [SetUp]
+ public void Setup() {
+ this._tmpDir = Path.Combine(Path.GetTempPath(), $"cwb_uc2_{Guid.NewGuid():N}");
+ Directory.CreateDirectory(this._tmpDir);
+ }
+
+ [TearDown]
+ public void Teardown() {
+ try { Directory.Delete(this._tmpDir, true); } catch { /* best effort */ }
+ }
+
+ // ── checksum and bitstream vectors (format.md §5, §6.10) ────────────
+
+ [TestCase("", 0xA55A)]
+ [TestCase("a", 0xA53B)]
+ [TestCase("ab", 0xC73B)]
+ [TestCase("abc", 0xC758)]
+ [TestCase("abcd", 0xA358)]
+ [TestCase("123456789", 0xAD63)]
+ [Category("HappyPath")]
+ public void GivenKnownInput_WhenChecksummed_ThenMatchesTheFormatsVector(string input, int expected)
+ => Assert.That(Uc2Archive.Fletch(System.Text.Encoding.ASCII.GetBytes(input)), Is.EqualTo((ushort)expected));
+
+ [Test, Category("HappyPath")]
+ public void GivenTheEmptyRawCdir_WhenChecksummed_ThenIsA55E() {
+ byte[] raw = [0x04, .. new byte[21]];
+ Assert.That(Uc2Archive.Fletch(raw), Is.EqualTo((ushort)0xA55E));
+ }
+
+ [Test, Category("HappyPath")]
+ public void GivenTheGoldenStreamForA_WhenDecodedWithNoMaster_ThenYieldsA() {
+ byte[] stream = [0xBF, 0xA0, 0x01, 0x7A, 0x00, 0x00];
+ Assert.That(Uc2Ultra.Decode(stream, 0, stream.Length, new byte[512], 1), Is.EqualTo("A"u8.ToArray()));
+ }
+
+ [Test, Category("HappyPath")]
+ public void GivenUc2sEmptyCdirStream_WhenDecodedToItsEnd_ThenYieldsTheEmptyCdir() {
+ byte[] stream = [0x09, 0xB2, 0xCF, 0xD2, 0x80, 0xDE, 0x00, 0x40];
+ byte[] expected = [0x04, .. new byte[21]];
+ Assert.That(Uc2Ultra.Decode(stream, 0, stream.Length, new byte[512], -1), Is.EqualTo(expected));
+ }
+
+ [Test, Category("HappyPath")]
+ public void GivenA_WhenEncodedWithDefaultTrees_ThenProducesTheGoldenStream() {
+ Assert.That(Uc2Ultra.Encode("A"u8, new byte[512]), Is.EqualTo(new byte[] { 0xBF, 0xA0, 0x01, 0x7A, 0x00, 0x00 }));
+ Assert.That(Uc2Ultra.Encode([], new byte[512]), Is.EqualTo(new byte[] { 0x00, 0x00 }));
+ }
+
+ // ── archives written by the original UC2 ────────────────────────────
+
+ private static readonly string[] Vectors = ["r2_def.uc2", "r2_prot.uc2", "r2_rev.uc2", "r2_tf.uc2", "r2_tst.uc2", "r2_tt.uc2", "v23_def.uc2", "v237b_lfn.uc2", "v237b_tt.uc2"];
+
+ [TestCaseSource(nameof(Vectors)), Category("ThemVsUs")]
+ public void GivenArchiveWrittenByUc2_WhenEveryFileIsExtracted_ThenItsSha256MatchesTheRecordedSum(string vector) {
+ var expected = Sums().Where(s => s.Archive == vector && s.Revision == 0).ToDictionary(s => s.Name, s => s.Sha8);
+ Assert.That(expected, Is.Not.Empty);
+ var archive = Uc2Archive.Read(new MemoryStream(LoadVector(vector)));
+ var files = archive.Entries.Where(e => !e.IsDirectory).ToList();
+
+ Assert.That(files.Select(e => e.Path), Is.EquivalentTo(expected.Keys));
+ foreach (var e in files)
+ Assert.That(Convert.ToHexStringLower(SHA256.HashData(Uc2Archive.Extract(archive, e)))[..16], Is.EqualTo(expected[e.Path]), $"{vector}: {e.Path}");
+ }
+
+ [Test, Category("ThemVsUs")]
+ public void GivenRevisionedArchive_WhenListed_ThenOnlyTheNewestRevisionAppears() {
+ var archive = Uc2Archive.Read(new MemoryStream(LoadVector("r2_rev.uc2")));
+ var newest = Sums().Single(s => s.Archive == "r2_rev.uc2" && s.Name == "VERS.TXT" && s.Revision == 0);
+ var e = archive.Entries.Single(x => x.Path == "VERS.TXT");
+ Assert.That(Convert.ToHexStringLower(SHA256.HashData(Uc2Archive.Extract(archive, e)))[..16], Is.EqualTo(newest.Sha8));
+ }
+
+ [Test, Category("Exception")]
+ public void GivenDamagedFileStream_WhenExtracted_ThenTheChecksumRefusesIt() {
+ var bytes = LoadVector("r2_def.uc2");
+ var probe = Uc2Archive.Read(new MemoryStream(bytes));
+ var text = probe.Entries.Single(e => e.Path == "TEXT.TXT");
+ // The first stream bit (MSB of the first word's high byte) says "a block follows";
+ // clearing it ends the stream before the recorded size.
+ bytes[text.Offset + 1] ^= 0x80;
+
+ var archive = Uc2Archive.Read(new MemoryStream(bytes));
+ Assert.That(() => Uc2Archive.Extract(archive, archive.Entries.Single(e => e.Path == "TEXT.TXT")), Throws.InstanceOf());
+ }
+
+ [Test, Category("Exception")]
+ public void GivenDamagedHeader_WhenRead_ThenRefused() {
+ var bytes = LoadVector("r2_def.uc2");
+ bytes[0] = (byte)'X';
+ Assert.That(() => Uc2Archive.Read(new MemoryStream(bytes)), Throws.InstanceOf());
+ }
+
+ [Test, Category("Exception")]
+ public void GivenUltraCryptArchive_WhenRead_ThenRefusedAsUnsupported() {
+ byte[] bytes = [(byte)'U', (byte)'E', (byte)'2', .. new byte[40]];
+ Assert.That(() => Uc2Archive.Read(new MemoryStream(bytes)), Throws.InstanceOf());
+ }
+
+ // ── our writer ──────────────────────────────────────────────────────
+
+ private static List SampleInputs() {
+ var text = System.Text.Encoding.ASCII.GetBytes(string.Concat(Enumerable.Repeat("The quick brown fox jumps over the lazy dog. ", 400)));
+ var noise = new byte[70_000];
+ new Random(7).NextBytes(noise);
+ var t = new DateTime(2001, 2, 3, 4, 5, 6, DateTimeKind.Local);
+ return [
+ new("HELLO.TXT", "hello uc2\r\n"u8.ToArray(), t),
+ new("Long File Name.text", text, t),
+ new("sub/deeper/noise.bin", noise, t),
+ new("sub/empty.dat", [], t),
+ new("zeros.bin", new byte[100_000], t),
+ ];
+ }
+
+ private byte[] WriteSample() {
+ using var ms = new MemoryStream();
+ Uc2Archive.Write(ms, SampleInputs());
+ return ms.ToArray();
+ }
+
+ [Test, Category("RoundTrip")]
+ public void GivenFilesAndFolders_WhenWrittenAndRead_ThenPathsAndBytesRoundTrip() {
+ var archive = Uc2Archive.Read(new MemoryStream(this.WriteSample()));
+ foreach (var input in SampleInputs()) {
+ var e = archive.Entries.Single(x => x.Path == input.Path);
+ Assert.That(Uc2Archive.Extract(archive, e), Is.EqualTo(input.Data), input.Path);
+ Assert.That(e.LastModified, Is.EqualTo(input.LastModified));
+ }
+ Assert.That(archive.Entries.Where(e => e.IsDirectory).Select(e => e.Path), Is.EquivalentTo(new[] { "sub", "sub/deeper" }));
+ }
+
+ [Test, Category("Boundary")]
+ public void GivenNoFiles_WhenWritten_ThenAnEmptyArchiveReadsBack() {
+ using var ms = new MemoryStream();
+ Uc2Archive.Write(ms, []);
+ Assert.That(Uc2Archive.Read(new MemoryStream(ms.ToArray())).Entries, Is.Empty);
+ }
+
+ [Test, Category("Boundary")]
+ public void GivenNamesThatCollideIn83_WhenWritten_ThenEachKeepsItsOwnContent() {
+ var t = DateTime.Now;
+ using var ms = new MemoryStream();
+ Uc2Archive.Write(ms, [new("readme.first.txt", [1], t), new("readme.second.txt", [2], t), new("README.TXT", [3], t)]);
+ var archive = Uc2Archive.Read(new MemoryStream(ms.ToArray()));
+ Assert.That(Uc2Archive.Extract(archive, archive.Entries.Single(e => e.Path == "readme.first.txt")), Is.EqualTo(new byte[] { 1 }));
+ Assert.That(Uc2Archive.Extract(archive, archive.Entries.Single(e => e.Path == "readme.second.txt")), Is.EqualTo(new byte[] { 2 }));
+ Assert.That(Uc2Archive.Extract(archive, archive.Entries.Single(e => e.Path == "README.TXT")), Is.EqualTo(new byte[] { 3 }));
+ Assert.That(archive.Entries.Select(e => e.ShortName).Distinct().Count(), Is.EqualTo(3));
+ }
+
+ [Test, Category("Exception")]
+ public void GivenPassword_WhenCreated_ThenRefused() {
+ Assert.That(() => new Uc2FormatDescriptor().Create(new MemoryStream(), [ArchiveInputInfo.InMemory("a", [1])], new FormatCreateOptions { Password = "x" }),
+ Throws.InstanceOf());
+ }
+
+ [Test, Category("ExternalInterop")]
+ public void GivenOurArchive_WhenUnuc2ExtractsIt_ThenEveryFileMatches() {
+ if (OperatingSystem.IsWindows() && !FsInteropToolbox.WslAvailable) Assert.Ignore("WSL is not available.");
+ if (!FsInteropToolbox.WslHasTool("unuc2")) Assert.Ignore("unuc2 (github.com/jan-bobrowski/unuc2) is not installed.");
+ var path = Path.Combine(this._tmpDir, "OURS.UC2");
+ File.WriteAllBytes(path, this.WriteSample());
+ var outDir = Path.Combine(this._tmpDir, "out");
+ Directory.CreateDirectory(outDir);
+
+ var t = FsInteropToolbox.RunWsl($"unuc2 -t '{FsInteropToolbox.WinToWsl(path)}'");
+ Assert.That(t.ExitCode, Is.EqualTo(0), $"unuc2 -t rejected our archive:\n{t.StdOut}\n{t.StdErr}");
+ var r = FsInteropToolbox.RunWsl($"unuc2 -f -d '{FsInteropToolbox.WinToWsl(outDir)}' '{FsInteropToolbox.WinToWsl(path)}'");
+ Assert.That(r.ExitCode, Is.EqualTo(0), $"unuc2 rejected our archive:\n{r.StdOut}\n{r.StdErr}");
+ var archive = Uc2Archive.Read(File.OpenRead(path));
+ foreach (var input in SampleInputs()) {
+ // unuc2 extracts under the 8.3 names, or the long name where the archive records one.
+ var candidates = Directory.EnumerateFiles(outDir, "*", SearchOption.AllDirectories)
+ .Where(f => string.Equals(Path.GetFileName(f), Path.GetFileName(input.Path), StringComparison.OrdinalIgnoreCase)
+ || string.Equals(Path.GetFileName(f), archive.Entries.Single(e => e.Path == input.Path).ShortName, StringComparison.OrdinalIgnoreCase))
+ .ToList();
+ Assert.That(candidates, Is.Not.Empty, $"unuc2 produced no {input.Path}");
+ Assert.That(candidates.Any(c => File.ReadAllBytes(c).AsSpan().SequenceEqual(input.Data)), $"{input.Path} differs");
+ }
+ }
+
+ // ── helpers ─────────────────────────────────────────────────────────
+
+ private static IEnumerable<(string Archive, string Sha8, int Revision, string Name)> Sums() {
+ var text = System.Text.Encoding.UTF8.GetString(LoadVector("sums.txt"));
+ foreach (var line in text.Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) {
+ var parts = line.Split(' ', 4);
+ yield return (parts[0], parts[1], int.Parse(parts[2], System.Globalization.CultureInfo.InvariantCulture), parts[3]);
+ }
+ }
+
+ private static byte[] LoadVector(string name) {
+ using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream($"Uc2Vectors.{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/FileFormats/FileFormat.Uc2/THIRD-PARTY-NOTICE.UltraCompressor2.txt b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Uc2/THIRD-PARTY-NOTICE.UltraCompressor2.txt
new file mode 100644
index 000000000..b10546fff
--- /dev/null
+++ b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Uc2/THIRD-PARTY-NOTICE.UltraCompressor2.txt
@@ -0,0 +1,21 @@
+UltraCompressor II support in this folder is converted from klauspost/uc2, a Go implementation of
+the UltraCompressor II archive format derived from the UC2 source code that its author released.
+
+Upstream: https://github.com/klauspost/uc2
+Revision: 53622f89ada1cf0d4a69f91b653bdd44ec5b0f92
+Files: internal/ultra/{ultra,decode,turbo}.go, internal/format/{format,cdir}.go, reader.go,
+ docs/format.md (the format specification the conversion follows)
+Licence: GNU Lesser General Public License v3.0 (LGPL-3.0); this repository is
+ LGPL-3.0-or-later.
+
+super.dat (embedded as FileFormat.Uc2.super.dat) is UltraCompressor II's built-in "supermaster"
+dictionary, 49,152 bytes, SHA-256 4f2e3fb48a288f66a76b0e26cb2830f3f0297f3686752853de65b82e646e840e,
+copied verbatim from internal/super/super.dat of the same revision. It is part of the
+UltraCompressor II release by Nico de Vries / Ad Infinitum Programs (AIP-NL), released under the
+GNU LGPL v3 in 2015.
+
+UltraCompressor II (c) 1994-1996 Ad Infinitum Programs (AIP-NL), Nico de Vries. Released under the
+GNU Lesser General Public License, version 3.
+
+The default code lengths, the delta-code matrices and the distance/length tables are the format's
+own constants; the encoder in Uc2Ultra (default trees, greedy hash-chain matcher) is original.
diff --git a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Uc2/Uc2Archive.cs b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Uc2/Uc2Archive.cs
new file mode 100644
index 000000000..8bb032b64
--- /dev/null
+++ b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Uc2/Uc2Archive.cs
@@ -0,0 +1,543 @@
+using System.Buffers.Binary;
+using System.Text;
+
+namespace FileFormat.Uc2;
+
+/// One file or directory of a UC2 archive (the newest revision of its name).
+public sealed class Uc2Entry {
+ /// Gets the path inside the archive, '/'-separated (long names where the archive records them).
+ public string Path { get; init; } = "";
+ /// Gets the DOS 8.3 name as stored.
+ public string ShortName { get; init; } = "";
+ /// Gets whether this is a directory.
+ public bool IsDirectory { get; init; }
+ /// Gets the uncompressed size.
+ public long Size { get; init; }
+ /// Gets the compressed stream size (excluding its master).
+ public long CompressedSize { get; init; }
+ /// Gets the compression method number.
+ public int Method { get; init; }
+ /// Gets the DOS attribute byte.
+ public byte Attributes { get; init; }
+ /// Gets the modification time (DOS local time, 2-second resolution).
+ public DateTime? LastModified { get; init; }
+
+ internal uint MasterPrefix { get; init; }
+ internal long Offset { get; init; }
+ internal ushort Fletch { get; init; }
+}
+
+///
+/// UltraCompressor II (.uc2) archives: FHEAD + XHEAD, headerless compressed streams for files and
+/// masters (shared dictionaries), and a compressed central directory; every stream is checked with
+/// UC2's 16-bit XOR checksum. Reads every UC2 release's archives (methods 1-9, 21-29, 30-37,
+/// 40-47 and turbo 80; supermaster, no-master and custom masters; delta filter) and writes archives
+/// UC2 r2 reads: one 512-byte custom master compressed with the supermaster, files with method 3.
+///
+///
+/// Converted from klauspost/uc2 (LGPL-3.0), whose format.md documents the format from the
+/// published UC2 r2 source; SUPER.DAT is the supermaster from that LGPL-3.0 UC2 release.
+///
+public static class Uc2Archive {
+ private const uint Magic = 0x1A324355; // "UC2\x1A"
+ private const uint AMag = 0x01B2C3D4;
+ private const int FHeadSize = 13, XHeadSize = 16, CompressSize = 10;
+ private const uint SuperMaster = 0, NoMaster = 1, LegacyPrefix = 0xDEDEDEDE;
+ private const byte BoDir = 1, BoFile = 2, BoMast = 3, BoEol = 4;
+ private const int MetaSize = 22, ExtSize = 21;
+ private const string LongNameTag = "AIP:Win95 LongN";
+ private const string Utf8NameTag = "UC2X:UTF8Name";
+
+ private static readonly Lazy SuperDat = new(LoadSuper);
+
+ private static byte[] LoadSuper() {
+ using var s = typeof(Uc2Archive).Assembly.GetManifestResourceStream("FileFormat.Uc2.super.dat")
+ ?? throw new InvalidOperationException("UC2 supermaster resource is missing.");
+ var b = new byte[s.Length];
+ s.ReadExactly(b);
+ if (Fletch(b) != 0x1E55) throw new InvalidOperationException("UC2 supermaster resource is damaged.");
+ return b;
+ }
+
+ private static readonly Lazy OemEncoding = new(() => {
+ Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
+ return Encoding.GetEncoding(437); // UC2 does not record the code page; CP437 is the DOS default
+ });
+
+ private static Encoding Oem => OemEncoding.Value;
+
+ /// UC2's "Fletcher": 0xA55A XOR the XOR of all little-endian 16-bit words (odd tail byte = low byte).
+ public static ushort Fletch(ReadOnlySpan data) {
+ ushort x = 0;
+ var i = 0;
+ for (; i + 1 < data.Length; i += 2) x ^= (ushort)(data[i] | (data[i + 1] << 8));
+ if (i < data.Length) x ^= data[i];
+ return (ushort)(0xA55A ^ x);
+ }
+
+ // ── reading ─────────────────────────────────────────────────────────
+
+ /// A parsed archive: entries plus what is needed to extract them.
+ public sealed class Parsed {
+ internal Parsed(byte[] data, List entries, Dictionary masters) {
+ this.Data = data; this.Entries = entries; this.Masters = masters;
+ }
+ internal byte[] Data { get; }
+ /// Gets the entries, directories included.
+ public IReadOnlyList Entries { get; }
+ internal Dictionary Masters { get; }
+ internal Dictionary MasterCache { get; } = [];
+ }
+
+ /// Parses an archive and verifies its central directory.
+ /// Not a UC2 archive, or its directory is damaged.
+ /// UltraCrypt-encrypted or private-profile (PCP) archive.
+ public static Parsed Read(Stream stream) {
+ ArgumentNullException.ThrowIfNull(stream);
+ if (stream.CanSeek) stream.Position = 0;
+ using var ms = new MemoryStream();
+ stream.CopyTo(ms);
+ var data = ms.ToArray();
+ if (data.Length >= 3 && data[0] == 'U' && data[1] == 'E' && data[2] == '2')
+ throw new NotSupportedException("UC2 archive is encrypted with UltraCrypt.");
+ if (data.Length < FHeadSize + XHeadSize || BinaryPrimitives.ReadUInt32LittleEndian(data) != Magic)
+ throw new InvalidDataException("Not an UltraCompressor II archive.");
+ var compLen = BinaryPrimitives.ReadUInt32LittleEndian(data.AsSpan(4));
+ if (BinaryPrimitives.ReadUInt32LittleEndian(data.AsSpan(8)) - AMag != compLen) {
+ // the spare FHEAD at the end carries the same values
+ var spare = data.AsSpan(data.Length - FHeadSize);
+ if (BinaryPrimitives.ReadUInt32LittleEndian(spare) != Magic || BinaryPrimitives.ReadUInt32LittleEndian(spare[8..]) - AMag != BinaryPrimitives.ReadUInt32LittleEndian(spare[4..]))
+ throw new InvalidDataException("UC2 header length check fails.");
+ }
+ var x = data.AsSpan(FHeadSize, XHeadSize);
+ var cdirVol = BinaryPrimitives.ReadUInt32LittleEndian(x);
+ var cdirOff = BinaryPrimitives.ReadUInt32LittleEndian(x[4..]);
+ var cdirFletch = BinaryPrimitives.ReadUInt16LittleEndian(x[8..]);
+ var needed = BinaryPrimitives.ReadUInt16LittleEndian(x[13..]);
+ if (needed == 203) throw new NotSupportedException("UC2 2.3 private compression profile (PCP) archives are not supported.");
+ if (needed > 204) throw new NotSupportedException($"UC2 archive needs UltraCompressor version {needed}.");
+ long cdirAt = needed == 204 ? ((long)(cdirVol - 1) << 32) | cdirOff : cdirOff;
+ if (needed != 204 && cdirVol != 1 || cdirAt < FHeadSize + XHeadSize || cdirAt + CompressSize > data.Length)
+ throw new InvalidDataException("UC2 central directory location is invalid.");
+ var cdirMethod = BinaryPrimitives.ReadUInt16LittleEndian(data.AsSpan((int)cdirAt + 4));
+ if (cdirMethod is < 1 or > 9)
+ throw new InvalidDataException($"UC2 central directory method {cdirMethod} is invalid.");
+ var start = (int)cdirAt + CompressSize;
+ var raw = Uc2Ultra.Decode(data, start, data.Length - start, new byte[512], -1);
+ if (Fletch(raw) != cdirFletch)
+ throw new InvalidDataException("UC2 central directory fails its checksum.");
+ return ParseCdir(data, raw, needed == 204);
+ }
+
+ private static Parsed ParseCdir(byte[] data, byte[] raw, bool extended) {
+ var p = 0;
+ ReadOnlySpan Take(int n) {
+ if (p + n > raw.Length) throw new InvalidDataException("UC2 central directory is truncated.");
+ var s = raw.AsSpan(p, n);
+ p += n;
+ return s;
+ }
+ long Loc(ReadOnlySpan b) {
+ var vol = BinaryPrimitives.ReadUInt32LittleEndian(b);
+ var off = BinaryPrimitives.ReadUInt32LittleEndian(b[4..]);
+ return extended ? ((long)(vol - 1) << 32) | off : off;
+ }
+
+ var dirs = new Dictionary { [0] = "" };
+ var masters = new Dictionary();
+ var byPath = new Dictionary(StringComparer.Ordinal); // newest revision wins
+ var order = new List();
+ while (true) {
+ var type = Take(1)[0];
+ if (type == BoEol) break;
+ if (type is BoDir or BoFile) {
+ var m = Take(MetaSize);
+ var parent = BinaryPrimitives.ReadUInt32LittleEndian(m);
+ var attr = m[4];
+ var time = BinaryPrimitives.ReadUInt16LittleEndian(m[5..]);
+ var date = BinaryPrimitives.ReadUInt16LittleEndian(m[7..]);
+ var shortName = ShortName(m.Slice(9, 11));
+ var hasTags = m[21] != 0;
+ uint index = 0, size = 0, prefix = 0;
+ ushort fletch = 0;
+ int method = 0;
+ long compLen = 0, offset = 0;
+ if (type == BoDir) {
+ index = BinaryPrimitives.ReadUInt32LittleEndian(Take(4));
+ } else {
+ var f = Take(6 + CompressSize + 8);
+ size = BinaryPrimitives.ReadUInt32LittleEndian(f);
+ fletch = BinaryPrimitives.ReadUInt16LittleEndian(f[4..]);
+ compLen = BinaryPrimitives.ReadUInt32LittleEndian(f[6..]);
+ method = BinaryPrimitives.ReadUInt16LittleEndian(f[10..]);
+ prefix = BinaryPrimitives.ReadUInt32LittleEndian(f[12..]);
+ offset = Loc(f[16..]);
+ }
+ string? longName = null;
+ long size64 = size;
+ while (hasTags) {
+ var h = Take(ExtSize);
+ var tagSize = BinaryPrimitives.ReadUInt32LittleEndian(h[16..]);
+ if (tagSize > 1_000_000) throw new InvalidDataException("UC2 tag is implausibly large.");
+ var nul = h[..16].IndexOf((byte)0);
+ var tagName = Encoding.ASCII.GetString(h[..(nul < 0 ? 16 : nul)]);
+ var payload = Take((int)tagSize);
+ if (tagName == Utf8NameTag) longName = Encoding.UTF8.GetString(payload);
+ else if (tagName == LongNameTag && longName is null) {
+ var z = payload.IndexOf((byte)0);
+ longName = Oem.GetString(payload[..(z < 0 ? payload.Length : z)]);
+ } else if (tagName == "UC2X:Size64" && payload.Length == 8) size64 = BinaryPrimitives.ReadInt64LittleEndian(payload);
+ hasTags = h[20] != 0;
+ }
+ if (!dirs.TryGetValue(parent, out var parentPath))
+ throw new InvalidDataException($"UC2 record refers to undefined directory {parent}.");
+ var leaf = Sanitize(longName ?? shortName);
+ var path = parentPath.Length == 0 ? leaf : parentPath + "/" + leaf;
+ if (type == BoDir) dirs[index] = path;
+ if (!byPath.ContainsKey(path)) order.Add(path);
+ byPath[path] = new Uc2Entry {
+ Path = path, ShortName = shortName, IsDirectory = type == BoDir, Size = size64, CompressedSize = compLen,
+ Method = method, Attributes = attr, LastModified = DosTime(date, time), MasterPrefix = prefix, Offset = offset, Fletch = fletch,
+ };
+ continue;
+ }
+ if (type == BoMast) {
+ var m = Take(20 + CompressSize + 8);
+ var index = BinaryPrimitives.ReadUInt32LittleEndian(m);
+ var length = BinaryPrimitives.ReadUInt16LittleEndian(m[16..]);
+ var compLen = BinaryPrimitives.ReadUInt32LittleEndian(m[20..]);
+ var method = BinaryPrimitives.ReadUInt16LittleEndian(m[24..]);
+ var prefix = BinaryPrimitives.ReadUInt32LittleEndian(m[26..]);
+ masters[index] = (method, prefix, Loc(m[30..]), compLen, length);
+ continue;
+ }
+ throw new InvalidDataException($"UC2 central directory record type {type} is invalid.");
+ }
+ // Reserved U$~ members (comment, banners, locks) are UC2's own, not archive content.
+ var entries = order.Select(k => byPath[k]).Where(e => !e.ShortName.StartsWith("U$~", StringComparison.Ordinal)).ToList();
+ return new Parsed(data, entries, masters);
+ }
+
+ /// Extracts one file, verifying its checksum.
+ public static byte[] Extract(Parsed archive, Uc2Entry entry) {
+ ArgumentNullException.ThrowIfNull(archive);
+ ArgumentNullException.ThrowIfNull(entry);
+ if (entry.IsDirectory || entry.Size == 0) return [];
+ if (entry.MasterPrefix < 2 && entry.MasterPrefix != SuperMaster && entry.MasterPrefix != NoMaster)
+ throw new InvalidDataException($"'{entry.Path}' uses an invalid master.");
+ var dict = Dictionary(archive, entry.MasterPrefix);
+ return DecodeStream(archive.Data, entry.Offset, entry.CompressedSize, entry.Method, dict, entry.Size, entry.Fletch, entry.Path);
+ }
+
+ private static byte[] Dictionary(Parsed archive, uint prefix) {
+ switch (prefix) {
+ case SuperMaster or LegacyPrefix: return SuperDat.Value;
+ case NoMaster: return new byte[512];
+ }
+ if (archive.MasterCache.TryGetValue(prefix, out var cached)) return cached;
+ if (!archive.Masters.TryGetValue(prefix, out var m))
+ throw new InvalidDataException($"UC2 master {prefix} is missing.");
+ if (m.Prefix is not (SuperMaster or NoMaster or LegacyPrefix))
+ throw new InvalidDataException($"UC2 master {prefix} is chained to another master.");
+ var dict = m.Prefix == NoMaster ? new byte[512] : SuperDat.Value;
+ var content = DecodeStream(archive.Data, m.Offset, m.CompLen, m.Method, dict, m.Length, null, $"master {prefix}");
+ archive.MasterCache[prefix] = content;
+ return content;
+ }
+
+ private static (int Delta, bool Turbo) MethodInfo(int method) => method switch {
+ >= 1 and <= 9 => (0, false),
+ >= 21 and <= 29 => (1, false),
+ >= 30 and <= 37 => (method - 29, false),
+ >= 40 and <= 47 => (method - 39, false),
+ 80 => (0, true),
+ _ => throw new InvalidDataException($"UC2 method {method} is invalid."),
+ };
+
+ private static byte[] DecodeStream(byte[] data, long offset, long compLen, int method, byte[] dictionary, long size, ushort? fletch, string what) {
+ if (offset < 0 || offset >= data.Length) throw new InvalidDataException($"UC2 {what} points outside the archive.");
+ var available = (int)Math.Min(compLen > 0 ? compLen : data.Length - offset, data.Length - offset);
+ var (delta, turbo) = MethodInfo(method);
+ if (turbo) {
+ var t = DecodeTurbo(data, (int)offset, available, dictionary, size);
+ if (fletch is { } ft && Fletch(t) != ft) throw new InvalidDataException($"UC2 checksum mismatch for {what}.");
+ return t;
+ }
+ // For delta methods the dictionary (unless it is the supermaster) is delta'd too.
+ var dict = delta > 0 && !ReferenceEquals(dictionary, SuperDat.Value) ? ForwardDelta(dictionary, delta) : dictionary;
+ var output = Uc2Ultra.Decode(data, (int)offset, available, dict, size);
+ if (fletch is { } f && Fletch(output) != f) throw new InvalidDataException($"UC2 checksum mismatch for {what}.");
+ if (delta > 0) InverseDelta(output, delta);
+ return output;
+ }
+
+ private static byte[] ForwardDelta(byte[] input, int size) {
+ var o = new byte[input.Length];
+ Span arr = stackalloc byte[8];
+ arr.Clear();
+ var ctr = 0;
+ for (var i = 0; i < input.Length; ++i) {
+ o[i] = (byte)(input[i] - arr[ctr]);
+ arr[ctr] = input[i];
+ ctr = (ctr + 1) % size;
+ }
+ return o;
+ }
+
+ private static void InverseDelta(byte[] buffer, int size) {
+ Span arr = stackalloc byte[8];
+ arr.Clear();
+ var ctr = 0;
+ for (var i = 0; i < buffer.Length; ++i) {
+ buffer[i] = (byte)(buffer[i] + arr[ctr]);
+ arr[ctr] = buffer[i];
+ ctr = (ctr + 1) % size;
+ }
+ }
+
+ private static byte[] DecodeTurbo(byte[] data, int offset, int length, byte[] dict, long size) {
+ var table = new byte[32768];
+ for (var i = 0; i + 10 < dict.Length; ++i) table[(dict[i] << 7) ^ dict[i + 1]] = dict[i + 2];
+ var output = new byte[size];
+ var o = 0;
+ byte p1 = 0, p2 = 0;
+ var pos = offset;
+ var end = offset + length;
+ while (o < size) {
+ if (pos + 2 > end) throw new InvalidDataException("UC2 turbo stream ended early.");
+ var n = BinaryPrimitives.ReadUInt16LittleEndian(data.AsSpan(pos));
+ pos += 2;
+ if (n == 0 || pos + n > end) throw new InvalidDataException("UC2 turbo stream ended early.");
+ var blockEnd = pos + n;
+ while (pos < blockEnd) {
+ int ctl = data[pos++];
+ for (var i = 0; i < 8 && (pos < blockEnd || (ctl & 0xFF) != 0); ++i) {
+ var h = (p2 << 7) ^ p1;
+ var c = table[h];
+ if ((ctl & 0x80) == 0) {
+ if (pos >= blockEnd) throw new InvalidDataException("UC2 turbo block is truncated.");
+ c = data[pos++];
+ table[h] = c;
+ }
+ if (o < size) output[o++] = c;
+ p2 = p1; p1 = c;
+ ctl = (ctl << 1) & 0xFF;
+ }
+ }
+ }
+ return output;
+ }
+
+ private static string ShortName(ReadOnlySpan fcb) {
+ var oem = Oem;
+ var name = oem.GetString(fcb[..8]).TrimEnd(' ');
+ var ext = oem.GetString(fcb[8..11]).TrimEnd(' ');
+ return ext.Length == 0 ? name : name + "." + ext;
+ }
+
+ private static string Sanitize(string name) {
+ var s = name.Replace('/', '_').Replace('\\', '_').Replace(':', '_');
+ return s is "" or "." or ".." ? "_" : s;
+ }
+
+ private static DateTime? DosTime(ushort date, ushort time) {
+ if (date == 0) return null;
+ try {
+ return new DateTime(1980 + (date >> 9), (date >> 5) & 15, date & 31, time >> 11, (time >> 5) & 63, (time & 31) * 2, DateTimeKind.Local);
+ } catch (ArgumentOutOfRangeException) {
+ return null;
+ }
+ }
+
+ // ── writing ─────────────────────────────────────────────────────────
+
+ /// A file to write: its archive path ('/'-separated), content and time.
+ public readonly record struct Input(string Path, byte[] Data, DateTime LastModified);
+
+ ///
+ /// Writes a UC2 archive that UC2 r2 reads (wVersionNeededToExtract 200): one custom master of
+ /// 512 zero bytes compressed with the supermaster, every file compressed with method 3 against
+ /// it, directories implied by the paths, 8.3 aliases plus "AIP:Win95 LongN" tags for names that
+ /// do not fit 8.3, and the CDIR compressed with no master.
+ ///
+ public static void Write(Stream output, IEnumerable inputs) {
+ ArgumentNullException.ThrowIfNull(output);
+ var files = inputs.ToList();
+ var body = new MemoryStream();
+ body.Write(new byte[FHeadSize + XHeadSize]);
+
+ // directories
+ var dirIndex = new Dictionary(StringComparer.Ordinal) { [""] = 0 };
+ var dirRecords = new List<(uint Parent, uint Index, string Leaf)>();
+ var aliases = new Dictionary>();
+ uint nextDir = 1;
+ uint DirOf(string dirPath) {
+ if (dirIndex.TryGetValue(dirPath, out var idx)) return idx;
+ var slash = dirPath.LastIndexOf('/');
+ var parent = DirOf(slash < 0 ? "" : dirPath[..slash]);
+ idx = nextDir++;
+ dirIndex[dirPath] = idx;
+ dirRecords.Add((parent, idx, slash < 0 ? dirPath : dirPath[(slash + 1)..]));
+ return idx;
+ }
+
+ // master: 512 zeros compressed with the supermaster (method 3)
+ var master = new byte[512];
+ var masterStream = Uc2Ultra.Encode(master, SuperDat.Value);
+
+ var fileRecords = new List<(uint Parent, string Leaf, byte[] Data, DateTime Time, long Offset, int CompLen)>();
+ foreach (var f in files) {
+ var path = f.Path.Replace('\\', '/').Trim('/');
+ if (path.Length == 0) continue;
+ var slash = path.LastIndexOf('/');
+ var parent = DirOf(slash < 0 ? "" : path[..slash]);
+ var stream = Uc2Ultra.Encode(f.Data, master);
+ var offset = body.Position;
+ body.Write(stream);
+ fileRecords.Add((parent, slash < 0 ? path : path[(slash + 1)..], f.Data, f.LastModified, offset, stream.Length));
+ }
+ var masterOffset = body.Position;
+ body.Write(masterStream);
+
+ // central directory (canonical order: per directory, child dirs, then files, then recurse)
+ var raw = new MemoryStream();
+ var children = dirRecords.GroupBy(d => d.Parent).ToDictionary(g => g.Key, g => g.ToList());
+ var filesByDir = fileRecords.GroupBy(f => f.Parent).ToDictionary(g => g.Key, g => g.ToList());
+ void Emit(uint dir) {
+ foreach (var c in children.GetValueOrDefault(dir) ?? []) WriteMeta(raw, BoDir, c.Parent, 0x10, null, Alias(aliases, c.Parent, c.Leaf), c.Leaf, w => { Span ib = stackalloc byte[4]; BinaryPrimitives.WriteUInt32LittleEndian(ib, c.Index); w.Write(ib); });
+ foreach (var f in filesByDir.GetValueOrDefault(dir) ?? []) {
+ WriteMeta(raw, BoFile, f.Parent, 0x20, f.Time, Alias(aliases, f.Parent, f.Leaf), f.Leaf, w => {
+ Span b = stackalloc byte[6 + CompressSize + 8];
+ BinaryPrimitives.WriteUInt32LittleEndian(b, (uint)f.Data.Length);
+ BinaryPrimitives.WriteUInt16LittleEndian(b[4..], Fletch(f.Data));
+ BinaryPrimitives.WriteUInt32LittleEndian(b[6..], (uint)f.CompLen);
+ BinaryPrimitives.WriteUInt16LittleEndian(b[10..], 3);
+ BinaryPrimitives.WriteUInt32LittleEndian(b[12..], 2);
+ BinaryPrimitives.WriteUInt32LittleEndian(b[16..], 1);
+ BinaryPrimitives.WriteUInt32LittleEndian(b[20..], checked((uint)f.Offset));
+ w.Write(b);
+ });
+ }
+ foreach (var c in children.GetValueOrDefault(dir) ?? []) Emit(c.Index);
+ }
+ Emit(0);
+ if (fileRecords.Count > 0) {
+ Span m = stackalloc byte[1 + 20 + CompressSize + 8];
+ m[0] = BoMast;
+ BinaryPrimitives.WriteUInt32LittleEndian(m[1..], 2); // index
+ BinaryPrimitives.WriteUInt32LittleEndian(m[5..], 0); // key: one UC2 never generates, so it is not reused
+ BinaryPrimitives.WriteUInt32LittleEndian(m[9..], (uint)fileRecords.Sum(f => (long)f.Data.Length));
+ BinaryPrimitives.WriteUInt32LittleEndian(m[13..], (uint)fileRecords.Count);
+ BinaryPrimitives.WriteUInt16LittleEndian(m[17..], 512);
+ BinaryPrimitives.WriteUInt16LittleEndian(m[19..], 0xDEDE);
+ BinaryPrimitives.WriteUInt32LittleEndian(m[21..], (uint)masterStream.Length);
+ BinaryPrimitives.WriteUInt16LittleEndian(m[25..], 3);
+ BinaryPrimitives.WriteUInt32LittleEndian(m[27..], SuperMaster);
+ BinaryPrimitives.WriteUInt32LittleEndian(m[31..], 1);
+ BinaryPrimitives.WriteUInt32LittleEndian(m[35..], checked((uint)masterOffset));
+ raw.Write(m);
+ }
+ raw.WriteByte(BoEol);
+ raw.Write(new byte[17 + 4]); // XTAIL + serial
+ var rawCdir = raw.ToArray();
+ var cdirStream = Uc2Ultra.Encode(rawCdir, new byte[512]);
+
+ var cdirAt = body.Position;
+ Span comp = stackalloc byte[CompressSize];
+ BinaryPrimitives.WriteUInt32LittleEndian(comp, (uint)cdirStream.Length);
+ BinaryPrimitives.WriteUInt16LittleEndian(comp[4..], 3);
+ BinaryPrimitives.WriteUInt32LittleEndian(comp[6..], NoMaster);
+ body.Write(comp);
+ body.Write(cdirStream);
+ var componentLength = checked((uint)(body.Position - FHeadSize));
+
+ var head = new byte[FHeadSize + XHeadSize];
+ BinaryPrimitives.WriteUInt32LittleEndian(head, Magic);
+ BinaryPrimitives.WriteUInt32LittleEndian(head.AsSpan(4), componentLength);
+ BinaryPrimitives.WriteUInt32LittleEndian(head.AsSpan(8), componentLength + AMag);
+ head[12] = 0;
+ BinaryPrimitives.WriteUInt32LittleEndian(head.AsSpan(13), 1);
+ BinaryPrimitives.WriteUInt32LittleEndian(head.AsSpan(17), checked((uint)cdirAt));
+ BinaryPrimitives.WriteUInt16LittleEndian(head.AsSpan(21), Fletch(rawCdir));
+ head[23] = 0;
+ BinaryPrimitives.WriteUInt16LittleEndian(head.AsSpan(24), 202); // made by
+ BinaryPrimitives.WriteUInt16LittleEndian(head.AsSpan(26), 200); // needed
+ head[28] = 0;
+ body.Position = 0;
+ body.Write(head);
+ body.Position = body.Length;
+ body.Write(head.AsSpan(0, FHeadSize)); // spare FHEAD
+ if (body.Length > int.MaxValue) throw new NotSupportedException("UC2 archives this writer produces stay below 2 GiB.");
+ output.Write(body.GetBuffer(), 0, (int)body.Length);
+ }
+
+ private static void WriteMeta(MemoryStream raw, byte type, uint parent, byte attr, DateTime? time, byte[] fcb, string leaf, Action tail) {
+ raw.WriteByte(type);
+ Span m = stackalloc byte[MetaSize];
+ BinaryPrimitives.WriteUInt32LittleEndian(m, parent);
+ m[4] = attr;
+ var (dosDate, dosTime) = ToDos(time);
+ BinaryPrimitives.WriteUInt16LittleEndian(m[5..], dosTime);
+ BinaryPrimitives.WriteUInt16LittleEndian(m[7..], dosDate);
+ fcb.CopyTo(m[9..]);
+ m[20] = 0xDE; // bHidden: UC2's own fill byte
+ var needsLongName = !string.Equals(ShortName(fcb), leaf, StringComparison.Ordinal);
+ m[21] = needsLongName ? (byte)1 : (byte)0;
+ raw.Write(m);
+ tail(raw);
+ if (needsLongName) {
+ Span h = stackalloc byte[ExtSize];
+ h.Clear();
+ Encoding.ASCII.GetBytes(LongNameTag).CopyTo(h);
+ var name = Oem.GetBytes(leaf);
+ BinaryPrimitives.WriteUInt32LittleEndian(h[16..], (uint)name.Length + 1);
+ h[20] = 0;
+ raw.Write(h);
+ raw.Write(name);
+ raw.WriteByte(0);
+ }
+ }
+
+ private static (ushort Date, ushort Time) ToDos(DateTime? t) {
+ if (t is not { } v) return (0, 0);
+ v = v.Kind == DateTimeKind.Utc ? v.ToLocalTime() : v;
+ if (v.Year is < 1980 or > 2107) return (0x21, 0); // 1980-01-01
+ return ((ushort)(((v.Year - 1980) << 9) | (v.Month << 5) | v.Day), (ushort)((v.Hour << 11) | (v.Minute << 5) | (v.Second / 2)));
+ }
+
+ /// An 8.3 FCB name unique within the directory: A-Z 0-9 _ ~, never a device name or U$~.
+ private static byte[] Alias(Dictionary> used, uint dir, string leaf) {
+ if (!used.TryGetValue(dir, out var set)) used[dir] = set = [];
+ static string Clean(string s, int max) {
+ var sb = new StringBuilder();
+ foreach (var ch in s.ToUpperInvariant()) {
+ if (sb.Length == max) break;
+ if (ch is >= 'A' and <= 'Z' or >= '0' and <= '9' or '_' or '~') sb.Append(ch);
+ else if (ch == '-' && sb.Length > 0) sb.Append(ch);
+ }
+ return sb.ToString();
+ }
+ var dot = leaf.LastIndexOf('.');
+ var baseRaw = dot > 0 ? leaf[..dot] : leaf;
+ var extRaw = dot > 0 ? leaf[(dot + 1)..] : "";
+ var b = Clean(baseRaw, 8);
+ var e = Clean(extRaw, 3);
+ if (b.Length == 0 || IsDevice(b) || b.StartsWith("U$~", StringComparison.Ordinal)) b = "FILE";
+ var candidate = e.Length == 0 ? b : b + "." + e;
+ for (var n = 1; set.Contains(candidate); ++n) {
+ var suffix = "~" + n;
+ var stem = b[..Math.Min(b.Length, 8 - suffix.Length)] + suffix;
+ candidate = e.Length == 0 ? stem : stem + "." + e;
+ }
+ set.Add(candidate);
+ var fcb = new byte[11];
+ fcb.AsSpan().Fill((byte)' ');
+ var cdot = candidate.IndexOf('.');
+ Encoding.ASCII.GetBytes(cdot < 0 ? candidate : candidate[..cdot]).CopyTo(fcb, 0);
+ if (cdot >= 0) Encoding.ASCII.GetBytes(candidate[(cdot + 1)..]).CopyTo(fcb, 8);
+ return fcb;
+ }
+
+ private static bool IsDevice(string b) => b is "CON" or "PRN" or "AUX" or "NUL" or "CLOCK$"
+ || (b.Length == 4 && (b.StartsWith("COM", StringComparison.Ordinal) || b.StartsWith("LPT", StringComparison.Ordinal)) && b[3] is >= '1' and <= '9');
+}
diff --git a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Uc2/Uc2FormatDescriptor.cs b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Uc2/Uc2FormatDescriptor.cs
new file mode 100644
index 000000000..53fa0327a
--- /dev/null
+++ b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Uc2/Uc2FormatDescriptor.cs
@@ -0,0 +1,66 @@
+using Compression.Registry;
+using static Compression.Registry.FormatHelpers;
+
+namespace FileFormat.Uc2;
+
+/// UltraCompressor II archive (.uc2), Nico de Vries / AIP-NL, 1994-1996.
+public sealed class Uc2FormatDescriptor : IFormatDescriptor, IArchiveFormatOperations, IArchiveCreatable {
+ ///
+ public string Id => "Uc2";
+ ///
+ public string DisplayName => "UltraCompressor II";
+ ///
+ public FormatCategory Category => FormatCategory.Archive;
+ ///
+ public FormatCapabilities Capabilities =>
+ FormatCapabilities.CanList | FormatCapabilities.CanExtract | FormatCapabilities.CanCreate | FormatCapabilities.CanTest |
+ FormatCapabilities.SupportsMultipleEntries | FormatCapabilities.SupportsDirectories;
+ ///
+ public string DefaultExtension => ".uc2";
+ ///
+ public IReadOnlyList Extensions => [".uc2", ".pu2"];
+ ///
+ public IReadOnlyList CompoundExtensions => [];
+ ///
+ public IReadOnlyList MagicSignatures => [new([0x55, 0x43, 0x32, 0x1A], Confidence: 0.95)];
+ ///
+ public IReadOnlyList Methods => [new("ultra", "Ultra (LZ77 + Huffman)")];
+ ///
+ public string? TarCompressionFormatId => null;
+ ///
+ public AlgorithmFamily Family => AlgorithmFamily.Archive;
+ ///
+ public string Description => "UltraCompressor II archive: LZ77+Huffman against shared master dictionaries, XOR-checksummed";
+
+ ///
+ public List List(Stream stream, string? password) {
+ var a = Uc2Archive.Read(stream);
+ return a.Entries.Select((e, i) => new ArchiveEntryInfo(i, e.Path, e.IsDirectory ? 0 : e.Size, e.CompressedSize,
+ e.IsDirectory ? "" : e.Method == 80 ? "Turbo" : $"Ultra {e.Method}", e.IsDirectory, false, e.LastModified)).ToList();
+ }
+
+ ///
+ public void Extract(Stream stream, string outputDir, string? password, string[]? files) {
+ var a = Uc2Archive.Read(stream);
+ foreach (var e in a.Entries) {
+ if (e.IsDirectory || files is { Length: > 0 } && !MatchesFilter(e.Path, files)) continue;
+ WriteFile(outputDir, e.Path, Uc2Archive.Extract(a, e));
+ }
+ }
+
+ ///
+ public byte[] ExtractEntryToMemory(Stream archive, string entryName, string? password) {
+ var a = Uc2Archive.Read(archive);
+ var e = a.Entries.FirstOrDefault(x => !x.IsDirectory && string.Equals(x.Path, entryName, StringComparison.OrdinalIgnoreCase));
+ return e is null ? [] : Uc2Archive.Extract(a, e);
+ }
+
+ ///
+ public void Create(Stream output, IReadOnlyList inputs, FormatCreateOptions options) {
+ ArgumentNullException.ThrowIfNull(inputs);
+ if (!string.IsNullOrEmpty(options.Password))
+ throw new NotSupportedException("UC2's UltraCrypt encryption is undocumented and is not written.");
+ Uc2Archive.Write(output, inputs.Where(i => !i.IsDirectory).Select(i =>
+ new Uc2Archive.Input(i.ArchiveName, i.ReadContent(), i.Metadata?.LastWriteTimeUtc?.LocalDateTime ?? DateTime.Now)));
+ }
+}
diff --git a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Uc2/Uc2Ultra.cs b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Uc2/Uc2Ultra.cs
new file mode 100644
index 000000000..154ddb462
--- /dev/null
+++ b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Uc2/Uc2Ultra.cs
@@ -0,0 +1,382 @@
+namespace FileFormat.Uc2;
+
+///
+/// UltraCompressor II's "Ultra" bitstream (methods 1-9, 21-29, 30-37, 40-47): LZ77 over a 64 KiB
+/// history that starts with a dictionary ("master"), Huffman-coded with a 316-symbol
+/// literal/distance alphabet and a 28-symbol length alphabet, trees either the built-in defaults
+/// or sent delta-coded against the previous block's. Bits are read most-significant first out of
+/// little-endian 16-bit words.
+///
+///
+/// Converted from klauspost/uc2 (internal/ultra, LGPL-3.0), which follows the published UC2 r2
+/// source; the default code lengths and the delta-code matrices are the format's own constants.
+///
+internal static class Uc2Ultra {
+ private const int NumLiterals = 256;
+ private const int NumLd = 316;
+ private const int NumL = 28;
+ private const int NumSymbols = NumLd + NumL;
+ private const int NumPre = 15;
+ private const int RepeatSymbol = 14;
+ private const int MinRepeat = 6;
+ private const int MaxCodeBits = 13;
+ private const int EobDistance = 64001;
+
+ /// Largest distance UC2's own encoder emits.
+ public const int MaxEncoderDistance = 64000;
+
+ /// Default ("base") code lengths, TREEENC.CPP BasePrev.
+ internal static readonly byte[] BaseLengths = BuildBaseLengths();
+
+ private static byte[] BuildBaseLengths() {
+ var b = new byte[NumSymbols];
+ void Fill(int lo, int hi, byte v) { for (var i = lo; i < hi; ++i) b[i] = v; }
+ Fill(0, 32, 9);
+ b[10] = b[12] = b[32] = 7;
+ Fill(33, 128, 8);
+ b[46] = b[58] = b[92] = 7;
+ Fill(128, 256, 10);
+ Fill(256, 272, 6);
+ Fill(272, 284, 7);
+ Fill(284, 290, 8);
+ Fill(290, 300, 9);
+ Fill(300, 316, 10);
+ Fill(316, 325, 4);
+ Fill(325, 334, 5);
+ Fill(334, 344, 6);
+ return b;
+ }
+
+ /// vval[prev][code] → new length (decoder side of the delta matrices, TREEENC.CPP InitTables).
+ private static readonly byte[,] DeltaDecode = BuildDeltaDecode();
+
+ private static byte[,] BuildDeltaDecode() {
+ const int n = 14;
+ var table = new byte[n, n];
+ var vval = new byte[n, n];
+ for (var i = 1; i < n; ++i) { vval[0, i] = (byte)(n - i); table[0, i] = (byte)(n - i); }
+ for (var i = 1; i < n; ++i) {
+ var prob = 0;
+ var x = i < 9 ? i - 1 : n - i + 1;
+ var j = 1;
+ for (; j < x; ++j) {
+ table[i, i + j - 1] = (byte)prob;
+ vval[i, prob] = (byte)(i + j - 1);
+ ++prob;
+ table[i, i - j] = (byte)prob;
+ vval[i, prob] = (byte)(i - j);
+ ++prob;
+ }
+ table[i, i - j] = (byte)(prob + 1);
+ vval[i, prob + 1] = (byte)(i - j);
+ if (i < 8) {
+ table[i, 0] = n - 1;
+ vval[i, n - 1] = 0;
+ table[i, i + j - 1] = (byte)prob;
+ vval[i, prob] = (byte)(i + j - 1);
+ for (j = n - 1; j >= i * 2 - 1; --j) {
+ table[i, j] = (byte)(j - 1);
+ vval[i, j - 1] = (byte)j;
+ }
+ } else {
+ table[i, 0] = (byte)prob;
+ vval[i, prob] = 0;
+ for (j = 1; j <= i * 2 - 15; ++j) {
+ table[i, j] = (byte)(n - j);
+ vval[i, n - j] = (byte)j;
+ }
+ }
+ }
+ return vval;
+ }
+
+ private static readonly ushort[] DistBase = new ushort[60];
+ private static readonly byte[] DistExtra = new byte[60];
+ private static readonly ushort[] LenBase = [3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 21, 23, 25, 27, 35, 43, 51, 59, 67, 75, 83, 91, 155, 667, 2715];
+ private static readonly byte[] LenExtra = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, 3, 3, 6, 9, 11, 15];
+
+ static Uc2Ultra() {
+ for (var i = 0; i < 15; ++i) {
+ DistBase[i] = (ushort)(i + 1);
+ DistBase[15 + i] = (ushort)((i + 1) << 4); DistExtra[15 + i] = 4;
+ DistBase[30 + i] = (ushort)((i + 1) << 8); DistExtra[30 + i] = 8;
+ DistBase[45 + i] = (ushort)((i + 1) << 12); DistExtra[45 + i] = 12;
+ }
+ }
+
+ // ── bit reader ──────────────────────────────────────────────────────
+
+ /// Reads MSB-first out of little-endian 16-bit words; past the end it supplies zero bits.
+ private sealed class BitReader(byte[] data, int offset, int length) {
+ private long _bit; // bits consumed
+
+ /// More than a word of zero padding has been consumed: the stream is truncated.
+ public bool Overrun => this._bit > (long)length * 8 + 16;
+
+ public int Bit() {
+ var word = this._bit >> 4;
+ var inWord = (int)(this._bit & 15);
+ var byteIndex = word * 2 + (inWord < 8 ? 1 : 0);
+ var b = byteIndex < length ? data[offset + byteIndex] : 0;
+ ++this._bit;
+ return (b >> (7 - (inWord & 7))) & 1;
+ }
+
+ public int Bits(int n) {
+ var v = 0;
+ for (var i = 0; i < n; ++i) v = (v << 1) | this.Bit();
+ return v;
+ }
+ }
+
+ // ── canonical code ──────────────────────────────────────────────────
+
+ private sealed class Code {
+ // first-code / count tables per length for canonical decoding (DEFLATE style, MSB first)
+ private readonly int[] _count = new int[MaxCodeBits + 1];
+ private readonly int[] _symbols;
+ private readonly int _maxLen;
+
+ public Code(ReadOnlySpan lengths, int maxLen) {
+ this._maxLen = maxLen;
+ var n = 0;
+ foreach (var l in lengths) {
+ if (l > maxLen) throw new InvalidDataException("UC2 code length exceeds the format maximum.");
+ if (l > 0) { ++this._count[l]; ++n; }
+ }
+ // reject oversubscribed codes; incomplete ones are legal
+ var left = 1;
+ for (var len = 1; len <= maxLen; ++len) {
+ left = (left << 1) - this._count[len];
+ if (left < 0) throw new InvalidDataException("UC2 Huffman code is oversubscribed.");
+ }
+ this._symbols = new int[n];
+ var offs = new int[maxLen + 2];
+ for (var len = 1; len <= maxLen; ++len) offs[len + 1] = offs[len] + this._count[len];
+ for (var s = 0; s < lengths.Length; ++s)
+ if (lengths[s] > 0) this._symbols[offs[lengths[s]]++] = s;
+ }
+
+ public int Decode(BitReader r) {
+ int code = 0, first = 0, index = 0;
+ for (var len = 1; len <= this._maxLen; ++len) {
+ code |= r.Bit();
+ var count = this._count[len];
+ if (code - first < count) return this._symbols[index + code - first];
+ index += count;
+ first += count;
+ first <<= 1;
+ code <<= 1;
+ }
+ throw new InvalidDataException("UC2 stream uses an unassigned Huffman code.");
+ }
+ }
+
+ private static readonly Lazy<(Code Ld, Code L)> BaseCodes =
+ new(() => (new Code(BaseLengths.AsSpan(0, NumLd), MaxCodeBits), new Code(BaseLengths.AsSpan(NumLd), MaxCodeBits)));
+
+ ///
+ /// Decodes one Ultra stream. < 0 decodes to the stream's
+ /// end-of-stream bit (the CDIR); otherwise decoding stops after that many bytes.
+ ///
+ public static byte[] Decode(byte[] input, int offset, int length, ReadOnlySpan dictionary, long outputLength, long cap = 100_000_000) {
+ if (outputLength == 0) return [];
+ var limit = outputLength < 0 ? cap : outputLength;
+ if (limit > int.MaxValue - 65536) throw new NotSupportedException("UC2 member exceeds the 2 GiB in-memory limit.");
+ var dictLen = dictionary.Length;
+ var window = new byte[dictLen + (int)Math.Min(limit, 1 << 20)];
+ dictionary.CopyTo(window);
+ var w = dictLen;
+ var end = (long)dictLen + limit;
+ var r = new BitReader(input, offset, length);
+ var prev = (byte[])BaseLengths.Clone();
+ Code? ld = null, l = null;
+
+ while (w < end) {
+ if (r.Bit() == 0) break; // end of stream
+ (ld, l) = ReadTrees(r, prev);
+ while (w < end) {
+ if (r.Overrun) throw new InvalidDataException("UC2 stream ended early.");
+ var sym = ld.Decode(r);
+ if (w + 35482 >= window.Length && window.Length < end)
+ Array.Resize(ref window, (int)Math.Min(end, Math.Max(window.Length * 2L, w + 65536L)));
+ if (sym < NumLiterals) {
+ window[w++] = (byte)sym;
+ continue;
+ }
+ sym -= NumLiterals;
+ var dist = DistBase[sym] + r.Bits(DistExtra[sym]);
+ var ls = l.Decode(r);
+ if (dist == EobDistance) break; // end of block: the length symbol's extra bits are not sent
+ var len = LenBase[ls] + r.Bits(LenExtra[ls]);
+ if (dist > w) throw new InvalidDataException("UC2 match reaches before the dictionary.");
+ if (len > end - w) len = (int)(end - w);
+ var src = w - dist;
+ for (var k = 0; k < len; ++k) window[w++] = window[src + k];
+ }
+ }
+ if (outputLength >= 0 && w < end)
+ throw new InvalidDataException("UC2 stream ended before the recorded size.");
+ return window.AsSpan(dictLen, w - dictLen).ToArray();
+ }
+
+ private static (Code Ld, Code L) ReadTrees(BitReader r, byte[] prev) {
+ if (r.Bit() == 0) {
+ BaseLengths.CopyTo(prev, 0);
+ return BaseCodes.Value;
+ }
+ var t = r.Bits(2);
+ var total = NumSymbols - ((t & 1) == 0 ? 28 : 0) - ((t & 2) == 0 ? 128 : 0);
+ var preLengths = new byte[NumPre];
+ for (var i = 0; i < NumPre; ++i) preLengths[i] = (byte)r.Bits(3);
+ var pre = new Code(preLengths, 7);
+ var st = new byte[NumSymbols + 20];
+ int count = 0;
+ byte val = 0;
+ while (count < total) {
+ var s = pre.Decode(r);
+ if (s != RepeatSymbol) {
+ val = (byte)s;
+ st[count++] = val;
+ continue;
+ }
+ var c = pre.Decode(r);
+ for (var k = 0; k < c + MinRepeat - 1 && count < st.Length; ++k) st[count++] = val;
+ }
+ var lens = new byte[NumSymbols];
+ var idx = 0;
+ void Put(int lo, int hi) { for (var s = lo; s < hi; ++s) lens[s] = DeltaDecode[prev[s], st[idx++]]; }
+ if ((t & 1) != 0) Put(0, 32);
+ else { Put(9, 11); Put(12, 14); }
+ Put(32, 128);
+ if ((t & 2) != 0) Put(128, 256);
+ Put(256, NumSymbols);
+ lens.CopyTo(prev, 0);
+ return (new Code(lens.AsSpan(0, NumLd), MaxCodeBits), new Code(lens.AsSpan(NumLd), MaxCodeBits));
+ }
+
+ // ── encoder (default trees, greedy hash-chain matcher) ───────────────
+
+ private sealed class BitWriter {
+ private readonly List _out = [];
+ private uint _acc;
+ private int _n;
+
+ public void Put(int value, int bits) {
+ for (var i = bits - 1; i >= 0; --i) {
+ this._acc = (this._acc << 1) | (uint)((value >> i) & 1);
+ if (++this._n == 16) {
+ this._out.Add((byte)this._acc);
+ this._out.Add((byte)(this._acc >> 8));
+ this._acc = 0;
+ this._n = 0;
+ }
+ }
+ }
+
+ public byte[] Finish() {
+ if (this._n > 0) this.Put(0, 16 - this._n);
+ return [.. this._out];
+ }
+ }
+
+ private static (int[] Codes, byte[] Lens) CanonicalCodes(ReadOnlySpan lengths) {
+ var codes = new int[lengths.Length];
+ var count = new int[MaxCodeBits + 2];
+ foreach (var l in lengths) if (l > 0) ++count[l];
+ var next = new int[MaxCodeBits + 2];
+ var code = 0;
+ for (var len = 1; len <= MaxCodeBits; ++len) {
+ code = (code + count[len - 1]) << 1;
+ next[len] = code;
+ }
+ for (var s = 0; s < lengths.Length; ++s)
+ if (lengths[s] > 0) codes[s] = next[lengths[s]]++;
+ return (codes, lengths.ToArray());
+ }
+
+ private static readonly Lazy<(int[] Codes, byte[] Lens)> BaseLdCodes = new(() => CanonicalCodes(BaseLengths.AsSpan(0, NumLd)));
+ private static readonly Lazy<(int[] Codes, byte[] Lens)> BaseLCodes = new(() => CanonicalCodes(BaseLengths.AsSpan(NumLd)));
+
+ ///
+ /// Encodes as one Ultra stream using the default trees, with
+ /// as history. Matches stay within distance 64000 and length 32760.
+ ///
+ public static byte[] Encode(ReadOnlySpan data, ReadOnlySpan dictionary) {
+ var w = new BitWriter();
+ if (data.IsEmpty) { w.Put(0, 1); return w.Finish(); }
+
+ var (ldc, ldl) = BaseLdCodes.Value;
+ var (lc, ll) = BaseLCodes.Value;
+ var buf = new byte[dictionary.Length + data.Length];
+ dictionary.CopyTo(buf);
+ data.CopyTo(buf.AsSpan(dictionary.Length));
+ var start = dictionary.Length;
+
+ const int hashBits = 15;
+ var head = new int[1 << hashBits];
+ Array.Fill(head, -1);
+ var chain = new int[buf.Length];
+ int Hash(int p) => ((buf[p] << 10) ^ (buf[p + 1] << 5) ^ buf[p + 2]) & ((1 << hashBits) - 1);
+ void Insert(int p) { if (p + 2 < buf.Length) { var h = Hash(p); chain[p] = head[h]; head[h] = p; } }
+ for (var p = 0; p < start; ++p) Insert(p);
+
+ w.Put(1, 1); // block
+ w.Put(0, 1); // default trees
+ var pos = start;
+ const int maxChain = 64, maxLen = 32760;
+ while (pos < buf.Length) {
+ int bestLen = 0, bestDist = 0;
+ if (pos + 2 < buf.Length) {
+ var cand = head[Hash(pos)];
+ for (var steps = 0; cand >= 0 && steps < maxChain; ++steps, cand = chain[cand]) {
+ var d = pos - cand;
+ if (d > MaxEncoderDistance) break;
+ if (d == EobDistance) continue;
+ var limit = Math.Min(maxLen, buf.Length - pos);
+ var l = 0;
+ while (l < limit && buf[cand + l] == buf[pos + l]) ++l;
+ if (l > bestLen) { bestLen = l; bestDist = d; if (l == limit) break; }
+ }
+ }
+ if (bestLen >= 3) {
+ EmitDistance(w, ldc, ldl, bestDist);
+ EmitLength(w, lc, ll, bestLen);
+ for (var k = 0; k < bestLen; ++k) Insert(pos + k);
+ pos += bestLen;
+ } else {
+ w.Put(ldc[buf[pos]], ldl[buf[pos]]);
+ Insert(pos);
+ ++pos;
+ }
+ }
+ // end of block: distance 64001 then one length code (L0) without extra bits
+ EmitDistance(w, ldc, ldl, EobDistance);
+ w.Put(lc[0], ll[0]);
+ w.Put(0, 1); // end of stream
+ return w.Finish();
+ }
+
+ private static void EmitDistance(BitWriter w, int[] codes, byte[] lens, int d) {
+ int sym, extra, bits;
+ if (d < 16) { sym = 255 + d; extra = 0; bits = 0; }
+ else if (d < 256) { sym = 270 + (d >> 4); extra = d & 15; bits = 4; }
+ else if (d < 4096) { sym = 285 + (d >> 8); extra = d & 255; bits = 8; }
+ else { sym = 300 + (d >> 12); extra = d & 4095; bits = 12; }
+ w.Put(codes[sym], lens[sym]);
+ if (bits > 0) w.Put(extra, bits);
+ }
+
+ private static void EmitLength(BitWriter w, int[] codes, byte[] lens, int n) {
+ int sym, extra, bits;
+ if (n < 11) { sym = n - 3; extra = 0; bits = 0; }
+ else if (n < 27) { sym = 8 + (n - 11) / 2; extra = (n - 11) & 1; bits = 1; }
+ else if (n < 91) { sym = 16 + (n - 27) / 8; extra = (n - 27) & 7; bits = 3; }
+ else if (n < 155) { sym = 24; extra = n - 91; bits = 6; }
+ else if (n < 667) { sym = 25; extra = n - 155; bits = 9; }
+ else if (n < 2715) { sym = 26; extra = n - 667; bits = 11; }
+ else { sym = 27; extra = n - 2715; bits = 15; }
+ w.Put(codes[sym], lens[sym]);
+ if (bits > 0) w.Put(extra, bits);
+ }
+}
diff --git a/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Uc2/super.dat b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Uc2/super.dat
new file mode 100644
index 000000000..2ae429253
--- /dev/null
+++ b/Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Uc2/super.dat
@@ -0,0 +1,1276 @@
+This software and accompanying documentation is protected by the
+Copyright laws of the Netherlands and also by International Treaty
+provisions. Any use of this software in violation of Copyright law or
+the terms of this limited license will be prosecuted to the best of
+our ability. The conditions under which you may copy this software
+and documentation are clearly outlined below under "Distribution
+Restrictions" (paragraph E).
+
+AIP-NL hereby grants you a limited license to use this software for
+evaluation purposes for a period not to exceed thirty (30) days. If
+you intend to continue using this software (and/or its documentation)
+after the thirty (30) day evaluation period, you MUST make a
+registration payment to AIP-NL.
+
+Using this software after the thirty (30) day evaluation period,
+without registering the software is a violation of the terms of this
+limited license.
+
+You shall not use, copy, emulate, clone, rent, lease, sell, modify,
+decompile, disassemble, otherwise reverse engineer, or transfer the
+licensed program, or any subset of the licensed program, except as
+provided for in this agreement. Any such unauthorized use shall result in
+immediate and automatic termination of this license.
+
+All rights not expressly granted here are reserved by AIP-NL.
+
+===================================================================
+
+
+IFS=C:\OS2\HPFS.IFS /CACHE:128 /CRECL:4 /AUTOCHECK:D
+PROTSHELL=C:\OS2\PMSHELL.EXE
+SET USER_INI=C:\OS2\OS2.INI
+SET SYSTEM_INI=C:\OS2\OS2SYS.INI
+SET OS2_SHELL=C:\OS2\CMD.EXE
+SET AUTOSTART=PROGRAMS,TASKLIST,FOLDERS,CONNECTIONS
+SET RUNWORKPLACE=C:\OS2\PMSHELL.EXE
+SET COMSPEC=C:\OS2\CMD.EXE
+LIBPATH=.;C:\OS2\DLL;C:\OS2\MDOS;C:\;C:\OS2\APPS\DLL;C:\MMOS2\DLL
+SET PATH=C:\OS2;C:\OS2\SYSTEM;C:\OS2\MDOS\WINOS2;C:\OS2\INSTALL;C:\;C:\OS2\MDOS;C:\OS2\APPS;C:\MMOS2
+SET DPATH=C:\OS2;C:\OS2\SYSTEM;C:\OS2\MDOS\WINOS2;C:\OS2\INSTALL;C:\;C:\OS2\BITMAP;C:\OS2\MDOS;C:\OS2\APPS;C:\MMOS2;C:\MMOS2\INSTALL
+SET PROMPT=$i[$p]
+SET HELP=C:\OS2\HELP;C:\OS2\HELP\TUTORIAL;C:\MMOS2\HELP
+SET GLOSSARY=C:\OS2\HELP\GLOSS;
+SET IPF_KEYS=SBCS
+PRIORITY_DISK_IO=YES
+FILES=50
+DEVICE=C:\OS2\TESTCFG.SYS
+DEVICE=C:\OS2\DOS.SYS
+DEVICE=C:\OS2\PMDD.SYS
+BUFFERS=100
+IOPL=YES
+DISKCACHE=256,LW,AC:CD
+MAXWAIT=3
+MEMMAN=SWAP,PROTECT
+SWAPPATH=C:\OS2\SYSTEM 2048 4096
+BREAK=OFF
+THREADS=256
+PRINTMONBUFSIZE=134,134,134
+COUNTRY=001,C:\OS2\SYSTEM\COUNTRY.SYS
+SET KEYS=ON
+REM SET DELDIR=C:\DELETE,512;
+BASEDEV=PRINT01.SYS
+BASEDEV=IBM1FLPY.ADD
+BASEDEV=IBM1S506.ADD
+BASEDEV=OS2DASD.DMD
+SET BOOKSHELF=C:\OS2\BOOK;C:\MMOS2;
+SET EPMPATH=C:\OS2\APPS;
+REM DEVICE=C:\OS2\APPS\SASYNCDA.SYS
+PROTECTONLY=NO
+SHELL=C:\OS2\MDOS\COMMAND.COM C:\OS2\MDOS
+FCBS=16,8
+RMSIZE=640
+DEVICE=C:\OS2\MDOS\VEMM.SYS
+DEVICE=C:\OS2\MDOS\ANSI.SYS
+DOS=LOW,NOUMB
+DEVICE=C:\OS2\MDOS\VDPX.SYS
+DEVICE=C:\OS2\MDOS\VXMS.SYS /UMB
+DEVICE=C:\OS2\MDOS\VDPMI.SYS
+DEVICE=C:\OS2\MDOS\VCDROM.SYS
+DEVICE=C:\OS2\MDOS\VWIN.SYS
+REM DEVICE=C:\OS2\PCMCIA.SYS
+REM DEVICE=C:\OS2\MDOS\VPCMCIA.SYS
+BASEDEV=AHA152X.ADD
+DEVICE=C:\OS2\MDOS\VMOUSE.SYS
+DEVICE=C:\OS2\POINTDD.SYS
+DEVICE=C:\OS2\MOUSE.SYS
+DEVICE=C:\OS2\COM.SYS
+DEVICE=C:\OS2\MDOS\VCOM.SYS
+CODEPAGE=437,850
+DEVINFO=KBD,US,C:\OS2\KEYBOARD.DCP
+SET EPMPATH=C:\OS2\APPS
+SET FAXPM=C:\OS2\APPS
+SET VIO_SVGA=DEVICE(BVHVGA,BVHSVGA)
+SET MMBASE=C:\MMOS2;
+SET DSPPATH=C:\MMOS2\DSP;
+SET NCDEBUG=4000
+DEVICE=C:\MMOS2\SSMDD.SYS
+DEVICE=C:\MMOS2\ADSHDD.SYS
+DEVICE=C:\MMOS2\SMVDD.SYS
+DEVICE=C:\MMOS2\SBD2.SYS 1 7 220 4 /N:SBAud1$
+BASEDEV=LOCKDRV.FLT /UNIT:0,1,2,3,4
+struct ITEM_STRUCT{
+ char szName[13]; /* dir/file name */
+ unsigned long ulVersion; /* file version number (0 == most recent) */
+ unsigned long ulFileSize; /* file size */
+ int iDay, iMonth, iYear; /* dir/file date */
+ int iHour, iMinute, iSecond; /* dir/file time */
+ char cArchive, cHidden, /* dir/file attributes */
+ cSystem, cReadOnly; /* */
+ int iTags; /* number of tags */
+ char aszTag[100][100]; /* dir/file tags (e.g. OS/2 EA's) */
+};
+
+int Exists (char *szFileName)
+{
+ /* use an attempt to open the file to determine its existance */
+ FILE *pfTmp = fopen (szFileName, "r");
+ if (pfTmp)
+ {
+ fclose (pfTmp);
+ return 1;
+ } else {
+ return 0;
+ }
+}
+1234567890-=_+{}|:"<>?~`ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890qwertyuiop[]\asdfghjkl;'zxcvbnm,./abcdefghijklmnopqrstuvwxyz1234567890-=_+{}|:"<>?~`ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890qwertyuiop
+#! rnews 1312
+Path:
+From: mathematicians extended primality infinite
+Newsgroups: comp.pictures.compression.general.german.french.dutch.american.crypt. number of following sequences digit opinion facilities
+Subject: beginning length programmable anything interpreter comments anything
+Message-ID: >>> ten thousand years
+In article <
+
+[disclaimer:
+Summary:
+Followup-To:
+Distribution: , The Netherlands
+International Scotland Edinburg London Finland Delft Los Angelos Utrecht Helsinki Japanese English Spain Spanish Seattle Thailand Canada Denmark Aalborg France Desktop Publishing
+Keywords: none Latin, CWI, Amsterdam
+shell script experience expertsystems operating systems computer hardware software location Technology questions
+Expires: address Laboratory for Foundation of Computing Science Department, University of project,
+Reply-To:
+~~
+ARPA: jim%
+UUCP: jim@
+JANET: jim@
+, problem solution Switzerland Stockholm language
+Telephone: Newcastle analog digital environment emacs rec.arts cut here
+Internet:
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(w-flush (window fill-colour empty-ground 'utilities)
+
+(defvar field-properties ((subfields)) 'aggregate-field-subfields
+
+(defun
+(declare
+ (special t)
+ (macros t))
+
+(eval-when (compile)
+ (load 'constants)
+
+(defstruct
+ (field ; generic fields
+ (:displace t)
+ (:list)
+ (:conc-name)
+ (type 'generic-field) ; type = generic
+ (zone (make-zone)) ; bounding zone
+ (properties (list nil)) ; empty property list
+)
+
+(solid pattern)
+background W-PATTERN-1))))
+ W-BACKGROUND))))
+ (pattern-zone-interior (field-zone f)
+ (apply (concat '
+ (type (field-type
+ (ncons f))) point-in-box-interior draw-labelled-button-field
+
+(defmacro (let ((b (get (field-properties f) 'fill-ground))
+
+(def lentenv
+expanded-bitmap-field-properties-ncols (divide-points draw-button-fields
+
+ clear-zone-interior draw-field-background subfields selection routines
+ (equal (return (setq add-points vector subtract multiply division dimensions
+(excluding boundaries) init-machine resulting sublist (mapcar '
+ second-level expression pairify alternating elements (nreverse manipulation
+ (penultimate tail-recursive local fun -installed-fonts nil) resize-field
+ toggle-field check-field alter-text make-point init-text-field
+ input-text-field duplicate distribute radio-button-field otherwise
+
+
+
+
+.pn 0
+.ls1
+.EQ
+delim $$
+.EN
+.ev1
+.ps-2
+.vs-2
+.ev
+.sp 5
+.ps+4
+.ce
+.ps-4
+.sp4
+.sp2
+.ce4
+.sp 8
+.in+1i
+.ll-1i
+.sp 3
+.in +0.5i
+.ti -0.5i
+.br
+.sh "Introduction"\fR \(em
+.pp
+.ul
+.[
+.]
+.sh " \fI \&
+.FN
+.EF
+.LB
+.nf
+.ta \w'u +0.5i +\w'[0.23354, \(dg [0,\ 1)
+.fi
+.LE
+$code_value$Max_frequency$low$high$value$
+.ev2
+.nr x2 \w'symbol'/2README DOC. Wat te doen?
+========================
+
+Geef het UC commando om toegang tot het HELP systeem te krijgen. Zoeken op
+onderwerp is mogelijk.
+
+Dit document bevat een korte beschrijving van de bedoeling van de "Ultra
+Compressor"; wat algemene informatie die u moet weten; een algemeen overzicht
+van de UltraCompressor; een overzicht van alle bestanden die bij UC2_DIST.EXE
+horen en het doel daarvan, en als laatste een overzicht van het materiaal dat
+hoort bij verwante AIP produkten.
+
+Als u de "Algemene Informatie" gelezen hebt, kunt u desgewenst de rest van dit
+document overslaan.
+
+Wat is UltraCompressor 2 (UC)
+==============================
+
+UC is een archiver. Een archiver stelt u in staat om meerdere bestanden en
+zelfs complete directories/bomen op te slaan in een enkel bestand; het "ar-
+chief". UC gebruikt "data compressie" om dit archief zo klein mogelijk te
+maken, meestal veel kleiner dan de bestanden waren voor ze gearchiveerd werden.
+UC kan archieven ook SCHADE BESTENDIG maken, zodat alle bestanden in een
+archief beveiligd zijn, zelfs in geval van schijf fouten. (hier zijn grenzen
+aan)
+
+Algemene informatie
+====================
+
+Toen u UC2-DIST.EXE uitvoerde, deed u twee dingen:
+ -U bepaalde waar u UC in uw systeem wil hebben.
+ -U voegde (naar keuze) het pad waar UC zit toe aan uw
+ AUTOEXEC.BAT.
+
+
+(De mogelijkheid bestaat dat er na het toevoegen van het UC pad "Out of
+environment space" op uw scherm verschijnt. Om dit op te lossen moet u uw
+CONFIG.SYS shell regel, veranderen in SHELL=C: MSDOS COMMAND.COM /P /E:512
+[aangenomen dat dos in C; MSDOS staat] en/of uw DOS handleiding raadplegen.)
+
+Om meer over UC te weten te komen, hoeft u alleen het "UC" commando te geven.
+Alle documentatie is in leesbaar ASCII zodat het mogelijk is het uit te printen
+etc. Om bijvoorbeeld BASIC.DOC uit te printen, moet u het dos commando "COPY
+BASIC.DOC PRN". intypen.
+
+UC IS NIET GRATIS. U moet ervoor betalen. Lees LICENCE.DOC voor informatie. UC
+is een "shareware"Qu'est ce qu'on peut faire.
+ entrer le systŠme AIDER
+Chercher au sujet etc. est possible.
+Ce document contient une brŠve description du but du "Ultra
+Compressor"; quelque information g‚n‚rale qui vous devez
+savoir; un r‚sum‚ de tous fichiers qui appartiennent … UC2_-
+DIST.EXE et leur but; et un r‚sum‚ du mat‚riel qui appartient
+… les produits contigu‰s de AIP-NL. Quand vous avez lu "l'in-
+formation g‚n‚ral" vous pouvez, quand vous voulez, passer sur
+plusieurs fichiers et mˆme des r‚pertoires complŠtes/arbres
+dans un seul fichier; "les archives". UC emploie "data com-
+pression" pour faire ces archives si bref comme possible,
+g‚n‚ralement plus bref qu'ils ‚taient avant qu'on les a entre-
+pos‚ dans les archives. UC est aussi capable de faire ces
+ mˆme en cas des d‚gƒts au
+disque magn‚tique. (dans les limites du possible)
+(C'est possible que "Out of environment space" apparait …
+votre ‚cran aprŠs ajouter le chemin d'UC. Pour r‚soudre cela
+vous devez changer votre CONFIG.SYS rŠgle d'interpr‚teur de
+commandes en SHELL=C: MSDOS COMMAND.COM /P /E:512 [… supposer
+que DOS est en C: MSDOS.], et/ou consultez votre notice expli-
+Pour apprendre plus d'UC, tous ce que vous devez faire est
+donner le commandement "UC". Toute documentation est en lisi-
+ble ASC11 de maniŠre que c'est possible de l'imprimer etc.
+Pour imprimer BASIC.DOC par exemple, vous devez taper … la
+machine le commandement de DOS "COPY BASIC.DOC PRN".
+UC n'est pas gratuit. Vous devez le payer. Lis LICENCE.DOC
+pour information. UC est un programme que vous pouvez trahir
+avant que vous l'achetez. En LICENCE.DOC vous trouvez aussi
+l'accord de licence de AIP-NL. Quand vous employez ce paquet
+am‚liorations vous pensez nous pouvons mettre en UC et nos
+autres produits. Les programmes de AIP-NL sont toujours d‚ve-
+lopp‚s en collaboration avec les utilisateurs. Commentaire …
+les notices explicatives en g‚n‚ral, et … les notices explica-
+tives dans une certaine langue, nous appr‚cions aussi. Des
+suggestions originales sont r‚mun‚r‚es.
+ Bundesrepubliek Deutschland Familienname Vorname Alter Wohnort Adresse M�nchen D�sseldorf Oberhausen K”ln Hamburg
+ mich das ist sehr gut Freundin Wie bitte wo wohnen Sie Halt! eins zwei f�nf sieben neun zw”lf dreizehn sechsundzwanzig
+ Welche Fragen kannst du stellen speichern richtig oder falsch Klassenlehrer Bonn Berlin Leverkusen Bremen Stuttgart Warum
+ J�rgen Hans Dieter KarlHeinz eigentlich Dortmund Aachen Frankfurt Hannover Ruhrgebied Aussprache Postleitzahl Vater
+ Mutter Geschwister der n„chtste Zug Hausfrau heiraten Ergebnisse Hauptstadt Schweiz ™sterreich Regierungssitz
+ Schwimmen Zeihnen Tischtennis Die Mannschaft Mittwoch Donnerstag Samtag sonntags montag freitags der Winter Herbst
+ Sommer Fr�hling Freunde das Alter gegnerisch L„ndern europ„ischen Nat�rlich m„nnlich weiblich s„chlich Polizei
+ Umleitung ausrechend befriedigend �berhaupt trotzdem versuchen Grundlage Vergleichstest CAD_Programme Seiten Programmierer
+ meistgekaufte Verbesserungen Speicherkapazit„t Unterst�tzt schneller Festplatten Laufwerken geschwindigkeit Schalter
+ ausf�hrlichen Selbstverst„ndlich Prozessor Fertigung Protokoll Hilfestellung Benutzer Anwendung Einbaurahmen 3,5 Zoll
+ Bildaufl”sung Steckerleiste Preis„nderungen Lieferm”glichkeit H„ndleranfragen Nadel-Drucker Sekunde abschaltbar Monitore
+ Pr„zisionssockel F„higkeiten erforderlich Bodensee Extrema Wendepunkte Nullstellen schiefe Asymptoten Kurvendiskussion
+ vielleicht Kickstart Skriptsprache kompatibel Merkmale Preis Bildkompression z.B. Sequenzerfunktionen Qualit„t Eing„nge
+ Einsteckkarte Darstellung Profis ausgestattet Erflogreich Farbdruck eingebaute Papiereinzug Schriftarten Kombinierter
+ Schubtraktor Textverarbeitung Durchgef�hrter Spezialisten Entwicklung .MODEL SMALL
+ ; Registers modified
+"bios.inc"
+$comm macro name,dist,size,count
+_BSS segment word public 'BSS'
+_DATA segment word public 'DATA'
+_s@ label byte
+_TEXT segment byte public 'CODE'
+_Transform proc near
+ adc al,40h
+ add al,90h
+ add ax,es:[bx]
+ add bx,2
+ add cx, dx
+ add cx, ax
+ add di, ax
+ add dl,'0'
+ add dl,'A'-'0'-10 ; Convert to A,B,C,D,E,F
+ add eax,3614090360
+ add eax,bp[ 0*4]
+ add eax,ebx
+ add si,ax
+ and al,0Fh
+ and dl,0fh
+ and esi,edx
+ and InpSta, ax
+ assume cs:_TEXT,ds:DGROUP
+ BP MOV ES,BP MOV AH,50 MOV BX,BP
+ BP,CS DEC BP MOV ES,BP SUB DX,AX
+ BX PUSH BX MOV AH,30 INT 21
+ call FileWrite
+ call IntDivide C,ax,NumberOfValues
+ call LoCase
+ call ProcByte
+ call Spaces
+ call StoreTab
+ call TabNext
+ cld
+ cli
+ cmp al, 0
+ cmp ax, 0
+ cmp cx,0
+ cmp dl,'9'
+ cmp SpcCnt, 0
+ comm dist name[size]:BYTE:count
+d@ label byte
+d@w label word
+ daa
+ dec ax
+ dec di
+ dec TabOff
+ dec [byte es:di]
+ DEC DX INC BP ADD BP,AX INC
+DGROUP group _DATA,_BSS
+ DOSSEG
+DX PUSH SI PUSH DI PUSH BP PUSH
+ end
+ES PUSH DS XOR DX,DX POP DS
+ES,BP POP ES PUSH CS
+ EXTRN _IntDivide:PROC
+ ifndef ??version
+ in al,dx
+ IN AL,DX INC DH INC DI INC
+ inc cx
+ inc di
+ INC SI DEC SP MOV SP,BP POP
+include "imacros.mac" "dos.inc" "idos.mac" "ibios.mac"
+ int 21h
+int DOSINT
+ IRET
+ jbe showhexdigit_showit
+ jc filopw1 ;jump if error
+ je stotab1
+ jmp SHORT parcmd1a
+ jnc prolin6
+ jne NoEqual
+ jnz filfil1
+ jz stospc1
+ lds si, [StrAddress]
+ lds si,num
+ lea bx, OutBlk
+ lea di, LinBuf
+ lea dx, Help
+ lea si, LinBuf
+ les bx,[bp+4]
+ les di,var2
+ loop AverageLoop
+ loop HexLoop
+ mov ah, 9
+ mov ah,al ;save it
+ mov ah,DOSPRINTCHAR
+ mov al, [di-1]
+ mov al, '?'
+ mov al, ' '
+ mov al,ah
+ mov al,byteCount
+ mov ax,@DATA
+ mov ax,bx
+ mov ax,es
+ mov bh,bl
+ mov bx,ax
+ mov BYTE PTR [si], EOF
+ mov ch,al
+ mov cl, [byte es:di]
+ mov cl,255
+ mov cl,ah
+ mov cx, dx
+ mov di, si
+ mov di,bx
+ mov ds, ax
+ mov dx, SpcCnt
+ mov dx,ds
+ mov edi,ebx
+ mov es:[bx],cl
+ mov esi,ebx
+ mov si,di
+ mov SpcCnt, 0
+ mov WORD PTR [bx], 1
+ mov [bx+10], ax
+ mov [bx+2], cx
+ mov [bx+4], dx
+ mov [bx+8], ax ;save handle
+MOV AX,BX MOV AX,F000 MOV ES,AX MOV
+NOP
+ not ax
+ not esi
+ or ax, ax
+ or cx, cx
+ or dx, dx
+ or edi,esi
+ or InpSta, LAS_LET
+ pop ax
+ pop bx
+ pop di
+ pop ds
+ pop dx
+ pop es
+POP DX POP CX POP BX POP AX
+POP ES POP BP POP DI POP SI
+ POPF
+ public _draw_box
+ public _Transform
+ public name
+ push ai
+ push ax
+ push bx
+ push cx
+ push di
+ push ds
+ push dx
+ push si
+ push WORD PTR [bp+8]
+ PUSH AX PUSH BX PUSH CX PUSH
+ PUSHF
+ repe cmpsb
+ RET
+ RETF REPZ MOVSB INC BP MOV
+ rol eax,7
+ shl di,1
+ shr al,1 ;extract high nibble
+ SI MOV DS,AX
+ stc
+ sti
+ STI
+ StoreSpc endp
+ stosb ;store
+ stosw
+ sub cx, ax
+ sub cx,di
+ sub dx, dx
+ sub TabOff, dx
+ test al,1
+ test InpSta, ax
+ test Options, MAK_CAP
+ XCHG AH,AL NEG AX XCHG AX,CX
+ xor ah,ah
+ xor al,al
+ XOR DI,DI POP SS MOV SI,DIDEFINT A-Z
+DECLARE SUB
+declare sub
+Player SetScreen Center ( GetInputs DoExplosion Results UpdateScores
+DECLARE FUNCTION StartX, AS INTEGER
+declare function
+END TYPE
+end type
+'Constants
+CONST TRUE = -1
+CONST FALSE = NOT TRUE
+DIM SHARED
+'Global Variables Mode Variables Color AS SINGLE
+ as single
+ INT(RND(1)
+ DEF SEG
+ KeyFlags = PEEK(1047)
+ IF AND OR XOR THEN
+ END IF
+ GOSUB InitVars
+END
+end if and or then
+data read sleep on error goto cls step call palette enseif screen timer randomize printloop until else
+return
+ DATA Restore RESTORE REDIM = FALSE
+ FOR i = 0 TO READ
+ NEXT i
+ ELSE
+RETURN
+ CLS
+ LOCATE 10, 5
+ PRINT
+ END
+ RESUME NEXT
+ resume next i
+REM $STATIC
+FUNCTION PLAY "
+ DO
+ LOOP UNTIL TIMER
+END FUNCTION
+
+TYPE
+ CIRCLE(circle(LINE(line(paint(PAINT(SUNATTR
+ pset(PSET (SELECT CASE r
+ case 1
+ CASE 1
+ CASE 2 THEN PUT (OBJECTCOLOR TO 1 STEP -1
+ CASE ELSE
+ CALL CHR$(8) = MID$(STRING$(RIGHT$(STR$(SPACE$(VAL(LEFT$(LEN(UCASE$(CHR$(13)
+ WHILE INKEY$ <> "": WEND 'Clear keyboard buffer
+ BEEP
+ SCREEN Mode
+ SetScreen
+ VIEW PRINT 9 TO 24
+ PALETTE OBJECTCOLOR ELSEIF
+ COLOR 7
+ J = 1
+ RANDOMIZE (TIMER)
+ SLEEP 1
+ (NOT CINT(PALETTE AS STRING * AS DOUBLE
+ ON ERROR GOTO OPEN FOR INPUT AS #1
+ CLOSE
+ CHR$(27)
+FIELD #1, head length finished AS INTEGER
+ EXIT SUB
+ MKD$(0)
+ VARPTR(
+ VARSEG(
+MS-DOS QBasic
+LPRINT TAB
+LINE INPUT #1,
+ CHR$(0) + = (currChoice + MaxChoice - 2) MOD MaxChoice + 1
+.Title
+.AType
+ CLOSE #2
+ DATE$ RESTORE colorTable(Y/N) STEP 2
+ POKE THEN END
+ WHILE NOT EOF(1)
+ "CON", "SCRN", "PRN", "COM1", "COM2" then end
+ data$ restore Q
] – N Z
+ ]
+
+ '
+ G O
+
+ ' 7 '
+
+ F
F U œ N 8 8 8 8 8 8 8 8 8 8
+ 8 @ ! / ÿ &