diff --git a/src/SharpCompress/Compressors/PPMd/H/ModelPPM.cs b/src/SharpCompress/Compressors/PPMd/H/ModelPPM.cs index 793dccae..1718cbb4 100644 --- a/src/SharpCompress/Compressors/PPMd/H/ModelPPM.cs +++ b/src/SharpCompress/Compressors/PPMd/H/ModelPPM.cs @@ -333,6 +333,65 @@ internal class ModelPpm return (_minContext.Address != 0); } + internal async ValueTask DecodeInitAsync( + IRarUnpack unpackRead, + int escChar, + CancellationToken cancellationToken = default + ) + { + var maxOrder = + await unpackRead.ReadCharAsync(cancellationToken).ConfigureAwait(false) & 0xff; + var reset = ((maxOrder & 0x20) != 0); + + var maxMb = 0; + if (reset) + { + maxMb = await unpackRead.ReadCharAsync(cancellationToken).ConfigureAwait(false); + } + else + { + if (SubAlloc.GetAllocatedMemory() == 0) + { + return false; + } + } + if ((maxOrder & 0x40) != 0) + { + escChar = await unpackRead.ReadCharAsync(cancellationToken).ConfigureAwait(false); + unpackRead.PpmEscChar = escChar; + } + Coder = new RangeCoder(); + await Coder.InitAsync(unpackRead, cancellationToken).ConfigureAwait(false); + if (reset) + { + maxOrder = (maxOrder & 0x1f) + 1; + if (maxOrder > 16) + { + maxOrder = 16 + ((maxOrder - 16) * 3); + } + if (maxOrder == 1) + { + SubAlloc.StopSubAllocator(); + return false; + } + SubAlloc.StartSubAllocator((maxMb + 1) << 20); + _minContext = new PpmContext(Heap); + + _maxContext = new PpmContext(Heap); + FoundState = new State(Heap); + _dummySee2Cont = new See2Context(); + for (var i = 0; i < 25; i++) + { + for (var j = 0; j < 16; j++) + { + _see2Cont[i][j] = new See2Context(); + } + } + StartModelRare(maxOrder); + } + return _minContext.Address != 0; + } + public virtual int DecodeChar() { // Debug diff --git a/src/SharpCompress/Compressors/PPMd/H/RangeCoder.cs b/src/SharpCompress/Compressors/PPMd/H/RangeCoder.cs index 0a3b2321..59bdd926 100644 --- a/src/SharpCompress/Compressors/PPMd/H/RangeCoder.cs +++ b/src/SharpCompress/Compressors/PPMd/H/RangeCoder.cs @@ -19,7 +19,7 @@ internal class RangeCoder private long _low, _code, _range; - private readonly IRarUnpack _unpackRead; + private IRarUnpack _unpackRead; private readonly Stream _stream; internal RangeCoder(IRarUnpack unpackRead) @@ -36,6 +36,24 @@ internal class RangeCoder internal RangeCoder() { } + internal async ValueTask InitAsync( + IRarUnpack unpackRead, + CancellationToken cancellationToken = default + ) + { + _unpackRead = unpackRead; + SubRange = new SubRange(); + + _low = _code = 0L; + _range = 0xFFFFffffL; + for (var i = 0; i < 4; i++) + { + _code = + ((_code << 8) | await ReadCharAsync(cancellationToken).ConfigureAwait(false)) + & UINT_MASK; + } + } + private void Init() { SubRange = new SubRange(); @@ -131,7 +149,7 @@ internal class RangeCoder { if (_unpackRead != null) { - return _unpackRead.Char; + return await _unpackRead.ReadCharAsync(cancellationToken).ConfigureAwait(false); } if (_stream != null) { diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.Async.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.Async.cs index 0581ba26..2bea6ce8 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.Async.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.Async.cs @@ -141,7 +141,7 @@ internal sealed partial class Unpack if (((wrPtr - unpPtr) & PackDef.MAXWINMASK) < 260 && wrPtr != unpPtr) { - UnpWriteBuf(); + await UnpWriteBufAsync(cancellationToken).ConfigureAwait(false); if (destUnpSize < 0) { return; @@ -154,7 +154,7 @@ internal sealed partial class Unpack } if (unpBlockType == BlockTypes.BLOCK_PPM) { - var Ch = ppm.DecodeChar(); + var Ch = await ppm.DecodeCharAsync(cancellationToken).ConfigureAwait(false); if (Ch == -1) { ppmError = true; @@ -162,7 +162,7 @@ internal sealed partial class Unpack } if (Ch == PpmEscChar) { - var NextCh = ppm.DecodeChar(); + var NextCh = await ppm.DecodeCharAsync(cancellationToken).ConfigureAwait(false); if (NextCh == 0) { if (!await ReadTablesAsync(cancellationToken).ConfigureAwait(false)) @@ -177,7 +177,7 @@ internal sealed partial class Unpack } if (NextCh == 3) { - if (!ReadVMCodePPM()) + if (!await ReadVMCodePPMAsync(cancellationToken).ConfigureAwait(false)) { break; } @@ -190,7 +190,8 @@ internal sealed partial class Unpack var failed = false; for (var I = 0; I < 4 && !failed; I++) { - var ch = ppm.DecodeChar(); + var ch = await ppm.DecodeCharAsync(cancellationToken) + .ConfigureAwait(false); if (ch == -1) { failed = true; @@ -216,7 +217,8 @@ internal sealed partial class Unpack } if (NextCh == 5) { - var Length = ppm.DecodeChar(); + var Length = await ppm.DecodeCharAsync(cancellationToken) + .ConfigureAwait(false); if (Length == -1) { break; @@ -354,7 +356,7 @@ internal sealed partial class Unpack CopyString(2, Distance); } } - UnpWriteBuf(); + await UnpWriteBufAsync(cancellationToken).ConfigureAwait(false); } private async Task UnpWriteBufAsync(CancellationToken cancellationToken = default) @@ -622,7 +624,8 @@ internal sealed partial class Unpack if ((bitField & 0x8000) != 0) { unpBlockType = BlockTypes.BLOCK_PPM; - return ppm.DecodeInit(this, PpmEscChar); + return await ppm.DecodeInitAsync(this, PpmEscChar, cancellationToken) + .ConfigureAwait(false); } unpBlockType = BlockTypes.BLOCK_LZ; @@ -793,4 +796,58 @@ internal sealed partial class Unpack } return AddVMCode(FirstByte, vmCode); } + + public async ValueTask ReadCharAsync(CancellationToken cancellationToken = default) + { + if (inAddr > MAX_SIZE - 30) + { + await unpReadBufAsync(cancellationToken).ConfigureAwait(false); + } + return InBuf[inAddr++] & 0xff; + } + + private async Task ReadVMCodePPMAsync(CancellationToken cancellationToken = default) + { + var FirstByte = await ppm.DecodeCharAsync(cancellationToken).ConfigureAwait(false); + if (FirstByte == -1) + { + return false; + } + var Length = (FirstByte & 7) + 1; + if (Length == 7) + { + var B1 = await ppm.DecodeCharAsync(cancellationToken).ConfigureAwait(false); + if (B1 == -1) + { + return false; + } + Length = B1 + 7; + } + else if (Length == 8) + { + var B1 = await ppm.DecodeCharAsync(cancellationToken).ConfigureAwait(false); + if (B1 == -1) + { + return false; + } + var B2 = await ppm.DecodeCharAsync(cancellationToken).ConfigureAwait(false); + if (B2 == -1) + { + return false; + } + Length = (B1 * 256) + B2; + } + + var vmCode = new List(); + for (var I = 0; I < Length; I++) + { + var Ch = await ppm.DecodeCharAsync(cancellationToken).ConfigureAwait(false); + if (Ch == -1) + { + return false; + } + vmCode.Add((byte)Ch); + } + return AddVMCode(FirstByte, vmCode); + } } diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack15.Async.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack15.Async.cs index 2975fbbf..f2e8c4ca 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack15.Async.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack15.Async.cs @@ -48,7 +48,7 @@ internal partial class Unpack } if (((wrPtr - unpPtr) & PackDef.MAXWINMASK) < 270 && wrPtr != unpPtr) { - oldUnpWriteBuf(); + await oldUnpWriteBufAsync(cancellationToken).ConfigureAwait(false); if (suspended) { return; @@ -105,7 +105,7 @@ internal partial class Unpack } } } - oldUnpWriteBuf(); + await oldUnpWriteBufAsync(cancellationToken).ConfigureAwait(false); } private async Task unpReadBufAsync(CancellationToken cancellationToken = default) diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack20.Async.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack20.Async.cs index 69bc6de0..9ca54f31 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack20.Async.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack20.Async.cs @@ -45,7 +45,7 @@ internal partial class Unpack } if (((wrPtr - unpPtr) & PackDef.MAXWINMASK) < 270 && wrPtr != unpPtr) { - oldUnpWriteBuf(); + await oldUnpWriteBufAsync(cancellationToken).ConfigureAwait(false); if (suspended) { return; @@ -157,8 +157,8 @@ internal partial class Unpack CopyString20(2, Distance); } } - ReadLastTables(); - oldUnpWriteBuf(); + await ReadLastTablesAsync(cancellationToken).ConfigureAwait(false); + await oldUnpWriteBufAsync(cancellationToken).ConfigureAwait(false); } private async Task ReadTables20Async(CancellationToken cancellationToken = default) @@ -272,4 +272,25 @@ internal partial class Unpack } return true; } + + private async Task ReadLastTablesAsync(CancellationToken cancellationToken = default) + { + if (readTop >= inAddr + 5) + { + if (UnpAudioBlock != 0) + { + if (this.decodeNumber(MD[UnpCurChannel]) == 256) + { + await ReadTables20Async(cancellationToken).ConfigureAwait(false); + } + } + else + { + if (this.decodeNumber(LD) == 269) + { + await ReadTables20Async(cancellationToken).ConfigureAwait(false); + } + } + } + } } diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack50.Async.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack50.Async.cs index 432922c4..42f7d439 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack50.Async.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack50.Async.cs @@ -118,7 +118,7 @@ internal partial class Unpack && WriteBorder != UnpPtr ) { - UnpWriteBuf(); + await UnpWriteBufAsync(cancellationToken).ConfigureAwait(false); if (WrittenFileSize > DestUnpSize) { return; @@ -197,7 +197,7 @@ internal partial class Unpack var Filter = new UnpackFilter(); if ( !await ReadFilterAsync(Filter, cancellationToken).ConfigureAwait(false) - || !AddFilter(Filter) + || !await AddFilterAsync(Filter, cancellationToken).ConfigureAwait(false) ) { break; @@ -232,7 +232,7 @@ internal partial class Unpack continue; } } - UnpWriteBuf(); + await UnpWriteBufAsync(cancellationToken).ConfigureAwait(false); } private async Task ReadBlockHeaderAsync(CancellationToken cancellationToken = default) @@ -318,4 +318,24 @@ internal partial class Unpack return true; } + + private async Task AddFilterAsync( + UnpackFilter Filter, + CancellationToken cancellationToken = default + ) + { + if (Filters.Count >= MAX_UNPACK_FILTERS) + { + await UnpWriteBufAsync(cancellationToken).ConfigureAwait(false); + if (Filters.Count >= MAX_UNPACK_FILTERS) + { + InitFilters(); + } + } + + Filter.NextWindow = WrPtr != UnpPtr && ((WrPtr - UnpPtr) & MaxWinMask) <= Filter.BlockStart; + Filter.uBlockStart = (uint)((Filter.BlockStart + UnpPtr) & MaxWinMask); + Filters.Add(Filter); + return true; + } } diff --git a/tests/SharpCompress.Test/Rar/RarReaderAsyncTests.cs b/tests/SharpCompress.Test/Rar/RarReaderAsyncTests.cs index d6d82e26..b922ee7e 100644 --- a/tests/SharpCompress.Test/Rar/RarReaderAsyncTests.cs +++ b/tests/SharpCompress.Test/Rar/RarReaderAsyncTests.cs @@ -14,6 +14,30 @@ namespace SharpCompress.Test.Rar; public class RarReaderAsyncTests : ReaderTests { + [Theory] + [InlineData("Rar15.rar")] + [InlineData("Rar.rar")] + [InlineData("Rar.Audio_program.rar")] + [InlineData("Rar5.rar")] + [InlineData("Rar5.solid.rar")] + public async ValueTask Rar_Reader_Async_Uses_Only_Async_Stream_Operations(string filename) + { + using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename)); + await using var reader = await ReaderFactory.OpenAsyncReader( + new AsyncOnlyStream(stream), + new ReaderOptions { LookForHeader = true } + ); + + while (await reader.MoveToNextEntryAsync()) + { + if (!reader.Entry.IsDirectory) + { + using var output = new SyncWriteNotSupportedStream(new MemoryStream()); + await reader.WriteEntryToAsync(output); + } + } + } + [Fact] public async ValueTask Rar_Multi_Reader_Async() => await DoRar_Multi_Reader_Async([ @@ -371,4 +395,56 @@ public class RarReaderAsyncTests : ReaderTests } VerifyFiles(); } + + private sealed class SyncWriteNotSupportedStream(Stream stream) : Stream + { + public override bool CanRead => stream.CanRead; + + public override bool CanSeek => stream.CanSeek; + + public override bool CanWrite => stream.CanWrite; + + public override long Length => stream.Length; + + public override long Position + { + get => stream.Position; + set => stream.Position = value; + } + + public override void Flush() => stream.Flush(); + + public override int Read(byte[] buffer, int offset, int count) => + stream.Read(buffer, offset, count); + + public override long Seek(long offset, SeekOrigin origin) => stream.Seek(offset, origin); + + public override void SetLength(long value) => stream.SetLength(value); + + public override void Write(byte[] buffer, int offset, int count) => + throw new NotSupportedException("Synchronous Write is not supported"); + + public override Task WriteAsync( + byte[] buffer, + int offset, + int count, + System.Threading.CancellationToken cancellationToken + ) => stream.WriteAsync(buffer, offset, count, cancellationToken); + +#if NET8_0_OR_GREATER + public override ValueTask WriteAsync( + ReadOnlyMemory buffer, + System.Threading.CancellationToken cancellationToken = default + ) => stream.WriteAsync(buffer, cancellationToken); +#endif + + protected override void Dispose(bool disposing) + { + if (disposing) + { + stream.Dispose(); + } + base.Dispose(disposing); + } + } }