mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-09-25 16:34:45 +00:00
more file scoped namespaces
This commit is contained in:
@@ -7,54 +7,53 @@ using System.Threading.Tasks;
|
||||
using SharpCompress.Common.GZip;
|
||||
using SharpCompress.Common.Tar;
|
||||
|
||||
namespace SharpCompress.Common.Arc
|
||||
namespace SharpCompress.Common.Arc;
|
||||
|
||||
public class ArcEntry : Entry
|
||||
{
|
||||
public class ArcEntry : Entry
|
||||
private readonly ArcFilePart? _filePart;
|
||||
|
||||
internal ArcEntry(ArcFilePart? filePart)
|
||||
{
|
||||
private readonly ArcFilePart? _filePart;
|
||||
|
||||
internal ArcEntry(ArcFilePart? filePart)
|
||||
{
|
||||
_filePart = filePart;
|
||||
}
|
||||
|
||||
public override long Crc
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_filePart == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return _filePart.Header.Crc16;
|
||||
}
|
||||
}
|
||||
|
||||
public override string? Key => _filePart?.Header.Name;
|
||||
|
||||
public override string? LinkTarget => null;
|
||||
|
||||
public override long CompressedSize => _filePart?.Header.CompressedSize ?? 0;
|
||||
|
||||
public override CompressionType CompressionType =>
|
||||
_filePart?.Header.CompressionMethod ?? CompressionType.Unknown;
|
||||
|
||||
public override long Size => throw new NotImplementedException();
|
||||
|
||||
public override DateTime? LastModifiedTime => null;
|
||||
|
||||
public override DateTime? CreatedTime => null;
|
||||
|
||||
public override DateTime? LastAccessedTime => null;
|
||||
|
||||
public override DateTime? ArchivedTime => null;
|
||||
|
||||
public override bool IsEncrypted => false;
|
||||
|
||||
public override bool IsDirectory => false;
|
||||
|
||||
public override bool IsSplitAfter => false;
|
||||
|
||||
internal override IEnumerable<FilePart> Parts => _filePart.Empty();
|
||||
_filePart = filePart;
|
||||
}
|
||||
}
|
||||
|
||||
public override long Crc
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_filePart == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return _filePart.Header.Crc16;
|
||||
}
|
||||
}
|
||||
|
||||
public override string? Key => _filePart?.Header.Name;
|
||||
|
||||
public override string? LinkTarget => null;
|
||||
|
||||
public override long CompressedSize => _filePart?.Header.CompressedSize ?? 0;
|
||||
|
||||
public override CompressionType CompressionType =>
|
||||
_filePart?.Header.CompressionMethod ?? CompressionType.Unknown;
|
||||
|
||||
public override long Size => throw new NotImplementedException();
|
||||
|
||||
public override DateTime? LastModifiedTime => null;
|
||||
|
||||
public override DateTime? CreatedTime => null;
|
||||
|
||||
public override DateTime? LastAccessedTime => null;
|
||||
|
||||
public override DateTime? ArchivedTime => null;
|
||||
|
||||
public override bool IsEncrypted => false;
|
||||
|
||||
public override bool IsDirectory => false;
|
||||
|
||||
public override bool IsSplitAfter => false;
|
||||
|
||||
internal override IEnumerable<FilePart> Parts => _filePart.Empty();
|
||||
}
|
||||
@@ -3,74 +3,73 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace SharpCompress.Common.Arc
|
||||
namespace SharpCompress.Common.Arc;
|
||||
|
||||
public class ArcEntryHeader
|
||||
{
|
||||
public class ArcEntryHeader
|
||||
public ArchiveEncoding ArchiveEncoding { get; }
|
||||
public CompressionType CompressionMethod { get; private set; }
|
||||
public string? Name { get; private set; }
|
||||
public long CompressedSize { get; private set; }
|
||||
public DateTime DateTime { get; private set; }
|
||||
public int Crc16 { get; private set; }
|
||||
public long OriginalSize { get; private set; }
|
||||
public long DataStartPosition { get; private set; }
|
||||
|
||||
public ArcEntryHeader(ArchiveEncoding archiveEncoding)
|
||||
{
|
||||
public ArchiveEncoding ArchiveEncoding { get; }
|
||||
public CompressionType CompressionMethod { get; private set; }
|
||||
public string? Name { get; private set; }
|
||||
public long CompressedSize { get; private set; }
|
||||
public DateTime DateTime { get; private set; }
|
||||
public int Crc16 { get; private set; }
|
||||
public long OriginalSize { get; private set; }
|
||||
public long DataStartPosition { get; private set; }
|
||||
|
||||
public ArcEntryHeader(ArchiveEncoding archiveEncoding)
|
||||
{
|
||||
this.ArchiveEncoding = archiveEncoding;
|
||||
}
|
||||
|
||||
public ArcEntryHeader? ReadHeader(Stream stream)
|
||||
{
|
||||
byte[] headerBytes = new byte[29];
|
||||
if (stream.Read(headerBytes, 0, headerBytes.Length) != headerBytes.Length)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
DataStartPosition = stream.Position;
|
||||
return LoadFrom(headerBytes);
|
||||
}
|
||||
|
||||
public ArcEntryHeader LoadFrom(byte[] headerBytes)
|
||||
{
|
||||
CompressionMethod = GetCompressionType(headerBytes[1]);
|
||||
|
||||
// Read name
|
||||
int nameEnd = Array.IndexOf(headerBytes, (byte)0, 1); // Find null terminator
|
||||
Name = Encoding.UTF8.GetString(headerBytes, 2, nameEnd > 0 ? nameEnd - 2 : 12);
|
||||
|
||||
int offset = 15;
|
||||
CompressedSize = BitConverter.ToUInt32(headerBytes, offset);
|
||||
offset += 4;
|
||||
uint rawDateTime = BitConverter.ToUInt32(headerBytes, offset);
|
||||
DateTime = ConvertToDateTime(rawDateTime);
|
||||
offset += 4;
|
||||
Crc16 = BitConverter.ToUInt16(headerBytes, offset);
|
||||
offset += 2;
|
||||
OriginalSize = BitConverter.ToUInt32(headerBytes, offset);
|
||||
return this;
|
||||
}
|
||||
|
||||
private CompressionType GetCompressionType(byte value)
|
||||
{
|
||||
return value switch
|
||||
{
|
||||
1 or 2 => CompressionType.None,
|
||||
3 => CompressionType.RLE90,
|
||||
4 => CompressionType.Squeezed,
|
||||
5 or 6 or 7 or 8 => CompressionType.Crunched,
|
||||
9 => CompressionType.Squashed,
|
||||
10 => CompressionType.Crushed,
|
||||
11 => CompressionType.Distilled,
|
||||
_ => CompressionType.Unknown,
|
||||
};
|
||||
}
|
||||
|
||||
public static DateTime ConvertToDateTime(long rawDateTime)
|
||||
{
|
||||
// Convert Unix timestamp to DateTime (UTC)
|
||||
return DateTimeOffset.FromUnixTimeSeconds(rawDateTime).UtcDateTime;
|
||||
}
|
||||
this.ArchiveEncoding = archiveEncoding;
|
||||
}
|
||||
}
|
||||
|
||||
public ArcEntryHeader? ReadHeader(Stream stream)
|
||||
{
|
||||
byte[] headerBytes = new byte[29];
|
||||
if (stream.Read(headerBytes, 0, headerBytes.Length) != headerBytes.Length)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
DataStartPosition = stream.Position;
|
||||
return LoadFrom(headerBytes);
|
||||
}
|
||||
|
||||
public ArcEntryHeader LoadFrom(byte[] headerBytes)
|
||||
{
|
||||
CompressionMethod = GetCompressionType(headerBytes[1]);
|
||||
|
||||
// Read name
|
||||
int nameEnd = Array.IndexOf(headerBytes, (byte)0, 1); // Find null terminator
|
||||
Name = Encoding.UTF8.GetString(headerBytes, 2, nameEnd > 0 ? nameEnd - 2 : 12);
|
||||
|
||||
int offset = 15;
|
||||
CompressedSize = BitConverter.ToUInt32(headerBytes, offset);
|
||||
offset += 4;
|
||||
uint rawDateTime = BitConverter.ToUInt32(headerBytes, offset);
|
||||
DateTime = ConvertToDateTime(rawDateTime);
|
||||
offset += 4;
|
||||
Crc16 = BitConverter.ToUInt16(headerBytes, offset);
|
||||
offset += 2;
|
||||
OriginalSize = BitConverter.ToUInt32(headerBytes, offset);
|
||||
return this;
|
||||
}
|
||||
|
||||
private CompressionType GetCompressionType(byte value)
|
||||
{
|
||||
return value switch
|
||||
{
|
||||
1 or 2 => CompressionType.None,
|
||||
3 => CompressionType.RLE90,
|
||||
4 => CompressionType.Squeezed,
|
||||
5 or 6 or 7 or 8 => CompressionType.Crunched,
|
||||
9 => CompressionType.Squashed,
|
||||
10 => CompressionType.Crushed,
|
||||
11 => CompressionType.Distilled,
|
||||
_ => CompressionType.Unknown,
|
||||
};
|
||||
}
|
||||
|
||||
public static DateTime ConvertToDateTime(long rawDateTime)
|
||||
{
|
||||
// Convert Unix timestamp to DateTime (UTC)
|
||||
return DateTimeOffset.FromUnixTimeSeconds(rawDateTime).UtcDateTime;
|
||||
}
|
||||
}
|
||||
@@ -13,63 +13,62 @@ using SharpCompress.Compressors.RLE90;
|
||||
using SharpCompress.Compressors.Squeezed;
|
||||
using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Common.Arc
|
||||
namespace SharpCompress.Common.Arc;
|
||||
|
||||
public class ArcFilePart : FilePart
|
||||
{
|
||||
public class ArcFilePart : FilePart
|
||||
private readonly Stream? _stream;
|
||||
|
||||
internal ArcFilePart(ArcEntryHeader localArcHeader, Stream? seekableStream)
|
||||
: base(localArcHeader.ArchiveEncoding)
|
||||
{
|
||||
private readonly Stream? _stream;
|
||||
|
||||
internal ArcFilePart(ArcEntryHeader localArcHeader, Stream? seekableStream)
|
||||
: base(localArcHeader.ArchiveEncoding)
|
||||
{
|
||||
_stream = seekableStream;
|
||||
Header = localArcHeader;
|
||||
}
|
||||
|
||||
internal ArcEntryHeader Header { get; set; }
|
||||
|
||||
internal override string? FilePartName => Header.Name;
|
||||
|
||||
internal override Stream GetCompressedStream()
|
||||
{
|
||||
if (_stream != null)
|
||||
{
|
||||
Stream compressedStream;
|
||||
switch (Header.CompressionMethod)
|
||||
{
|
||||
case CompressionType.None:
|
||||
compressedStream = new ReadOnlySubStream(
|
||||
_stream,
|
||||
Header.DataStartPosition,
|
||||
Header.CompressedSize
|
||||
);
|
||||
break;
|
||||
case CompressionType.RLE90:
|
||||
compressedStream = new RunLength90Stream(
|
||||
_stream,
|
||||
(int)Header.CompressedSize
|
||||
);
|
||||
break;
|
||||
case CompressionType.Squeezed:
|
||||
compressedStream = new SqueezeStream(_stream, (int)Header.CompressedSize);
|
||||
break;
|
||||
case CompressionType.Crunched:
|
||||
compressedStream = new ArcLzwStream(
|
||||
_stream,
|
||||
(int)Header.CompressedSize,
|
||||
true
|
||||
);
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException(
|
||||
"CompressionMethod: " + Header.CompressionMethod
|
||||
);
|
||||
}
|
||||
return compressedStream;
|
||||
}
|
||||
return _stream.NotNull();
|
||||
}
|
||||
|
||||
internal override Stream? GetRawStream() => _stream;
|
||||
_stream = seekableStream;
|
||||
Header = localArcHeader;
|
||||
}
|
||||
}
|
||||
|
||||
internal ArcEntryHeader Header { get; set; }
|
||||
|
||||
internal override string? FilePartName => Header.Name;
|
||||
|
||||
internal override Stream GetCompressedStream()
|
||||
{
|
||||
if (_stream != null)
|
||||
{
|
||||
Stream compressedStream;
|
||||
switch (Header.CompressionMethod)
|
||||
{
|
||||
case CompressionType.None:
|
||||
compressedStream = new ReadOnlySubStream(
|
||||
_stream,
|
||||
Header.DataStartPosition,
|
||||
Header.CompressedSize
|
||||
);
|
||||
break;
|
||||
case CompressionType.RLE90:
|
||||
compressedStream = new RunLength90Stream(
|
||||
_stream,
|
||||
(int)Header.CompressedSize
|
||||
);
|
||||
break;
|
||||
case CompressionType.Squeezed:
|
||||
compressedStream = new SqueezeStream(_stream, (int)Header.CompressedSize);
|
||||
break;
|
||||
case CompressionType.Crunched:
|
||||
compressedStream = new ArcLzwStream(
|
||||
_stream,
|
||||
(int)Header.CompressedSize,
|
||||
true
|
||||
);
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException(
|
||||
"CompressionMethod: " + Header.CompressionMethod
|
||||
);
|
||||
}
|
||||
return compressedStream;
|
||||
}
|
||||
return _stream.NotNull();
|
||||
}
|
||||
|
||||
internal override Stream? GetRawStream() => _stream;
|
||||
}
|
||||
@@ -6,11 +6,10 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Readers;
|
||||
|
||||
namespace SharpCompress.Common.Arc
|
||||
namespace SharpCompress.Common.Arc;
|
||||
|
||||
public class ArcVolume : Volume
|
||||
{
|
||||
public class ArcVolume : Volume
|
||||
{
|
||||
public ArcVolume(Stream stream, ReaderOptions readerOptions, int index = 0)
|
||||
: base(stream, readerOptions, index) { }
|
||||
}
|
||||
}
|
||||
public ArcVolume(Stream stream, ReaderOptions readerOptions, int index = 0)
|
||||
: base(stream, readerOptions, index) { }
|
||||
}
|
||||
@@ -1,36 +1,35 @@
|
||||
using System.IO;
|
||||
|
||||
namespace SharpCompress.Compressors.Filters
|
||||
namespace SharpCompress.Compressors.Filters;
|
||||
|
||||
internal class DeltaFilter : Filter
|
||||
{
|
||||
internal class DeltaFilter : Filter
|
||||
private const int DISTANCE_MIN = 1;
|
||||
private const int DISTANCE_MAX = 256;
|
||||
private const int DISTANCE_MASK = DISTANCE_MAX - 1;
|
||||
|
||||
private int _distance;
|
||||
private byte[] _history;
|
||||
private int _position;
|
||||
|
||||
public DeltaFilter(bool isEncoder, Stream baseStream, byte[] info)
|
||||
: base(isEncoder, baseStream, 1)
|
||||
{
|
||||
private const int DISTANCE_MIN = 1;
|
||||
private const int DISTANCE_MAX = 256;
|
||||
private const int DISTANCE_MASK = DISTANCE_MAX - 1;
|
||||
|
||||
private int _distance;
|
||||
private byte[] _history;
|
||||
private int _position;
|
||||
|
||||
public DeltaFilter(bool isEncoder, Stream baseStream, byte[] info)
|
||||
: base(isEncoder, baseStream, 1)
|
||||
{
|
||||
_distance = info[0];
|
||||
_history = new byte[DISTANCE_MAX];
|
||||
_position = 0;
|
||||
}
|
||||
|
||||
protected override int Transform(byte[] buffer, int offset, int count)
|
||||
{
|
||||
var end = offset + count;
|
||||
|
||||
for (var i = offset; i < end; i++)
|
||||
{
|
||||
buffer[i] += _history[(_distance + _position--) & DISTANCE_MASK];
|
||||
_history[_position & DISTANCE_MASK] = buffer[i];
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
_distance = info[0];
|
||||
_history = new byte[DISTANCE_MAX];
|
||||
_position = 0;
|
||||
}
|
||||
}
|
||||
|
||||
protected override int Transform(byte[] buffer, int offset, int count)
|
||||
{
|
||||
var end = offset + count;
|
||||
|
||||
for (var i = offset; i < end; i++)
|
||||
{
|
||||
buffer[i] += _history[(_distance + _position--) & DISTANCE_MASK];
|
||||
_history[_position & DISTANCE_MASK] = buffer[i];
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
@@ -1,65 +1,64 @@
|
||||
namespace SharpCompress.Compressors.Lzw
|
||||
namespace SharpCompress.Compressors.Lzw;
|
||||
|
||||
/// <summary>
|
||||
/// This class contains constants used for LZW
|
||||
/// </summary>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage(
|
||||
"Naming",
|
||||
"CA1707:Identifiers should not contain underscores",
|
||||
Justification = "kept for backwards compatibility"
|
||||
)]
|
||||
public sealed class LzwConstants
|
||||
{
|
||||
/// <summary>
|
||||
/// This class contains constants used for LZW
|
||||
/// Magic number found at start of LZW header: 0x1f 0x9d
|
||||
/// </summary>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage(
|
||||
"Naming",
|
||||
"CA1707:Identifiers should not contain underscores",
|
||||
Justification = "kept for backwards compatibility"
|
||||
)]
|
||||
public sealed class LzwConstants
|
||||
{
|
||||
/// <summary>
|
||||
/// Magic number found at start of LZW header: 0x1f 0x9d
|
||||
/// </summary>
|
||||
public const int MAGIC = 0x1f9d;
|
||||
public const int MAGIC = 0x1f9d;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of bits per code
|
||||
/// </summary>
|
||||
public const int MAX_BITS = 16;
|
||||
/// <summary>
|
||||
/// Maximum number of bits per code
|
||||
/// </summary>
|
||||
public const int MAX_BITS = 16;
|
||||
|
||||
/* 3rd header byte:
|
||||
* bit 0..4 Number of compression bits
|
||||
* bit 5 Extended header
|
||||
* bit 6 Free
|
||||
* bit 7 Block mode
|
||||
*/
|
||||
/* 3rd header byte:
|
||||
* bit 0..4 Number of compression bits
|
||||
* bit 5 Extended header
|
||||
* bit 6 Free
|
||||
* bit 7 Block mode
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Mask for 'number of compression bits'
|
||||
/// </summary>
|
||||
public const int BIT_MASK = 0x1f;
|
||||
/// <summary>
|
||||
/// Mask for 'number of compression bits'
|
||||
/// </summary>
|
||||
public const int BIT_MASK = 0x1f;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the presence of a fourth header byte
|
||||
/// </summary>
|
||||
public const int EXTENDED_MASK = 0x20;
|
||||
/// <summary>
|
||||
/// Indicates the presence of a fourth header byte
|
||||
/// </summary>
|
||||
public const int EXTENDED_MASK = 0x20;
|
||||
|
||||
//public const int FREE_MASK = 0x40;
|
||||
//public const int FREE_MASK = 0x40;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved bits
|
||||
/// </summary>
|
||||
public const int RESERVED_MASK = 0x60;
|
||||
/// <summary>
|
||||
/// Reserved bits
|
||||
/// </summary>
|
||||
public const int RESERVED_MASK = 0x60;
|
||||
|
||||
/// <summary>
|
||||
/// Block compression: if table is full and compression rate is dropping,
|
||||
/// clear the dictionary.
|
||||
/// </summary>
|
||||
public const int BLOCK_MODE_MASK = 0x80;
|
||||
/// <summary>
|
||||
/// Block compression: if table is full and compression rate is dropping,
|
||||
/// clear the dictionary.
|
||||
/// </summary>
|
||||
public const int BLOCK_MODE_MASK = 0x80;
|
||||
|
||||
/// <summary>
|
||||
/// LZW file header size (in bytes)
|
||||
/// </summary>
|
||||
public const int HDR_SIZE = 3;
|
||||
/// <summary>
|
||||
/// LZW file header size (in bytes)
|
||||
/// </summary>
|
||||
public const int HDR_SIZE = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Initial number of bits per code
|
||||
/// </summary>
|
||||
public const int INIT_BITS = 9;
|
||||
/// <summary>
|
||||
/// Initial number of bits per code
|
||||
/// </summary>
|
||||
public const int INIT_BITS = 9;
|
||||
|
||||
private LzwConstants() { }
|
||||
}
|
||||
}
|
||||
private LzwConstants() { }
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,52 +1,51 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace SharpCompress.Compressors.RLE90
|
||||
namespace SharpCompress.Compressors.RLE90;
|
||||
|
||||
public static class RLE
|
||||
{
|
||||
public static class RLE
|
||||
private const byte DLE = 0x90;
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks an RLE compressed buffer.
|
||||
/// Format: <char> DLE <count>, where count == 0 -> DLE
|
||||
/// </summary>
|
||||
/// <param name="compressedBuffer">The compressed buffer to unpack.</param>
|
||||
/// <returns>A list of unpacked bytes.</returns>
|
||||
public static List<byte> UnpackRLE(byte[] compressedBuffer)
|
||||
{
|
||||
private const byte DLE = 0x90;
|
||||
var result = new List<byte>(compressedBuffer.Length * 2); // Optimized initial capacity
|
||||
var countMode = false;
|
||||
byte last = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks an RLE compressed buffer.
|
||||
/// Format: <char> DLE <count>, where count == 0 -> DLE
|
||||
/// </summary>
|
||||
/// <param name="compressedBuffer">The compressed buffer to unpack.</param>
|
||||
/// <returns>A list of unpacked bytes.</returns>
|
||||
public static List<byte> UnpackRLE(byte[] compressedBuffer)
|
||||
foreach (var c in compressedBuffer)
|
||||
{
|
||||
var result = new List<byte>(compressedBuffer.Length * 2); // Optimized initial capacity
|
||||
var countMode = false;
|
||||
byte last = 0;
|
||||
|
||||
foreach (var c in compressedBuffer)
|
||||
if (!countMode)
|
||||
{
|
||||
if (!countMode)
|
||||
if (c == DLE)
|
||||
{
|
||||
if (c == DLE)
|
||||
{
|
||||
countMode = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Add(c);
|
||||
last = c;
|
||||
}
|
||||
countMode = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
countMode = false;
|
||||
if (c == 0)
|
||||
{
|
||||
result.Add(DLE);
|
||||
}
|
||||
else
|
||||
{
|
||||
result.AddRange(Enumerable.Repeat(last, c - 1));
|
||||
}
|
||||
result.Add(c);
|
||||
last = c;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
countMode = false;
|
||||
if (c == 0)
|
||||
{
|
||||
result.Add(DLE);
|
||||
}
|
||||
else
|
||||
{
|
||||
result.AddRange(Enumerable.Repeat(last, c - 1));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,91 +6,90 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Compressors.RLE90
|
||||
namespace SharpCompress.Compressors.RLE90;
|
||||
|
||||
public class RunLength90Stream : Stream, IStreamStack
|
||||
{
|
||||
public class RunLength90Stream : Stream, IStreamStack
|
||||
#if DEBUG_STREAMS
|
||||
long IStreamStack.InstanceId { get; set; }
|
||||
#endif
|
||||
int IStreamStack.DefaultBufferSize { get; set; }
|
||||
|
||||
Stream IStreamStack.BaseStream() => _stream;
|
||||
|
||||
int IStreamStack.BufferSize
|
||||
{
|
||||
get => 0;
|
||||
set { }
|
||||
}
|
||||
int IStreamStack.BufferPosition
|
||||
{
|
||||
get => 0;
|
||||
set { }
|
||||
}
|
||||
|
||||
void IStreamStack.SetPosition(long position) { }
|
||||
|
||||
private readonly Stream _stream;
|
||||
private const byte DLE = 0x90;
|
||||
private int _compressedSize;
|
||||
private bool _processed = false;
|
||||
|
||||
public RunLength90Stream(Stream stream, int compressedSize)
|
||||
{
|
||||
_stream = stream;
|
||||
_compressedSize = compressedSize;
|
||||
#if DEBUG_STREAMS
|
||||
this.DebugConstruct(typeof(RunLength90Stream));
|
||||
#endif
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
#if DEBUG_STREAMS
|
||||
long IStreamStack.InstanceId { get; set; }
|
||||
this.DebugDispose(typeof(RunLength90Stream));
|
||||
#endif
|
||||
int IStreamStack.DefaultBufferSize { get; set; }
|
||||
|
||||
Stream IStreamStack.BaseStream() => _stream;
|
||||
|
||||
int IStreamStack.BufferSize
|
||||
{
|
||||
get => 0;
|
||||
set { }
|
||||
}
|
||||
int IStreamStack.BufferPosition
|
||||
{
|
||||
get => 0;
|
||||
set { }
|
||||
}
|
||||
|
||||
void IStreamStack.SetPosition(long position) { }
|
||||
|
||||
private readonly Stream _stream;
|
||||
private const byte DLE = 0x90;
|
||||
private int _compressedSize;
|
||||
private bool _processed = false;
|
||||
|
||||
public RunLength90Stream(Stream stream, int compressedSize)
|
||||
{
|
||||
_stream = stream;
|
||||
_compressedSize = compressedSize;
|
||||
#if DEBUG_STREAMS
|
||||
this.DebugConstruct(typeof(RunLength90Stream));
|
||||
#endif
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
#if DEBUG_STREAMS
|
||||
this.DebugDispose(typeof(RunLength90Stream));
|
||||
#endif
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
public override bool CanRead => true;
|
||||
|
||||
public override bool CanSeek => false;
|
||||
|
||||
public override bool CanWrite => false;
|
||||
|
||||
public override long Length => throw new NotImplementedException();
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get => _stream.Position;
|
||||
set => throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void Flush() => throw new NotImplementedException();
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (_processed)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
_processed = true;
|
||||
|
||||
using var binaryReader = new BinaryReader(_stream);
|
||||
byte[] compressedBuffer = binaryReader.ReadBytes(_compressedSize);
|
||||
|
||||
var unpacked = RLE.UnpackRLE(compressedBuffer);
|
||||
unpacked.CopyTo(buffer);
|
||||
|
||||
return unpacked.Count;
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin) =>
|
||||
throw new NotImplementedException();
|
||||
|
||||
public override void SetLength(long value) => throw new NotImplementedException();
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count) =>
|
||||
throw new NotImplementedException();
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool CanRead => true;
|
||||
|
||||
public override bool CanSeek => false;
|
||||
|
||||
public override bool CanWrite => false;
|
||||
|
||||
public override long Length => throw new NotImplementedException();
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get => _stream.Position;
|
||||
set => throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void Flush() => throw new NotImplementedException();
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (_processed)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
_processed = true;
|
||||
|
||||
using var binaryReader = new BinaryReader(_stream);
|
||||
byte[] compressedBuffer = binaryReader.ReadBytes(_compressedSize);
|
||||
|
||||
var unpacked = RLE.UnpackRLE(compressedBuffer);
|
||||
unpacked.CopyTo(buffer);
|
||||
|
||||
return unpacked.Count;
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin) =>
|
||||
throw new NotImplementedException();
|
||||
|
||||
public override void SetLength(long value) => throw new NotImplementedException();
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count) =>
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
@@ -1,79 +1,78 @@
|
||||
namespace SharpCompress.Compressors.Shrink
|
||||
namespace SharpCompress.Compressors.Shrink;
|
||||
|
||||
internal class BitStream
|
||||
{
|
||||
internal class BitStream
|
||||
private byte[] _src;
|
||||
private int _srcLen;
|
||||
private int _byteIdx;
|
||||
private int _bitIdx;
|
||||
private int _bitsLeft;
|
||||
private ulong _bitBuffer;
|
||||
private static uint[] _maskBits = new uint[17]
|
||||
{
|
||||
private byte[] _src;
|
||||
private int _srcLen;
|
||||
private int _byteIdx;
|
||||
private int _bitIdx;
|
||||
private int _bitsLeft;
|
||||
private ulong _bitBuffer;
|
||||
private static uint[] _maskBits = new uint[17]
|
||||
{
|
||||
0U,
|
||||
1U,
|
||||
3U,
|
||||
7U,
|
||||
15U,
|
||||
31U,
|
||||
63U,
|
||||
(uint)sbyte.MaxValue,
|
||||
(uint)byte.MaxValue,
|
||||
511U,
|
||||
1023U,
|
||||
2047U,
|
||||
4095U,
|
||||
8191U,
|
||||
16383U,
|
||||
(uint)short.MaxValue,
|
||||
(uint)ushort.MaxValue,
|
||||
};
|
||||
0U,
|
||||
1U,
|
||||
3U,
|
||||
7U,
|
||||
15U,
|
||||
31U,
|
||||
63U,
|
||||
(uint)sbyte.MaxValue,
|
||||
(uint)byte.MaxValue,
|
||||
511U,
|
||||
1023U,
|
||||
2047U,
|
||||
4095U,
|
||||
8191U,
|
||||
16383U,
|
||||
(uint)short.MaxValue,
|
||||
(uint)ushort.MaxValue,
|
||||
};
|
||||
|
||||
public BitStream(byte[] src, int srcLen)
|
||||
{
|
||||
_src = src;
|
||||
_srcLen = srcLen;
|
||||
_byteIdx = 0;
|
||||
_bitIdx = 0;
|
||||
}
|
||||
|
||||
public int BytesRead => (_byteIdx << 3) + _bitIdx;
|
||||
|
||||
private int NextByte()
|
||||
{
|
||||
if (_byteIdx >= _srcLen)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return _src[_byteIdx++];
|
||||
}
|
||||
|
||||
public int NextBits(int nbits)
|
||||
{
|
||||
var result = 0;
|
||||
if (nbits > _bitsLeft)
|
||||
{
|
||||
int num;
|
||||
while (_bitsLeft <= 24 && (num = NextByte()) != 1234)
|
||||
{
|
||||
_bitBuffer |= (ulong)num << _bitsLeft;
|
||||
_bitsLeft += 8;
|
||||
}
|
||||
}
|
||||
result = (int)((long)_bitBuffer & (long)_maskBits[nbits]);
|
||||
_bitBuffer >>= nbits;
|
||||
_bitsLeft -= nbits;
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool Advance(int count)
|
||||
{
|
||||
if (_byteIdx > _srcLen)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
public BitStream(byte[] src, int srcLen)
|
||||
{
|
||||
_src = src;
|
||||
_srcLen = srcLen;
|
||||
_byteIdx = 0;
|
||||
_bitIdx = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public int BytesRead => (_byteIdx << 3) + _bitIdx;
|
||||
|
||||
private int NextByte()
|
||||
{
|
||||
if (_byteIdx >= _srcLen)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return _src[_byteIdx++];
|
||||
}
|
||||
|
||||
public int NextBits(int nbits)
|
||||
{
|
||||
var result = 0;
|
||||
if (nbits > _bitsLeft)
|
||||
{
|
||||
int num;
|
||||
while (_bitsLeft <= 24 && (num = NextByte()) != 1234)
|
||||
{
|
||||
_bitBuffer |= (ulong)num << _bitsLeft;
|
||||
_bitsLeft += 8;
|
||||
}
|
||||
}
|
||||
result = (int)((long)_bitBuffer & (long)_maskBits[nbits]);
|
||||
_bitBuffer >>= nbits;
|
||||
_bitsLeft -= nbits;
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool Advance(int count)
|
||||
{
|
||||
if (_byteIdx > _srcLen)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,275 +1,297 @@
|
||||
using System;
|
||||
|
||||
namespace SharpCompress.Compressors.Shrink
|
||||
namespace SharpCompress.Compressors.Shrink;
|
||||
|
||||
public class HwUnshrink
|
||||
{
|
||||
public class HwUnshrink
|
||||
private const int MIN_CODE_SIZE = 9;
|
||||
private const int MAX_CODE_SIZE = 13;
|
||||
|
||||
private const ushort MAX_CODE = (ushort)((1U << MAX_CODE_SIZE) - 1);
|
||||
private const ushort INVALID_CODE = ushort.MaxValue;
|
||||
private const ushort CONTROL_CODE = 256;
|
||||
private const ushort INC_CODE_SIZE = 1;
|
||||
private const ushort PARTIAL_CLEAR = 2;
|
||||
|
||||
private const int HASH_BITS = MAX_CODE_SIZE + 1; // For a load factor of 0.5.
|
||||
private const int HASHTAB_SIZE = 1 << HASH_BITS;
|
||||
private const ushort UNKNOWN_LEN = ushort.MaxValue;
|
||||
|
||||
private struct CodeTabEntry
|
||||
{
|
||||
private const int MIN_CODE_SIZE = 9;
|
||||
private const int MAX_CODE_SIZE = 13;
|
||||
public int prefixCode; // INVALID_CODE means the entry is invalid.
|
||||
public byte extByte;
|
||||
public ushort len;
|
||||
public int lastDstPos;
|
||||
}
|
||||
|
||||
private const ushort MAX_CODE = (ushort)((1U << MAX_CODE_SIZE) - 1);
|
||||
private const ushort INVALID_CODE = ushort.MaxValue;
|
||||
private const ushort CONTROL_CODE = 256;
|
||||
private const ushort INC_CODE_SIZE = 1;
|
||||
private const ushort PARTIAL_CLEAR = 2;
|
||||
|
||||
private const int HASH_BITS = MAX_CODE_SIZE + 1; // For a load factor of 0.5.
|
||||
private const int HASHTAB_SIZE = 1 << HASH_BITS;
|
||||
private const ushort UNKNOWN_LEN = ushort.MaxValue;
|
||||
|
||||
private struct CodeTabEntry
|
||||
private static void CodeTabInit(CodeTabEntry[] codeTab)
|
||||
{
|
||||
for (var i = 0; i <= byte.MaxValue; i++)
|
||||
{
|
||||
public int prefixCode; // INVALID_CODE means the entry is invalid.
|
||||
public byte extByte;
|
||||
public ushort len;
|
||||
public int lastDstPos;
|
||||
codeTab[i].prefixCode = (ushort)i;
|
||||
codeTab[i].extByte = (byte)i;
|
||||
codeTab[i].len = 1;
|
||||
}
|
||||
|
||||
private static void CodeTabInit(CodeTabEntry[] codeTab)
|
||||
for (var i = byte.MaxValue + 1; i <= MAX_CODE; i++)
|
||||
{
|
||||
for (var i = 0; i <= byte.MaxValue; i++)
|
||||
{
|
||||
codeTab[i].prefixCode = (ushort)i;
|
||||
codeTab[i].extByte = (byte)i;
|
||||
codeTab[i].len = 1;
|
||||
}
|
||||
codeTab[i].prefixCode = INVALID_CODE;
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = byte.MaxValue + 1; i <= MAX_CODE; i++)
|
||||
private static void UnshrinkPartialClear(CodeTabEntry[] codeTab, ref CodeQueue queue)
|
||||
{
|
||||
var isPrefix = new bool[MAX_CODE + 1];
|
||||
int codeQueueSize;
|
||||
|
||||
// Scan for codes that have been used as a prefix.
|
||||
for (var i = CONTROL_CODE + 1; i <= MAX_CODE; i++)
|
||||
{
|
||||
if (codeTab[i].prefixCode != INVALID_CODE)
|
||||
{
|
||||
isPrefix[codeTab[i].prefixCode] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Clear "non-prefix" codes in the table; populate the code queue.
|
||||
codeQueueSize = 0;
|
||||
for (var i = CONTROL_CODE + 1; i <= MAX_CODE; i++)
|
||||
{
|
||||
if (!isPrefix[i])
|
||||
{
|
||||
codeTab[i].prefixCode = INVALID_CODE;
|
||||
queue.codes[codeQueueSize++] = (ushort)i;
|
||||
}
|
||||
}
|
||||
|
||||
private static void UnshrinkPartialClear(CodeTabEntry[] codeTab, ref CodeQueue queue)
|
||||
queue.codes[codeQueueSize] = INVALID_CODE; // End-of-queue marker.
|
||||
queue.nextIdx = 0;
|
||||
}
|
||||
|
||||
private static bool ReadCode(
|
||||
BitStream stream,
|
||||
ref int codeSize,
|
||||
CodeTabEntry[] codeTab,
|
||||
ref CodeQueue queue,
|
||||
out int nextCode
|
||||
)
|
||||
{
|
||||
int code,
|
||||
controlCode;
|
||||
|
||||
code = (int)stream.NextBits(codeSize);
|
||||
if (!stream.Advance(codeSize))
|
||||
{
|
||||
var isPrefix = new bool[MAX_CODE + 1];
|
||||
int codeQueueSize;
|
||||
|
||||
// Scan for codes that have been used as a prefix.
|
||||
for (var i = CONTROL_CODE + 1; i <= MAX_CODE; i++)
|
||||
{
|
||||
if (codeTab[i].prefixCode != INVALID_CODE)
|
||||
{
|
||||
isPrefix[codeTab[i].prefixCode] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Clear "non-prefix" codes in the table; populate the code queue.
|
||||
codeQueueSize = 0;
|
||||
for (var i = CONTROL_CODE + 1; i <= MAX_CODE; i++)
|
||||
{
|
||||
if (!isPrefix[i])
|
||||
{
|
||||
codeTab[i].prefixCode = INVALID_CODE;
|
||||
queue.codes[codeQueueSize++] = (ushort)i;
|
||||
}
|
||||
}
|
||||
|
||||
queue.codes[codeQueueSize] = INVALID_CODE; // End-of-queue marker.
|
||||
queue.nextIdx = 0;
|
||||
nextCode = INVALID_CODE;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool ReadCode(
|
||||
BitStream stream,
|
||||
ref int codeSize,
|
||||
CodeTabEntry[] codeTab,
|
||||
ref CodeQueue queue,
|
||||
out int nextCode
|
||||
)
|
||||
// Handle regular codes (the common case).
|
||||
if (code != CONTROL_CODE)
|
||||
{
|
||||
int code,
|
||||
controlCode;
|
||||
|
||||
code = (int)stream.NextBits(codeSize);
|
||||
if (!stream.Advance(codeSize))
|
||||
{
|
||||
nextCode = INVALID_CODE;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Handle regular codes (the common case).
|
||||
if (code != CONTROL_CODE)
|
||||
{
|
||||
nextCode = code;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle control codes.
|
||||
controlCode = (ushort)stream.NextBits(codeSize);
|
||||
if (!stream.Advance(codeSize))
|
||||
{
|
||||
nextCode = INVALID_CODE;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (controlCode == INC_CODE_SIZE && codeSize < MAX_CODE_SIZE)
|
||||
{
|
||||
codeSize++;
|
||||
return ReadCode(stream, ref codeSize, codeTab, ref queue, out nextCode);
|
||||
}
|
||||
|
||||
if (controlCode == PARTIAL_CLEAR)
|
||||
{
|
||||
UnshrinkPartialClear(codeTab, ref queue);
|
||||
return ReadCode(stream, ref codeSize, codeTab, ref queue, out nextCode);
|
||||
}
|
||||
nextCode = code;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle control codes.
|
||||
controlCode = (ushort)stream.NextBits(codeSize);
|
||||
if (!stream.Advance(codeSize))
|
||||
{
|
||||
nextCode = INVALID_CODE;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void CopyFromPrevPos(byte[] dst, int prevPos, int dstPos, int len)
|
||||
if (controlCode == INC_CODE_SIZE && codeSize < MAX_CODE_SIZE)
|
||||
{
|
||||
if (dstPos + len > dst.Length)
|
||||
{
|
||||
// Not enough room in dst for the sloppy copy below.
|
||||
Array.Copy(dst, prevPos, dst, dstPos, len);
|
||||
return;
|
||||
}
|
||||
|
||||
if (prevPos + len > dstPos)
|
||||
{
|
||||
// Benign one-byte overlap possible in the KwKwK case.
|
||||
//assert(prevPos + len == dstPos + 1);
|
||||
//assert(dst[prevPos] == dst[prevPos + len - 1]);
|
||||
}
|
||||
|
||||
Buffer.BlockCopy(dst, prevPos, dst, dstPos, len);
|
||||
codeSize++;
|
||||
return ReadCode(stream, ref codeSize, codeTab, ref queue, out nextCode);
|
||||
}
|
||||
|
||||
private static UnshrnkStatus OutputCode(
|
||||
int code,
|
||||
byte[] dst,
|
||||
int dstPos,
|
||||
int dstCap,
|
||||
int prevCode,
|
||||
CodeTabEntry[] codeTab,
|
||||
ref CodeQueue queue,
|
||||
out byte firstByte,
|
||||
out int len
|
||||
)
|
||||
if (controlCode == PARTIAL_CLEAR)
|
||||
{
|
||||
int prefixCode;
|
||||
UnshrinkPartialClear(codeTab, ref queue);
|
||||
return ReadCode(stream, ref codeSize, codeTab, ref queue, out nextCode);
|
||||
}
|
||||
|
||||
//assert(code <= MAX_CODE && code != CONTROL_CODE);
|
||||
//assert(dstPos < dstCap);
|
||||
nextCode = INVALID_CODE;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void CopyFromPrevPos(byte[] dst, int prevPos, int dstPos, int len)
|
||||
{
|
||||
if (dstPos + len > dst.Length)
|
||||
{
|
||||
// Not enough room in dst for the sloppy copy below.
|
||||
Array.Copy(dst, prevPos, dst, dstPos, len);
|
||||
return;
|
||||
}
|
||||
|
||||
if (prevPos + len > dstPos)
|
||||
{
|
||||
// Benign one-byte overlap possible in the KwKwK case.
|
||||
//assert(prevPos + len == dstPos + 1);
|
||||
//assert(dst[prevPos] == dst[prevPos + len - 1]);
|
||||
}
|
||||
|
||||
Buffer.BlockCopy(dst, prevPos, dst, dstPos, len);
|
||||
}
|
||||
|
||||
private static UnshrnkStatus OutputCode(
|
||||
int code,
|
||||
byte[] dst,
|
||||
int dstPos,
|
||||
int dstCap,
|
||||
int prevCode,
|
||||
CodeTabEntry[] codeTab,
|
||||
ref CodeQueue queue,
|
||||
out byte firstByte,
|
||||
out int len
|
||||
)
|
||||
{
|
||||
int prefixCode;
|
||||
|
||||
//assert(code <= MAX_CODE && code != CONTROL_CODE);
|
||||
//assert(dstPos < dstCap);
|
||||
firstByte = 0;
|
||||
if (code <= byte.MaxValue)
|
||||
{
|
||||
// Output literal byte.
|
||||
firstByte = (byte)code;
|
||||
len = 1;
|
||||
dst[dstPos] = (byte)code;
|
||||
return UnshrnkStatus.Ok;
|
||||
}
|
||||
|
||||
if (codeTab[code].prefixCode == INVALID_CODE || codeTab[code].prefixCode == code)
|
||||
{
|
||||
// Reject invalid codes. Self-referential codes may exist in the table but cannot be used.
|
||||
firstByte = 0;
|
||||
if (code <= byte.MaxValue)
|
||||
{
|
||||
// Output literal byte.
|
||||
firstByte = (byte)code;
|
||||
len = 1;
|
||||
dst[dstPos] = (byte)code;
|
||||
return UnshrnkStatus.Ok;
|
||||
}
|
||||
len = 0;
|
||||
return UnshrnkStatus.Error;
|
||||
}
|
||||
|
||||
if (codeTab[code].prefixCode == INVALID_CODE || codeTab[code].prefixCode == code)
|
||||
{
|
||||
// Reject invalid codes. Self-referential codes may exist in the table but cannot be used.
|
||||
firstByte = 0;
|
||||
len = 0;
|
||||
return UnshrnkStatus.Error;
|
||||
}
|
||||
|
||||
if (codeTab[code].len != UNKNOWN_LEN)
|
||||
{
|
||||
// Output string with known length (the common case).
|
||||
if (dstCap - dstPos < codeTab[code].len)
|
||||
{
|
||||
firstByte = 0;
|
||||
len = 0;
|
||||
return UnshrnkStatus.Full;
|
||||
}
|
||||
|
||||
CopyFromPrevPos(dst, codeTab[code].lastDstPos, dstPos, codeTab[code].len);
|
||||
firstByte = dst[dstPos];
|
||||
len = codeTab[code].len;
|
||||
return UnshrnkStatus.Ok;
|
||||
}
|
||||
|
||||
// Output a string of unknown length.
|
||||
//assert(codeTab[code].len == UNKNOWN_LEN);
|
||||
prefixCode = codeTab[code].prefixCode;
|
||||
// assert(prefixCode > CONTROL_CODE);
|
||||
|
||||
if (prefixCode == queue.codes[queue.nextIdx])
|
||||
{
|
||||
// The prefix code hasn't been added yet, but we were just about to: the KwKwK case.
|
||||
//assert(codeTab[prevCode].prefixCode != INVALID_CODE);
|
||||
codeTab[prefixCode].prefixCode = prevCode;
|
||||
codeTab[prefixCode].extByte = firstByte;
|
||||
codeTab[prefixCode].len = (ushort)(codeTab[prevCode].len + 1);
|
||||
codeTab[prefixCode].lastDstPos = codeTab[prevCode].lastDstPos;
|
||||
dst[dstPos] = firstByte;
|
||||
}
|
||||
else if (codeTab[prefixCode].prefixCode == INVALID_CODE)
|
||||
{
|
||||
// The prefix code is still invalid.
|
||||
firstByte = 0;
|
||||
len = 0;
|
||||
return UnshrnkStatus.Error;
|
||||
}
|
||||
|
||||
// Output the prefix string, then the extension byte.
|
||||
len = codeTab[prefixCode].len + 1;
|
||||
if (dstCap - dstPos < len)
|
||||
if (codeTab[code].len != UNKNOWN_LEN)
|
||||
{
|
||||
// Output string with known length (the common case).
|
||||
if (dstCap - dstPos < codeTab[code].len)
|
||||
{
|
||||
firstByte = 0;
|
||||
len = 0;
|
||||
return UnshrnkStatus.Full;
|
||||
}
|
||||
|
||||
CopyFromPrevPos(dst, codeTab[prefixCode].lastDstPos, dstPos, codeTab[prefixCode].len);
|
||||
dst[dstPos + len - 1] = codeTab[code].extByte;
|
||||
CopyFromPrevPos(dst, codeTab[code].lastDstPos, dstPos, codeTab[code].len);
|
||||
firstByte = dst[dstPos];
|
||||
|
||||
// Update the code table now that the string has a length and pos.
|
||||
//assert(prevCode != code);
|
||||
codeTab[code].len = (ushort)len;
|
||||
codeTab[code].lastDstPos = dstPos;
|
||||
|
||||
len = codeTab[code].len;
|
||||
return UnshrnkStatus.Ok;
|
||||
}
|
||||
|
||||
public static UnshrnkStatus Unshrink(
|
||||
byte[] src,
|
||||
int srcLen,
|
||||
out int srcUsed,
|
||||
byte[] dst,
|
||||
int dstCap,
|
||||
out int dstUsed
|
||||
)
|
||||
// Output a string of unknown length.
|
||||
//assert(codeTab[code].len == UNKNOWN_LEN);
|
||||
prefixCode = codeTab[code].prefixCode;
|
||||
// assert(prefixCode > CONTROL_CODE);
|
||||
|
||||
if (prefixCode == queue.codes[queue.nextIdx])
|
||||
{
|
||||
var codeTab = new CodeTabEntry[HASHTAB_SIZE];
|
||||
var queue = new CodeQueue();
|
||||
var stream = new BitStream(src, srcLen);
|
||||
int codeSize,
|
||||
dstPos,
|
||||
len;
|
||||
int currCode,
|
||||
prevCode,
|
||||
newCode;
|
||||
byte firstByte;
|
||||
// The prefix code hasn't been added yet, but we were just about to: the KwKwK case.
|
||||
//assert(codeTab[prevCode].prefixCode != INVALID_CODE);
|
||||
codeTab[prefixCode].prefixCode = prevCode;
|
||||
codeTab[prefixCode].extByte = firstByte;
|
||||
codeTab[prefixCode].len = (ushort)(codeTab[prevCode].len + 1);
|
||||
codeTab[prefixCode].lastDstPos = codeTab[prevCode].lastDstPos;
|
||||
dst[dstPos] = firstByte;
|
||||
}
|
||||
else if (codeTab[prefixCode].prefixCode == INVALID_CODE)
|
||||
{
|
||||
// The prefix code is still invalid.
|
||||
firstByte = 0;
|
||||
len = 0;
|
||||
return UnshrnkStatus.Error;
|
||||
}
|
||||
|
||||
CodeTabInit(codeTab);
|
||||
CodeQueueInit(ref queue);
|
||||
codeSize = MIN_CODE_SIZE;
|
||||
dstPos = 0;
|
||||
// Output the prefix string, then the extension byte.
|
||||
len = codeTab[prefixCode].len + 1;
|
||||
if (dstCap - dstPos < len)
|
||||
{
|
||||
firstByte = 0;
|
||||
len = 0;
|
||||
return UnshrnkStatus.Full;
|
||||
}
|
||||
|
||||
// Handle the first code separately since there is no previous code.
|
||||
if (!ReadCode(stream, ref codeSize, codeTab, ref queue, out currCode))
|
||||
CopyFromPrevPos(dst, codeTab[prefixCode].lastDstPos, dstPos, codeTab[prefixCode].len);
|
||||
dst[dstPos + len - 1] = codeTab[code].extByte;
|
||||
firstByte = dst[dstPos];
|
||||
|
||||
// Update the code table now that the string has a length and pos.
|
||||
//assert(prevCode != code);
|
||||
codeTab[code].len = (ushort)len;
|
||||
codeTab[code].lastDstPos = dstPos;
|
||||
|
||||
return UnshrnkStatus.Ok;
|
||||
}
|
||||
|
||||
public static UnshrnkStatus Unshrink(
|
||||
byte[] src,
|
||||
int srcLen,
|
||||
out int srcUsed,
|
||||
byte[] dst,
|
||||
int dstCap,
|
||||
out int dstUsed
|
||||
)
|
||||
{
|
||||
var codeTab = new CodeTabEntry[HASHTAB_SIZE];
|
||||
var queue = new CodeQueue();
|
||||
var stream = new BitStream(src, srcLen);
|
||||
int codeSize,
|
||||
dstPos,
|
||||
len;
|
||||
int currCode,
|
||||
prevCode,
|
||||
newCode;
|
||||
byte firstByte;
|
||||
|
||||
CodeTabInit(codeTab);
|
||||
CodeQueueInit(ref queue);
|
||||
codeSize = MIN_CODE_SIZE;
|
||||
dstPos = 0;
|
||||
|
||||
// Handle the first code separately since there is no previous code.
|
||||
if (!ReadCode(stream, ref codeSize, codeTab, ref queue, out currCode))
|
||||
{
|
||||
srcUsed = stream.BytesRead;
|
||||
dstUsed = 0;
|
||||
return UnshrnkStatus.Ok;
|
||||
}
|
||||
|
||||
//assert(currCode != CONTROL_CODE);
|
||||
if (currCode > byte.MaxValue)
|
||||
{
|
||||
srcUsed = stream.BytesRead;
|
||||
dstUsed = 0;
|
||||
return UnshrnkStatus.Error; // The first code must be a literal.
|
||||
}
|
||||
|
||||
if (dstPos == dstCap)
|
||||
{
|
||||
srcUsed = stream.BytesRead;
|
||||
dstUsed = 0;
|
||||
return UnshrnkStatus.Full;
|
||||
}
|
||||
|
||||
firstByte = (byte)currCode;
|
||||
dst[dstPos] = (byte)currCode;
|
||||
codeTab[currCode].lastDstPos = dstPos;
|
||||
dstPos++;
|
||||
|
||||
prevCode = currCode;
|
||||
while (ReadCode(stream, ref codeSize, codeTab, ref queue, out currCode))
|
||||
{
|
||||
if (currCode == INVALID_CODE)
|
||||
{
|
||||
srcUsed = stream.BytesRead;
|
||||
dstUsed = 0;
|
||||
return UnshrnkStatus.Ok;
|
||||
}
|
||||
|
||||
//assert(currCode != CONTROL_CODE);
|
||||
if (currCode > byte.MaxValue)
|
||||
{
|
||||
srcUsed = stream.BytesRead;
|
||||
dstUsed = 0;
|
||||
return UnshrnkStatus.Error; // The first code must be a literal.
|
||||
return UnshrnkStatus.Error;
|
||||
}
|
||||
|
||||
if (dstPos == dstCap)
|
||||
@@ -279,153 +301,130 @@ namespace SharpCompress.Compressors.Shrink
|
||||
return UnshrnkStatus.Full;
|
||||
}
|
||||
|
||||
firstByte = (byte)currCode;
|
||||
dst[dstPos] = (byte)currCode;
|
||||
codeTab[currCode].lastDstPos = dstPos;
|
||||
dstPos++;
|
||||
|
||||
prevCode = currCode;
|
||||
while (ReadCode(stream, ref codeSize, codeTab, ref queue, out currCode))
|
||||
// Handle KwKwK: next code used before being added.
|
||||
if (currCode == queue.codes[queue.nextIdx])
|
||||
{
|
||||
if (currCode == INVALID_CODE)
|
||||
if (codeTab[prevCode].prefixCode == INVALID_CODE)
|
||||
{
|
||||
// The previous code is no longer valid.
|
||||
srcUsed = stream.BytesRead;
|
||||
dstUsed = 0;
|
||||
return UnshrnkStatus.Error;
|
||||
}
|
||||
|
||||
if (dstPos == dstCap)
|
||||
{
|
||||
srcUsed = stream.BytesRead;
|
||||
dstUsed = 0;
|
||||
return UnshrnkStatus.Full;
|
||||
}
|
||||
|
||||
// Handle KwKwK: next code used before being added.
|
||||
if (currCode == queue.codes[queue.nextIdx])
|
||||
{
|
||||
if (codeTab[prevCode].prefixCode == INVALID_CODE)
|
||||
{
|
||||
// The previous code is no longer valid.
|
||||
srcUsed = stream.BytesRead;
|
||||
dstUsed = 0;
|
||||
return UnshrnkStatus.Error;
|
||||
}
|
||||
|
||||
// Extend the previous code with its first byte.
|
||||
//assert(currCode != prevCode);
|
||||
codeTab[currCode].prefixCode = prevCode;
|
||||
codeTab[currCode].extByte = firstByte;
|
||||
codeTab[currCode].len = (ushort)(codeTab[prevCode].len + 1);
|
||||
codeTab[currCode].lastDstPos = codeTab[prevCode].lastDstPos;
|
||||
//assert(dstPos < dstCap);
|
||||
dst[dstPos] = firstByte;
|
||||
}
|
||||
|
||||
// Output the string represented by the current code.
|
||||
var status = OutputCode(
|
||||
currCode,
|
||||
dst,
|
||||
dstPos,
|
||||
dstCap,
|
||||
prevCode,
|
||||
codeTab,
|
||||
ref queue,
|
||||
out firstByte,
|
||||
out len
|
||||
);
|
||||
if (status != UnshrnkStatus.Ok)
|
||||
{
|
||||
srcUsed = stream.BytesRead;
|
||||
dstUsed = 0;
|
||||
return status;
|
||||
}
|
||||
|
||||
// Verify that the output matches walking the prefixes.
|
||||
var c = currCode;
|
||||
for (var i = 0; i < len; i++)
|
||||
{
|
||||
// assert(codeTab[c].len == len - i);
|
||||
//assert(codeTab[c].extByte == dst[dstPos + len - i - 1]);
|
||||
c = codeTab[c].prefixCode;
|
||||
}
|
||||
|
||||
// Add a new code to the string table if there's room.
|
||||
// The string is the previous code's string extended with the first byte of the current code's string.
|
||||
newCode = CodeQueueRemoveNext(ref queue);
|
||||
if (newCode != INVALID_CODE)
|
||||
{
|
||||
//assert(codeTab[prevCode].lastDstPos < dstPos);
|
||||
codeTab[newCode].prefixCode = prevCode;
|
||||
codeTab[newCode].extByte = firstByte;
|
||||
codeTab[newCode].len = (ushort)(codeTab[prevCode].len + 1);
|
||||
codeTab[newCode].lastDstPos = codeTab[prevCode].lastDstPos;
|
||||
|
||||
if (codeTab[prevCode].prefixCode == INVALID_CODE)
|
||||
{
|
||||
// prevCode was invalidated in a partial clearing. Until that code is re-used, the
|
||||
// string represented by newCode is indeterminate.
|
||||
codeTab[newCode].len = UNKNOWN_LEN;
|
||||
}
|
||||
// If prevCode was invalidated in a partial clearing, it's possible that newCode == prevCode,
|
||||
// in which case it will never be used or cleared.
|
||||
}
|
||||
|
||||
codeTab[currCode].lastDstPos = dstPos;
|
||||
dstPos += len;
|
||||
|
||||
prevCode = currCode;
|
||||
// Extend the previous code with its first byte.
|
||||
//assert(currCode != prevCode);
|
||||
codeTab[currCode].prefixCode = prevCode;
|
||||
codeTab[currCode].extByte = firstByte;
|
||||
codeTab[currCode].len = (ushort)(codeTab[prevCode].len + 1);
|
||||
codeTab[currCode].lastDstPos = codeTab[prevCode].lastDstPos;
|
||||
//assert(dstPos < dstCap);
|
||||
dst[dstPos] = firstByte;
|
||||
}
|
||||
|
||||
srcUsed = stream.BytesRead;
|
||||
dstUsed = dstPos;
|
||||
|
||||
return UnshrnkStatus.Ok;
|
||||
}
|
||||
|
||||
public enum UnshrnkStatus
|
||||
{
|
||||
Ok,
|
||||
Full,
|
||||
Error,
|
||||
}
|
||||
|
||||
private struct CodeQueue
|
||||
{
|
||||
public int nextIdx;
|
||||
public ushort[] codes;
|
||||
}
|
||||
|
||||
private static void CodeQueueInit(ref CodeQueue q)
|
||||
{
|
||||
int codeQueueSize;
|
||||
ushort code;
|
||||
|
||||
codeQueueSize = 0;
|
||||
q.codes = new ushort[MAX_CODE - CONTROL_CODE + 2];
|
||||
|
||||
for (code = CONTROL_CODE + 1; code <= MAX_CODE; code++)
|
||||
// Output the string represented by the current code.
|
||||
var status = OutputCode(
|
||||
currCode,
|
||||
dst,
|
||||
dstPos,
|
||||
dstCap,
|
||||
prevCode,
|
||||
codeTab,
|
||||
ref queue,
|
||||
out firstByte,
|
||||
out len
|
||||
);
|
||||
if (status != UnshrnkStatus.Ok)
|
||||
{
|
||||
q.codes[codeQueueSize++] = code;
|
||||
srcUsed = stream.BytesRead;
|
||||
dstUsed = 0;
|
||||
return status;
|
||||
}
|
||||
|
||||
//assert(codeQueueSize < q.codes.Length);
|
||||
q.codes[codeQueueSize] = INVALID_CODE; // End-of-queue marker.
|
||||
q.nextIdx = 0;
|
||||
}
|
||||
|
||||
private static ushort CodeQueueNext(ref CodeQueue q) =>
|
||||
//assert(q.nextIdx < q.codes.Length);
|
||||
q.codes[q.nextIdx];
|
||||
|
||||
private static ushort CodeQueueRemoveNext(ref CodeQueue q)
|
||||
{
|
||||
var code = CodeQueueNext(ref q);
|
||||
if (code != INVALID_CODE)
|
||||
// Verify that the output matches walking the prefixes.
|
||||
var c = currCode;
|
||||
for (var i = 0; i < len; i++)
|
||||
{
|
||||
q.nextIdx++;
|
||||
// assert(codeTab[c].len == len - i);
|
||||
//assert(codeTab[c].extByte == dst[dstPos + len - i - 1]);
|
||||
c = codeTab[c].prefixCode;
|
||||
}
|
||||
return code;
|
||||
|
||||
// Add a new code to the string table if there's room.
|
||||
// The string is the previous code's string extended with the first byte of the current code's string.
|
||||
newCode = CodeQueueRemoveNext(ref queue);
|
||||
if (newCode != INVALID_CODE)
|
||||
{
|
||||
//assert(codeTab[prevCode].lastDstPos < dstPos);
|
||||
codeTab[newCode].prefixCode = prevCode;
|
||||
codeTab[newCode].extByte = firstByte;
|
||||
codeTab[newCode].len = (ushort)(codeTab[prevCode].len + 1);
|
||||
codeTab[newCode].lastDstPos = codeTab[prevCode].lastDstPos;
|
||||
|
||||
if (codeTab[prevCode].prefixCode == INVALID_CODE)
|
||||
{
|
||||
// prevCode was invalidated in a partial clearing. Until that code is re-used, the
|
||||
// string represented by newCode is indeterminate.
|
||||
codeTab[newCode].len = UNKNOWN_LEN;
|
||||
}
|
||||
// If prevCode was invalidated in a partial clearing, it's possible that newCode == prevCode,
|
||||
// in which case it will never be used or cleared.
|
||||
}
|
||||
|
||||
codeTab[currCode].lastDstPos = dstPos;
|
||||
dstPos += len;
|
||||
|
||||
prevCode = currCode;
|
||||
}
|
||||
|
||||
srcUsed = stream.BytesRead;
|
||||
dstUsed = dstPos;
|
||||
|
||||
return UnshrnkStatus.Ok;
|
||||
}
|
||||
}
|
||||
|
||||
public enum UnshrnkStatus
|
||||
{
|
||||
Ok,
|
||||
Full,
|
||||
Error,
|
||||
}
|
||||
|
||||
private struct CodeQueue
|
||||
{
|
||||
public int nextIdx;
|
||||
public ushort[] codes;
|
||||
}
|
||||
|
||||
private static void CodeQueueInit(ref CodeQueue q)
|
||||
{
|
||||
int codeQueueSize;
|
||||
ushort code;
|
||||
|
||||
codeQueueSize = 0;
|
||||
q.codes = new ushort[MAX_CODE - CONTROL_CODE + 2];
|
||||
|
||||
for (code = CONTROL_CODE + 1; code <= MAX_CODE; code++)
|
||||
{
|
||||
q.codes[codeQueueSize++] = code;
|
||||
}
|
||||
|
||||
//assert(codeQueueSize < q.codes.Length);
|
||||
q.codes[codeQueueSize] = INVALID_CODE; // End-of-queue marker.
|
||||
q.nextIdx = 0;
|
||||
}
|
||||
|
||||
private static ushort CodeQueueNext(ref CodeQueue q) =>
|
||||
//assert(q.nextIdx < q.codes.Length);
|
||||
q.codes[q.nextIdx];
|
||||
|
||||
private static ushort CodeQueueRemoveNext(ref CodeQueue q)
|
||||
{
|
||||
var code = CodeQueueNext(ref q);
|
||||
if (code != INVALID_CODE)
|
||||
{
|
||||
q.nextIdx++;
|
||||
}
|
||||
return code;
|
||||
}
|
||||
}
|
||||
@@ -7,139 +7,138 @@ using System.Threading.Tasks;
|
||||
using SharpCompress.Compressors.RLE90;
|
||||
using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Compressors.Squeezed
|
||||
namespace SharpCompress.Compressors.Squeezed;
|
||||
|
||||
public class SqueezeStream : Stream, IStreamStack
|
||||
{
|
||||
public class SqueezeStream : Stream, IStreamStack
|
||||
{
|
||||
#if DEBUG_STREAMS
|
||||
long IStreamStack.InstanceId { get; set; }
|
||||
#endif
|
||||
int IStreamStack.DefaultBufferSize { get; set; }
|
||||
int IStreamStack.DefaultBufferSize { get; set; }
|
||||
|
||||
Stream IStreamStack.BaseStream() => _stream;
|
||||
Stream IStreamStack.BaseStream() => _stream;
|
||||
|
||||
int IStreamStack.BufferSize
|
||||
{
|
||||
get => 0;
|
||||
set { }
|
||||
}
|
||||
int IStreamStack.BufferPosition
|
||||
{
|
||||
get => 0;
|
||||
set { }
|
||||
}
|
||||
int IStreamStack.BufferSize
|
||||
{
|
||||
get => 0;
|
||||
set { }
|
||||
}
|
||||
int IStreamStack.BufferPosition
|
||||
{
|
||||
get => 0;
|
||||
set { }
|
||||
}
|
||||
|
||||
void IStreamStack.SetPosition(long position) { }
|
||||
void IStreamStack.SetPosition(long position) { }
|
||||
|
||||
private readonly Stream _stream;
|
||||
private readonly int _compressedSize;
|
||||
private const int NUMVALS = 257;
|
||||
private const int SPEOF = 256;
|
||||
private bool _processed = false;
|
||||
private readonly Stream _stream;
|
||||
private readonly int _compressedSize;
|
||||
private const int NUMVALS = 257;
|
||||
private const int SPEOF = 256;
|
||||
private bool _processed = false;
|
||||
|
||||
public SqueezeStream(Stream stream, int compressedSize)
|
||||
{
|
||||
_stream = stream;
|
||||
_compressedSize = compressedSize;
|
||||
public SqueezeStream(Stream stream, int compressedSize)
|
||||
{
|
||||
_stream = stream;
|
||||
_compressedSize = compressedSize;
|
||||
#if DEBUG_STREAMS
|
||||
this.DebugConstruct(typeof(SqueezeStream));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
#if DEBUG_STREAMS
|
||||
this.DebugDispose(typeof(SqueezeStream));
|
||||
#endif
|
||||
base.Dispose(disposing);
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
public override bool CanRead => true;
|
||||
|
||||
public override bool CanSeek => false;
|
||||
|
||||
public override bool CanWrite => false;
|
||||
|
||||
public override long Length => throw new NotImplementedException();
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get => _stream.Position;
|
||||
set => throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void Flush() => throw new NotImplementedException();
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (_processed)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
_processed = true;
|
||||
using var binaryReader = new BinaryReader(_stream);
|
||||
|
||||
// Read numnodes (equivalent to convert_u16!(numnodes, buf))
|
||||
var numnodes = binaryReader.ReadUInt16();
|
||||
|
||||
// Validation: numnodes should be within bounds
|
||||
if (numnodes >= NUMVALS)
|
||||
{
|
||||
throw new InvalidDataException(
|
||||
$"Invalid number of nodes {numnodes} (max {NUMVALS - 1})"
|
||||
);
|
||||
}
|
||||
|
||||
public override bool CanRead => true;
|
||||
|
||||
public override bool CanSeek => false;
|
||||
|
||||
public override bool CanWrite => false;
|
||||
|
||||
public override long Length => throw new NotImplementedException();
|
||||
|
||||
public override long Position
|
||||
// Handle the case where no nodes exist
|
||||
if (numnodes == 0)
|
||||
{
|
||||
get => _stream.Position;
|
||||
set => throw new NotImplementedException();
|
||||
return 0;
|
||||
}
|
||||
|
||||
public override void Flush() => throw new NotImplementedException();
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
// Build dnode (tree of nodes)
|
||||
var dnode = new int[numnodes, 2];
|
||||
for (int j = 0; j < numnodes; j++)
|
||||
{
|
||||
if (_processed)
|
||||
dnode[j, 0] = binaryReader.ReadInt16();
|
||||
dnode[j, 1] = binaryReader.ReadInt16();
|
||||
}
|
||||
|
||||
// Initialize BitReader for reading bits
|
||||
var bitReader = new BitReader(_stream);
|
||||
var decoded = new List<byte>();
|
||||
|
||||
int i = 0;
|
||||
// Decode the buffer using the dnode tree
|
||||
while (true)
|
||||
{
|
||||
i = dnode[i, bitReader.ReadBit() ? 1 : 0];
|
||||
if (i < 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
_processed = true;
|
||||
using var binaryReader = new BinaryReader(_stream);
|
||||
|
||||
// Read numnodes (equivalent to convert_u16!(numnodes, buf))
|
||||
var numnodes = binaryReader.ReadUInt16();
|
||||
|
||||
// Validation: numnodes should be within bounds
|
||||
if (numnodes >= NUMVALS)
|
||||
{
|
||||
throw new InvalidDataException(
|
||||
$"Invalid number of nodes {numnodes} (max {NUMVALS - 1})"
|
||||
);
|
||||
}
|
||||
|
||||
// Handle the case where no nodes exist
|
||||
if (numnodes == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Build dnode (tree of nodes)
|
||||
var dnode = new int[numnodes, 2];
|
||||
for (int j = 0; j < numnodes; j++)
|
||||
{
|
||||
dnode[j, 0] = binaryReader.ReadInt16();
|
||||
dnode[j, 1] = binaryReader.ReadInt16();
|
||||
}
|
||||
|
||||
// Initialize BitReader for reading bits
|
||||
var bitReader = new BitReader(_stream);
|
||||
var decoded = new List<byte>();
|
||||
|
||||
int i = 0;
|
||||
// Decode the buffer using the dnode tree
|
||||
while (true)
|
||||
{
|
||||
i = dnode[i, bitReader.ReadBit() ? 1 : 0];
|
||||
if (i < 0)
|
||||
i = (short)-(i + 1);
|
||||
if (i == SPEOF)
|
||||
{
|
||||
i = (short)-(i + 1);
|
||||
if (i == SPEOF)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
decoded.Add((byte)i);
|
||||
i = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
decoded.Add((byte)i);
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Unpack the decoded buffer using the RLE class
|
||||
var unpacked = RLE.UnpackRLE(decoded.ToArray());
|
||||
unpacked.CopyTo(buffer, 0);
|
||||
return unpacked.Count();
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin) =>
|
||||
throw new NotImplementedException();
|
||||
|
||||
public override void SetLength(long value) => throw new NotImplementedException();
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count) =>
|
||||
throw new NotImplementedException();
|
||||
// Unpack the decoded buffer using the RLE class
|
||||
var unpacked = RLE.UnpackRLE(decoded.ToArray());
|
||||
unpacked.CopyTo(buffer, 0);
|
||||
return unpacked.Count();
|
||||
}
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin) =>
|
||||
throw new NotImplementedException();
|
||||
|
||||
public override void SetLength(long value) => throw new NotImplementedException();
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count) =>
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
@@ -10,37 +10,36 @@ using SharpCompress.Readers;
|
||||
using SharpCompress.Readers.Arc;
|
||||
using static System.Net.Mime.MediaTypeNames;
|
||||
|
||||
namespace SharpCompress.Factories
|
||||
namespace SharpCompress.Factories;
|
||||
|
||||
public class ArcFactory : Factory, IReaderFactory
|
||||
{
|
||||
public class ArcFactory : Factory, IReaderFactory
|
||||
public override string Name => "Arc";
|
||||
|
||||
public override ArchiveType? KnownArchiveType => ArchiveType.Arc;
|
||||
|
||||
public override IEnumerable<string> GetSupportedExtensions()
|
||||
{
|
||||
public override string Name => "Arc";
|
||||
|
||||
public override ArchiveType? KnownArchiveType => ArchiveType.Arc;
|
||||
|
||||
public override IEnumerable<string> GetSupportedExtensions()
|
||||
{
|
||||
yield return "arc";
|
||||
}
|
||||
|
||||
public override bool IsArchive(
|
||||
Stream stream,
|
||||
string? password = null,
|
||||
int bufferSize = ReaderOptions.DefaultBufferSize
|
||||
)
|
||||
{
|
||||
//You may have to use some(paranoid) checks to ensure that you actually are
|
||||
//processing an ARC file, since other archivers also adopted the idea of putting
|
||||
//a 01Ah byte at offset 0, namely the Hyper archiver. To check if you have a
|
||||
//Hyper - archive, check the next two bytes for "HP" or "ST"(or look below for
|
||||
//"HYP").Also the ZOO archiver also does put a 01Ah at the start of the file,
|
||||
//see the ZOO entry below.
|
||||
var bytes = new byte[2];
|
||||
stream.Read(bytes, 0, 2);
|
||||
return bytes[0] == 0x1A && bytes[1] < 10; //rather thin, but this is all we have
|
||||
}
|
||||
|
||||
public IReader OpenReader(Stream stream, ReaderOptions? options) =>
|
||||
ArcReader.Open(stream, options);
|
||||
yield return "arc";
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsArchive(
|
||||
Stream stream,
|
||||
string? password = null,
|
||||
int bufferSize = ReaderOptions.DefaultBufferSize
|
||||
)
|
||||
{
|
||||
//You may have to use some(paranoid) checks to ensure that you actually are
|
||||
//processing an ARC file, since other archivers also adopted the idea of putting
|
||||
//a 01Ah byte at offset 0, namely the Hyper archiver. To check if you have a
|
||||
//Hyper - archive, check the next two bytes for "HP" or "ST"(or look below for
|
||||
//"HYP").Also the ZOO archiver also does put a 01Ah at the start of the file,
|
||||
//see the ZOO entry below.
|
||||
var bytes = new byte[2];
|
||||
stream.Read(bytes, 0, 2);
|
||||
return bytes[0] == 0x1A && bytes[1] < 10; //rather thin, but this is all we have
|
||||
}
|
||||
|
||||
public IReader OpenReader(Stream stream, ReaderOptions? options) =>
|
||||
ArcReader.Open(stream, options);
|
||||
}
|
||||
@@ -1,45 +1,39 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace SharpCompress.IO
|
||||
namespace SharpCompress.IO;
|
||||
|
||||
public interface IStreamStack
|
||||
{
|
||||
public interface IStreamStack
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the default buffer size to be applied when buffering is enabled for this stream stack.
|
||||
/// This value is used by the SetBuffer extension method to configure buffering on the appropriate stream
|
||||
/// in the stack hierarchy. A value of 0 indicates no default buffer size is set.
|
||||
/// </summary>
|
||||
int DefaultBufferSize { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the default buffer size to be applied when buffering is enabled for this stream stack.
|
||||
/// This value is used by the SetBuffer extension method to configure buffering on the appropriate stream
|
||||
/// in the stack hierarchy. A value of 0 indicates no default buffer size is set.
|
||||
/// </summary>
|
||||
int DefaultBufferSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the immediate underlying stream in the stack.
|
||||
/// </summary>
|
||||
Stream BaseStream();
|
||||
/// <summary>
|
||||
/// Returns the immediate underlying stream in the stack.
|
||||
/// </summary>
|
||||
Stream BaseStream();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the size of the buffer if the stream supports buffering; otherwise, returns 0.
|
||||
/// This property must not throw.
|
||||
/// </summary>
|
||||
int BufferSize { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the size of the buffer if the stream supports buffering; otherwise, returns 0.
|
||||
/// This property must not throw.
|
||||
/// </summary>
|
||||
int BufferSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current position within the buffer if the stream supports buffering; otherwise, returns 0.
|
||||
/// This property must not throw.
|
||||
/// </summary>
|
||||
int BufferPosition { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the current position within the buffer if the stream supports buffering; otherwise, returns 0.
|
||||
/// This property must not throw.
|
||||
/// </summary>
|
||||
int BufferPosition { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Updates the internal position state of the stream. This should not perform seeking on the underlying stream,
|
||||
/// but should update any internal position or buffer state as appropriate for the stream implementation.
|
||||
/// </summary>
|
||||
/// <param name="position">The absolute position to set within the stream stack.</param>
|
||||
void SetPosition(long position);
|
||||
/// <summary>
|
||||
/// Updates the internal position state of the stream. This should not perform seeking on the underlying stream,
|
||||
/// but should update any internal position or buffer state as appropriate for the stream implementation.
|
||||
/// </summary>
|
||||
/// <param name="position">The absolute position to set within the stream stack.</param>
|
||||
void SetPosition(long position);
|
||||
|
||||
#if DEBUG_STREAMS
|
||||
/// <summary>
|
||||
@@ -47,318 +41,4 @@ namespace SharpCompress.IO
|
||||
/// </summary>
|
||||
long InstanceId { get; set; }
|
||||
#endif
|
||||
}
|
||||
|
||||
internal static class StackStreamExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the logical position of the first buffering stream in the stack, or 0 if none exist.
|
||||
/// </summary>
|
||||
/// <param name="stream">The most derived (outermost) stream in the stack.</param>
|
||||
/// <returns>The position of the first buffering stream, or 0 if not found.</returns>
|
||||
internal static long GetPosition(this IStreamStack stream)
|
||||
{
|
||||
IStreamStack? current = stream;
|
||||
|
||||
while (current != null)
|
||||
{
|
||||
if (current.BufferSize != 0 && current is Stream st)
|
||||
{
|
||||
return st.Position;
|
||||
}
|
||||
current = current?.BaseStream() as IStreamStack;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rewinds the buffer of the outermost buffering stream in the stack by the specified count, if supported.
|
||||
/// Only the most derived buffering stream is affected.
|
||||
/// </summary>
|
||||
/// <param name="stream">The most derived (outermost) stream in the stack.</param>
|
||||
/// <param name="count">The number of bytes to rewind within the buffer.</param>
|
||||
internal static void Rewind(this IStreamStack stream, int count)
|
||||
{
|
||||
Stream baseStream = stream.BaseStream();
|
||||
Stream thisStream = (Stream)stream;
|
||||
IStreamStack? buffStream = null;
|
||||
IStreamStack? current = stream;
|
||||
|
||||
while (buffStream == null && current != null)
|
||||
{
|
||||
if (current.BufferSize != 0)
|
||||
{
|
||||
buffStream = current;
|
||||
buffStream.BufferPosition -= Math.Min(buffStream.BufferPosition, count);
|
||||
}
|
||||
current = current?.BaseStream() as IStreamStack;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the buffer size on the first buffering stream in the stack, or on the outermost stream if none exist.
|
||||
/// If <paramref name="force"/> is true, sets the buffer size regardless of current value.
|
||||
/// </summary>
|
||||
/// <param name="stream">The most derived (outermost) stream in the stack.</param>
|
||||
/// <param name="bufferSize">The buffer size to set.</param>
|
||||
/// <param name="force">If true, forces the buffer size to be set even if already set.</param>
|
||||
internal static void SetBuffer(this IStreamStack stream, int bufferSize, bool force)
|
||||
{
|
||||
if (bufferSize == 0 || stream == null)
|
||||
return;
|
||||
|
||||
IStreamStack? current = stream;
|
||||
IStreamStack defaultBuffer = stream;
|
||||
IStreamStack? buffer = null;
|
||||
|
||||
// First pass: find the deepest IStreamStack
|
||||
while (current != null)
|
||||
{
|
||||
defaultBuffer = current;
|
||||
if (buffer == null && ((current.BufferSize != 0 && bufferSize != 0) || force))
|
||||
buffer = current;
|
||||
if (defaultBuffer.DefaultBufferSize != 0)
|
||||
break;
|
||||
current = current.BaseStream() as IStreamStack;
|
||||
}
|
||||
if (defaultBuffer.DefaultBufferSize == 0)
|
||||
defaultBuffer.DefaultBufferSize = bufferSize;
|
||||
(buffer ?? stream).BufferSize = bufferSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to set the position in the stream stack. If a buffering stream is present and the position is within its buffer,
|
||||
/// BufferPosition is set on the outermost buffering stream and all intermediate streams update their internal state via SetPosition.
|
||||
/// If no buffering stream is present, seeks as close to the root stream as possible and updates all intermediate streams' state via SetPosition.
|
||||
/// Seeking is never performed if any intermediate stream in the stack is buffering.
|
||||
/// Throws if the position cannot be set.
|
||||
/// </summary>
|
||||
/// <param name="stream">
|
||||
/// The most derived (outermost) stream in the stack. The method traverses up the stack via BaseStream() until a stream can satisfy the buffer or seek request.
|
||||
/// </param>
|
||||
/// <param name="position">The absolute position to set.</param>
|
||||
/// <returns>The position that was set.</returns>
|
||||
internal static long StackSeek(this IStreamStack stream, long position)
|
||||
{
|
||||
var stack = new List<IStreamStack>();
|
||||
Stream? current = stream as Stream;
|
||||
int lastBufferingIndex = -1;
|
||||
int firstSeekableIndex = -1;
|
||||
Stream? firstSeekableStream = null;
|
||||
|
||||
// Traverse the stack, collecting info
|
||||
while (current is IStreamStack stackStream)
|
||||
{
|
||||
stack.Add(stackStream);
|
||||
if (stackStream.BufferSize > 0)
|
||||
{
|
||||
lastBufferingIndex = stack.Count - 1;
|
||||
break;
|
||||
}
|
||||
current = stackStream.BaseStream();
|
||||
}
|
||||
|
||||
// Find the first seekable stream (closest to the root)
|
||||
if (current != null && current.CanSeek)
|
||||
{
|
||||
firstSeekableIndex = stack.Count;
|
||||
firstSeekableStream = current;
|
||||
}
|
||||
|
||||
// If any buffering stream exists, try to set BufferPosition on the outermost one
|
||||
if (lastBufferingIndex != -1)
|
||||
{
|
||||
var bufferingStream = stack[lastBufferingIndex];
|
||||
if (position >= 0 && position < bufferingStream.BufferSize)
|
||||
{
|
||||
bufferingStream.BufferPosition = (int)position;
|
||||
return position;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If position is not in buffer, reset buffer and proceed as non-buffering
|
||||
bufferingStream.BufferPosition = 0;
|
||||
}
|
||||
// Continue to seek as if no buffer is present
|
||||
}
|
||||
|
||||
// If no buffering, or buffer was reset, seek at the first seekable stream (closest to the root)
|
||||
if (firstSeekableStream != null)
|
||||
{
|
||||
firstSeekableStream.Seek(position, SeekOrigin.Begin);
|
||||
return firstSeekableStream.Position;
|
||||
}
|
||||
|
||||
throw new NotSupportedException(
|
||||
"Cannot set position on this stream stack (no seekable or buffering stream supports the requested position)."
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads bytes from the stream, using the position to observe how much was actually consumed and rewind the buffer to ensure further reads are correct.
|
||||
/// This is required to prevent buffered reads from skipping data, while also benefiting from buffering and reduced stream IO reads.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to read from.</param>
|
||||
/// <param name="buffer">The buffer to read data into.</param>
|
||||
/// <param name="offset">The offset in the buffer to start writing data.</param>
|
||||
/// <param name="count">The maximum number of bytes to read.</param>
|
||||
/// <param name="buffStream">Returns the buffering stream found in the stack, or null if none exists.</param>
|
||||
/// <param name="baseReadCount">Returns the number of bytes actually read from the base stream, or -1 if no buffering stream was found.</param>
|
||||
/// <returns>The number of bytes read into the buffer.</returns>
|
||||
internal static int Read(
|
||||
this IStreamStack stream,
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
int count,
|
||||
out IStreamStack? buffStream,
|
||||
out int baseReadCount
|
||||
)
|
||||
{
|
||||
Stream baseStream = stream.BaseStream();
|
||||
Stream thisStream = (Stream)stream;
|
||||
IStreamStack? current = stream;
|
||||
buffStream = null;
|
||||
baseReadCount = -1;
|
||||
|
||||
while (buffStream == null && (current = current?.BaseStream() as IStreamStack) != null)
|
||||
{
|
||||
if (current.BufferSize != 0)
|
||||
{
|
||||
buffStream = current;
|
||||
}
|
||||
}
|
||||
|
||||
long buffPos = buffStream == null ? -1 : ((Stream)buffStream).Position;
|
||||
|
||||
int read = baseStream.Read(buffer, offset, count); //amount read in to buffer
|
||||
|
||||
if (buffPos != -1)
|
||||
{
|
||||
baseReadCount = (int)(((Stream)buffStream!).Position - buffPos);
|
||||
}
|
||||
return read;
|
||||
}
|
||||
|
||||
#if DEBUG_STREAMS
|
||||
private static long _instanceCounter = 0;
|
||||
|
||||
private static string cleansePos(long pos)
|
||||
{
|
||||
if (pos < 0)
|
||||
return "";
|
||||
return "Px" + pos.ToString("x");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or creates a unique instance ID for the stream stack for debugging purposes.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream stack.</param>
|
||||
/// <param name="instanceId">Reference to the instance ID field.</param>
|
||||
/// <param name="construct">Whether this is being called during construction.</param>
|
||||
/// <returns>The instance ID.</returns>
|
||||
public static long GetInstanceId(
|
||||
this IStreamStack stream,
|
||||
ref long instanceId,
|
||||
bool construct
|
||||
)
|
||||
{
|
||||
if (instanceId == 0) //will not be equal to 0 when inherited IStackStream types are being used
|
||||
instanceId = System.Threading.Interlocked.Increment(ref _instanceCounter);
|
||||
return instanceId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a debug message for stream construction.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream stack.</param>
|
||||
/// <param name="constructing">The type being constructed.</param>
|
||||
public static void DebugConstruct(this IStreamStack stream, Type constructing)
|
||||
{
|
||||
long id = stream.InstanceId;
|
||||
stream.InstanceId = GetInstanceId(stream, ref id, true);
|
||||
var frame = (new StackTrace()).GetFrame(3);
|
||||
string parentInfo =
|
||||
frame != null
|
||||
? $"{frame.GetMethod()?.DeclaringType?.Name}.{frame.GetMethod()?.Name}()"
|
||||
: "Unknown";
|
||||
if (constructing.FullName == stream.GetType().FullName) //don't debug base IStackStream types
|
||||
Debug.WriteLine(
|
||||
$"{GetStreamStackString(stream, true)} : Constructed by [{parentInfo}]"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a debug message for stream disposal.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream stack.</param>
|
||||
/// <param name="constructing">The type being disposed.</param>
|
||||
public static void DebugDispose(this IStreamStack stream, Type constructing)
|
||||
{
|
||||
var frame = (new StackTrace()).GetFrame(3);
|
||||
string parentInfo =
|
||||
frame != null
|
||||
? $"{frame.GetMethod()?.DeclaringType?.Name}.{frame.GetMethod()?.Name}()"
|
||||
: "Unknown";
|
||||
if (constructing.FullName == stream.GetType().FullName) //don't debug base IStackStream types
|
||||
Debug.WriteLine($"{GetStreamStackString(stream, false)} : Disposed by [{parentInfo}]");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a debug trace message for the stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream stack.</param>
|
||||
/// <param name="message">The debug message to write.</param>
|
||||
public static void DebugTrace(this IStreamStack stream, string message)
|
||||
{
|
||||
Debug.WriteLine(
|
||||
$"{GetStreamStackString(stream, false)} : [{stream.GetType().Name}]{message}"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the full stream chain as a string, including instance IDs and positions.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream stack to represent.</param>
|
||||
/// <param name="construct">Whether this is being called during construction.</param>
|
||||
/// <returns>A string representation of the entire stream stack.</returns>
|
||||
public static string GetStreamStackString(this IStreamStack stream, bool construct)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
Stream? current = stream as Stream;
|
||||
while (current != null)
|
||||
{
|
||||
IStreamStack? sStack = current as IStreamStack;
|
||||
string id = sStack != null ? "#" + sStack.InstanceId.ToString() : "";
|
||||
string buffSize = sStack != null ? "Bx" + sStack.BufferSize.ToString("x") : "";
|
||||
string defBuffSize =
|
||||
sStack != null ? "Dx" + sStack.DefaultBufferSize.ToString("x") : "";
|
||||
|
||||
if (sb.Length > 0)
|
||||
sb.Insert(0, "/");
|
||||
try
|
||||
{
|
||||
sb.Insert(
|
||||
0,
|
||||
$"{current.GetType().Name}{id}[{cleansePos(current.Position)}:{buffSize}:{defBuffSize}]"
|
||||
);
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (current is SharpCompressStream scs)
|
||||
sb.Insert(
|
||||
0,
|
||||
$"{current.GetType().Name}{id}[{cleansePos(scs.InternalPosition)}:{buffSize}:{defBuffSize}]"
|
||||
);
|
||||
else
|
||||
sb.Insert(0, $"{current.GetType().Name}{id}[:{buffSize}]");
|
||||
}
|
||||
if (sStack != null)
|
||||
current = sStack.BaseStream(); //current may not be a IStreamStack, allow one more loop
|
||||
else
|
||||
break;
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
320
src/SharpCompress/IO/StackStreamExtensions.cs
Normal file
320
src/SharpCompress/IO/StackStreamExtensions.cs
Normal file
@@ -0,0 +1,320 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace SharpCompress.IO;
|
||||
|
||||
internal static class StackStreamExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the logical position of the first buffering stream in the stack, or 0 if none exist.
|
||||
/// </summary>
|
||||
/// <param name="stream">The most derived (outermost) stream in the stack.</param>
|
||||
/// <returns>The position of the first buffering stream, or 0 if not found.</returns>
|
||||
internal static long GetPosition(this IStreamStack stream)
|
||||
{
|
||||
IStreamStack? current = stream;
|
||||
|
||||
while (current != null)
|
||||
{
|
||||
if (current.BufferSize != 0 && current is Stream st)
|
||||
{
|
||||
return st.Position;
|
||||
}
|
||||
current = current?.BaseStream() as IStreamStack;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rewinds the buffer of the outermost buffering stream in the stack by the specified count, if supported.
|
||||
/// Only the most derived buffering stream is affected.
|
||||
/// </summary>
|
||||
/// <param name="stream">The most derived (outermost) stream in the stack.</param>
|
||||
/// <param name="count">The number of bytes to rewind within the buffer.</param>
|
||||
internal static void Rewind(this IStreamStack stream, int count)
|
||||
{
|
||||
Stream baseStream = stream.BaseStream();
|
||||
Stream thisStream = (Stream)stream;
|
||||
IStreamStack? buffStream = null;
|
||||
IStreamStack? current = stream;
|
||||
|
||||
while (buffStream == null && current != null)
|
||||
{
|
||||
if (current.BufferSize != 0)
|
||||
{
|
||||
buffStream = current;
|
||||
buffStream.BufferPosition -= Math.Min(buffStream.BufferPosition, count);
|
||||
}
|
||||
current = current?.BaseStream() as IStreamStack;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the buffer size on the first buffering stream in the stack, or on the outermost stream if none exist.
|
||||
/// If <paramref name="force"/> is true, sets the buffer size regardless of current value.
|
||||
/// </summary>
|
||||
/// <param name="stream">The most derived (outermost) stream in the stack.</param>
|
||||
/// <param name="bufferSize">The buffer size to set.</param>
|
||||
/// <param name="force">If true, forces the buffer size to be set even if already set.</param>
|
||||
internal static void SetBuffer(this IStreamStack stream, int bufferSize, bool force)
|
||||
{
|
||||
if (bufferSize == 0 || stream == null)
|
||||
return;
|
||||
|
||||
IStreamStack? current = stream;
|
||||
IStreamStack defaultBuffer = stream;
|
||||
IStreamStack? buffer = null;
|
||||
|
||||
// First pass: find the deepest IStreamStack
|
||||
while (current != null)
|
||||
{
|
||||
defaultBuffer = current;
|
||||
if (buffer == null && ((current.BufferSize != 0 && bufferSize != 0) || force))
|
||||
buffer = current;
|
||||
if (defaultBuffer.DefaultBufferSize != 0)
|
||||
break;
|
||||
current = current.BaseStream() as IStreamStack;
|
||||
}
|
||||
if (defaultBuffer.DefaultBufferSize == 0)
|
||||
defaultBuffer.DefaultBufferSize = bufferSize;
|
||||
(buffer ?? stream).BufferSize = bufferSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to set the position in the stream stack. If a buffering stream is present and the position is within its buffer,
|
||||
/// BufferPosition is set on the outermost buffering stream and all intermediate streams update their internal state via SetPosition.
|
||||
/// If no buffering stream is present, seeks as close to the root stream as possible and updates all intermediate streams' state via SetPosition.
|
||||
/// Seeking is never performed if any intermediate stream in the stack is buffering.
|
||||
/// Throws if the position cannot be set.
|
||||
/// </summary>
|
||||
/// <param name="stream">
|
||||
/// The most derived (outermost) stream in the stack. The method traverses up the stack via BaseStream() until a stream can satisfy the buffer or seek request.
|
||||
/// </param>
|
||||
/// <param name="position">The absolute position to set.</param>
|
||||
/// <returns>The position that was set.</returns>
|
||||
internal static long StackSeek(this IStreamStack stream, long position)
|
||||
{
|
||||
var stack = new List<IStreamStack>();
|
||||
Stream? current = stream as Stream;
|
||||
int lastBufferingIndex = -1;
|
||||
int firstSeekableIndex = -1;
|
||||
Stream? firstSeekableStream = null;
|
||||
|
||||
// Traverse the stack, collecting info
|
||||
while (current is IStreamStack stackStream)
|
||||
{
|
||||
stack.Add(stackStream);
|
||||
if (stackStream.BufferSize > 0)
|
||||
{
|
||||
lastBufferingIndex = stack.Count - 1;
|
||||
break;
|
||||
}
|
||||
current = stackStream.BaseStream();
|
||||
}
|
||||
|
||||
// Find the first seekable stream (closest to the root)
|
||||
if (current != null && current.CanSeek)
|
||||
{
|
||||
firstSeekableIndex = stack.Count;
|
||||
firstSeekableStream = current;
|
||||
}
|
||||
|
||||
// If any buffering stream exists, try to set BufferPosition on the outermost one
|
||||
if (lastBufferingIndex != -1)
|
||||
{
|
||||
var bufferingStream = stack[lastBufferingIndex];
|
||||
if (position >= 0 && position < bufferingStream.BufferSize)
|
||||
{
|
||||
bufferingStream.BufferPosition = (int)position;
|
||||
return position;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If position is not in buffer, reset buffer and proceed as non-buffering
|
||||
bufferingStream.BufferPosition = 0;
|
||||
}
|
||||
// Continue to seek as if no buffer is present
|
||||
}
|
||||
|
||||
// If no buffering, or buffer was reset, seek at the first seekable stream (closest to the root)
|
||||
if (firstSeekableStream != null)
|
||||
{
|
||||
firstSeekableStream.Seek(position, SeekOrigin.Begin);
|
||||
return firstSeekableStream.Position;
|
||||
}
|
||||
|
||||
throw new NotSupportedException(
|
||||
"Cannot set position on this stream stack (no seekable or buffering stream supports the requested position)."
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads bytes from the stream, using the position to observe how much was actually consumed and rewind the buffer to ensure further reads are correct.
|
||||
/// This is required to prevent buffered reads from skipping data, while also benefiting from buffering and reduced stream IO reads.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to read from.</param>
|
||||
/// <param name="buffer">The buffer to read data into.</param>
|
||||
/// <param name="offset">The offset in the buffer to start writing data.</param>
|
||||
/// <param name="count">The maximum number of bytes to read.</param>
|
||||
/// <param name="buffStream">Returns the buffering stream found in the stack, or null if none exists.</param>
|
||||
/// <param name="baseReadCount">Returns the number of bytes actually read from the base stream, or -1 if no buffering stream was found.</param>
|
||||
/// <returns>The number of bytes read into the buffer.</returns>
|
||||
internal static int Read(
|
||||
this IStreamStack stream,
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
int count,
|
||||
out IStreamStack? buffStream,
|
||||
out int baseReadCount
|
||||
)
|
||||
{
|
||||
Stream baseStream = stream.BaseStream();
|
||||
Stream thisStream = (Stream)stream;
|
||||
IStreamStack? current = stream;
|
||||
buffStream = null;
|
||||
baseReadCount = -1;
|
||||
|
||||
while (buffStream == null && (current = current?.BaseStream() as IStreamStack) != null)
|
||||
{
|
||||
if (current.BufferSize != 0)
|
||||
{
|
||||
buffStream = current;
|
||||
}
|
||||
}
|
||||
|
||||
long buffPos = buffStream == null ? -1 : ((Stream)buffStream).Position;
|
||||
|
||||
int read = baseStream.Read(buffer, offset, count); //amount read in to buffer
|
||||
|
||||
if (buffPos != -1)
|
||||
{
|
||||
baseReadCount = (int)(((Stream)buffStream!).Position - buffPos);
|
||||
}
|
||||
return read;
|
||||
}
|
||||
|
||||
#if DEBUG_STREAMS
|
||||
private static long _instanceCounter = 0;
|
||||
|
||||
private static string cleansePos(long pos)
|
||||
{
|
||||
if (pos < 0)
|
||||
return "";
|
||||
return "Px" + pos.ToString("x");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or creates a unique instance ID for the stream stack for debugging purposes.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream stack.</param>
|
||||
/// <param name="instanceId">Reference to the instance ID field.</param>
|
||||
/// <param name="construct">Whether this is being called during construction.</param>
|
||||
/// <returns>The instance ID.</returns>
|
||||
public static long GetInstanceId(
|
||||
this IStreamStack stream,
|
||||
ref long instanceId,
|
||||
bool construct
|
||||
)
|
||||
{
|
||||
if (instanceId == 0) //will not be equal to 0 when inherited IStackStream types are being used
|
||||
instanceId = System.Threading.Interlocked.Increment(ref _instanceCounter);
|
||||
return instanceId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a debug message for stream construction.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream stack.</param>
|
||||
/// <param name="constructing">The type being constructed.</param>
|
||||
public static void DebugConstruct(this IStreamStack stream, Type constructing)
|
||||
{
|
||||
long id = stream.InstanceId;
|
||||
stream.InstanceId = GetInstanceId(stream, ref id, true);
|
||||
var frame = (new StackTrace()).GetFrame(3);
|
||||
string parentInfo =
|
||||
frame != null
|
||||
? $"{frame.GetMethod()?.DeclaringType?.Name}.{frame.GetMethod()?.Name}()"
|
||||
: "Unknown";
|
||||
if (constructing.FullName == stream.GetType().FullName) //don't debug base IStackStream types
|
||||
Debug.WriteLine(
|
||||
$"{GetStreamStackString(stream, true)} : Constructed by [{parentInfo}]"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a debug message for stream disposal.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream stack.</param>
|
||||
/// <param name="constructing">The type being disposed.</param>
|
||||
public static void DebugDispose(this IStreamStack stream, Type constructing)
|
||||
{
|
||||
var frame = (new StackTrace()).GetFrame(3);
|
||||
string parentInfo =
|
||||
frame != null
|
||||
? $"{frame.GetMethod()?.DeclaringType?.Name}.{frame.GetMethod()?.Name}()"
|
||||
: "Unknown";
|
||||
if (constructing.FullName == stream.GetType().FullName) //don't debug base IStackStream types
|
||||
Debug.WriteLine($"{GetStreamStackString(stream, false)} : Disposed by [{parentInfo}]");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a debug trace message for the stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream stack.</param>
|
||||
/// <param name="message">The debug message to write.</param>
|
||||
public static void DebugTrace(this IStreamStack stream, string message)
|
||||
{
|
||||
Debug.WriteLine(
|
||||
$"{GetStreamStackString(stream, false)} : [{stream.GetType().Name}]{message}"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the full stream chain as a string, including instance IDs and positions.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream stack to represent.</param>
|
||||
/// <param name="construct">Whether this is being called during construction.</param>
|
||||
/// <returns>A string representation of the entire stream stack.</returns>
|
||||
public static string GetStreamStackString(this IStreamStack stream, bool construct)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
Stream? current = stream as Stream;
|
||||
while (current != null)
|
||||
{
|
||||
IStreamStack? sStack = current as IStreamStack;
|
||||
string id = sStack != null ? "#" + sStack.InstanceId.ToString() : "";
|
||||
string buffSize = sStack != null ? "Bx" + sStack.BufferSize.ToString("x") : "";
|
||||
string defBuffSize =
|
||||
sStack != null ? "Dx" + sStack.DefaultBufferSize.ToString("x") : "";
|
||||
|
||||
if (sb.Length > 0)
|
||||
sb.Insert(0, "/");
|
||||
try
|
||||
{
|
||||
sb.Insert(
|
||||
0,
|
||||
$"{current.GetType().Name}{id}[{cleansePos(current.Position)}:{buffSize}:{defBuffSize}]"
|
||||
);
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (current is SharpCompressStream scs)
|
||||
sb.Insert(
|
||||
0,
|
||||
$"{current.GetType().Name}{id}[{cleansePos(scs.InternalPosition)}:{buffSize}:{defBuffSize}]"
|
||||
);
|
||||
else
|
||||
sb.Insert(0, $"{current.GetType().Name}{id}[:{buffSize}]");
|
||||
}
|
||||
if (sStack != null)
|
||||
current = sStack.BaseStream(); //current may not be a IStreamStack, allow one more loop
|
||||
else
|
||||
break;
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -7,35 +7,34 @@ using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Common.Arc;
|
||||
|
||||
namespace SharpCompress.Readers.Arc
|
||||
namespace SharpCompress.Readers.Arc;
|
||||
|
||||
public class ArcReader : AbstractReader<ArcEntry, ArcVolume>
|
||||
{
|
||||
public class ArcReader : AbstractReader<ArcEntry, ArcVolume>
|
||||
private ArcReader(Stream stream, ReaderOptions options)
|
||||
: base(options, ArchiveType.Arc) => Volume = new ArcVolume(stream, options, 0);
|
||||
|
||||
public override ArcVolume Volume { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Opens an ArcReader for Non-seeking usage with a single volume
|
||||
/// </summary>
|
||||
/// <param name="stream"></param>
|
||||
/// <param name="options"></param>
|
||||
/// <returns></returns>
|
||||
public static ArcReader Open(Stream stream, ReaderOptions? options = null)
|
||||
{
|
||||
private ArcReader(Stream stream, ReaderOptions options)
|
||||
: base(options, ArchiveType.Arc) => Volume = new ArcVolume(stream, options, 0);
|
||||
stream.CheckNotNull(nameof(stream));
|
||||
return new ArcReader(stream, options ?? new ReaderOptions());
|
||||
}
|
||||
|
||||
public override ArcVolume Volume { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Opens an ArcReader for Non-seeking usage with a single volume
|
||||
/// </summary>
|
||||
/// <param name="stream"></param>
|
||||
/// <param name="options"></param>
|
||||
/// <returns></returns>
|
||||
public static ArcReader Open(Stream stream, ReaderOptions? options = null)
|
||||
protected override IEnumerable<ArcEntry> GetEntries(Stream stream)
|
||||
{
|
||||
ArcEntryHeader headerReader = new ArcEntryHeader(new ArchiveEncoding());
|
||||
ArcEntryHeader? header;
|
||||
while ((header = headerReader.ReadHeader(stream)) != null)
|
||||
{
|
||||
stream.CheckNotNull(nameof(stream));
|
||||
return new ArcReader(stream, options ?? new ReaderOptions());
|
||||
}
|
||||
|
||||
protected override IEnumerable<ArcEntry> GetEntries(Stream stream)
|
||||
{
|
||||
ArcEntryHeader headerReader = new ArcEntryHeader(new ArchiveEncoding());
|
||||
ArcEntryHeader? header;
|
||||
while ((header = headerReader.ReadHeader(stream)) != null)
|
||||
{
|
||||
yield return new ArcEntry(new ArcFilePart(header, stream));
|
||||
}
|
||||
yield return new ArcEntry(new ArcFilePart(header, stream));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user