Files
Aaru/Aaru.Compression/ADC.cs

179 lines
7.4 KiB
C#

// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : ADC.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// Component : Compression algorithms.
//
// --[ Description ] ----------------------------------------------------------
//
// Decompresses Apple Data Compression.
//
// --[ License ] --------------------------------------------------------------
//
// 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.
//
// ----------------------------------------------------------------------------
// Copyright © 2016-2022 Natalia Portillo
// ****************************************************************************/
namespace Aaru.Compression;
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
/// <summary>Implements the Apple version of RLE</summary>
public static class ADC
{
const int PLAIN = 1;
const int TWO_BYTE = 2;
const int THREE_BYTE = 3;
/// <summary>Set to <c>true</c> if this algorithm is supported, <c>false</c> otherwise.</summary>
public static bool IsSupported => true;
[DllImport("libAaru.Compression.Native", SetLastError = true)]
static extern int AARU_adc_decode_buffer(byte[] dst_buffer, int dst_size, byte[] src_buffer, int src_size);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static int GetChunkType(byte byt) => (byt & 0x80) == 0x80
? PLAIN
: (byt & 0x40) == 0x40
? THREE_BYTE
: TWO_BYTE;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static int GetChunkSize(byte byt) => GetChunkType(byt) switch
{
PLAIN => (byt & 0x7F) + 1,
TWO_BYTE => ((byt & 0x3F) >> 2) + 3,
THREE_BYTE => (byt & 0x3F) + 4,
_ => -1
};
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static int GetOffset(ReadOnlySpan<byte> chunk) => GetChunkType(chunk[0]) switch
{
PLAIN => 0,
TWO_BYTE => ((chunk[0] & 0x03) << 8) + chunk[1],
THREE_BYTE => (chunk[1] << 8) + chunk[2],
_ => -1
};
/// <summary>Decompresses a byte buffer that's compressed with ADC</summary>
/// <param name="source">Compressed buffer</param>
/// <param name="destination">Buffer to hold decompressed data</param>
/// <returns>How many bytes are stored on <paramref name="destination" /></returns>
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public static int DecodeBuffer(byte[] source, byte[] destination)
{
if(Native.IsSupported)
return AARU_adc_decode_buffer(destination, destination.Length, source, source.Length);
var inputPosition = 0;
int chunkSize;
int offset;
int chunkType;
var outPosition = 0;
Span<byte> temp = stackalloc byte[3];
while(inputPosition < source.Length)
{
byte readByte = source[inputPosition++];
chunkType = GetChunkType(readByte);
switch(chunkType)
{
case PLAIN:
chunkSize = GetChunkSize(readByte);
if(outPosition + chunkSize > destination.Length)
goto finished;
Array.Copy(source, inputPosition, destination, outPosition, chunkSize);
outPosition += chunkSize;
inputPosition += chunkSize;
break;
case TWO_BYTE:
chunkSize = GetChunkSize(readByte);
temp[0] = readByte;
temp[1] = source[inputPosition++];
offset = GetOffset(temp);
if(outPosition + chunkSize > destination.Length)
goto finished;
if(offset == 0)
{
byte lastByte = destination[outPosition - 1];
for(var i = 0; i < chunkSize; i++)
{
destination[outPosition] = lastByte;
outPosition++;
}
}
else
for(var i = 0; i < chunkSize; i++)
{
destination[outPosition] = destination[outPosition - offset - 1];
outPosition++;
}
break;
case THREE_BYTE:
chunkSize = GetChunkSize(readByte);
temp[0] = readByte;
temp[1] = source[inputPosition++];
temp[2] = source[inputPosition++];
offset = GetOffset(temp);
if(outPosition + chunkSize > destination.Length)
goto finished;
if(offset == 0)
{
byte lastByte = destination[outPosition - 1];
for(var i = 0; i < chunkSize; i++)
{
destination[outPosition] = lastByte;
outPosition++;
}
}
else
for(var i = 0; i < chunkSize; i++)
{
destination[outPosition] = destination[outPosition - offset - 1];
outPosition++;
}
break;
}
}
finished:
return outPosition;
}
}