Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Compression.Core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ Use the concrete version you intend to consume; this document does not predict a

<!-- API:BEGIN generated by Hawkynt/RepositoryTemplate/package-readme — edit the XML docs in source, not here -->

Every public and protected member of all 734 types, generated from the built assembly and its XML documentation, is in [REFERENCE.md](https://github.com/Hawkynt/CompressionWorkbench/blob/main/Compression.Core/REFERENCE.md).
Every public and protected member of all 735 types, generated from the built assembly and its XML documentation, is in [REFERENCE.md](https://github.com/Hawkynt/CompressionWorkbench/blob/main/Compression.Core/REFERENCE.md).

<!-- API:END -->

Expand Down
10 changes: 9 additions & 1 deletion Compression.Core/REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -9284,7 +9284,15 @@ Implements `IEquatable<StreamingArchiveInput>`.

### Namespace `FileFormat.Bzip2`

[`Bzip2BuildingBlock`](#bzip2buildingblock) · [`Bzip2FormatDescriptor`](#bzip2formatdescriptor) · [`Bzip2Stream`](#bzip2stream)
[`AlzBzip2`](#alzbzip2) · [`Bzip2BuildingBlock`](#bzip2buildingblock) · [`Bzip2FormatDescriptor`](#bzip2formatdescriptor) · [`Bzip2Stream`](#bzip2stream)

#### `AlzBzip2`

ALZip's bzip2 variant (ALZ compression method 1): no "BZh" stream header, each block introduced by the 32-bit marker "DLZ\x01" instead of the 48-bit block magic, no block CRC and no randomised bit (the block starts at its BWT origin pointer), and the stream ended by "DLZ\x02" without a combined CRC. The block payload is standard bzip2 (900k blocks). Layout as documented by alkegi/unalz (MIT) and confirmed against its CC0 test archives; integrity rests on the ALZ entry's CRC-32.

| Member | Signature | Summary |
| --- | --- | --- |
| `Decompress` | `static byte[] Decompress(ReadOnlySpan<byte> data, long expectedSize)` | Decodes an ALZ bzip2 stream; stops at the end marker or once `expectedSize` bytes are out. |

#### `Bzip2BuildingBlock`

Expand Down
39 changes: 39 additions & 0 deletions Compression.Core/Streams/Bzip2/AlzBzip2.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using Compression.Core.BitIO;

namespace FileFormat.Bzip2;

/// <summary>
/// ALZip's bzip2 variant (ALZ compression method 1): no "BZh" stream header, each block introduced by
/// the 32-bit marker "DLZ\x01" instead of the 48-bit block magic, no block CRC and no randomised bit
/// (the block starts at its BWT origin pointer), and the stream ended by "DLZ\x02" without a combined
/// CRC. The block payload is standard bzip2 (900k blocks). Layout as documented by alkegi/unalz
/// (MIT) and confirmed against its CC0 test archives; integrity rests on the ALZ entry's CRC-32.
/// </summary>
public static class AlzBzip2 {
private const uint BlockMarker = 0x444C5A01; // "DLZ\x01" read MSB first
private const uint EndMarker = 0x444C5A02; // "DLZ\x02"

/// <summary>Decodes an ALZ bzip2 stream; stops at the end marker or once <paramref name="expectedSize"/> bytes are out.</summary>
public static byte[] Decompress(ReadOnlySpan<byte> data, long expectedSize) {
if (expectedSize > int.MaxValue)
throw new NotSupportedException("ALZ members larger than 2 GiB are not supported.");
using var input = new MemoryStream(data.ToArray(), writable: false);
var bits = new BitBuffer<MsbBitOrder>(input);
using var output = new MemoryStream((int)expectedSize);
try {
while (output.Length < expectedSize) {
var marker = (bits.ReadBits(16) << 16) | bits.ReadBits(16);
if (marker == EndMarker)
break;
if (marker != BlockMarker)
throw new InvalidDataException($"ALZ bzip2: expected a DLZ block marker, found 0x{marker:X8}.");
output.Write(Bzip2Decompressor.DecodeBlockBody(bits));
}
} catch (EndOfStreamException e) {
throw new InvalidDataException("Truncated ALZ bzip2 stream.", e);
}
if (output.Length != expectedSize)
throw new InvalidDataException($"ALZ bzip2 produced {output.Length} bytes, the entry declares {expectedSize}.");
return output.ToArray();
}
}
36 changes: 20 additions & 16 deletions Compression.Core/Streams/Bzip2/Bzip2Decompressor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ private bool ReadNextBlock() {
if (randomized != 0)
throw new InvalidDataException("Randomized bzip2 blocks are not supported.");

var blockData = this.DecodeBlockBody();
var blockData = DecodeBlockBody(this._bits);

// Verify block CRC
var computedCrc = Crc32Bzip2(blockData);
Expand All @@ -122,20 +122,24 @@ private bool ReadNextNsisBlock() {
this._finished = true;
return false;
case NsisBlockMarker:
this._currentBlock = this.DecodeBlockBody();
this._currentBlock = DecodeBlockBody(this._bits);
this._currentBlockPos = 0;
return true;
case var other:
throw new InvalidDataException($"Invalid NSIS bzip2 block marker: 0x{other:X2}");
}
}

private byte[] DecodeBlockBody() {
/// <summary>
/// Decodes one block from its BWT origin pointer onward (what follows the block magic, CRC and
/// randomised bit in a standard stream; ALZip's variant omits those three).
/// </summary>
internal static byte[] DecodeBlockBody(BitBuffer<MsbBitOrder> bits) {
// BWT original pointer (24 bits)
var bwtIndex = (int)this._bits.ReadBits(24);
var bwtIndex = (int)bits.ReadBits(24);

// Read symbol bitmap
var symbolUsed = ReadSymbolBitmap();
var symbolUsed = ReadSymbolBitmap(bits);

// Count symbols in use
var numSymbolsInUse = 0;
Expand All @@ -146,16 +150,16 @@ private byte[] DecodeBlockBody() {
var alphaSize = eobSymbol + 1;

// Number of trees (3 bits)
var numTrees = (int)this._bits.ReadBits(3);
var numTrees = (int)bits.ReadBits(3);

// Number of selectors (15 bits)
var numSelectors = (int)this._bits.ReadBits(15);
var numSelectors = (int)bits.ReadBits(15);

// Read selectors (unary coded, MTF encoded)
var selectorsMtf = new int[numSelectors];
for (var i = 0; i < numSelectors; ++i) {
var v = 0;
while (this._bits.ReadBits(1) == 1)
while (bits.ReadBits(1) == 1)
++v;
selectorsMtf[i] = v;
}
Expand All @@ -180,10 +184,10 @@ private byte[] DecodeBlockBody() {
var huffTables = new CanonicalHuffman[numTrees];
for (var t = 0; t < numTrees; ++t) {
var codeLens = new int[alphaSize];
var currentLen = (int)this._bits.ReadBits(5);
var currentLen = (int)bits.ReadBits(5);
for (var s = 0; s < alphaSize; ++s) {
while (this._bits.ReadBits(1) == 1) {
if (this._bits.ReadBits(1) == 0)
while (bits.ReadBits(1) == 1) {
if (bits.ReadBits(1) == 0)
++currentLen;
else
--currentLen;
Expand All @@ -205,7 +209,7 @@ private byte[] DecodeBlockBody() {
}

var tableIdx = selectors[Math.Min(groupIndex, numSelectors - 1)];
var sym = huffTables[tableIdx].DecodeSymbol(this._bits);
var sym = huffTables[tableIdx].DecodeSymbol(bits);
++symbolInGroup;

if (sym == eobSymbol)
Expand Down Expand Up @@ -236,15 +240,15 @@ private byte[] DecodeBlockBody() {
return Bzip2Compressor.Rle1Decode(rle1Data);
}

private bool[] ReadSymbolBitmap() {
private static bool[] ReadSymbolBitmap(BitBuffer<MsbBitOrder> bits) {
var used = new bool[256];
var inUse16 = this._bits.ReadBits(16);
var inUse16 = bits.ReadBits(16);

for (var i = 0; i < 16; ++i) {
if ((inUse16 & (1u << (15 - i))) != 0) {
var bits = this._bits.ReadBits(16);
var word = bits.ReadBits(16);
for (var j = 0; j < 16; ++j) {
if ((bits & (1u << (15 - j))) != 0)
if ((word & (1u << (15 - j))) != 0)
used[i * 16 + j] = true;
}
}
Expand Down
111 changes: 0 additions & 111 deletions Compression.Tests/AlZip/AlZipModifierTests.cs

This file was deleted.

Loading
Loading