more file scoped namespaces

This commit is contained in:
Adam Hathcock
2025-10-14 08:52:10 +01:00
parent 5d6b94f8c3
commit 6de3735f0f
16 changed files with 1877 additions and 1891 deletions

View File

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

View File

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

View File

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

View File

@@ -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) { }
}

View File

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

View File

@@ -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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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);
}

View File

@@ -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
}
}

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

View File

@@ -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));
}
}
}
}