mirror of
https://github.com/SabreTools/SabreTools.IO.git
synced 2026-11-15 16:41:58 +00:00
* Decode short codes at the end of the inflate input InflateCodes waited for as many bits as its lookup table uses before it decoded a literal, length or distance code. When the input ended with fewer bits than that, the last codes were not decoded, even though they were complete. An MS-ZIP block is a deflate stream on its own, so its last match was lost and the block came out short (#1). When no more input is there, the code is now looked up with the bits that are left and used if it is not longer than those. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep a full 32 KiB inflate dictionary SetDictionary kept only the last 32767 bytes of a 32 KiB dictionary, because the window needs one free byte between its read and write positions. MS-ZIP sets the previous 32 KiB block as the dictionary, and a match at the start of a block can copy from 32768 bytes back, which then read a byte that was not in the window. A dictionary that fills the window now starts with the read and write positions at 0, so the first byte written replaces the oldest byte of the dictionary. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
112 lines
3.8 KiB
C#
112 lines
3.8 KiB
C#
using System;
|
|
using System.IO;
|
|
using SabreTools.IO.Compression.MSZIP;
|
|
using SabreTools.IO.Extensions;
|
|
using Xunit;
|
|
|
|
namespace SabreTools.IO.Compression.Test
|
|
{
|
|
public class MSZIPTests
|
|
{
|
|
[Fact]
|
|
public void DecompressorTest()
|
|
{
|
|
// Testing Note: This is a fake file that has multiple blocks
|
|
// sequentially. In real cabinet files, these are embedded in
|
|
// CFDATA blocks.
|
|
string path = Path.Combine(Environment.CurrentDirectory, "TestData", "test-archive.msz");
|
|
byte[] inputBytes = File.ReadAllBytes(path);
|
|
var input = new MemoryStream(inputBytes);
|
|
var output = new MemoryStream();
|
|
|
|
var decompressor = Decompressor.Create();
|
|
input.SeekIfPossible(0x0000);
|
|
decompressor.CopyTo(input, output);
|
|
input.SeekIfPossible(0x3969);
|
|
decompressor.CopyTo(input, output);
|
|
|
|
Assert.Equal(65536, output.Length);
|
|
}
|
|
|
|
[Fact]
|
|
public void DecompressorShortDistanceCodeAtEndTest()
|
|
{
|
|
// One dynamic deflate block with 778 bytes of 'A'. The last match has a
|
|
// 1-bit distance code, followed by a 2-bit end of block code and 1 bit
|
|
// of padding, fewer than the 6 bits of the longest distance code
|
|
byte[] block =
|
|
[
|
|
0x43, 0x4B, 0xED, 0xC6, 0x81, 0x01, 0xC0, 0x30, 0x0C, 0xC3, 0xA0,
|
|
0xDB, 0xF4, 0xFF, 0x53, 0xD2, 0x21, 0x42, 0x4E, 0x37, 0xB6, 0x45,
|
|
];
|
|
|
|
var output = new MemoryStream();
|
|
var decompressor = Decompressor.Create();
|
|
decompressor.CopyTo(block, output);
|
|
|
|
byte[] data = output.ToArray();
|
|
for (int i = 0; i < 778; i++)
|
|
{
|
|
Assert.Equal(0x41, data[i]);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void DecompressorShortLiteralCodeAtEndTest()
|
|
{
|
|
// One dynamic deflate block with 518 bytes of 'A'. The last literal has a
|
|
// 1-bit code, followed by a 2-bit end of block code and 5 bits of padding,
|
|
// fewer than the 9 bits of the longest literal/length code
|
|
byte[] block =
|
|
[
|
|
0x43, 0x4B, 0xED, 0xC1, 0x01, 0x82, 0xE4, 0x30, 0x0C, 0xC3, 0xB0, 0xB7,
|
|
0xE1, 0xFF, 0x9F, 0xA2, 0x9C, 0xCE, 0xDE, 0x2B, 0x0E, 0xF0, 0xDF, 0x7F,
|
|
0x02,
|
|
];
|
|
|
|
var output = new MemoryStream();
|
|
var decompressor = Decompressor.Create();
|
|
decompressor.CopyTo(block, output);
|
|
|
|
byte[] data = output.ToArray();
|
|
for (int i = 0; i < 518; i++)
|
|
{
|
|
Assert.Equal(0x41, data[i]);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void DecompressorFullHistoryTest()
|
|
{
|
|
// First block: 32 KiB of data in a stored deflate block
|
|
byte[] first = new byte[7 + 0x8000];
|
|
first[0] = 0x43;
|
|
first[1] = 0x4B;
|
|
first[2] = 0x01;
|
|
first[3] = 0x00;
|
|
first[4] = 0x80;
|
|
first[5] = 0xFF;
|
|
first[6] = 0x7F;
|
|
for (int i = 0; i < 0x8000; i++)
|
|
{
|
|
first[7 + i] = (byte)((i % 251) + 1);
|
|
}
|
|
|
|
// Second block: one match of 258 bytes at a distance of 32768,
|
|
// which starts with the first byte of the first block
|
|
byte[] second = [0x43, 0x4B, 0x1B, 0xBD, 0xFF, 0x1F, 0x00];
|
|
|
|
var output = new MemoryStream();
|
|
var decompressor = Decompressor.Create();
|
|
decompressor.CopyTo(first, output);
|
|
decompressor.CopyTo(second, output);
|
|
|
|
byte[] data = output.ToArray();
|
|
for (int i = 0; i < 258; i++)
|
|
{
|
|
Assert.Equal(data[i], data[0x8000 + i]);
|
|
}
|
|
}
|
|
}
|
|
}
|