mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-09-23 07:25:11 +00:00
Adds support for Apple Data Compression. (#168)
This commit is contained in:
210
src/SharpCompress/Compressor/ADC/ADCBase.cs
Normal file
210
src/SharpCompress/Compressor/ADC/ADCBase.cs
Normal file
@@ -0,0 +1,210 @@
|
||||
//
|
||||
// ADC.cs
|
||||
//
|
||||
// Author:
|
||||
// Natalia Portillo <claunia@claunia.com>
|
||||
//
|
||||
// Copyright (c) 2016 © Claunia.com
|
||||
//
|
||||
// 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.
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace SharpCompress.Compressor.ADC
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides static methods for decompressing Apple Data Compression data
|
||||
/// </summary>
|
||||
public static class ADCBase
|
||||
{
|
||||
const int Plain = 1;
|
||||
const int TwoByte = 2;
|
||||
const int ThreeByte = 3;
|
||||
|
||||
static int GetChunkType(byte byt)
|
||||
{
|
||||
if ((byt & 0x80) == 0x80)
|
||||
return Plain;
|
||||
if ((byt & 0x40) == 0x40)
|
||||
return ThreeByte;
|
||||
return TwoByte;
|
||||
}
|
||||
|
||||
static int GetChunkSize(byte byt)
|
||||
{
|
||||
switch (GetChunkType(byt))
|
||||
{
|
||||
case Plain:
|
||||
return (byt & 0x7F) + 1;
|
||||
case TwoByte:
|
||||
return ((byt & 0x3F) >> 2) + 3;
|
||||
case ThreeByte:
|
||||
return (byt & 0x3F) + 4;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static int GetOffset(byte[] chunk, int position)
|
||||
{
|
||||
switch (GetChunkType(chunk[position]))
|
||||
{
|
||||
case Plain:
|
||||
return 0;
|
||||
case TwoByte:
|
||||
return ((chunk[position] & 0x03) << 8) + chunk[position + 1];
|
||||
case ThreeByte:
|
||||
return (chunk[position + 1] << 8) + chunk[position + 2];
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decompresses a byte buffer that's compressed with ADC
|
||||
/// </summary>
|
||||
/// <param name="input">Compressed buffer</param>
|
||||
/// <param name="output">Buffer to hold decompressed data</param>
|
||||
/// <param name="bufferSize">Max size for decompressed data</param>
|
||||
/// <returns>How many bytes are stored on <paramref name="output"/></returns>
|
||||
public static int Decompress(byte[] input, out byte[] output, int bufferSize = 262144)
|
||||
{
|
||||
return Decompress(new MemoryStream(input), out output, bufferSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decompresses a stream that's compressed with ADC
|
||||
/// </summary>
|
||||
/// <param name="input">Stream containing compressed data</param>
|
||||
/// <param name="output">Buffer to hold decompressed data</param>
|
||||
/// <param name="bufferSize">Max size for decompressed data</param>
|
||||
/// <returns>How many bytes are stored on <paramref name="output"/></returns>
|
||||
public static int Decompress(Stream input, out byte[] output, int bufferSize = 262144)
|
||||
{
|
||||
output = null;
|
||||
|
||||
if (input == null || input.Length == 0)
|
||||
return 0;
|
||||
|
||||
int start = (int)input.Position;
|
||||
int position = (int)input.Position;
|
||||
int chunkSize;
|
||||
int offset;
|
||||
int chunkType;
|
||||
byte[] buffer = new byte[bufferSize];
|
||||
int outPosition = 0;
|
||||
bool full = false;
|
||||
MemoryStream tempMs;
|
||||
|
||||
while (position < input.Length)
|
||||
{
|
||||
int readByte = input.ReadByte();
|
||||
if (readByte == -1)
|
||||
break;
|
||||
|
||||
chunkType = GetChunkType((byte)readByte);
|
||||
|
||||
switch (chunkType)
|
||||
{
|
||||
case Plain:
|
||||
chunkSize = GetChunkSize((byte)readByte);
|
||||
if (outPosition + chunkSize > bufferSize)
|
||||
{
|
||||
full = true;
|
||||
break;
|
||||
}
|
||||
input.Read(buffer, outPosition, chunkSize);
|
||||
outPosition += chunkSize;
|
||||
position += chunkSize + 1;
|
||||
break;
|
||||
case TwoByte:
|
||||
tempMs = new MemoryStream();
|
||||
chunkSize = GetChunkSize((byte)readByte);
|
||||
tempMs.WriteByte((byte)readByte);
|
||||
tempMs.WriteByte((byte)input.ReadByte());
|
||||
offset = GetOffset(tempMs.ToArray(), 0);
|
||||
if (outPosition + chunkSize > bufferSize)
|
||||
{
|
||||
full = true;
|
||||
break;
|
||||
}
|
||||
if (offset == 0)
|
||||
{
|
||||
byte lastByte = buffer[outPosition - 1];
|
||||
for (int i = 0; i < chunkSize; i++)
|
||||
{
|
||||
buffer[outPosition] = lastByte;
|
||||
outPosition++;
|
||||
}
|
||||
position += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < chunkSize; i++)
|
||||
{
|
||||
buffer[outPosition] = buffer[outPosition - offset - 1];
|
||||
outPosition++;
|
||||
}
|
||||
position += 2;
|
||||
}
|
||||
break;
|
||||
case ThreeByte:
|
||||
tempMs = new MemoryStream();
|
||||
chunkSize = GetChunkSize((byte)readByte);
|
||||
tempMs.WriteByte((byte)readByte);
|
||||
tempMs.WriteByte((byte)input.ReadByte());
|
||||
tempMs.WriteByte((byte)input.ReadByte());
|
||||
offset = GetOffset(tempMs.ToArray(), 0);
|
||||
if (outPosition + chunkSize > bufferSize)
|
||||
{
|
||||
full = true;
|
||||
break;
|
||||
}
|
||||
if (offset == 0)
|
||||
{
|
||||
byte lastByte = buffer[outPosition - 1];
|
||||
for (int i = 0; i < chunkSize; i++)
|
||||
{
|
||||
buffer[outPosition] = lastByte;
|
||||
outPosition++;
|
||||
}
|
||||
position += 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < chunkSize; i++)
|
||||
{
|
||||
buffer[outPosition] = buffer[outPosition - offset - 1];
|
||||
outPosition++;
|
||||
}
|
||||
position += 3;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (full)
|
||||
break;
|
||||
}
|
||||
|
||||
output = new byte[outPosition];
|
||||
Array.Copy(buffer, 0, output, 0, outPosition);
|
||||
return position - start;
|
||||
}
|
||||
}
|
||||
}
|
||||
169
src/SharpCompress/Compressor/ADC/ADCStream.cs
Normal file
169
src/SharpCompress/Compressor/ADC/ADCStream.cs
Normal file
@@ -0,0 +1,169 @@
|
||||
//
|
||||
// ADC.cs
|
||||
//
|
||||
// Author:
|
||||
// Natalia Portillo <claunia@claunia.com>
|
||||
//
|
||||
// Copyright (c) 2016 © Claunia.com
|
||||
//
|
||||
// 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.
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace SharpCompress.Compressor.ADC
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a forward readable only stream that decompresses ADC data
|
||||
/// </summary>
|
||||
public class ADCStream : Stream
|
||||
{
|
||||
/// <summary>
|
||||
/// This stream holds the compressed data
|
||||
/// </summary>
|
||||
private readonly Stream stream;
|
||||
/// <summary>
|
||||
/// Is this instance disposed?
|
||||
/// </summary>
|
||||
private bool isDisposed;
|
||||
/// <summary>
|
||||
/// Position in decompressed data
|
||||
/// </summary>
|
||||
private long position = 0;
|
||||
/// <summary>
|
||||
/// Buffer with currently used chunk of decompressed data
|
||||
/// </summary>
|
||||
private byte[] outBuffer;
|
||||
/// <summary>
|
||||
/// Position in buffer of decompressed data
|
||||
/// </summary>
|
||||
private int outPosition;
|
||||
|
||||
/// <summary>
|
||||
/// Initializates a stream that decompresses ADC data on the fly
|
||||
/// </summary>
|
||||
/// <param name="stream">Stream that contains the compressed data</param>
|
||||
/// <param name="compressionMode">Must be set to <see cref="CompressionMode.Decompress"/> because compression is not implemented</param>
|
||||
public ADCStream(Stream stream, CompressionMode compressionMode = CompressionMode.Decompress)
|
||||
{
|
||||
if (compressionMode == CompressionMode.Compress)
|
||||
throw new NotSupportedException();
|
||||
|
||||
this.stream = stream;
|
||||
}
|
||||
|
||||
public override bool CanRead
|
||||
{
|
||||
get { return stream.CanRead; }
|
||||
}
|
||||
|
||||
public override bool CanSeek
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public override bool CanWrite
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public override long Length
|
||||
{
|
||||
get { throw new NotSupportedException(); }
|
||||
}
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get { return position; }
|
||||
set { throw new NotSupportedException(); }
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (isDisposed)
|
||||
return;
|
||||
isDisposed = true;
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (count == 0)
|
||||
return 0;
|
||||
if (buffer == null)
|
||||
throw new ArgumentNullException("buffer");
|
||||
if (count < 0)
|
||||
throw new ArgumentOutOfRangeException("count");
|
||||
if (offset < buffer.GetLowerBound(0))
|
||||
throw new ArgumentOutOfRangeException("offset");
|
||||
if ((offset + count) > buffer.GetLength(0))
|
||||
throw new ArgumentOutOfRangeException("count");
|
||||
|
||||
int size = -1;
|
||||
|
||||
if (outBuffer == null)
|
||||
{
|
||||
size = ADCBase.Decompress(stream, out outBuffer);
|
||||
outPosition = 0;
|
||||
}
|
||||
|
||||
int inPosition = offset;
|
||||
int toCopy = count;
|
||||
int copied = 0;
|
||||
|
||||
while (outPosition + toCopy >= outBuffer.Length)
|
||||
{
|
||||
int piece = outBuffer.Length - outPosition;
|
||||
Array.Copy(outBuffer, outPosition, buffer, inPosition, piece);
|
||||
inPosition += piece;
|
||||
copied += piece;
|
||||
position += piece;
|
||||
toCopy -= piece;
|
||||
size = ADCBase.Decompress(stream, out outBuffer);
|
||||
outPosition = 0;
|
||||
if (size == 0 || outBuffer == null || outBuffer.Length == 0)
|
||||
return copied;
|
||||
}
|
||||
|
||||
Array.Copy(outBuffer, outPosition, buffer, inPosition, toCopy);
|
||||
outPosition += toCopy;
|
||||
position += toCopy;
|
||||
copied += toCopy;
|
||||
return copied;
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
127
test/SharpCompress.Test/ADCTest.cs
Normal file
127
test/SharpCompress.Test/ADCTest.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
//
|
||||
// ADC.cs
|
||||
//
|
||||
// Author:
|
||||
// Natalia Portillo <claunia@claunia.com>
|
||||
//
|
||||
// Copyright (c) 2016 © Claunia.com
|
||||
//
|
||||
// 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.
|
||||
using SharpCompress.Compressor.ADC;
|
||||
using System.IO;
|
||||
using Xunit;
|
||||
|
||||
namespace SharpCompress.Test
|
||||
{
|
||||
public class ADCTest : TestBase
|
||||
{
|
||||
[Fact]
|
||||
public void TestBuffer()
|
||||
{
|
||||
using (FileStream decFs = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "adc_decompressed.bin")))
|
||||
{
|
||||
byte[] decompressed = new byte[decFs.Length];
|
||||
decFs.Read(decompressed, 0, decompressed.Length);
|
||||
|
||||
using (FileStream cmpFs = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "adc_compressed.bin")))
|
||||
{
|
||||
byte[] compressed = new byte[cmpFs.Length];
|
||||
cmpFs.Read(compressed, 0, compressed.Length);
|
||||
byte[] test;
|
||||
|
||||
ADCBase.Decompress(compressed, out test);
|
||||
|
||||
Assert.Equal(decompressed, test);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestBaseStream()
|
||||
{
|
||||
using (FileStream decFs = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "adc_decompressed.bin")))
|
||||
{
|
||||
byte[] decompressed = new byte[decFs.Length];
|
||||
decFs.Read(decompressed, 0, decompressed.Length);
|
||||
|
||||
using (FileStream cmpFs = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "adc_compressed.bin")))
|
||||
{
|
||||
byte[] test;
|
||||
|
||||
ADCBase.Decompress(cmpFs, out test);
|
||||
|
||||
Assert.Equal(decompressed, test);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestADCStreamWholeChunk()
|
||||
{
|
||||
using (FileStream decFs = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "adc_decompressed.bin")))
|
||||
{
|
||||
byte[] decompressed = new byte[decFs.Length];
|
||||
decFs.Read(decompressed, 0, decompressed.Length);
|
||||
|
||||
using (FileStream cmpFs = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "adc_compressed.bin")))
|
||||
{
|
||||
using (ADCStream decStream = new ADCStream(cmpFs, Compressor.CompressionMode.Decompress))
|
||||
{
|
||||
byte[] test = new byte[262144];
|
||||
|
||||
decStream.Read(test, 0, test.Length);
|
||||
|
||||
Assert.Equal(decompressed, test);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestADCStream()
|
||||
{
|
||||
using (FileStream decFs = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "adc_decompressed.bin")))
|
||||
{
|
||||
byte[] decompressed = new byte[decFs.Length];
|
||||
decFs.Read(decompressed, 0, decompressed.Length);
|
||||
|
||||
using (FileStream cmpFs = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "adc_compressed.bin")))
|
||||
{
|
||||
using (ADCStream decStream = new ADCStream(cmpFs, Compressor.CompressionMode.Decompress))
|
||||
{
|
||||
using (MemoryStream decMs = new MemoryStream())
|
||||
{
|
||||
byte[] test = new byte[512];
|
||||
int count = 0;
|
||||
|
||||
do
|
||||
{
|
||||
count = decStream.Read(test, 0, test.Length);
|
||||
decMs.Write(test, 0, count);
|
||||
}
|
||||
while (count > 0);
|
||||
|
||||
Assert.Equal(decompressed, decMs.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
test/TestArchives/Archives/adc_compressed.bin
Normal file
BIN
test/TestArchives/Archives/adc_compressed.bin
Normal file
Binary file not shown.
BIN
test/TestArchives/Archives/adc_decompressed.bin
Normal file
BIN
test/TestArchives/Archives/adc_decompressed.bin
Normal file
Binary file not shown.
Reference in New Issue
Block a user