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 ]–NZ +] + +' +GO + +'7 ' + + F FUœN888888888 8 +8 @ !/ÿ&q G + + + + + + +   +    Gd š™žð›@á +áá +á á áá +á  PRINT_BLOCK ƒ : Q`–N_`7 +7 +<<E (7 +(7 (7 (7===LœH.""""""  +  /ÿÿ&ñ G    Gd š™žn€4žgwba8fwfw~ €fwÏfwÔfwÝfwæfwìfw÷fwüfwgwgw gwgwgwgw%gw+gw1gw6gw\RADOX\TABLES\SAMPAPP\RESTTEMP.DBDateEmployeeCustomerStateHome GroupYearTotalDOW #DOWMonth #MonthVolItem #QuantEmp #CustPrice  ÿPà`*ICROSOFT PIFEX‡q*INDOWS 386 3.0�h€�d2Œ *INDOWS 286 3.0ÿÿ[windows] +device=HP *aserJet 500+,HPPCL,LPT1.OS2 +load = +run= +Beep=yes +Spooler=yes +NullPort=None +BorderWidth=3 +KeyboardSpeed=31 +CursorBlinkRate=530 +DoubleClickSpeed=452 +Programs=com exe bat pif +Documents= +DeviceNotSelectedTimeout=15 +TransmissionRetryTimeout=45 +swapdisk= +MouseThreshold1=10 +MouseThreshold2=0 +MouseSpeed=0 + +[Desktop] +Pattern=(None) +Wallpaper=(None) +TileWallpaper=0 +GridGranularity=0 +IconSpacing=120 + +[Extensions] + +[intl] +sCountry=United States +iCountry=1 +iDate=0 +iTime=0 +iTLZero=0 +iCurrency=0 +iCurrDigits=2 +iNegCurr=0 +iLzero=1 +iDigits=2 +iMeasure=1 +s1159=AM +s2359=PM +sCurrency=$ +sThousand=, +sDecimal=. +sDate=/ +sTime=: +sList=, +sShortDate=M/d/yy +sLongDate=dddd, MMMM dd, yyyy + +[ports] +; A line with [filename].PRN followed by an equal sign causes +; [filename] to appear in the Control Panel's Printer Configuration dialog +; box. A printer connected to [filename] directs its output into this file. +LPT1.OS2= +LPT2.OS2= +FILE:= +EPT:= +LPT1:= +LPT2:= +LPT3:= +COM1:=9600,n,8,1 +COM2:=9600,n,8,1 +COM3:=9600,n,8,1 +COM4:=9600,n,8,1 + +[fonts] +Roman (All res)=ROMAN.FON +Script (All res)=SCRIPT.FON +Modern (All res)=MODERN.FON +Symbol 8,10,12,14,18,24 (VGA res)=SYMBOLE.FON +Helv 8,10,12,14,18,24 (VGA res)=HELVE.FON +Tms Rmn 8,10,12,14,18,24 (VGA res)=TMSRE.FON +Courier 10,12,15 (VGA res)=COURE.FON + +[PSCRIPT] +External Printers=6 +Printer1=40291760 +Printer2=40291730 +Printer3=40293930 +Printer4=40293960 +Printer5=IBM17521 +Printer6=IBM39521 + +[PrinterPorts] +HP *aserJet 500+=HPPCL,LPT1.OS2,45,15 + +[devices] +HP *aserJet 500+=HPPCL,LPT1.OS2 + +[Clock] +iFormat=1 + +[Settings] +Window=38 24 581 363 1 +SaveSettings=1 +MinOnRun=0 +AutoArrange=0 + +[Groups] +Group1= +# include +#include + + ICON .ico + +STRINGTABLE +BEGIN + IDS_NAME, "hapsnap" +END + +MAIN DIALOG LOADONCALL MOVEABLE DISCARDABLE 10, 19, 270, 123 +CAPTION "hapsnap" +STYLE WS_BORDER | WS_CAPTION | WS_DLGFRAME | WS_SYSMENU | WS_GROUP | WS_VISIBLE | WS_POPUP +BEGIN + CONTROL "Pattern:", 101, "static", SS_LEFT | WS_CHILD, 3, 7, 32, 7 + CONTROL "", 102, "edit", ES_LEFT | WS_BORDER | WS_TABSTOP | WS_CHILD, 36, 5, 104, 12 + CONTROL "Drives:", 103, "static", SS_LEFT | WS_CHILD, 3, 22, 28, 8 + CONTROL "", 104, "listbox", LBS_NOTIFY | WS_BORDER | WS_VSCROLL | WS_CHILD, 2, 41, 253, 81 + CONTROL "#: 0", 105, "static", SS_RIGHT | WS_CHILD, 216, 22, 50, 8 + CONTROL "", 106, "static", SS_LEFT | WS_CHILD, 4, 31, 250, 9 + CONTROL "Search", 107, "button", BS_DEFPUSHBUTTON | WS_TABSTOP | WS_CHILD, + CONTROL "Clear", 108, "button", BS_PUSHBUTTON | WS_TABSTOP | WS_CHILD, + CONTROL "1:", 110, "button", BS_AUTOCHECKBOX | WS_TABSTOP | WS_CHILD, +END + +SQLHistory: Created Edited +grant select on lookup to public +/ +commit +/ +declare +select distinct for update +delete from +insert into demp values ( +update set +where +create table +create index +create view +group by +order by +alter table +alter database controlfile reuse maxlogfiles maxdatafiles maxinstances noarchivelog exclusive +alter rollback segment +alter session set sql_trace true +drop table number(char(date, not null, +drop view +drop sequence +exception + cluster rollback to savepoint pctused pctfree size initrans maxtrans default storage backup dismount shared open noresetlogs close immediate +audit whenever not successfull; +abs(ceil(floor(mod(power(round(sign(sqrt(trunc(chr(initcap(lower(lpad(rpad(ltrim(rtrim(soundex(replace(substr(translate(upper(ascii(instr(length(avg(count(min(max(stddev(variance(decode(least(greatest(to_char(to_number(to_date(userenv(round(sysdate +.currval.nextval.level.rowid +union intersect minus prior increment by synonym character decimal long varchar double precision nomaxvalue nominvalue nocycle noorder modify constraint tablespace connect to identified by using ' + user_users dba_tablespaces dba_data_files dba_segments sys.dual dba_free_space dba_users +GRANT SELECT ON LOOKUP TO PUBLIC +/ +COMMIT +/ +DECLARE +SELECT DISTINCT FOR UPDATE OF +DELETE FROM +INSERT INTO DEMP VALUES (7900,',',00); +UPDATE SET +WHERE +CREATE TABLE +CREATE INDEX +CREATE VIEW SALES AS +GROUP BY +ORDER BY +ALTER TABLE +ALTER DATABASE CONTROLFILE REUSE MAXLOGFILES MAXDATAFILES MAXINSTANCES NOARCHIVELOG EXCLUSIVE +ALTER ROLLBACK SEGMENT +ALTER SESSION SET SQL_TRACE TRUE +DROP TABLE NUMBER(CHAR(DATE, NOT NULL, +DROP VIEW +DROP SEQUENCE +CREATE SEQUENCE ORDID +INCREMENT BY 1 +START WITH 622 +NOCACHE; +EXCEPTION + CLUSTER ROLLBACK TO SAVEPOINT PCTUSED PCTFREE SIZE INITRANS MAXTRANS DEFAULT STORAGE BACKUP DISMOUNT SHARED OPEN NORESETLOGS CLOSE IMMEDIATE +AUDIT WHENEVER NOT SUCCESSFULL; +ABS(CEIL(FLOOR(MOD(POWER(ROUND(SIGN(SQRT(TRUNC(CHR(INITCAP(LOWER(UPPER(LPAD(RPAD(SOUNDEX(LTRIN(RTRIM(REPLACE(SUBSTR(TRANSLATE(ASCII(INSTR(LENGTH(AVG(COUNT(MIN(MAX(STDDEV(VARIANCE(DECODE(LEAST(GREATEST(TO_CHAR(TO_NUMBER(TO_DATE(USERENV(ROUND(SYSDATE +.CURRVAL.NEXTVAL.LEVEL.ROWID +UNION INTERSECT MINUS PRIOR INCREMENT BY SYNONYM CHARACTER DECIMAL LONG VARCHAR DOUBLE PRECISION NOMAXVALUE NOMINVALUE NOCYCLE NOORDER MODIFY CONSTRAINT TABLESPACE CONNECT TO IDENTIFIED BY USING ' + USER_USERS DBA_TABLESPACES DBA_DATA_FILES DBA_SEGMENTS SYS.DUAL DBA_FREE_SPACE DBA_USERS +host echo "Building demonstration tables. Please wait." +set termout off +set feedback off Common LISP Pascal batch files numeric updating decrementing incrementing DEBUG contents introduction predicates conditionals local global iterations technique quick reference + pattern matching implementation arithmetic entering compatible methods and theory definitions adjacent successor predecessor edge Level computer failure evaluation nothing minus + routine possible terminate simulation compare rounding up common resulting headerfile generation obsolete digital analog communications engineering blitter graphics linker women + assembler compiler generate harddisk PC constants partial the attributes object-oriented disadvantage makefile syntax semantic overloading boundaries matrix exception spool only + information hiding conventions tree head interpreters static dynamic abstraction smalltalk Prolog declaration separate libraries hexadecimal binary octal statement executable + scope volatile assignment unary preprocessor resolution starting signal wait ANSI ASCII machine essential dereferencing keywords solve special remove extract determine verify + calculate feature history duplicate controller improvements polymorphic garbage collection raytracing organization edition languages classification sequential parallel serial + vector program counter mainframes magnetic frequency networks distributed systems asynchronous transmission equivalent interrupts kernel Management semaphores demand questions + loading interactive requirements hierarchical workstation identifier transformation differential generalize anti-aliasing Bresenham algorithm rotation surface perspective Huffman + projection dimensions pattern displayed quality plotters complement difference business maintenance toolbox environment sliding dictionary Shannon-Fano bibliographic techniques + leaves statistics decoding following Fibonacci concerning forever heuristics conclusions readable writable LempelZivStorer parent collections properties with from the pointer + for the information representing instructions components formulas blocks users guide notepad ctrl backspace insert overwrite typeover asterisk semi-colon scientific calculator + connector track sector heads physical device driver tested certified wordprocessing warning double sided automatic tracks per inch address miscellaneous appendices experiment the + anything something description internal external 65535 4095 16384 32768 32767 0xFFFF versus windows gadgets requesters menus viewport joystick suicide handshaking bidirectional + disclaimer DISCLAIMER transmit receive acknowledgement trademark thresholds overflow parity stopbits framing listing understand calendar help replace save move searching printing + quit cut paste forward backward compatibility uppercase lowercase underlining punctuation manipulating warning failure truncated word_wrapped linesize pagesize heading numwidth + unique primary secondary foreign Database Administrator concatenation composite key integrity constraint join column keyword logical expression precedence consistency glossary + registration player participant emerald handicap you were he was she is they are I am have been them incorporated international this that if then than where what which with until + TCP/IP SPX/IPX acquire security record tuple shadow 1,000,000 prototype shareware context configuration suitable LARGEINT credit card screen saver RS232 shortcut key profiling + performance workbench paradigms structured hypercubes multitasking microprocessor EISA development embedded {*******************************************************} +{ } +{ *urbo Pascal Version 6.0 } +{ *urbo Vision Unit } +{ } +{ Copyright (c) 1990 International } + +(* +*) + +const + ('DefaultFont', 'TriplexFont', 'SmallFont', 'SansSerifFont', 'GothicFont'); + ('SolidLn', 'DottedLn', 'CenterLn', 'DashedLn', 'UserBitLn'); + ('EmptyFill', 'SolidFill', 'LineFill', 'LtBkSlashFill', 'XHatchFill', 'InterleaveFill', 'WideDotFill', 'CloseDotFill'); + FillStyles TextDirect conditional ] of string[8] = ('HorizDir', 'VertDir'); + = packed array[1..] = ('LeftText', 'CenterText', 'RightText'); +'BottomText', 'TopText'] of Char; + +type + +var + GraphDriver : integer; + MaxX, MaxY : word; + ErrorCode : integer; + OldExitProc : Pointer; +{$F+} + +PROCEDURE = ARRAY[1.. := false; +procedure Initialize; +begin +BEGIN + InGraphicsMode : boolean; { DirectVideo repeat until REPEAT UNTIL INTEGER; +BOOLEAN; +REAL; +TYPE VAR CONST := FALSE; +FUNCTION ] OF CHAR; +{$IFDEF Use := true; +{$ELSE} +{$ENDIF} +InitGraph(GraphDriver, GraphMode, PathToDriver); +Randomize; +end; { +function DrawBorder; +{ ViewPort : ViewPortType; +SetLineStyle(GetViewSettings(with ViewPort do +WITHRectangle(SetViewPort(OutTextXY(SetTextStyle(SetTextJustify(FullPort; +LineSettingsType; +FillSettingsType; +TextSettingsType; + IF <> NIL THEN ELSE + if WriteOut('do then + else + Right := Left := Depth := trunc(Trunc(OutTextXY(Round(round(Cos(Sin(Circle(MoveTo(LineTo(Points[KeyPressed; +while (not Width, Height Ellipse(FreeMem(Rectangle(Writeln('Write('unit App; +WHILE (NOT width, height writeln('write(':= TRUE; +{$F+,O+,S-,X+,D-} + index BlackWhite TApplication CBackground = nil; +implementation + WriteLine(Dispose(HandleEvent(Message(Boolean; + Readln; + readln; +virtual; +constructor destructor Dialogs record MenuBar LongInt; +end. +QuickSort(assembler; +ScrollBar, PutPeerViewPtr SetCmdState(RegisterType(NumInputLine.FileNameCheckCollide(ReversesbHandleKeyboardShowTerminalWindow; + +uses + Dos, Objects, Drivers, Memory, Views, Menus, Dialogs, StdDlg, MsgBox, App, + DemoCmds, Gadgets, Puzzle, Calendar, AsciiTab, Calc, FViewer, HelpFile, + DemoHelp, ColorSel, MouseDlg, Overlay; + Screen, Bricks; + Dos, Crt, Graph; + TCUtil, TCLStr, TCScreen, TCHash; + +USES + DOS, CRT, GRAPH; + dos, crt, graph; + + cmRetrieveDesktop cmCalculator ClearEvent(Event); + PProtectedStream HelpCounter Offset Length ReadParagraph(TClockView = PSortedCollection; +FOR i := 1 TO RECORD +for i := 1 to NewSubMenu(WriteCard(NewItem('R.Assign(^.Next; <> nil NumLines MagicHeader = + NodePtr = ^Node; + Node = record + Item: PointPtr; + Next: NodePtr; + end; + +RootNode List.Item HeapStatus(' Modified Clicked MouseDown ovrNotFound := Board[Y, X]; + := MemAlloc(SizeOf( and Directory Control := False; + CellTypes = (ClEmpty, ClValue, ClText, ClFormula, ClRepeat); + Justification = (JLeft, JCenter, JRight); + CellHashTable Empty := True; + LegalValue Overwrite Found Total FirstItem result value label 99; + Exit; + GetQuit or (S <> '') ClrEOLXY(System.NewLen < OldLenprogram interval(input,output); +{dataflow simulator for real interval arithmetic} +{number of maxint; + Sreal = record +(finite,infinite)cardinality mantissa exponent hulp help Shift Dummy (not eof) mod MOD DIV div MAXINT PROGRAM (INPUT,OUTPUT); +END. + END;README.DOC: This document describes how to get started, features etc. + +JUST GIVE THE UC COMMAND TO GET ACCESS TO THE HELP SYSTEM! + +This document describes the purpose of "UltraCompressor II". + +UC IS NOT A FREE PRODUCT. You have to pay for it if you use it. Read +LICENSE.DOC for more information. UC is a "try-before-you-buy" program +so you can try it before you buy it. LICENSE.DOC also contains the AIP +licence agreement. By using this package, you are agreeing to be bound +by the terms of this agreement. + +This document consists of the following paragraphs: + + - A. What is Ultra Compressor II + - B. Support, feedback + - C. Ultra Compressor main feature overview + - D. Related products + - E. Manual overview + - F. Included files + +To jump to a certain paragraph, press the corresponding letter. To +jump to a specific document/chapter, press the digit. See paragraph E +for an overview of all documentation. + + +1.A WHAT IS ULTRA COMPRESSOR II (UC). +===================================== + +UltraCompressor II (UC) from Ad Infinitum Programs (AIP) is an +archiver. An archiver is a file handling and compression utility. It +will make most of your files significantly smaller and it can keep +related files together. When you need the files, you can decompress +them with the same UC utility. UC is very easy to use, thanks to its +simple user interface and has support for all archiving needs + +An archive behaves just like a floppy disk. It has a directory +structure, you can copy from the archive with extraction and copy to +the archive by adding. It is also possible to delete files from the +archive. + +UC can also make such an archive DAMAGE PROTECTED, so ALL files in the +archive are safe, even if there are disk errors (within reasonable +limits). With UC an archive can contain multiple versions of the same +file. + +With a clear manual and a limited number of commands, UC will turn out +to be very easy to use. Most of the options are configurable, so the +commands are ve +NAME + +DESCRIPTION 'adad' + +CODE PRELOAD MOVEABLE DISCARDABLE +DATA PRELOAD MOVEABLE MULTIPLE + +EXETYPE WINDOWS + +SEGMENTS + _RES PRELOAD MOVEABLE DISCARDABLE + +HEAPSIZE 128 +STACKSIZE 8192 + +EXPORTS + TstAppWndProc + About + DDEClientWndProc + +NAME DLLDEMO +DESCRIPTION 'C++ Windows DLL Demo' +EXETYPE WINDOWS +CODE PRELOAD MOVEABLE +DATA PRELOAD MOVEABLE MULTIPLE +HEAPSIZE 1024 +STACKSIZE 5120 +CC = BCC +AS = TASM +RC = RC +.asm.obj: + $(AS) $(AFLAGS) $&.asm +.c.exe: + $(CC) $(CFLAGS) $&.c +.c.obj: + $(CC) $(CFLAGS) /c $&.c +.cpp.obj: + $(CC) $(CPPFLAGS) /c $&.cpp +.rc.res: + $(RC) $(RFLAGS) /r $& +.SUFFIXES: .exe .obj .asm .c .res .rc +AUTODEPEND +# *Translator Definitions* +# *Implicit Rules* +.c.obj: + $(CC) -c {$< } +.cpp.obj: + $(CC) -c {$< } +# *List Macros* +EXE_dependencies = \ + genprime.obj \ + init.obj \ + mathsup.obj \ + basics.obj \ + random.obj \ + md5p.obj \ + fprims.obj +# *Explicit Rules* +genprime.exe: genprime.cfg $(EXE_dependencies) + # no map file +# *Individual File Dependencies* +genprime.obj: genprime.cfg genprime.c +random.obj: genprime.cfg random.c +# *Compiler Configuration File* +genprime.cfg: genprime.mak + copy &&| +-f- +-C +-w+ +-N +-v +-G +-O +-Og +-Oe +-Om +-Ov +-Ol +-Ob +-Op +-Oi +-Z +-k- +-vi- +(C) Copyright 1991 by International + +// Example Program +*/ +#include +#include + include files initialise goto global variables + + + + + + +#include +#error +#asm +#endasm +#define NR_ +#define MAX +void main(void) +{ +(argc, argv) +{ +int argc, char *argv[]) +{ + int i; + FILE *fp; + for(i = 0;i <;i++) + for(;;) + printf(" float scanf("%d",&,NULL); + getch(); + detectgraph(&g_driver, &g_mode); + initgraph(&g_driver, &g_mode, "..\\bgi"); + wide = (int)((getmaxx()) / ((ARRAYMAX * 2 ) + 1)); + bottom = getmaxy() - 20; + return; +int DoWMCommand(WORD wParam); +void DoLButtonDown(HWND hWnd,LONG lParam); +UpdateWindow(DoLButtonUp(DoMouseMove(RECTANGLE: +long FAR PASCAL function Background character delay(total; +struct +typedef struct WS_OVERLAPPEDWINDOW ELLIPSE: + char *str; + char str[ p->next; + int counter; + while (1) + while(1) + return 0; + switch( + case + break; + continue; + default: +static left = top = total = min(max(parameters wclass + return 1; +typedef unsigned int WORD; +typedef unsigned char BYTE; +typedef unsigned long DWORD; +extern char *tmp WM_DDE_FIRST, GWW_HINSTANCE | CS_HREDRAW | CS_VREDRAW; +hInstancehCursor CW_USEDEFAULT, if (Execute(Command + int len; +GlobalUnlock(GlobalAlloc(GMEM_MOVEABLE, WM_QUEYENDSESSION: +WM_DDE_FIRST,WS_CAPTION | + WS_SYSMENU | + WS_MINIMIZEBOX, + time_t t1; + time(&t1); + do +union { + lfHeight lfWidth lfCharSet message = FALSE; +cprintf("sprintf("UpdateWindow(UnlockResource(FreeResource(!CreateDialogIndirect( +#pragma argsused +#ifdef _Cdecl +#ifndef +#else +#endif +#undef _FARFUNC far + || && += -= != == >> << <<= >>= ' ' " " "\n"; +__cplusplus + bioskey(0); +_HEAPEMPTY_HEAPOK_FREEENTRY_USEDENTRY_HEAPEND_HEAPCORRUPT_BADNODE_BADVALUE_STDDEF_PTRDIFF_T_PTRDIFF_T +*_Cdecl calloc(free(realloc(brk(sbrk(heapcheck(coreleft(farmalloc( friend bcd +#pragma option -Vo- +#pragma inline +abs(acos(acos(asin(atan(cosh(exp(log(log10(pow(sinh(sqrt(tanh(operator+(const return *this; +private: +protected: +enum LASTMODECOLORS { + BLACK, BLUE, GREEN, CYAN, RED, MAGENTA, BROWN, LIGHTGRAY, DARKGRAY, LIGHTBLUE, LIGHTGREEN, LIGHTCYAN, LIGHTRED, LIGHTMAGENTA, YELLOW, WHITE +clreol(clrscr(gotoxy(wherex(wherey(getch(getche(kbhit(putch(ungetch(malloc(sizeof( +class : public virtual +inline void isalnum(isalpha(isascii(iscntrl(isdigit(isgraph(islower(isprint(ispunct(isspace(isupper(isxdigit(tolower(toupper( +extern int errno; +hourminutesecondhsecondsleep(_EPSILONgetpixel(moveto(_CLASSDEF(memcpy(read(ostream(istream(ceil(ldexp(memchr(memcmp(memcpy(memmove(memset(set_new_handler(new(abort( SEEK_CUR SEEK_END SEEK_SET stdin stdout stderr fclose(fgetc(fopen(fputc(fread(fscanf(fseek(ftell(fwrite(feof(getchar(putchar(atof(atoi(atol(getenv(rand(system(randomize(); +strcat(strchr(strcmp(strcpy(strcspn(strlen(strncat(strncmp(strncpy(strrchr(strpbrk(strstr(time( +optionS*InBuffer,*OutBufferwcHdumpClass.lp +typedef struct scrollkeys +SCROLLKEYS key2scroll [] = +VK_HOME,WM_COMMAND,IDM_HOME,VK_END,WM_VSCROLL,SB_BOTTOM,VK_PRIOR,SB_PAGEUP,VK_NEXT,SB_PAGEDOWN,VK_UP,SB_LINEUP,VK_DOWN,SB_LINEDOWN,VK_LEFT,WM_HSCROLL,SB_PAGEUP,VK_RIGHT,SB_PAGEDOWN}; +RegisterClass(GetInstanceData(CreateWindow(cursfont.lfSelectObject(getcwd(curpath,setmode( - 'A'; + printf("Hello, world\n"); +::AbstractArray::ArrayIterator::Point::HashTable::Arc::Circle::Sortable::Queue::Error::Location::GMessage::String::Definition::Container::DoubleList::Stack::SortedArray::Set::Object::BaseTime::ListElement::ListIterator::BaseDate::FilesByName::FileData::DoubleListElement::Directory::Deque::Dictionary::DoubleListIterator::ContainerIterator::Collection::Class::Bag::Array::Association::The following symbols control inclusion of various parts of this file: +WINVERversion numberNOKERNEL APIsNOUSERNOSOUNDNOCOMMNODRIVERSNOMINMAXNOLOGERRORNOPROFILERNOLFILEIOPENFILENORESOURCENOATOMNOLANGUAGENOLSTRINGNODBCSNOKEYBOARDINFONOGDICAPMASKSNOCOLORNOGDIOBJNODRAWTEXTNOTEXTMETRIC +NOSCALABLEFONTNOBITMAPNORASTEROPSNOMETAFILENOSYSMETRICSNOSYSTEMPARAMSINFONOMSGNOWINSTYLESNOWINOFFSETSNOSHOWWINDOWPOSNOVIRTUALKEYCODESNOKEYSTATESNOWHNOMENUSNOSCROLLNOCLIPBOARDNOICONSNOMBNOSYSCOMMANDSNOMDINOCTLMGRNOWINMESSAGESNOHELP +SELECTORFIELDOFFSETDECLARE_HANDLE32GetVersionGetFreeSpaceGetCurrentPDBGetWindowsDirectoryGetSystemDirectoryGetFreeSystemResourcesGFSR_SYSTEMRESOURCES0x0000GFSR_GDIRESOURCES0x0001GFSR_USERRESOURCES0x0002GetWinFlags +WF_PMODEWF_CPU286WF_CPU3860x0004WF_CPU486WF_STANDARDWF_WIN286WF_ENHANCEDWF_WIN386WF_CPU086WF_CPU186WF_LARGEFRAMEWF_SMALLFRAMEWF_80x87WF_PAGINGWF_WLO + GetDOSEnvironmentGetCurrentTimeGetTickCountGetTimerResolutionNOLOGERRORLogErrorLogParamErrorLogParamError +ERR_WARNINGERR_PARAMERR_SIZE_MASKERR_BYTERR_WORDERR_DWORDERR_BAD_VALUERR_BAD_FLAGSERR_BAD_INDEXERR_BAD_DVALUERR_BAD_DFLAGSERR_BAD_DINDEXERR_BAD_PTRERR_BAD_FUNC_PTRERR_BAD_SELECTORERR_BAD_STRING_PTRERR_BAD_HANDLERR_BAD_HINSTANCERR_BAD_HMODULE +ERR_BAD_GLOBAL_HANDLERR_BAD_LOCAL_HANDLERR_BAD_ATOMERR_BAD_HFILERR_BAD_HWNDERR_BAD_HMENUERR_BAD_HCURSORERR_BAD_HICONERR_BAD_HDWPERR_BAD_CIDERR_BAD_HDRVRERR_BAD_COORDSERR_BAD_GDI_OBJECTERR_BAD_HDCERR_BAD_HPENERR_BAD_HFONTERR_BAD_HBRUSH +ERR_BAD_HBITMAPERR_BAD_HRGNERR_BAD_HPALETTERR_BAD_HMETAFILERR_GALLOCERR_GREALLOCERR_GLOCKERR_LALLOCERR_LREALLOCERR_LLOCKERR_ALLOCRESERR_LOCKRESERR_LOADMODULERR_CREATEDLGERR_CREATEDLG2ERR_REGISTERCLASSERR_DCBUSYERR_CREATEWNDERR_STRUCEXTRA +ERR_LOADSTRERR_LOADMENUERR_NESTEDBEGINPAINTERR_BADINDEXERR_CREATEMENUERR_CREATEDCERR_CREATEMETAERR_DELOBJSELECTEDERR_SELBITMAP +dwOptionsdwFilterachAllocModule[8]dwAllocBreakdwAllocCountWINDEBUGINFOGetWinDebugInfoSetWinDebugInfoDebugOutputWDI_OPTIONSWDI_FILTERWDI_ALLOCBREAKDBO_CHECKHEAPDBO_BUFFERFILLDBO_DISABLEGPTRAPPINGDBO_CHECKFREEDBO_TRACEBREAKDBO_WARNINGBREAKDBO_NOERRORBREAK +DBO_NOFATALBREAKDBO_INT3BREAKDBF_TRACEDBF_WARNINGDBF_ERRORDBF_FATALDBF_KERNELDBF_KRN_MEMMANDBF_KRN_LOADMODULEDBF_KRN_SEGMENTLOADDBF_USERDBF_GDIDBF_MMSYSTEMDBF_PENWINDBF_APPLICATIONDBF_DRIVERFatalExitWindowsExecFatalAppExitWindowsEW_RESTARTWINDOWS +EW_REBOOTSYSTEMDebugBreakOutputDebugStringSEM_FAILCRITICALERRORSSEM_NOGPFAULTERRORBOXSEM_NOOPENFILEERRORBOXSetErrorModeIsBadReadPtrIsBadWritePtrIsBadHugeReadPtrIsBadHugeWritePtrIsBadCodePtrIsBadStringPtrProfInsChkProfSetupProfSampRateProfStartProfStop +ProfClearProfFlushProfFinishNOPROFILERSwitchStackBackSwitchStackToHINSTANCE_ERRORLoadModuleFreeModuleLoadLibraryFreeLibraryWinExecGetModuleHandleGetModuleUsageGetModuleFileNameGetProcAddressGetInstanceDataGetCodeHandleoffSegmentcbSegmentcb +AllocalignShiftreserved[2]SEGINFOGetCodeInfoMakeProcInstanceFreeProcInstanceMakeProcInstanceFreeProcInstanceSetSwapAreaSizeSwapRecordingValidateCodeSegmentsWEP_SYSTEM_EXITWEP_FREE_DLLNOKERNELGetNumTasksGetCurrentTaskDirectedYieldGMEM_FIXEDGMEM_MOVEABLE +GMEM_NOCOMPACTGMEM_NODISCARDGMEM_ZEROINITGMEM_MODIFYGMEM_DISCARDABLEGMEM_NOT_BANKEDGMEM_SHAREGMEM_DDESHAREGMEM_NOTIFYGMEM_LOWERGlobalDiscardGlobalAllocGlobalReAllocGlobalFreeGlobalDosAllocGlobalDosFreeGlobalLockGlobalUnlockGlobalSizeGlobalHandle +GMEM_DISCARDEDGMEM_LOCKCOUNTGlobalFlagsGlobalUnWireGlobalPageUnlockGlobalUnfixGlobalLRUNewestGlobalLRUOldestGlobalCompactGlobalNotifyLockSegmentAllocSelectorFreeSelectorAllocDStoCSAliasPrestoChangoSelectorGetSelectorBase +SetSelectorBaseLimitEmsPagesValidateFreeSpacesWM_COMPACTINGLMEM_FIXEDLMEM_MOVEABLELMEM_NOCOMPACTLMEM_NODISCARDLMEM_ZEROINITLMEM_MODIFYLMEM_DISCARDABLELHNDLPTRNONZEROLPTRLocalDiscardLocalAllocLocalReAllocLocalFreeLocalLockLocalUnlockUINTLocalSize +LMEM_DISCARDEDLMEM_LOCKCOUNTLocalFlagsLocalInitLocalCompactLocalShrinkNOLFILEIOOF_READOF_WRITEOF_READWRITEOF_SHARE_COMPATOF_SHARE_EXCLUSIVEOF_SHARE_DENY_WRITEOF_SHARE_DENY_READOF_SHARE_DENY_NONEOF_PARSEOF_DELETEOF_VERIFYOF_SEARCHOF_CANCELF_CREATE +OF_PROMPTOF_EXISTOF_REOPENOpenFileREAD_WRITE_lopen_lcreat_lclose_llseek_lread_lwrite_hread_hwriteTF_FORCEDRIVEGetTempFileNameGetTempDriveDRIVE_REMOVABLEDRIVE_FIXEDDRIVE_REMOTESetHandleCountWNetAddConnectionWNetGetConnectionAllocResourceSetResourceHandler +MAKEINTRESOURCEOBM_CHECKBOXESIsCharUpperIsCharLowerlstrcmpilstrcpylstrcatlstrlenhmemcpyGetEnvironmentSetViewportOrgGetViewportOrgSetViewportExtGetViewportExtOffsetViewportOrgScaleViewportExtGetBValue(rgb)COLOR_MENUTEXTCOLOR_WINDOWTEXTCOLOR_INACTIVEBORDERCOLOR_HIGHLIGHTTEXT +BS_NULLBS_HATCHEDBS_PATTERNSetSystemPaletteUseGetClipBoxMoveToGetCurrentPositionSetTextCharacterExtraRASTER_FONTTYPEDEVICE_FONTTYPETRUETYPE_FONTTYPETT_AVAILABLETT_ENABLEDWM_QUITCreateWindowIsWindowVisibleShowWindowSW_HIDESW_NORMALSW_MAXIMIZESW_MINIMIZESW_RESTOREHIDE_WINDOW +SHOW_OPENWINDOWSHOW_FULLSCREENGetNextWindowGW_CHILDGetPropVK_RBUTTONVK_CANCELVK_BACKVK_TABVK_CLEARVK_RETURNVK_SHIFTVK_CONTROLVK_MENUVK_PAUSEVK_CAPITALVK_ESCAPEVK_SPACEVK_SELECTVK_PRINTVK_EXECUTEVK_SNAPSHOTVK_INSERTVK_DELETEVK_HELPVK_NUMPADVK_MULTIPLYVK_ADDVK_SEPARATORVK_SUBTRACTVK_DECIMALVK_DIVIDEVK_F24VK_NUMLOCKVK_SCROLL +WM_LBUTTONDOWNWM_LBUTTONUPWM_LBUTTONDBLCLKWM_RBUTTONDOWM_RBUTTONUPWM_RBUTTONDBLCLKWM_MBUTTONDOWNWM_MBUTTONUPWM_MBUTTONDBLCLKGetSystemMenuDrawMenuBarChangeMenuWM_CUTWM_COPYWM_PASTEWM_CLEARWM_UNDO +DrawIconIDI_APPLICATIONMAKEINTRESOURCEIDI_HANDIDI_QUESTIONIDI_EXCLAMATIONIDI_ASTERISKHELP_CONTENTSStartSoundSendDriverMessagedel @echo off +cls +if not exist goto end +copy /b \tmp\msdos\tools\text\dos\utils\include\manual\games\docs\*.txt +dir +call \windows\*.bak +cd test.tst goto errexit +:loop +:exit +:ok +:error +:end +:errexit +:skip +device=c:\;c:\*.bat +dos=high +path=c:\;c:\loadhi batch file backup restore errorlevel driver startup drive parameters diskette format subdirectory installation error checking config.sys autoexec.bat > nul +mode con +ncd +pause +set include= +image +rem +ver +expand %_fd% +dosshell +dosswap +diskcomp +diskcopy +comp +maker .mak +shell= +files= +buffers= +lastdrive= + command.com +\*.dat +\*.* +\*.sys +\*.doc +uc2 a +uc2 l +DEL @ECHO OFF +CLS +IF NOT EXIST GOTO END +COPY /b \TMP\MSDOS\TOOLS\TEXT\DOS\UTILS\INCLUDE\MANUAL\GAMES\DOCS\*.TXT +DIR +CALL \WINDOWS\*.BAK +CD TEST.TST GOTO ERREXIT +:LOOP +:EXIT +:OK +:ERROR +:END +:ERREXIT +:SKIP +DEVICE=C:\;C:\*.BAT +DOS=HIGH +PATH=C:\;C:\LOADHI BATCH FILE BACKUP RESTORE ERRORLEVEL DRIVER STARTUP DRIVE PARAMETERS DISKETTE FORMAT SUBDIRECTORY INSTALLATION ERROR CHECKING CONFIG.SYS AUTOEXEC.BAT > NUL +MODE CON +NCD +PAUSE +SET INCLUDE= +IMAGE +REM +VER +EXPAND %_FD% +DOSSHELL +DOSSWAP +DISKCOMP +DISKCOPY +COMP +MAKER .MAK +SHELL= +FILES= +BUFFERS= +LASTDRIVE= + COMMAND.COM +\*.DAT +\*.* +\*.SYS +\*.DOC +UC2 A +UC2 E \ No newline at end of file diff --git a/Hawkynt.FileFormats.Archives/Hawkynt.FileFormats.Archives.csproj b/Hawkynt.FileFormats.Archives/Hawkynt.FileFormats.Archives.csproj index 43a2696cb..bc5b2a36a 100644 --- a/Hawkynt.FileFormats.Archives/Hawkynt.FileFormats.Archives.csproj +++ b/Hawkynt.FileFormats.Archives/Hawkynt.FileFormats.Archives.csproj @@ -22,6 +22,12 @@ $(TargetsForTfmSpecificContentInPackage);_AddBundledAssembliesToPackage + + + + + diff --git a/Hawkynt.FileFormats.Archives/README.md b/Hawkynt.FileFormats.Archives/README.md index 9357f828e..b3a0773e4 100644 --- a/Hawkynt.FileFormats.Archives/README.md +++ b/Hawkynt.FileFormats.Archives/README.md @@ -144,6 +144,7 @@ Every State, Test, Maintenance, Compress/Decompress and Demux/Mux/Remux cell is | [Split WIM (.swm)](https://en.wikipedia.org/wiki/Windows_Imaging_Format) | `Swm` | `.swm` `.swm2` `.swm3` `.swm4` … | WORM | ✅ | wipe | Reads wimlib-written sets from part 1 with its siblings beside it; `SwmFormatDescriptor.CreateSplit` writes multi-part sets that wimlib-imagex verifies and applies (XPRESS, LZX, LZMS, none); the generic single-stream Create writes a one-part set | [Microsoft Learn](https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/wim-and-esd-windows-image-files-overview) | | [T64 (Commodore tape image)](https://en.wikipedia.org/wiki/T64_(file_format)) | `T64` | `.t64` | R/W | ✅ | defrag · wipe | | [vice-emu.sourceforge.net](https://vice-emu.sourceforge.io/) | | [TAR](https://en.wikipedia.org/wiki/Tar_(computing)) | `Tar` | `.tar` | R/W | ✅ | shrink · wipe | | [pubs.opengroup.org](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html) | +| [UltraCompressor II](http://fileformats.archiveteam.org/wiki/UltraCompressor_II) | `Uc2` | `.uc2` `.pu2` | WORM | ✅ | — | Reads every UC2 release (masters, delta, turbo, long names); writes UC2 r2-readable archives with one custom master | [GitHub](https://github.com/klauspost/uc2/blob/main/docs/format.md) | | UHARC | `Uharc` | `.uha` | R/W | ✅ | defrag · wipe | | [Archive Team](http://fileformats.archiveteam.org/wiki/UHARC) | | [WIM](https://en.wikipedia.org/wiki/Windows_Imaging_Format) | `Wim` | `.wim` `.swm` `.esd` | WORM | ✅ | wipe | LZX / XPRESS / LZMS paths; kept create-only because an append edit would break the checksum chain | [wimlib.net](https://wimlib.net/) | | Wrapster | `Wrapster` | | WORM | ✅ | defrag · wipe | MP3-wrapper archive carrying one member; stays WORM by design | [Archive Team](http://fileformats.archiveteam.org/wiki/Wrapster) | @@ -544,7 +545,7 @@ Use it when a .NET process needs to enumerate, extract, test, create, edit or in -Every public and protected member of all 2571 types, generated from the built assembly and its XML documentation, is in [REFERENCE.md](https://github.com/Hawkynt/CompressionWorkbench/blob/main/Hawkynt.FileFormats.Archives/REFERENCE.md). +Every public and protected member of all 2576 types, generated from the built assembly and its XML documentation, is in [REFERENCE.md](https://github.com/Hawkynt/CompressionWorkbench/blob/main/Hawkynt.FileFormats.Archives/REFERENCE.md). diff --git a/Hawkynt.FileFormats.Archives/REFERENCE.md b/Hawkynt.FileFormats.Archives/REFERENCE.md index 464eb0ba6..4bf3fdae7 100644 --- a/Hawkynt.FileFormats.Archives/REFERENCE.md +++ b/Hawkynt.FileFormats.Archives/REFERENCE.md @@ -29589,6 +29589,84 @@ Implements `IDisposable`. | `Dispose` | `void Dispose()` | | | `Finish` | `void Finish()` | Finalizes and writes the archive. | +### Namespace `FileFormat.Uc2` + +[`Uc2Archive`](#uc2archive) · [`Uc2Archive.Input`](#uc2archiveinput) · [`Uc2Archive.Parsed`](#uc2archiveparsed) · [`Uc2Entry`](#uc2entry) · [`Uc2FormatDescriptor`](#uc2formatdescriptor) + +#### `Uc2Archive` + +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. + +| Member | Signature | Summary | +| --- | --- | --- | +| `Extract` | `static byte[] Extract(Parsed archive, Uc2Entry entry)` | Extracts one file, verifying its checksum. | +| `Fletch` | `static ushort Fletch(ReadOnlySpan data)` | UC2's "Fletcher": 0xA55A XOR the XOR of all little-endian 16-bit words (odd tail byte = low byte). | +| `Read` | `static Parsed Read(Stream stream)` | Parses an archive and verifies its central directory. | +| `Write` | `static void Write(Stream output, IEnumerable inputs)` | 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. | + +#### `Uc2Archive.Input` + +A file to write: its archive path ('/'-separated), content and time. + +Implements `IEquatable`. + +| Member | Signature | Summary | +| --- | --- | --- | +| `Input` | `Input(string Path, byte[] Data, DateTime LastModified)` | A file to write: its archive path ('/'-separated), content and time. | +| `Data` | `byte[] Data { get; init; }` | | +| `LastModified` | `DateTime LastModified { get; init; }` | | +| `Path` | `string Path { get; init; }` | | + +#### `Uc2Archive.Parsed` + +A parsed archive: entries plus what is needed to extract them. + +| Member | Signature | Summary | +| --- | --- | --- | +| `Entries` | `IReadOnlyList Entries { get; }` | Gets the entries, directories included. | + +#### `Uc2Entry` + +One file or directory of a UC2 archive (the newest revision of its name). + +| Member | Signature | Summary | +| --- | --- | --- | +| `Uc2Entry` | `Uc2Entry()` | | +| `Attributes` | `byte Attributes { get; init; }` | Gets the DOS attribute byte. | +| `CompressedSize` | `long CompressedSize { get; init; }` | Gets the compressed stream size (excluding its master). | +| `IsDirectory` | `bool IsDirectory { get; init; }` | Gets whether this is a directory. | +| `LastModified` | `DateTime? LastModified { get; init; }` | Gets the modification time (DOS local time, 2-second resolution). | +| `Method` | `int Method { get; init; }` | Gets the compression method number. | +| `Path` | `string Path { get; init; }` | Gets the path inside the archive, '/'-separated (long names where the archive records them). | +| `ShortName` | `string ShortName { get; init; }` | Gets the DOS 8.3 name as stored. | +| `Size` | `long Size { get; init; }` | Gets the uncompressed size. | + +#### `Uc2FormatDescriptor` + +UltraCompressor II archive (.uc2), Nico de Vries / AIP-NL, 1994-1996. + +Implements `IArchiveCreatable`, `IArchiveFormatOperations`, `IFormatDescriptor`. + +| Member | Signature | Summary | +| --- | --- | --- | +| `Uc2FormatDescriptor` | `Uc2FormatDescriptor()` | | +| `Capabilities` | `FormatCapabilities Capabilities { get; }` | | +| `Category` | `FormatCategory Category { get; }` | | +| `CompoundExtensions` | `IReadOnlyList CompoundExtensions { get; }` | | +| `DefaultExtension` | `string DefaultExtension { get; }` | | +| `Description` | `string Description { get; }` | | +| `DisplayName` | `string DisplayName { get; }` | | +| `Extensions` | `IReadOnlyList Extensions { get; }` | | +| `Family` | `AlgorithmFamily Family { get; }` | | +| `Id` | `string Id { get; }` | | +| `MagicSignatures` | `IReadOnlyList MagicSignatures { get; }` | | +| `Methods` | `IReadOnlyList Methods { get; }` | | +| `TarCompressionFormatId` | `string TarCompressionFormatId { get; }` | | +| `Create` | `void Create(Stream output, IReadOnlyList inputs, FormatCreateOptions options)` | | +| `ExtractEntryToMemory` | `byte[] ExtractEntryToMemory(Stream archive, string entryName, string password)` | | +| `Extract` | `void Extract(Stream stream, string outputDir, string password, string[] files)` | | +| `List` | `List List(Stream stream, string password)` | | + ### Namespace `FileFormat.Uharc` [`UharcEntry`](#uharcentry) · [`UharcFormatDescriptor`](#uharcformatdescriptor) · [`UharcReader`](#uharcreader) · [`UharcWriter`](#uharcwriter) diff --git a/Hawkynt.FileFormats.FileSystems/FileFormats/FileFormat.FirmwareHex/FirmwareHexWriter.cs b/Hawkynt.FileFormats.FileSystems/FileFormats/FileFormat.FirmwareHex/FirmwareHexWriter.cs index 6c7d4b452..1c9e17266 100644 --- a/Hawkynt.FileFormats.FileSystems/FileFormats/FileFormat.FirmwareHex/FirmwareHexWriter.cs +++ b/Hawkynt.FileFormats.FileSystems/FileFormats/FileFormat.FirmwareHex/FirmwareHexWriter.cs @@ -33,6 +33,7 @@ public static class FirmwareHexWriter { /// one the reader rendered. An input list with no payload describes an image /// with no data, which both formats can write. /// + /// More than one non-metadata payload is supplied. public static FirmwareImage ImageFrom(IReadOnlyList inputs, string sourceFormat) { ArgumentNullException.ThrowIfNull(inputs); @@ -53,7 +54,9 @@ public static FirmwareImage ImageFrom(IReadOnlyList inputs, st } // Any other single input is the payload, whatever it is called: a caller // converting a .bin into a .hex should not have to rename it first. - payload ??= input.ReadContent(); + if (payload is not null) + throw new InvalidDataException($"{sourceFormat} represents one logical firmware payload; multiple payload inputs are ambiguous."); + payload = input.ReadContent(); } List<(uint Address, byte[] Data)> segments; diff --git a/docs/LICENCE-PROVENANCE.md b/docs/LICENCE-PROVENANCE.md index ca50e4c15..6725b7389 100644 --- a/docs/LICENCE-PROVENANCE.md +++ b/docs/LICENCE-PROVENANCE.md @@ -31,7 +31,7 @@ package carries; everything else keeps a `THIRD-PARTY-NOTICE..txt` beside | Class | Upstream rows | Notes | |---|---|---| -| A | 18 | | +| A | 19 | | B | 17 | FFmpeg alone covers 38 codec folders | | C | 3 | specifications: `docs/CVF-CODECS-ON-DISK.md` (rewrite already under way), `docs/IT-SAMPLE-COMPRESSION-SPEC.md`, `docs/FDS-EXPANSION-SOUND-SPEC.md` | | R | 8 | | @@ -70,6 +70,7 @@ package carries; everything else keeps a `THIRD-PARTY-NOTICE..txt` beside | Claim site | Upstream | Licence | Where the notice is | |---|---|---|---| +| `Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Uc2/` | klauspost/uc2, revision `53622f89ada1cf0d4a69f91b653bdd44ec5b0f92`, format parsing and Ultra/Turbo decoding; UC2 supermaster dictionary from the same revision | LGPL-3.0 | `Hawkynt.FileFormats.Archives/FileFormats/FileFormat.Uc2/THIRD-PARTY-NOTICE.UltraCompressor2.txt` | | `Hawkynt.FileFormats.FileSystems/FileFormats/FileFormat.AndroidBootImage/AndroidBootImage.cs`, `Hawkynt.FileFormats.FileSystems/FileFormats/FileFormat.AndroidBootImage/AndroidBootImageMetadata.cs` | AOSP mkbootimg `bootimg.h`, `mkbootimg.py` and `unpack_bootimg.py`, revision `d2bb0af5ba6d3198a3e99529c97eda1be0b5a093` | BSD-3-Clause and Apache-2.0 | `Hawkynt.FileFormats.FileSystems/FileFormats/FileFormat.AndroidBootImage/THIRD-PARTY-NOTICE.mkbootimg.txt` | | `Hawkynt.FileFormats.FileSystems/FileFormats/FileFormat.AndroidVbmeta/AvbVbmeta.cs`, `Hawkynt.FileFormats.FileSystems/FileFormats/FileFormat.AndroidVbmeta/AvbDescriptorText.cs` | AOSP Android Verified Boot headers and `avbtool.py`, revision `c5066a96caa7bf4150c0a8cc8cc14ab81733fdc7` | MIT | `Hawkynt.FileFormats.FileSystems/FileFormats/FileFormat.AndroidVbmeta/THIRD-PARTY-NOTICE.avb.txt` | | `Compression.Core/Dictionary/Lzbitmap/` | libzbitmap (Ernesto A. Fernández, Corellium) | MIT | `THIRD-PARTY-NOTICE.libzbitmap.txt` | diff --git a/docs/catalogue/our-registry.csv b/docs/catalogue/our-registry.csv index bd9da9071..b2be89549 100644 --- a/docs/catalogue/our-registry.csv +++ b/docs/catalogue/our-registry.csv @@ -540,6 +540,7 @@ format,Tux2,TUX2,Archive,Y,.tux2,Y,Y,Y,Y format,Tux3,TUX3,Archive,Y,.tux3,Y,Y,, format,Txw,TXW (Yamaha TX16W),Audio,,.txw,Y,Y,Y, format,U8,Nintendo U8,Archive,,.u8;.arc,Y,Y,Y,Y +format,Uc2,UltraCompressor II,Archive,,.uc2;.pu2,Y,Y,Y, format,Ubifs,UBIFS,Archive,Y,.ubifs;.ubi;.img,Y,Y,Y,Y format,Udf,UDF,Archive,Y,.udf,Y,Y,Y,Y format,UefiFv,UEFI Firmware Volume,Archive,,.fv;.fd,Y,Y,Y,Y diff --git a/docs/catalogue/reference-formats.csv b/docs/catalogue/reference-formats.csv index d168a2e19..9e96708f0 100644 --- a/docs/catalogue/reference-formats.csv +++ b/docs/catalogue/reference-formats.csv @@ -84,7 +84,7 @@ Quadruple D,qda,archive,,,,,,,Y,1,,,,,,MISSING,, RK / WinRK,rk,archive,,,,,,,Y,1,,,,,,MISSING,, Self Dissolving ARChive,sda,archive,,,,,,,Y,1,,,,,,MISSING,, C64/C128 self-extracting archive,sfx,archive,,,,,,,Y,1,,,,,,MISSING,, -UltraCompressor II,uc;uc0;uc2;ucn;ur2;ue2,archive,,,,,,,Y,1,,,,,,MISSING,, +UltraCompressor II,uc;uc0;uc2;ucn;ur2;ue2,archive,,,,,,,Y,1,https://github.com/klauspost/uc2/blob/main/docs/format.md,Uc2,Y,Y,,PARTIAL,,"UltraCrypt (.ue2) and 2.3 private compression profiles (PCP) unsupported; no in-place modify" PerfectCompress,uca,archive,,,,,,,Y,1,,,,,,MISSING,, YZ1,yz1,archive,,,,,,,Y,1,,,,,,MISSING,, Zzip,zz,archive,,,,,,,Y,1,,,,,,MISSING,,