Adds support for Apple Data Compression. (#168)

This commit is contained in:
2016-09-12 17:41:31 +01:00
committed by Adam Hathcock
parent 7037161c07
commit ddbbc3b847
5 changed files with 506 additions and 0 deletions

View 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;
}
}
}

View 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();
}
}
}

View 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());
}
}
}
}
}
}
}

Binary file not shown.

Binary file not shown.