mirror of
https://github.com/ReneLergner/WPinternals.git
synced 2026-06-21 22:51:02 +10:00
Initial commit - WPinternals 2.6
This commit is contained in:
@@ -0,0 +1,314 @@
|
||||
//
|
||||
// Copyright (c) 2008-2011, Kenneth Bell
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the "Software"),
|
||||
// to deal in the Software without restriction, including without limitation
|
||||
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
// and/or sell copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
//
|
||||
// Contributions by bsobel:
|
||||
// - Compression algorithm distantly derived from Puyo tools (BSD license)*
|
||||
// - Decompression adjusted to support variety of block sizes
|
||||
//
|
||||
// (*) Puyo tools implements a different LZ-style algorithm
|
||||
//
|
||||
|
||||
namespace DiscUtils.Ntfs
|
||||
{
|
||||
using System;
|
||||
using DiscUtils.Compression;
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of the LZNT1 algorithm used for compressing NTFS files.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Due to apparent bugs in Window's LZNT1 decompressor, it is <b>strongly</b> recommended that
|
||||
/// only the block size of 4096 is used. Other block sizes corrupt data on decompression.
|
||||
/// </remarks>
|
||||
internal sealed class LZNT1 : BlockCompressor
|
||||
{
|
||||
private const ushort SubBlockIsCompressedFlag = 0x8000;
|
||||
private const ushort SubBlockSizeMask = 0x0fff;
|
||||
|
||||
// LZNT1 appears to ignore the actual block size requested, most likely due to
|
||||
// a bug in the decompressor, which assumes 4KB block size. To be bug-compatible,
|
||||
// we assume each block is 4KB on decode also.
|
||||
private const int FixedBlockSize = 0x1000;
|
||||
|
||||
private static byte[] s_compressionBits = CalcCompressionBits();
|
||||
|
||||
public LZNT1()
|
||||
{
|
||||
BlockSize = 4096;
|
||||
}
|
||||
|
||||
public override CompressionResult Compress(byte[] source, int sourceOffset, int sourceLength, byte[] compressed, int compressedOffset, ref int compressedLength)
|
||||
{
|
||||
uint sourcePointer = 0;
|
||||
uint sourceCurrentBlock = 0;
|
||||
uint destPointer = 0;
|
||||
|
||||
// Set up the Lz Compression Dictionary
|
||||
LzWindowDictionary lzDictionary = new LzWindowDictionary();
|
||||
bool nonZeroDataFound = false;
|
||||
|
||||
for (int subBlock = 0; subBlock < sourceLength; subBlock += BlockSize)
|
||||
{
|
||||
lzDictionary.MinMatchAmount = 3;
|
||||
sourceCurrentBlock = sourcePointer;
|
||||
|
||||
uint decompressedSize = (uint)Math.Min(sourceLength - subBlock, BlockSize);
|
||||
uint compressedSize = 0;
|
||||
|
||||
// Start compression
|
||||
uint headerPosition = destPointer;
|
||||
compressed[compressedOffset + destPointer] = compressed[compressedOffset + destPointer + 1] = 0;
|
||||
destPointer += 2;
|
||||
|
||||
while (sourcePointer - subBlock < decompressedSize)
|
||||
{
|
||||
if (destPointer + 1 >= compressedLength)
|
||||
{
|
||||
return CompressionResult.Incompressible;
|
||||
}
|
||||
|
||||
byte bitFlag = 0x0;
|
||||
uint flagPosition = destPointer;
|
||||
|
||||
compressed[compressedOffset + destPointer] = bitFlag; // It will be filled in later
|
||||
compressedSize++;
|
||||
destPointer++;
|
||||
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
int lengthBits = 16 - s_compressionBits[sourcePointer - subBlock];
|
||||
ushort lengthMask = (ushort)((1 << s_compressionBits[sourcePointer - subBlock]) - 1);
|
||||
|
||||
lzDictionary.MaxMatchAmount = Math.Min(1 << lengthBits, BlockSize - 1);
|
||||
|
||||
int[] lzSearchMatch = lzDictionary.Search(source, sourceOffset + subBlock, (uint)(sourcePointer - subBlock), decompressedSize);
|
||||
if (lzSearchMatch[1] > 0)
|
||||
{
|
||||
// There is a compression match
|
||||
if (destPointer + 2 >= compressedLength)
|
||||
{
|
||||
return CompressionResult.Incompressible;
|
||||
}
|
||||
|
||||
bitFlag |= (byte)(1 << i);
|
||||
|
||||
int rawOffset = lzSearchMatch[0];
|
||||
int rawLength = lzSearchMatch[1];
|
||||
|
||||
int convertedOffset = (rawOffset - 1) << lengthBits;
|
||||
int convertedSize = (rawLength - 3) & ((1 << lengthMask) - 1);
|
||||
|
||||
ushort convertedData = (ushort)(convertedOffset | convertedSize);
|
||||
Utilities.WriteBytesLittleEndian(convertedData, compressed, compressedOffset + (int)destPointer);
|
||||
|
||||
lzDictionary.AddEntryRange(source, sourceOffset + subBlock, (int)(sourcePointer - subBlock), lzSearchMatch[1]);
|
||||
sourcePointer += (uint)lzSearchMatch[1];
|
||||
destPointer += 2;
|
||||
compressedSize += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
// There wasn't a match
|
||||
if (destPointer + 1 >= compressedLength)
|
||||
{
|
||||
return CompressionResult.Incompressible;
|
||||
}
|
||||
|
||||
bitFlag |= (byte)(0 << i);
|
||||
|
||||
if (source[sourceOffset + sourcePointer] != 0)
|
||||
{
|
||||
nonZeroDataFound = true;
|
||||
}
|
||||
|
||||
compressed[compressedOffset + destPointer] = source[sourceOffset + sourcePointer];
|
||||
lzDictionary.AddEntry(source, sourceOffset + subBlock, (int)(sourcePointer - subBlock));
|
||||
|
||||
sourcePointer++;
|
||||
destPointer++;
|
||||
compressedSize++;
|
||||
}
|
||||
|
||||
// Check for out of bounds
|
||||
if (sourcePointer - subBlock >= decompressedSize)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Write the real flag.
|
||||
compressed[compressedOffset + flagPosition] = bitFlag;
|
||||
}
|
||||
|
||||
// If compressed size >= block size just store block
|
||||
if (compressedSize >= BlockSize)
|
||||
{
|
||||
// Set the header to indicate non-compressed block
|
||||
Utilities.WriteBytesLittleEndian((ushort)(0x3000 | (BlockSize - 1)), compressed, compressedOffset + (int)headerPosition);
|
||||
|
||||
Array.Copy(source, (int)sourceOffset + sourceCurrentBlock, compressed, compressedOffset + headerPosition + 2, BlockSize);
|
||||
destPointer = (uint)(headerPosition + 2 + BlockSize);
|
||||
|
||||
// Make sure decompression stops by setting the next two bytes to null, prevents us from having to
|
||||
// clear the rest of the array.
|
||||
compressed[destPointer] = 0;
|
||||
compressed[destPointer + 1] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set the header to indicate compressed and the right length
|
||||
Utilities.WriteBytesLittleEndian((ushort)(0xb000 | (compressedSize - 1)), compressed, compressedOffset + (int)headerPosition);
|
||||
}
|
||||
|
||||
lzDictionary.Reset();
|
||||
}
|
||||
|
||||
if (destPointer >= sourceLength)
|
||||
{
|
||||
compressedLength = 0;
|
||||
return CompressionResult.Incompressible;
|
||||
}
|
||||
else if (nonZeroDataFound)
|
||||
{
|
||||
compressedLength = (int)destPointer;
|
||||
return CompressionResult.Compressed;
|
||||
}
|
||||
else
|
||||
{
|
||||
compressedLength = 0;
|
||||
return CompressionResult.AllZeros;
|
||||
}
|
||||
}
|
||||
|
||||
public override int Decompress(byte[] source, int sourceOffset, int sourceLength, byte[] decompressed, int decompressedOffset)
|
||||
{
|
||||
int sourceIdx = 0;
|
||||
int destIdx = 0;
|
||||
|
||||
while (sourceIdx < sourceLength)
|
||||
{
|
||||
ushort header = Utilities.ToUInt16LittleEndian(source, sourceOffset + sourceIdx);
|
||||
sourceIdx += 2;
|
||||
|
||||
// Look for null-terminating sub-block header
|
||||
if (header == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if ((header & SubBlockIsCompressedFlag) == 0)
|
||||
{
|
||||
int blockSize = (header & SubBlockSizeMask) + 1;
|
||||
Array.Copy(source, sourceOffset + sourceIdx, decompressed, decompressedOffset + destIdx, blockSize);
|
||||
sourceIdx += blockSize;
|
||||
destIdx += blockSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
// compressed
|
||||
int destSubBlockStart = destIdx;
|
||||
int srcSubBlockEnd = sourceIdx + (header & SubBlockSizeMask) + 1;
|
||||
while (sourceIdx < srcSubBlockEnd)
|
||||
{
|
||||
byte tag = source[sourceOffset + sourceIdx];
|
||||
++sourceIdx;
|
||||
|
||||
for (int token = 0; token < 8; ++token)
|
||||
{
|
||||
// We might have hit the end of the sub block whilst still working though
|
||||
// a tag - abort if we have...
|
||||
if (sourceIdx >= srcSubBlockEnd)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if ((tag & 1) == 0)
|
||||
{
|
||||
if (decompressedOffset + destIdx >= decompressed.Length)
|
||||
{
|
||||
return destIdx;
|
||||
}
|
||||
|
||||
decompressed[decompressedOffset + destIdx] = source[sourceOffset + sourceIdx];
|
||||
++destIdx;
|
||||
++sourceIdx;
|
||||
}
|
||||
else
|
||||
{
|
||||
ushort lengthBits = (ushort)(16 - s_compressionBits[destIdx - destSubBlockStart]);
|
||||
ushort lengthMask = (ushort)((1 << lengthBits) - 1);
|
||||
|
||||
ushort phraseToken = Utilities.ToUInt16LittleEndian(source, sourceOffset + sourceIdx);
|
||||
sourceIdx += 2;
|
||||
|
||||
int destBackAddr = destIdx - (phraseToken >> lengthBits) - 1;
|
||||
int length = (phraseToken & lengthMask) + 3;
|
||||
|
||||
for (int i = 0; i < length; ++i)
|
||||
{
|
||||
decompressed[decompressedOffset + destIdx++] = decompressed[decompressedOffset + destBackAddr++];
|
||||
}
|
||||
}
|
||||
|
||||
tag >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Bug-compatible - if we decompressed less than 4KB, jump to next 4KB boundary. If
|
||||
// that would leave less than a 4KB remaining, abort with data decompressed so far.
|
||||
if (decompressedOffset + destIdx + FixedBlockSize > decompressed.Length)
|
||||
{
|
||||
return destIdx;
|
||||
}
|
||||
else if (destIdx < destSubBlockStart + FixedBlockSize)
|
||||
{
|
||||
int skip = (destSubBlockStart + FixedBlockSize) - destIdx;
|
||||
Array.Clear(decompressed, decompressedOffset + destIdx, skip);
|
||||
destIdx += skip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return destIdx;
|
||||
}
|
||||
|
||||
private static byte[] CalcCompressionBits()
|
||||
{
|
||||
byte[] result = new byte[4096];
|
||||
byte offsetBits = 0;
|
||||
|
||||
int y = 0x10;
|
||||
for (int x = 0; x < result.Length; x++)
|
||||
{
|
||||
result[x] = (byte)(4 + offsetBits);
|
||||
if (x == y)
|
||||
{
|
||||
y <<= 1;
|
||||
offsetBits++;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user